320 lines
12 KiB
Rust
320 lines
12 KiB
Rust
use super::*;
|
|
|
|
enum AbiOutput<'a> {
|
|
Direct(Option<&'a str>),
|
|
Optional(Option<&'a Type>),
|
|
}
|
|
|
|
pub(super) const DECLARATIONS: &str = "\
|
|
declare i32 @dever_rt_v1_time_sleep(i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_new(ptr, i64, ptr, ptr)
|
|
declare i8 @dever_rt_v1_text_equal(ptr, ptr)
|
|
declare i64 @dever_rt_v1_text_hash(ptr)
|
|
declare i32 @dever_rt_v1_text_compare(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_concat(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_length(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_trim(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_lower(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_upper(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_codepoint(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_from_codepoint(i64, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_at(ptr, i64, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_slice(ptr, i64, i64, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_index_of(ptr, ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_contains(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_starts_with(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_ends_with(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_replace(ptr, ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_text_split(ptr, ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_int_parse(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_float_parse(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_decimal_parse(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_int_to_text(i64, ptr)
|
|
declare i32 @dever_rt_v1_float_to_text(double, ptr)
|
|
declare i32 @dever_rt_v1_decimal_to_text(i64, i64, ptr)
|
|
declare i32 @dever_rt_v1_decimal_from_int(i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_decimal_add(i64, i64, i64, i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_decimal_sub(i64, i64, i64, i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_decimal_mul(i64, i64, i64, i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_decimal_div(i64, i64, i64, i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_decimal_neg(i64, i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_decimal_round(i64, i64, i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_decimal_compare(i64, i64, i64, i64, ptr, ptr)
|
|
declare i8 @dever_rt_v1_decimal_equal(i64, i64, i64, i64)
|
|
declare i8 @dever_rt_v1_bytes_equal(ptr, ptr)
|
|
declare i32 @dever_rt_v1_bytes_from_text(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_bytes_length(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_bytes_at(ptr, i64, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_bytes_concat(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_bytes_slice(ptr, i64, i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_bytes_to_text_handle(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_bytes_from_ints(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_list_new(ptr, ptr, i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_list_append_take(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_list_first_take(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_list_length(ptr, ptr, ptr)
|
|
declare i8 @dever_rt_v1_list_equal(ptr, ptr)
|
|
declare i32 @dever_rt_v1_list_cursor_take(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_list_cursor_next(ptr, ptr, ptr, ptr)
|
|
declare void @dever_rt_v1_list_cursor_release(ptr)
|
|
declare i32 @dever_rt_v1_map_new(ptr, ptr, ptr, ptr, i64, ptr, ptr)
|
|
declare i32 @dever_rt_v1_map_put_take(ptr, ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_map_remove_take(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_map_get_take(ptr, ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_map_length(ptr, ptr, ptr)
|
|
declare i8 @dever_rt_v1_map_equal(ptr, ptr)
|
|
declare i32 @dever_rt_v1_map_cursor_take(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_map_cursor_next(ptr, ptr, ptr, ptr, ptr)
|
|
declare void @dever_rt_v1_map_cursor_release(ptr)
|
|
declare i32 @dever_rt_v1_file_open(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_file_create(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_file_read(ptr, i64, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_file_write(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_file_close(ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_file_chunks(ptr, i64, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_stream_pull(ptr, ptr, ptr, ptr)
|
|
declare i32 @dever_rt_v1_stream_close(ptr, ptr, ptr)
|
|
declare double @llvm.sqrt.f64(double)
|
|
declare double @llvm.sin.f64(double)
|
|
declare double @llvm.cos.f64(double)
|
|
declare double @llvm.log.f64(double)
|
|
declare double @llvm.pow.f64(double, double)
|
|
";
|
|
|
|
impl Module<'_> {
|
|
pub(super) fn text_literal(&mut self, value: &str) -> (String, usize) {
|
|
let len = value.len();
|
|
if len == 0 {
|
|
return ("null".into(), 0);
|
|
}
|
|
let name = format!("@dever_text_literal_{}", self.next_literal);
|
|
self.next_literal += 1;
|
|
let bytes = value
|
|
.as_bytes()
|
|
.iter()
|
|
.map(|byte| format!("\\{byte:02X}"))
|
|
.collect::<String>();
|
|
writeln!(
|
|
self.declarations,
|
|
"{name} = private constant [{len} x i8] c\"{bytes}\""
|
|
)
|
|
.unwrap();
|
|
(name, len)
|
|
}
|
|
}
|
|
|
|
impl FunctionEmitter<'_, '_> {
|
|
pub(super) fn check_runtime_status(&mut self, status: &str, error: &str, span: Span) {
|
|
let failed = self.temp();
|
|
self.line(format!("{failed} = icmp ne i32 {status}, 0"));
|
|
let fault = self.label("runtime_fault");
|
|
let success = self.label("runtime_ok");
|
|
self.line(format!("br i1 {failed}, label %{fault}, label %{success}"));
|
|
self.start(&fault);
|
|
let message = self.temp();
|
|
self.line(format!("{message} = load {{ ptr, i64 }}, ptr {error}"));
|
|
if self.database_errors_override.is_some() {
|
|
self.line(format!("store {{ ptr, i64 }} {message}, ptr %error"));
|
|
self.exit("2");
|
|
self.start(&success);
|
|
return;
|
|
}
|
|
let destination = self.temp();
|
|
self.line(format!(
|
|
"{destination} = getelementptr %dever.fault, ptr %fault, i32 0, i32 4"
|
|
));
|
|
self.line(format!("store {{ ptr, i64 }} {message}, ptr {destination}"));
|
|
if self.protocol_callback {
|
|
self.exit(status);
|
|
} else {
|
|
self.write_fault(&(FaultCode::RuntimeAbi as i32).to_string(), span);
|
|
}
|
|
self.start(&success);
|
|
}
|
|
|
|
pub(super) fn runtime_call(
|
|
&mut self,
|
|
symbol: &str,
|
|
arguments: Vec<String>,
|
|
result_type: &Type,
|
|
nullable: bool,
|
|
physical_type: Option<&str>,
|
|
span: Span,
|
|
) -> String {
|
|
let output = if nullable {
|
|
assert!(
|
|
physical_type.is_none(),
|
|
"optional ABI output uses its concrete row type"
|
|
);
|
|
AbiOutput::Optional(None)
|
|
} else {
|
|
AbiOutput::Direct(physical_type)
|
|
};
|
|
self.runtime_call_with_output(symbol, arguments, result_type, output, span)
|
|
}
|
|
|
|
pub(super) fn runtime_optional_row(
|
|
&mut self,
|
|
symbol: &str,
|
|
arguments: Vec<String>,
|
|
row_type: &Type,
|
|
result_type: &Type,
|
|
span: Span,
|
|
) -> String {
|
|
self.runtime_call_with_output(
|
|
symbol,
|
|
arguments,
|
|
result_type,
|
|
AbiOutput::Optional(Some(row_type)),
|
|
span,
|
|
)
|
|
}
|
|
|
|
fn runtime_call_with_output(
|
|
&mut self,
|
|
symbol: &str,
|
|
mut arguments: Vec<String>,
|
|
result_type: &Type,
|
|
output_kind: AbiOutput<'_>,
|
|
span: Span,
|
|
) -> String {
|
|
let (value_type, physical_type, nullable) = match output_kind {
|
|
AbiOutput::Direct(physical) => (result_type, physical, false),
|
|
AbiOutput::Optional(row) => {
|
|
let Type::Nullable(inner) = result_type else {
|
|
unreachable!("nullable ABI result type")
|
|
};
|
|
(row.unwrap_or(inner.as_ref()), None, true)
|
|
}
|
|
};
|
|
let output_type = physical_type
|
|
.map(str::to_owned)
|
|
.unwrap_or_else(|| self.module.ty(value_type));
|
|
let output = self.entry_slot_ir(&output_type);
|
|
arguments.push(format!("ptr {output}"));
|
|
let present = nullable.then(|| {
|
|
let pointer = self.temp();
|
|
writeln!(self.prologue, " {pointer} = alloca i8").unwrap();
|
|
arguments.push(format!("ptr {pointer}"));
|
|
pointer
|
|
});
|
|
let error = self.temp();
|
|
writeln!(self.prologue, " {error} = alloca {{ ptr, i64 }}").unwrap();
|
|
arguments.push(format!("ptr {error}"));
|
|
let status = self.temp();
|
|
self.line(format!(
|
|
"{status} = call i32 @{symbol}({})",
|
|
arguments.join(", ")
|
|
));
|
|
self.check_runtime_status(&status, &error, span);
|
|
if let Some(present) = present {
|
|
self.optional_row(result_type, value_type, &output, &present)
|
|
} else {
|
|
let result = self.temp();
|
|
self.line(format!("{result} = load {output_type}, ptr {output}"));
|
|
if output_type == "i8" && matches!(result_type, Type::Bool) {
|
|
let boolean = self.temp();
|
|
self.line(format!("{boolean} = icmp ne i8 {result}, 0"));
|
|
boolean
|
|
} else {
|
|
result
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Presence guards the concrete row; a nullable row is transferred intact.
|
|
/// Collection lookups and Channel receive use the same flattening contract.
|
|
pub(super) fn optional_row(
|
|
&mut self,
|
|
result: &Type,
|
|
row_type: &Type,
|
|
row: &str,
|
|
present: &str,
|
|
) -> String {
|
|
let slot = self.entry_slot(result);
|
|
self.line(format!(
|
|
"store {} zeroinitializer, ptr {slot}",
|
|
self.module.ty(result)
|
|
));
|
|
let flag = self.temp();
|
|
self.line(format!("{flag} = load i8, ptr {present}"));
|
|
let has_value = self.temp();
|
|
self.line(format!("{has_value} = icmp ne i8 {flag}, 0"));
|
|
let some = self.label("runtime_some");
|
|
let done = self.label("runtime_optional_done");
|
|
self.line(format!("br i1 {has_value}, label %{some}, label %{done}"));
|
|
self.start(&some);
|
|
let value = self.temp();
|
|
self.line(format!(
|
|
"{value} = load {}, ptr {row}",
|
|
self.module.ty(row_type)
|
|
));
|
|
if row_type == result {
|
|
self.line(format!(
|
|
"store {} {value}, ptr {slot}",
|
|
self.module.ty(result)
|
|
));
|
|
} else {
|
|
let flag_pointer = self.temp();
|
|
self.line(format!(
|
|
"{flag_pointer} = getelementptr {}, ptr {slot}, i32 0, i32 0",
|
|
self.module.ty(result)
|
|
));
|
|
self.line(format!("store i1 1, ptr {flag_pointer}"));
|
|
let payload = self.temp();
|
|
self.line(format!(
|
|
"{payload} = getelementptr {}, ptr {slot}, i32 0, i32 1",
|
|
self.module.ty(result)
|
|
));
|
|
self.line(format!(
|
|
"store {} {value}, ptr {payload}",
|
|
self.module.ty(row_type)
|
|
));
|
|
}
|
|
self.line(format!("br label %{done}"));
|
|
self.start(&done);
|
|
let value = self.temp();
|
|
self.line(format!(
|
|
"{value} = load {}, ptr {slot}",
|
|
self.module.ty(result)
|
|
));
|
|
value
|
|
}
|
|
|
|
pub(super) fn render_text(
|
|
&mut self,
|
|
symbol: &str,
|
|
arguments: Vec<String>,
|
|
span: Span,
|
|
) -> String {
|
|
let buffer = self.entry_slot_ir("{ ptr, i64 }");
|
|
let mut arguments = arguments;
|
|
arguments.push(format!("ptr {buffer}"));
|
|
let status = self.temp();
|
|
self.line(format!(
|
|
"{status} = call i32 @{symbol}({})",
|
|
arguments.join(", ")
|
|
));
|
|
self.check_runtime_status(&status, &buffer, span);
|
|
let bytes = self.temp();
|
|
self.line(format!("{bytes} = load {{ ptr, i64 }}, ptr {buffer}"));
|
|
let pointer = self.temp();
|
|
self.line(format!(
|
|
"{pointer} = extractvalue {{ ptr, i64 }} {bytes}, 0"
|
|
));
|
|
let length = self.temp();
|
|
self.line(format!("{length} = extractvalue {{ ptr, i64 }} {bytes}, 1"));
|
|
let output = self.entry_slot(&Type::Text);
|
|
let error = self.entry_slot_ir("{ ptr, i64 }");
|
|
let created = self.temp();
|
|
self.line(format!("{created} = call i32 @dever_rt_v1_text_new(ptr {pointer}, i64 {length}, ptr {output}, ptr {error})"));
|
|
self.line(format!(
|
|
"call void @dever_rt_v1_buffer_free(ptr {pointer}, i64 {length})"
|
|
));
|
|
self.check_runtime_status(&created, &error, span);
|
|
let result = self.temp();
|
|
self.line(format!("{result} = load ptr, ptr {output}"));
|
|
result
|
|
}
|
|
}
|