feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,319 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
//! Static request adapters call concrete App functions inside the runtime request scope.
|
||||
use super::*;
|
||||
|
||||
struct RouteDescriptor<'a> {
|
||||
method: &'a str,
|
||||
path: &'a str,
|
||||
directory: &'a [String],
|
||||
components: &'a [String],
|
||||
permission: Option<usize>,
|
||||
auth: Option<usize>,
|
||||
handler: &'a str,
|
||||
anonymous: bool,
|
||||
multipart: bool,
|
||||
detail: bool,
|
||||
}
|
||||
|
||||
pub(super) const DECLARATIONS: &str = "\
|
||||
%dever.api_handler = type { ptr, ptr, ptr, ptr, ptr, ptr }
|
||||
%dever.api_auth = type { ptr, ptr, ptr, ptr, ptr, ptr }
|
||||
%dever.api_permission = type { { ptr, i64 }, { ptr, i64 }, { ptr, i64 }, { ptr, i64 }, { ptr, i64 }, { ptr, i64 } }
|
||||
%dever.api_route = type { { ptr, i64 }, { ptr, i64 }, ptr, i64, ptr, i64, ptr, ptr, ptr, i8, i8, i8 }
|
||||
declare i32 @dever_rt_v1_api_input_field(ptr, ptr, i8, i32, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_input_upload(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_input_raw_field(ptr, ptr, i8, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_text_bounds(ptr, ptr, i64, i64, i8, ptr)
|
||||
declare i32 @dever_rt_v1_api_pagination(i64, i64, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_session_new(ptr, i64, ptr, i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_session_database(ptr, ptr, ptr)
|
||||
declare void @dever_rt_v1_api_session_release(ptr)
|
||||
declare i32 @dever_rt_v1_api_application_root(ptr, ptr, ptr, ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_api_initialize(ptr, ptr, ptr, i64, i8, ptr)
|
||||
declare void @dever_rt_v1_api_cause_append(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_api_serve(ptr, ptr, i64, ptr, i64, ptr)
|
||||
declare i32 @dever_rt_v1_api_has_tenant(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_api_tenant_scope(i64, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_api_require_components(ptr, ptr, i64, ptr, i64, ptr)
|
||||
declare ptr @dever_rt_v1_api_tenant_owner(ptr, i64, ptr, i64, ptr)
|
||||
declare ptr @dever_rt_v1_api_tenant_component(ptr, i64, ptr, i8, ptr, i64, ptr)
|
||||
@dever_api_session = private global ptr null
|
||||
";
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn api_enabled(&self) -> bool {
|
||||
if self
|
||||
.application
|
||||
.as_ref()
|
||||
.is_some_and(|app| app.test.is_some())
|
||||
{
|
||||
return self.jobs_enabled();
|
||||
}
|
||||
self.application.is_some()
|
||||
&& (!self.program.jobs.is_empty()
|
||||
|| !self.program.api_routes.is_empty()
|
||||
|| !self.program.api_rest.is_empty()
|
||||
|| !self.program.auth.is_empty()
|
||||
|| !self.program.tenant_components.is_empty()
|
||||
|| self.instances.iter().any(|instance| {
|
||||
let mut scoped_capability = false;
|
||||
crate::check::visit(&self.program.functions[instance.function], |expression| {
|
||||
if let ExpressionKind::Intrinsic { operation, .. } = expression.kind {
|
||||
// Logs read invocation Settings even without HTTP routes.
|
||||
scoped_capability |= api_context::supported(operation)
|
||||
|| matches!(
|
||||
operation,
|
||||
Intrinsic::LogDebug
|
||||
| Intrinsic::LogInfo
|
||||
| Intrinsic::LogWarn
|
||||
| Intrinsic::LogError
|
||||
);
|
||||
}
|
||||
});
|
||||
scoped_capability
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) fn api_name(&mut self, value: &str) -> String {
|
||||
let (pointer, length) = self.text_literal(value);
|
||||
format!("{{ ptr, i64 }} {{ ptr {pointer}, i64 {length} }}")
|
||||
}
|
||||
|
||||
pub(super) fn emit_api_descriptors(&mut self) {
|
||||
if !self.database_enabled() {
|
||||
self.declarations
|
||||
.push_str("%dever.db_binding = type { ptr, { ptr, i64 }, i8 }\n");
|
||||
}
|
||||
self.declarations.push_str("define internal void @dever_api_append_cause(ptr %fault, ptr %message) {\nentry:\n %cause = getelementptr %dever.fault, ptr %fault, i32 0, i32 8\n call void @dever_rt_v1_api_cause_append(ptr %cause, ptr %message)\n ret void\n}\n");
|
||||
self.api_fault_status(false);
|
||||
self.api_fault_status(true);
|
||||
self.rest_fault_message();
|
||||
for (index, hook) in self.program.auth.clone().iter().enumerate() {
|
||||
self.emit_auth_descriptor(index, hook);
|
||||
}
|
||||
let permissions = self.program.permissions.clone();
|
||||
let mut entries = Vec::new();
|
||||
for (index, permission) in permissions.iter().enumerate() {
|
||||
let fields = [
|
||||
&permission.key,
|
||||
&permission.component,
|
||||
&permission.domain,
|
||||
&permission.site,
|
||||
&permission.action,
|
||||
permission.method,
|
||||
]
|
||||
.iter()
|
||||
.map(|value| self.api_name(value))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@dever_permission_{index} = private constant %dever.api_permission {{ {fields} }}"
|
||||
)
|
||||
.unwrap();
|
||||
entries.push(format!("%dever.api_permission {{ {fields} }}"));
|
||||
}
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@dever_permissions = private constant [{} x %dever.api_permission] [{}]",
|
||||
entries.len(),
|
||||
entries.join(", ")
|
||||
)
|
||||
.unwrap();
|
||||
let mut routes = Vec::new();
|
||||
for (index, route) in self.program.api_routes.clone().iter().enumerate() {
|
||||
let handler = self.api_handler(index);
|
||||
routes.push(self.api_route_value(RouteDescriptor {
|
||||
method: route.method,
|
||||
path: &route.path,
|
||||
directory: &route.directory,
|
||||
components: &route.components,
|
||||
permission: route.permission,
|
||||
auth: route.auth,
|
||||
handler: &handler,
|
||||
anonymous: route.anonymous,
|
||||
multipart: route.multipart,
|
||||
detail: false,
|
||||
}));
|
||||
}
|
||||
for (index, handler) in self
|
||||
.application
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.rest_handlers
|
||||
.clone()
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let route = self.program.api_rest[handler.route].clone();
|
||||
let permission = match handler.method {
|
||||
"GET" => route.permissions.read,
|
||||
"POST" => route.permissions.create,
|
||||
"PUT" => route.permissions.replace,
|
||||
"DELETE" => route.permissions.delete,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let descriptor = self.rest_handler(index, handler);
|
||||
routes.push(self.api_route_value(RouteDescriptor {
|
||||
method: handler.method,
|
||||
path: &route.path,
|
||||
directory: &route.directory,
|
||||
components: &route.components,
|
||||
permission,
|
||||
auth: route.auth,
|
||||
handler: &descriptor,
|
||||
anonymous: false,
|
||||
multipart: false,
|
||||
detail: handler.detail,
|
||||
}));
|
||||
}
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@dever_api_routes = private constant [{} x %dever.api_route] [{}]",
|
||||
routes.len(),
|
||||
routes.join(", ")
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn api_route_value(&mut self, route: RouteDescriptor<'_>) -> String {
|
||||
let RouteDescriptor {
|
||||
method,
|
||||
path,
|
||||
directory,
|
||||
components,
|
||||
permission,
|
||||
auth,
|
||||
handler,
|
||||
anonymous,
|
||||
multipart,
|
||||
detail,
|
||||
} = route;
|
||||
let method = self.api_name(method);
|
||||
let path = self.api_name(path);
|
||||
let names = self.wire_names(directory);
|
||||
let components_pointer = self.wire_names(components);
|
||||
let permission = permission.map_or_else(
|
||||
|| "null".into(),
|
||||
|index| format!("@dever_permission_{index}"),
|
||||
);
|
||||
let auth = auth.map_or_else(|| "null".into(), |index| format!("@dever_api_auth_{index}"));
|
||||
format!(
|
||||
"%dever.api_route {{ {method}, {path}, ptr {names}, i64 {}, ptr {components_pointer}, i64 {}, ptr {permission}, ptr {auth}, ptr {handler}, i8 {}, i8 {}, i8 {} }}",
|
||||
directory.len(),
|
||||
components.len(),
|
||||
u8::from(anonymous),
|
||||
u8::from(multipart),
|
||||
u8::from(detail)
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_auth_descriptor(&mut self, index: usize, hook: &crate::hir::AuthHook) {
|
||||
let instance = Specialization {
|
||||
function: hook.function,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let input = asynchronous::input_type(&self.program.functions[hook.function]);
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.protocol_callback = true;
|
||||
emitter.start("entry");
|
||||
let Shape::Record(fields) = emitter.module.program.types[hook.claims].shape.clone() else {
|
||||
unreachable!();
|
||||
};
|
||||
let mut record = "zeroinitializer".to_owned();
|
||||
for (field, pointer) in ["%subject", "%session", "%tenant", "%site"]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let value = if field == 2 {
|
||||
let present = emitter.temp();
|
||||
emitter.line(format!("{present} = icmp ne ptr {pointer}, null"));
|
||||
let flag = emitter.temp();
|
||||
let row = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{flag} = insertvalue {{ i1, ptr }} zeroinitializer, i1 {present}, 0"
|
||||
));
|
||||
emitter.line(format!(
|
||||
"{row} = insertvalue {{ i1, ptr }} {flag}, ptr {pointer}, 1"
|
||||
));
|
||||
row
|
||||
} else {
|
||||
pointer.to_string()
|
||||
};
|
||||
let row = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{row} = insertvalue %T{} {record}, {} {value}, {field}",
|
||||
hook.claims,
|
||||
emitter.module.ty(&fields[field].ty)
|
||||
));
|
||||
record = row;
|
||||
}
|
||||
let source = emitter.row_pointer(&Type::Named(hook.claims), &record);
|
||||
emitter.line(format!(
|
||||
"call void @dever_clone_{}(ptr {source}, ptr %out)",
|
||||
emitter.module.type_index(&Type::Named(hook.claims))
|
||||
));
|
||||
let callback = format!("dever_auth_pack_{index}");
|
||||
let body = emitter.finish_protocol_callback(
|
||||
&callback,
|
||||
"ptr %subject, ptr %session, ptr %tenant, ptr %site, ptr %out, ptr %error",
|
||||
);
|
||||
self.declarations.push_str(&body);
|
||||
// Claims is the only input field, so its layout equals the one-field
|
||||
// input tuple; all owned Text fields were cloned by the typed helper.
|
||||
let mut unpack = format!(
|
||||
"define internal i32 @dever_auth_unpack_{index}(ptr %input, ptr %id, ptr %user, ptr %user_present, ptr %tenant, ptr %tenant_present, ptr %error) {{\nentry:\n %identity = load %T{}, ptr %input\n %subject = extractvalue %T{} %identity, 0\n %owned = call ptr @dever_rt_v1_text_retain(ptr %subject)\n store ptr %owned, ptr %id\n",
|
||||
hook.identity, hook.identity
|
||||
);
|
||||
for (field, name) in [(1, "user"), (2, "tenant")] {
|
||||
writeln!(unpack, " %{name}_value = extractvalue %T{} %identity, {field}\n %{name}_flag = extractvalue {{ i1, i64 }} %{name}_value, 0\n %{name}_number = extractvalue {{ i1, i64 }} %{name}_value, 1\n %{name}_byte = zext i1 %{name}_flag to i8\n store i64 %{name}_number, ptr %{name}\n store i8 %{name}_byte, ptr %{name}_present", hook.identity).unwrap();
|
||||
}
|
||||
unpack.push_str(" store { ptr, i64 } zeroinitializer, ptr %error\n ret i32 0\n}\n");
|
||||
self.declarations.push_str(&unpack);
|
||||
let name = &self.names[&instance];
|
||||
let (asynchronous, synchronous) = if specialize::suspends(self.program, &instance) {
|
||||
(format!("@dever_async_{name}"), "null".into())
|
||||
} else {
|
||||
("null".into(), format!("@dever_sync_{name}"))
|
||||
};
|
||||
writeln!(self.declarations, "@dever_api_auth_{index} = private constant %dever.api_auth {{ ptr {asynchronous}, ptr {synchronous}, ptr @dever_owned_{}, ptr @{callback}, ptr @dever_auth_unpack_{index}, ptr @dever_api_fault_status }}", self.type_index(&input)).unwrap();
|
||||
}
|
||||
pub(super) fn emit_api_body(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
index: usize,
|
||||
) -> Result<(), String> {
|
||||
let route = self.program.api_routes[index].clone();
|
||||
let wrapper = self.program.functions[instance.function].clone();
|
||||
let app = self.program.functions[route.function].clone();
|
||||
let target = Specialization {
|
||||
function: route.function,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
let parameters = emitter.begin_generated_function(&wrapper);
|
||||
let password = [
|
||||
Intrinsic::CryptoPasswordHash,
|
||||
Intrinsic::CryptoPasswordVerify,
|
||||
]
|
||||
.iter()
|
||||
.any(|operation| specialize::uses_intrinsic(emitter.module.program, &target, *operation));
|
||||
let transaction = !password && emitter.begin_command_transaction(&target, route.span);
|
||||
let arguments = app
|
||||
.parameters
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, parameter)| {
|
||||
(
|
||||
parameter.value_type().unwrap().clone(),
|
||||
emitter.parameter(index),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let output = output_type(&app.outputs);
|
||||
let (status, row) = emitter.invoke_values(&target, &arguments, &output, route.span);
|
||||
emitter.propagate_status(&status, route.span, false);
|
||||
let value = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{value} = load {}, ptr {row}",
|
||||
emitter.module.ty(&output)
|
||||
));
|
||||
emitter.own_value(&output, &value, false);
|
||||
let encoded = emitter.encode_api_output(&output, &row, route.span);
|
||||
let guard = emitter.own_value(&Type::Text, &encoded, false);
|
||||
if transaction {
|
||||
emitter.commit_command_transaction();
|
||||
}
|
||||
emitter.line(format!("store ptr {encoded}, ptr %out"));
|
||||
emitter.disarm(guard);
|
||||
emitter.exit("0");
|
||||
let name = emitter.module.names[instance].clone();
|
||||
let body = emitter.finish_function(&name, ¶meters);
|
||||
self.functions.push_str(&body);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn api_fault_status(&mut self, rest: bool) -> String {
|
||||
let name = if rest {
|
||||
"dever_rest_fault_status"
|
||||
} else {
|
||||
"dever_api_fault_status"
|
||||
};
|
||||
let mut cases = Vec::new();
|
||||
for (id, definition) in self.program.types.iter().enumerate() {
|
||||
let Shape::Choice(variants) = &definition.shape else {
|
||||
continue;
|
||||
};
|
||||
for (index, variant) in variants.iter().enumerate() {
|
||||
let status = match (definition.name.as_str(), variant.name.as_str()) {
|
||||
("dever.api.Error", "Invalid") => 400,
|
||||
("dever.api.Error", "Unauthorized") => 401,
|
||||
("dever.api.Error", "Forbidden") => 403,
|
||||
("dever.api.Error", "NotFound") => 404,
|
||||
("dever.api.Error", "Conflict") => 409,
|
||||
("dever.api.Error", "TooManyRequests") => 429,
|
||||
("dever.database.Error", "NotFound") if rest => 404,
|
||||
("<generated REST Error>", "Invalid") => 400,
|
||||
("<generated REST Error>", "Forbidden") => 403,
|
||||
("<generated REST Error>", "NotFound") => 404,
|
||||
_ => continue,
|
||||
};
|
||||
cases.push((id + 1, index, status));
|
||||
}
|
||||
}
|
||||
let mut body = format!(
|
||||
"define internal i64 @{name}(ptr %fault) {{\nentry:\n %code_ptr = getelementptr %dever.fault, ptr %fault, i32 0, i32 0\n %code = load i32, ptr %code_ptr\n %business = icmp eq i32 %code, 4\n br i1 %business, label %identity, label %internal\nidentity:\n %type_ptr = getelementptr %dever.fault, ptr %fault, i32 0, i32 5\n %type = load i32, ptr %type_ptr\n %variant_ptr = getelementptr %dever.fault, ptr %fault, i32 0, i32 6\n %variant = load i32, ptr %variant_ptr\n"
|
||||
);
|
||||
for (index, (id, variant, status)) in cases.iter().enumerate() {
|
||||
writeln!(body, " %type{index} = icmp eq i32 %type, {id}\n %variant{index} = icmp eq i32 %variant, {variant}\n %matches{index} = and i1 %type{index}, %variant{index}\n br i1 %matches{index}, label %status{index}, label %next{index}\nstatus{index}:\n ret i64 {status}\nnext{index}:").unwrap();
|
||||
}
|
||||
body.push_str(" br label %internal\ninternal:\n ret i64 500\n}\n");
|
||||
self.declarations.push_str(&body);
|
||||
format!("@{name}")
|
||||
}
|
||||
|
||||
pub(super) fn api_handler(&mut self, index: usize) -> String {
|
||||
let route = self.program.api_routes[index].clone();
|
||||
let function = self.application.as_ref().unwrap().api_handlers[index];
|
||||
let instance = Specialization {
|
||||
function,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let input = asynchronous::input_type(&self.program.functions[function]);
|
||||
let parameters = self.program.functions[function].parameters.clone();
|
||||
let fields = route
|
||||
.inputs
|
||||
.iter()
|
||||
.zip(parameters)
|
||||
.map(|(name, parameter)| {
|
||||
let ty = parameter.value_type().unwrap().clone();
|
||||
let callback = if ty == Type::Upload {
|
||||
String::new()
|
||||
} else {
|
||||
self.wire_callback(&ty, crate::wire::Policy::ApiInput, false)
|
||||
};
|
||||
(name.clone(), ty, callback)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.protocol_callback = true;
|
||||
emitter.start("entry");
|
||||
let value = emitter.decode_fields(&input, &fields, "%inputs", wire::FieldInput::Api);
|
||||
emitter.line(format!(
|
||||
"store {} {value}, ptr %out",
|
||||
emitter.module.ty(&input)
|
||||
));
|
||||
let callback = format!("dever_api_decode_{index}");
|
||||
let body = emitter.finish_protocol_callback(
|
||||
&callback,
|
||||
"ptr %inputs, i64 %detail_id, ptr %out, ptr %error",
|
||||
);
|
||||
self.declarations.push_str(&body);
|
||||
let name = &self.names[&instance];
|
||||
let descriptor = format!("@dever_api_handler_{index}");
|
||||
writeln!(self.declarations, "{descriptor} = private constant %dever.api_handler {{ ptr @dever_async_{name}, ptr null, ptr @dever_owned_{}, ptr @{callback}, ptr @dever_api_fault_status, ptr null }}", self.type_index(&input)).unwrap();
|
||||
descriptor
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn initialize_api(
|
||||
&mut self,
|
||||
bindings: &str,
|
||||
binding_count: usize,
|
||||
transactions: &str,
|
||||
transaction_count: usize,
|
||||
service_start: &str,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let session = self.protocol_call(
|
||||
"dever_rt_v1_api_session_new",
|
||||
vec![
|
||||
format!("ptr {bindings}"),
|
||||
format!("i64 {binding_count}"),
|
||||
format!("ptr {transactions}"),
|
||||
format!("i64 {transaction_count}"),
|
||||
],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
self.line(format!("store ptr {session}, ptr @dever_api_session"));
|
||||
if self.module.jobs_enabled() && self.module.application.as_ref().unwrap().test.is_none() {
|
||||
let configure = self.label("application_configure_services");
|
||||
let ready = self.label("application_resources_ready");
|
||||
self.line(format!(
|
||||
"br i1 {service_start}, label %{configure}, label %{ready}"
|
||||
));
|
||||
self.start(&configure);
|
||||
self.wire_write(
|
||||
"dever_rt_v1_job_configure",
|
||||
vec![
|
||||
format!("ptr {session}"),
|
||||
format!(
|
||||
"i8 {}",
|
||||
u8::from(
|
||||
!self.module.program.api_routes.is_empty()
|
||||
|| !self.module.program.api_rest.is_empty()
|
||||
)
|
||||
),
|
||||
"i8 1".into(),
|
||||
],
|
||||
);
|
||||
self.line(format!("br label %{ready}"));
|
||||
self.start(&ready);
|
||||
}
|
||||
self.register_jobs(span);
|
||||
let tenant_models = self
|
||||
.module
|
||||
.program
|
||||
.models
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, model)| model.scope == crate::model::ModelScope::Tenant)
|
||||
.collect::<Vec<_>>();
|
||||
let fingerprint =
|
||||
crate::native::orm::tenant_fingerprint(&tenant_models, self.module.program);
|
||||
let fingerprint = self.module.wire_names(&[fingerprint]);
|
||||
let service = self.temp();
|
||||
self.line(format!("{service} = zext i1 {service_start} to i8"));
|
||||
let operation = self.async_operation(
|
||||
"api_initialize",
|
||||
vec![
|
||||
format!("ptr {session}"),
|
||||
format!("ptr {fingerprint}"),
|
||||
"ptr @dever_permissions".into(),
|
||||
format!("i64 {}", self.module.program.permissions.len()),
|
||||
format!("i8 {service}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Unit, span, true);
|
||||
session
|
||||
}
|
||||
|
||||
pub(super) fn serve_api(&mut self, span: Span) {
|
||||
let session = self.temp();
|
||||
self.line(format!("{session} = load ptr, ptr @dever_api_session"));
|
||||
let components = self
|
||||
.module
|
||||
.wire_names(&self.module.program.tenant_components.clone());
|
||||
let mut arguments = vec![
|
||||
format!("ptr {session}"),
|
||||
"ptr @dever_api_routes".into(),
|
||||
format!(
|
||||
"i64 {}",
|
||||
self.module.program.api_routes.len()
|
||||
+ self
|
||||
.module
|
||||
.application
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.rest_handlers
|
||||
.len()
|
||||
),
|
||||
format!("ptr {components}"),
|
||||
format!("i64 {}", self.module.program.tenant_components.len()),
|
||||
];
|
||||
let symbol = if self.module.jobs_enabled() {
|
||||
let jobs = self.temp();
|
||||
self.line(format!("{jobs} = load ptr, ptr @dever_job_session"));
|
||||
arguments.push(format!("ptr {jobs}"));
|
||||
"api_serve_with_jobs"
|
||||
} else {
|
||||
"api_serve"
|
||||
};
|
||||
let operation = self.async_operation(symbol, arguments, span);
|
||||
self.await_operation(&operation, &Type::Unit, span, true);
|
||||
}
|
||||
|
||||
pub(super) fn begin_generated_function(&mut self, function: &crate::hir::Function) -> String {
|
||||
let mut parameters = function
|
||||
.parameters
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, parameter)| {
|
||||
format!(
|
||||
"{} %p{index}, ",
|
||||
self.module.ty(parameter.value_type().unwrap())
|
||||
)
|
||||
})
|
||||
.collect::<String>();
|
||||
if self.module.database_function(self.instance) {
|
||||
parameters.push_str("ptr %database, ");
|
||||
self.database_context = "%database".into();
|
||||
}
|
||||
self.start("entry");
|
||||
self.prologue
|
||||
.push_str(" %exit_status = alloca i32\n store i32 0, ptr %exit_status\n");
|
||||
self.begin_coroutine(function);
|
||||
parameters
|
||||
}
|
||||
|
||||
pub(super) fn encode_api_output(&mut self, ty: &Type, row: &str, span: Span) -> String {
|
||||
let encoder = self
|
||||
.module
|
||||
.wire_callback(ty, crate::wire::Policy::Output, true);
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_wire_encode",
|
||||
vec![
|
||||
format!("ptr {row}"),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(ty)),
|
||||
format!("ptr @{encoder}"),
|
||||
],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
//! Request-only capabilities keep context and affine resources inside the runtime scope.
|
||||
use super::*;
|
||||
|
||||
pub(super) const DECLARATIONS: &str = "\
|
||||
%dever.api_cookie_options = type { ptr, ptr, i64, i8, i8, i64, i8 }
|
||||
declare i32 @dever_rt_v1_api_request_id(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_method(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_path(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_client_address(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_header(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_cookie(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_secret_cookie(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_response_header(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_response_cookie(ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_api_response_secret_cookie(ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_auth_site_key(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_auth_id(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_auth_session(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_auth_user_id(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_auth_tenant_id(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_auth_owns_user(i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_auth_issue(ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_auth_issue_cookie(ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_auth_clear_cookie(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_upload_filename(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_upload_content_type(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_upload_size(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_upload_close_take(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_upload_store_take(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_auth_permissions(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_auth_save_role(ptr, ptr, i8, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_auth_grant_role(i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_auth_revoke_role(i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_auth_disable_role(ptr, ptr)
|
||||
";
|
||||
|
||||
pub(super) fn supported(operation: Intrinsic) -> bool {
|
||||
use Intrinsic::*;
|
||||
matches!(
|
||||
operation,
|
||||
ApiServe
|
||||
| ApiRequestId
|
||||
| ApiMethod
|
||||
| ApiPath
|
||||
| ApiClientAddress
|
||||
| ApiHeader
|
||||
| ApiCookie
|
||||
| ApiSecretCookie
|
||||
| ApiResponseHeader
|
||||
| ApiResponseCookie
|
||||
| ApiResponseSecretCookie
|
||||
| AuthIssue
|
||||
| AuthIssueCookie
|
||||
| AuthClearCookie
|
||||
| AuthOwnsUser
|
||||
| AuthId
|
||||
| AuthSession
|
||||
| AuthUserId
|
||||
| AuthTenantId
|
||||
| AuthPermissions
|
||||
| AuthSaveRole
|
||||
| AuthGrantRole
|
||||
| AuthRevokeRole
|
||||
| AuthDisableRole
|
||||
| SiteKey
|
||||
| UploadFilename
|
||||
| UploadContentType
|
||||
| UploadSize
|
||||
| UploadClose
|
||||
| UploadStore
|
||||
)
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn api_intrinsic(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
arguments: &[Expression],
|
||||
values: &[String],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
use Intrinsic::*;
|
||||
let mut args = arguments
|
||||
.iter()
|
||||
.zip(values)
|
||||
.map(|(argument, value)| format!("{} {value}", self.module.ty(&argument.ty)))
|
||||
.collect::<Vec<_>>();
|
||||
let symbol = match operation {
|
||||
ApiServe => {
|
||||
self.serve_api(result.span);
|
||||
return "zeroinitializer".into();
|
||||
}
|
||||
ApiRequestId => "api_request_id",
|
||||
ApiMethod => "api_method",
|
||||
ApiPath => "api_path",
|
||||
ApiClientAddress => "api_client_address",
|
||||
ApiHeader => "api_header",
|
||||
ApiCookie => "api_cookie",
|
||||
ApiSecretCookie => "api_secret_cookie",
|
||||
ApiResponseHeader => "api_response_header",
|
||||
ApiResponseCookie | ApiResponseSecretCookie => {
|
||||
let options = self.api_cookie_options(&arguments[2].ty, &values[2]);
|
||||
args[2] = format!("ptr {options}");
|
||||
if operation == ApiResponseCookie {
|
||||
"api_response_cookie"
|
||||
} else {
|
||||
"api_response_secret_cookie"
|
||||
}
|
||||
}
|
||||
AuthIssue | AuthIssueCookie => {
|
||||
let tenant = self.nullable_handle(&arguments[2].ty, &values[2]);
|
||||
args[2] = format!("ptr {tenant}");
|
||||
if operation == AuthIssue {
|
||||
"auth_issue"
|
||||
} else {
|
||||
"auth_issue_cookie"
|
||||
}
|
||||
}
|
||||
AuthClearCookie => "auth_clear_cookie",
|
||||
AuthOwnsUser => "auth_owns_user",
|
||||
AuthId => "auth_id",
|
||||
AuthSession => "auth_session",
|
||||
AuthUserId => "auth_user_id",
|
||||
AuthTenantId => "auth_tenant_id",
|
||||
SiteKey => "auth_site_key",
|
||||
UploadFilename => "upload_filename",
|
||||
UploadContentType => "upload_content_type",
|
||||
UploadSize => "upload_size",
|
||||
UploadClose | UploadStore => {
|
||||
let owned = self.temp();
|
||||
self.line(format!(
|
||||
"{owned} = call ptr @dever_rt_v1_upload_retain(ptr {})",
|
||||
values[0]
|
||||
));
|
||||
args[0] = format!("ptr {owned}");
|
||||
if operation == UploadStore {
|
||||
let operation = self.async_operation(
|
||||
"upload_store_take",
|
||||
vec![format!("ptr {owned}")],
|
||||
result.span,
|
||||
);
|
||||
return self.async_resource_result(&operation, result, "Stored", None);
|
||||
}
|
||||
"upload_close_take"
|
||||
}
|
||||
AuthPermissions | AuthSaveRole | AuthGrantRole | AuthRevokeRole | AuthDisableRole => {
|
||||
return self.api_authorization_intrinsic(operation, arguments, values, result);
|
||||
}
|
||||
_ => unreachable!("checked request intrinsic"),
|
||||
};
|
||||
let value = self.runtime_call(
|
||||
&format!("dever_rt_v1_{symbol}"),
|
||||
args,
|
||||
&result.ty,
|
||||
matches!(result.ty, Type::Nullable(_)),
|
||||
matches!(result.ty, Type::Bool | Type::Unit).then_some("i8"),
|
||||
result.span,
|
||||
);
|
||||
if result.ty == Type::Unit {
|
||||
"zeroinitializer".into()
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn api_authorization_intrinsic(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
arguments: &[Expression],
|
||||
values: &[String],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
use Intrinsic::*;
|
||||
let mut args = arguments
|
||||
.iter()
|
||||
.zip(values)
|
||||
.map(|(argument, value)| format!("{} {value}", self.module.ty(&argument.ty)))
|
||||
.collect::<Vec<_>>();
|
||||
let symbol = match operation {
|
||||
AuthPermissions => {
|
||||
let Type::List(element) = &result.ty else {
|
||||
unreachable!("checked permission list");
|
||||
};
|
||||
let Type::Named(id) = element.as_ref() else {
|
||||
unreachable!();
|
||||
};
|
||||
let callback = self.module.api_permission_pack(*id);
|
||||
args.extend([
|
||||
format!("ptr @dever_type_{}", self.module.type_index(element)),
|
||||
format!("ptr {callback}"),
|
||||
]);
|
||||
"auth_permissions"
|
||||
}
|
||||
AuthSaveRole => {
|
||||
let flag = self.temp();
|
||||
self.line(format!("{flag} = zext i1 {} to i8", values[2]));
|
||||
args[2] = format!("i8 {flag}");
|
||||
"auth_save_role"
|
||||
}
|
||||
AuthGrantRole => "auth_grant_role",
|
||||
AuthRevokeRole => "auth_revoke_role",
|
||||
AuthDisableRole => "auth_disable_role",
|
||||
_ => unreachable!("authorization intrinsic"),
|
||||
};
|
||||
let operation = self.async_operation(symbol, args, result.span);
|
||||
self.await_operation(&operation, &result.ty, result.span, true)
|
||||
}
|
||||
|
||||
pub(super) fn nullable_handle(&mut self, ty: &Type, value: &str) -> String {
|
||||
let present = self.temp();
|
||||
let handle = self.temp();
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
self.line(format!(
|
||||
"{handle} = extractvalue {} {value}, 1",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
self.line(format!(
|
||||
"{result} = select i1 {present}, ptr {handle}, ptr null"
|
||||
));
|
||||
result
|
||||
}
|
||||
|
||||
fn api_cookie_options(&mut self, ty: &Type, value: &str) -> String {
|
||||
let Type::Named(id) = ty else {
|
||||
unreachable!("checked CookieOptions");
|
||||
};
|
||||
let Shape::Record(fields) = self.module.program.types[*id].shape.clone() else {
|
||||
unreachable!();
|
||||
};
|
||||
let mut values = Vec::new();
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
let part = self.temp();
|
||||
self.line(format!("{part} = extractvalue %T{id} {value}, {index}"));
|
||||
values.push((field.ty.clone(), part));
|
||||
}
|
||||
let domain = self.nullable_handle(&values[1].0, &values[1].1);
|
||||
let Type::Named(same_site) = values[2].0 else {
|
||||
unreachable!();
|
||||
};
|
||||
let Shape::Choice(variants) = self.module.program.types[same_site].shape.clone() else {
|
||||
unreachable!();
|
||||
};
|
||||
let tag = self.temp();
|
||||
self.line(format!(
|
||||
"{tag} = extractvalue %T{same_site} {}, 0",
|
||||
values[2].1
|
||||
));
|
||||
let mut same_site = "0".to_owned();
|
||||
for (name, kind) in [("Lax", 1), ("None", 2)] {
|
||||
let index = variants
|
||||
.iter()
|
||||
.position(|variant| variant.name == name)
|
||||
.unwrap();
|
||||
let matches = self.temp();
|
||||
let selected = self.temp();
|
||||
self.line(format!("{matches} = icmp eq i32 {tag}, {index}"));
|
||||
self.line(format!(
|
||||
"{selected} = select i1 {matches}, i64 {kind}, i64 {same_site}"
|
||||
));
|
||||
same_site = selected;
|
||||
}
|
||||
let secure = self.temp();
|
||||
let http_only = self.temp();
|
||||
let max_present = self.temp();
|
||||
let max_age = self.temp();
|
||||
let max_flag = self.temp();
|
||||
self.line(format!("{secure} = zext i1 {} to i8", values[3].1));
|
||||
self.line(format!("{http_only} = zext i1 {} to i8", values[4].1));
|
||||
self.line(format!(
|
||||
"{max_present} = extractvalue {{ i1, i64 }} {}, 0",
|
||||
values[5].1
|
||||
));
|
||||
self.line(format!(
|
||||
"{max_age} = extractvalue {{ i1, i64 }} {}, 1",
|
||||
values[5].1
|
||||
));
|
||||
self.line(format!("{max_flag} = zext i1 {max_present} to i8"));
|
||||
let slot = self.entry_slot_ir("%dever.api_cookie_options");
|
||||
let mut row = "zeroinitializer".to_owned();
|
||||
for (index, field) in [
|
||||
format!("ptr {}", values[0].1),
|
||||
format!("ptr {domain}"),
|
||||
format!("i64 {same_site}"),
|
||||
format!("i8 {secure}"),
|
||||
format!("i8 {http_only}"),
|
||||
format!("i64 {max_age}"),
|
||||
format!("i8 {max_flag}"),
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let next = self.temp();
|
||||
self.line(format!(
|
||||
"{next} = insertvalue %dever.api_cookie_options {row}, {field}, {index}"
|
||||
));
|
||||
row = next;
|
||||
}
|
||||
self.line(format!("store %dever.api_cookie_options {row}, ptr {slot}"));
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
impl Module<'_> {
|
||||
fn api_permission_pack(&mut self, id: usize) -> String {
|
||||
let name = format!("@dever_permission_pack_{id}");
|
||||
// The compiler-owned callback is reused by every call of permissions().
|
||||
if self.api_permission_types.insert(id) {
|
||||
let mut body = format!(
|
||||
"define internal void {name}(ptr %key, ptr %component, ptr %domain, ptr %site, ptr %action, ptr %method, ptr %out) {{\nentry:\n"
|
||||
);
|
||||
for (index, field) in ["key", "component", "domain", "site", "action", "method"]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
writeln!(body, " %owned{index} = call ptr @dever_rt_v1_text_retain(ptr %{field})\n %field{index} = getelementptr %T{id}, ptr %out, i32 0, i32 {index}\n store ptr %owned{index}, ptr %field{index}").unwrap();
|
||||
}
|
||||
body.push_str(" ret void\n}\n");
|
||||
self.declarations.push_str(&body);
|
||||
}
|
||||
name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,798 @@
|
||||
//! Compiler-owned application roots. Settings live until the root Scope drains.
|
||||
use super::*;
|
||||
|
||||
/// Textual code and borrowed binary initializers form one compilation input.
|
||||
pub struct ResourceModule<'a> {
|
||||
pub ir: String,
|
||||
pub resources: Vec<BinaryResource<'a>>,
|
||||
}
|
||||
|
||||
pub struct BinaryResource<'a> {
|
||||
pub symbol: String,
|
||||
pub bytes: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Application {
|
||||
pub root: usize,
|
||||
pub ports: Vec<usize>,
|
||||
pub adapters: Vec<usize>,
|
||||
pub api_handlers: Vec<usize>,
|
||||
pub command_handlers: Vec<usize>,
|
||||
pub rest_handlers: Vec<rest::Handler>,
|
||||
pub rest_error: Option<usize>,
|
||||
pub job_handlers: Vec<usize>,
|
||||
pub test: Option<usize>,
|
||||
pub resources: Option<(String, usize)>,
|
||||
pub executable: bool,
|
||||
}
|
||||
|
||||
/// Emit the checked production application entry, independently of ordinary kernels.
|
||||
/// The caller supplies Unit output and the module's fault slot, then releases
|
||||
/// both using the same generated ownership exports as other LLVM entries.
|
||||
pub fn emit_application(program: &Program, sources: &SourceMap) -> Result<String, String> {
|
||||
emit_application_with_resources(program, sources, &[]).map(|module| module.ir)
|
||||
}
|
||||
|
||||
/// Embed already prepared Worker resources; registry resolution stays with the packager.
|
||||
pub fn emit_application_with_resources<'a>(
|
||||
program: &Program,
|
||||
sources: &SourceMap,
|
||||
resources: &'a [crate::native::EmbeddedResource],
|
||||
) -> Result<ResourceModule<'a>, String> {
|
||||
emit_program_with_resources(program, sources, None, resources, false)
|
||||
}
|
||||
|
||||
/// Emit a complete production executable with the C ABI process entry.
|
||||
pub fn emit_executable(program: &Program, sources: &SourceMap) -> Result<String, String> {
|
||||
emit_executable_with_resources(program, sources, &[]).map(|module| module.ir)
|
||||
}
|
||||
|
||||
/// The same application entry with already verified, immutable Worker resources.
|
||||
pub fn emit_executable_with_resources<'a>(
|
||||
program: &Program,
|
||||
sources: &SourceMap,
|
||||
resources: &'a [crate::native::EmbeddedResource],
|
||||
) -> Result<ResourceModule<'a>, String> {
|
||||
emit_program_with_resources(program, sources, None, resources, true)
|
||||
}
|
||||
|
||||
pub(super) fn emit_program(
|
||||
program: &Program,
|
||||
sources: &SourceMap,
|
||||
test: Option<usize>,
|
||||
) -> Result<String, String> {
|
||||
emit_program_with_resources(program, sources, test, &[], false).map(|module| module.ir)
|
||||
}
|
||||
|
||||
fn emit_program_with_resources<'a>(
|
||||
program: &Program,
|
||||
sources: &SourceMap,
|
||||
test: Option<usize>,
|
||||
resources: &'a [crate::native::EmbeddedResource],
|
||||
executable: bool,
|
||||
) -> Result<ResourceModule<'a>, String> {
|
||||
crate::native::validate_embedded_resources(resources)?;
|
||||
validate_http_bindings(program, sources)?;
|
||||
let span = test
|
||||
.map(|index| program.tests[index].span)
|
||||
.or_else(|| {
|
||||
program
|
||||
.api_routes
|
||||
.first()
|
||||
.map(|route| route.span)
|
||||
.or_else(|| program.api_rest.first().map(|route| route.span))
|
||||
.or_else(|| {
|
||||
program
|
||||
.api_commands
|
||||
.first()
|
||||
.map(|command| program.functions[command.function].span)
|
||||
})
|
||||
.or_else(|| {
|
||||
program
|
||||
.jobs
|
||||
.first()
|
||||
.map(|job| program.functions[job.function].span)
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| "LLVM application has no API, REST, CMD, or Job entry".to_owned())?;
|
||||
let mut checked = program.clone();
|
||||
let (rest_handlers, rest_error) = rest::prepare(&mut checked);
|
||||
let roots = program
|
||||
.api_commands
|
||||
.iter()
|
||||
.map(|command| command.function)
|
||||
.chain(program.api_routes.iter().map(|route| route.function))
|
||||
.chain(program.auth.iter().map(|hook| hook.function))
|
||||
.chain(rest_handlers.iter().map(|handler| handler.function))
|
||||
.chain(
|
||||
program
|
||||
.jobs
|
||||
.iter()
|
||||
.filter(|_| test.is_none())
|
||||
.map(|job| job.function),
|
||||
)
|
||||
.chain(test.map(|index| program.tests[index].function))
|
||||
.map(|function| Specialization {
|
||||
function,
|
||||
handlers: Vec::new(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut instances = specialize::execution_reachable(&checked.functions, roots)
|
||||
.map_err(|span| located(sources, span, "recursive specialized call"))?;
|
||||
checked.jobs.retain(|job| {
|
||||
instances
|
||||
.iter()
|
||||
.any(|instance| instance.function == job.function)
|
||||
});
|
||||
for instance in &instances {
|
||||
if instance.function < program.functions.len() {
|
||||
validate_function(&checked, sources, instance, true)?;
|
||||
}
|
||||
}
|
||||
let ports = instances
|
||||
.iter()
|
||||
.filter_map(|instance| {
|
||||
checked.functions[instance.function]
|
||||
.port
|
||||
.as_ref()
|
||||
.map(|_| instance.function)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let owners = ports
|
||||
.iter()
|
||||
.map(|port| program.functions[*port].owner)
|
||||
.collect::<BTreeSet<_>>();
|
||||
let adapters = program
|
||||
.adapters
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(id, adapter)| {
|
||||
(test.is_none() && !adapter.fake && owners.contains(&adapter.port_owner)).then_some(id)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for owner in owners {
|
||||
if test.is_some() {
|
||||
continue;
|
||||
}
|
||||
if !adapters
|
||||
.iter()
|
||||
.any(|id| program.adapters[*id].port_owner == owner)
|
||||
{
|
||||
return Err(located(
|
||||
sources,
|
||||
span,
|
||||
"reachable Port has no production Adapter",
|
||||
));
|
||||
}
|
||||
}
|
||||
// The synthetic root has no source-callable identity. Its body is generated
|
||||
// below; the existing descriptors still own all concrete entry callees.
|
||||
let mut command_handlers = Vec::new();
|
||||
for (index, command) in program.api_commands.iter().enumerate() {
|
||||
let original = &program.functions[command.function];
|
||||
let mut function = generated_function(format!("<generated CMD {index}>"), original.span);
|
||||
function.parameters = vec![Parameter::Value(Type::Text)];
|
||||
let id = append_handler(&mut checked, command.function, function);
|
||||
command_handlers.push(id);
|
||||
instances.insert(Specialization {
|
||||
function: id,
|
||||
handlers: Vec::new(),
|
||||
});
|
||||
}
|
||||
let mut api_handlers = Vec::new();
|
||||
for (index, route) in program.api_routes.iter().enumerate() {
|
||||
let original = &program.functions[route.function];
|
||||
let mut function = generated_function(format!("<generated API {index}>"), route.span);
|
||||
function.parameters = original.parameters.clone();
|
||||
function.outputs = vec![crate::types::Field {
|
||||
name: "encoded".into(),
|
||||
ty: Type::Text,
|
||||
private: false,
|
||||
bounds: Vec::new(),
|
||||
}];
|
||||
let id = append_handler(&mut checked, route.function, function);
|
||||
api_handlers.push(id);
|
||||
instances.insert(Specialization {
|
||||
function: id,
|
||||
handlers: Vec::new(),
|
||||
});
|
||||
}
|
||||
let mut job_handlers = Vec::new();
|
||||
for job in checked.jobs.clone() {
|
||||
let original = checked.functions[job.function].clone();
|
||||
let mut function =
|
||||
generated_function(format!("<generated Job {}>", job.function), original.span);
|
||||
function.parameters = original.parameters.clone();
|
||||
let id = append_handler(&mut checked, job.function, function);
|
||||
job_handlers.push(id);
|
||||
instances.insert(Specialization {
|
||||
function: id,
|
||||
handlers: Vec::new(),
|
||||
});
|
||||
}
|
||||
let root = append_generated(
|
||||
&mut checked,
|
||||
generated_function("<generated application entry>".into(), span),
|
||||
);
|
||||
checked.failures[root] = instances
|
||||
.iter()
|
||||
.flat_map(|instance| specialize::failures(&checked, instance))
|
||||
.collect();
|
||||
let database = test.map_or(
|
||||
!checked.models.is_empty() || !checked.jobs.is_empty(),
|
||||
|index| checked.tests[index].uses_database(),
|
||||
);
|
||||
if database {
|
||||
checked.failures[root].extend(crate::capture::database_errors(&checked.types));
|
||||
}
|
||||
let root_instance = Specialization {
|
||||
function: root,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
instances.insert(root_instance.clone());
|
||||
let mut module = Module::new(&checked, sources, instances);
|
||||
module.collect_external_types();
|
||||
for handler in &rest_handlers {
|
||||
let decoded = Type::Outputs(
|
||||
handler.inputs[usize::from(handler.detail)..]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, input)| crate::types::Field {
|
||||
name: format!("p{index}"),
|
||||
ty: input.ty.clone(),
|
||||
private: false,
|
||||
bounds: Vec::new(),
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
collect_owned_types(&checked, &decoded, &mut module.value_types);
|
||||
}
|
||||
for model in checked.models.iter().filter(|_| database) {
|
||||
for id in [model.record, model.page, model.cursor]
|
||||
.into_iter()
|
||||
.chain(model.sql.iter().map(|sql| sql.result.record))
|
||||
{
|
||||
collect_types(&checked, &Type::Named(id), &mut module.type_ids);
|
||||
collect_owned_types(&checked, &Type::Named(id), &mut module.value_types);
|
||||
}
|
||||
for ty in [
|
||||
Type::List(Box::new(Type::Named(model.record))),
|
||||
Type::Nullable(Box::new(Type::Named(model.record))),
|
||||
Type::Map(
|
||||
Box::new(Type::Int),
|
||||
Box::new(Type::List(Box::new(Type::Named(model.record)))),
|
||||
),
|
||||
] {
|
||||
collect_owned_types(&checked, &ty, &mut module.value_types);
|
||||
}
|
||||
}
|
||||
for ty in [
|
||||
Type::Text,
|
||||
Type::Json,
|
||||
Type::Bool,
|
||||
Type::Int,
|
||||
Type::List(Box::new(Type::Text)),
|
||||
Type::Nullable(Box::new(Type::Text)),
|
||||
] {
|
||||
collect_owned_types(&checked, &ty, &mut module.value_types);
|
||||
}
|
||||
for adapter in &adapters {
|
||||
if let Some(id) = checked.adapters[*adapter].setting {
|
||||
let ty = Type::Named(id);
|
||||
collect_types(&checked, &ty, &mut module.type_ids);
|
||||
collect_owned_types(&checked, &ty, &mut module.value_types);
|
||||
}
|
||||
}
|
||||
module.application = Some(Application {
|
||||
root,
|
||||
ports,
|
||||
adapters,
|
||||
api_handlers,
|
||||
command_handlers,
|
||||
rest_handlers,
|
||||
rest_error,
|
||||
job_handlers,
|
||||
test,
|
||||
resources: None,
|
||||
executable,
|
||||
});
|
||||
module.validate_async_boundaries(sources)?;
|
||||
module.emit_types()?;
|
||||
module.declarations.push_str(wire::DECLARATIONS);
|
||||
module.declarations.push_str(ports::DECLARATIONS);
|
||||
let inputs = crate::native::resource_inputs(resources);
|
||||
let attachments = inputs
|
||||
.unique
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, resource)| BinaryResource {
|
||||
symbol: format!("dever_external_bytes_{index}"),
|
||||
bytes: resource.bytes.as_slice(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
module.emit_external_descriptors(resources, &inputs.indices, &attachments);
|
||||
if module.api_enabled() {
|
||||
module.declarations.push_str(api::DECLARATIONS);
|
||||
module.declarations.push_str(api_context::DECLARATIONS);
|
||||
module.emit_api_descriptors();
|
||||
}
|
||||
if module.database_enabled() {
|
||||
module.declarations.push_str(database::DECLARATIONS);
|
||||
module.emit_database_slots();
|
||||
}
|
||||
module.emit_job_descriptors();
|
||||
module.declarations.push_str("declare i32 @dever_rt_v1_text_copy_utf8(ptr, i64, ptr)\ndeclare i32 @dever_rt_v1_text_export(ptr, ptr)\n");
|
||||
module.emit_port_slots();
|
||||
module.emit_functions()?;
|
||||
module.emit_named_entry(&root_instance, "dever_application_run", true);
|
||||
module.emit_application_entry(&root_instance);
|
||||
if test.is_some() {
|
||||
module.emit_test_exports(&root_instance);
|
||||
}
|
||||
if executable {
|
||||
module.emit_process_entry(&root_instance);
|
||||
}
|
||||
Ok(ResourceModule {
|
||||
ir: module.finish(),
|
||||
resources: attachments,
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_http_bindings(program: &Program, sources: &SourceMap) -> Result<(), String> {
|
||||
for route in &program.api_routes {
|
||||
if route.auth.is_none() || (!route.anonymous && route.permission.is_none()) {
|
||||
return Err(located(
|
||||
sources,
|
||||
route.span,
|
||||
"LLVM API application requires a configured site, auth hook, and protected-route permission; check the application with settings",
|
||||
));
|
||||
}
|
||||
}
|
||||
for route in &program.api_rest {
|
||||
if route.auth.is_none()
|
||||
|| [
|
||||
route.permissions.read,
|
||||
route.permissions.create,
|
||||
route.permissions.replace,
|
||||
route.permissions.delete,
|
||||
]
|
||||
.iter()
|
||||
.any(Option::is_none)
|
||||
{
|
||||
return Err(located(
|
||||
sources,
|
||||
route.span,
|
||||
"LLVM REST application requires a configured site, auth hook, and all four permissions; check the application with settings",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn generated_function(name: String, span: Span) -> crate::hir::Function {
|
||||
crate::hir::Function {
|
||||
name,
|
||||
owner: 0,
|
||||
public: false,
|
||||
parameters: Vec::new(),
|
||||
outputs: Vec::new(),
|
||||
clauses: Vec::new(),
|
||||
span,
|
||||
suspends: true,
|
||||
pure: false,
|
||||
recovery: None,
|
||||
kind: FunctionKind::Ordinary,
|
||||
port: None,
|
||||
implementation: false,
|
||||
setting: None,
|
||||
external: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn append_generated(program: &mut Program, function: crate::hir::Function) -> usize {
|
||||
let id = program.functions.len();
|
||||
program.functions.push(function);
|
||||
program.effects.push(BTreeSet::new());
|
||||
program.recoveries.push(BTreeSet::new());
|
||||
program.suspension_handlers.push(BTreeSet::new());
|
||||
program.failure_handlers.push(BTreeSet::new());
|
||||
program.database_effects.push(BTreeSet::new());
|
||||
program.failures.push(BTreeSet::new());
|
||||
id
|
||||
}
|
||||
|
||||
fn append_handler(
|
||||
program: &mut Program,
|
||||
target: usize,
|
||||
mut function: crate::hir::Function,
|
||||
) -> usize {
|
||||
let original = &program.functions[target];
|
||||
function.owner = original.owner;
|
||||
// Generated bodies keep their original checked callee's transitive
|
||||
// Send, failure, and database effects through this analysis-only edge.
|
||||
function.clauses = generated_call_edge(original, target);
|
||||
let id = append_generated(program, function);
|
||||
program.effects[id] = program.effects[target].clone();
|
||||
program.failures[id] = program.failures[target].clone();
|
||||
program.database_effects[id] = program.database_effects[target].clone();
|
||||
id
|
||||
}
|
||||
|
||||
fn generated_call_edge(function: &crate::hir::Function, target: usize) -> Vec<crate::hir::Clause> {
|
||||
vec![crate::hir::Clause {
|
||||
patterns: Vec::new(),
|
||||
bindings: Vec::new(),
|
||||
locals: Vec::new(),
|
||||
outputs: Vec::new(),
|
||||
terminates: false,
|
||||
body: vec![Statement::Call(Expression {
|
||||
kind: ExpressionKind::Call {
|
||||
target: CallTarget::Function(target),
|
||||
arguments: function
|
||||
.parameters
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, parameter)| {
|
||||
CallArgument::Value(Expression {
|
||||
kind: ExpressionKind::Local(index),
|
||||
ty: parameter.value_type().expect("entry value input").clone(),
|
||||
span: function.span,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
ty: output_type(&function.outputs),
|
||||
span: function.span,
|
||||
})],
|
||||
}]
|
||||
}
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn emit_command_body(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
index: usize,
|
||||
) -> Result<(), String> {
|
||||
let function = self.program.functions[instance.function].clone();
|
||||
let command = self.program.api_commands[index].clone();
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
let parameters = emitter.begin_generated_function(&function);
|
||||
let body = emitter.parameter(0);
|
||||
emitter.emit_command(&command, index, &body)?;
|
||||
let name = emitter.module.names[instance].clone();
|
||||
let body = emitter.finish_function(&name, ¶meters);
|
||||
self.functions.push_str(&body);
|
||||
Ok(())
|
||||
}
|
||||
pub(super) fn emit_application_body(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
) -> Result<(), String> {
|
||||
let function = self.program.functions[instance.function].clone();
|
||||
let commands = self.program.api_commands.clone();
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
emitter.start("entry");
|
||||
emitter
|
||||
.prologue
|
||||
.push_str(" %exit_status = alloca i32\n store i32 0, ptr %exit_status\n");
|
||||
emitter.begin_coroutine(&function);
|
||||
let span = function.span;
|
||||
emitter.initialize_clock(span);
|
||||
emitter.initialize_external_resources(span);
|
||||
let arguments_ty = Type::List(Box::new(Type::Text));
|
||||
let arguments = emitter.runtime_call(
|
||||
"dever_rt_v1_process_arguments",
|
||||
vec![format!(
|
||||
"ptr @dever_type_{}",
|
||||
emitter.module.type_index(&Type::Text)
|
||||
)],
|
||||
&arguments_ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
emitter.own_value(&arguments_ty, &arguments, false);
|
||||
let length = emitter.runtime_call(
|
||||
"dever_rt_v1_list_length",
|
||||
vec![format!("ptr {arguments}")],
|
||||
&Type::Int,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let empty = emitter.temp();
|
||||
emitter.line(format!("{empty} = icmp eq i64 {length}, 0"));
|
||||
let migration = emitter.initialize_database(span, &empty);
|
||||
emitter.line(format!(
|
||||
"br i1 {empty}, label %command_empty, label %command_check_arity"
|
||||
));
|
||||
emitter.start("command_empty");
|
||||
emitter.initialize_ports(span);
|
||||
if !emitter.module.program.api_routes.is_empty()
|
||||
|| !emitter.module.program.api_rest.is_empty()
|
||||
|| emitter.module.jobs_enabled()
|
||||
{
|
||||
emitter.serve_api(span);
|
||||
emitter.exit("0");
|
||||
} else {
|
||||
emitter.application_fault(
|
||||
"application has no service entry; select a CMD and JSON input",
|
||||
span,
|
||||
);
|
||||
}
|
||||
emitter.start("command_check_arity");
|
||||
let retained = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{retained} = call ptr @dever_rt_v1_list_retain(ptr {arguments})"
|
||||
));
|
||||
let cursor = emitter.protocol_call(
|
||||
"dever_rt_v1_list_cursor_take",
|
||||
vec![format!("ptr {retained}")],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
let cursor_slot = emitter.entry_slot_ir("ptr");
|
||||
emitter.line(format!("store ptr {cursor}, ptr {cursor_slot}"));
|
||||
let guard = emitter.register_owned(OwnedKind::ListCursor, cursor_slot);
|
||||
emitter.mark_live(guard);
|
||||
let first = emitter.application_argument(&cursor, span);
|
||||
let (name, body, tenant) = emitter.application_command_arguments(
|
||||
&first,
|
||||
&cursor,
|
||||
&length,
|
||||
migration.as_ref(),
|
||||
span,
|
||||
)?;
|
||||
emitter.release_guard(guard);
|
||||
emitter.initialize_ports(span);
|
||||
for (index, command) in commands.iter().enumerate() {
|
||||
let command_name = emitter.application_text(&command.name, span);
|
||||
let equal = emitter.equals(&Type::Text, &name, &command_name)?;
|
||||
emitter.line(format!(
|
||||
"br i1 {equal}, label %command_{index}, label %command_next_{index}"
|
||||
));
|
||||
emitter.start(&format!("command_{index}"));
|
||||
let function = emitter
|
||||
.module
|
||||
.application
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.command_handlers[index];
|
||||
let target = Specialization {
|
||||
function,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
emitter.application_invoke_command(command, &target, &body, &tenant, span);
|
||||
emitter.exit("0");
|
||||
emitter.start(&format!("command_next_{index}"));
|
||||
}
|
||||
let prefix = emitter.application_text("unknown CMD '", span);
|
||||
let suffix = emitter.application_text("'", span);
|
||||
let message = emitter.application_concat(&prefix, &name, span);
|
||||
let message = emitter.application_concat(&message, &suffix, span);
|
||||
emitter.application_fault_text(&message, span);
|
||||
let name = emitter.module.names[instance].clone();
|
||||
let body = emitter.finish_function(&name, "");
|
||||
self.functions.push_str(&body);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_application_entry(&mut self, root: &Specialization) {
|
||||
let app = self.application.clone().unwrap();
|
||||
let gated = !app.ports.is_empty()
|
||||
|| self.database_enabled()
|
||||
|| self.api_enabled()
|
||||
|| self.clock_enabled();
|
||||
let span = self.program.functions[root.function].span;
|
||||
self.functions.push_str("define i32 @dever_application_entry(ptr %out, ptr %fault) {\nentry:\n store {} zeroinitializer, ptr %out\n store %dever.fault zeroinitializer, ptr %fault\n");
|
||||
if gated {
|
||||
self.declarations
|
||||
.push_str("@dever_application_active = private global i8 0\n");
|
||||
let (message, length) = self.text_literal("application entry is already active");
|
||||
let location = self.location(span);
|
||||
writeln!(self.functions, " %gate = cmpxchg ptr @dever_application_active, i8 0, i8 1 acq_rel acquire\n %entered = extractvalue {{ i8, i1 }} %gate, 1\n br i1 %entered, label %run, label %busy\nbusy:\n %message = getelementptr %dever.fault, ptr %fault, i32 0, i32 4\n %copied = call i32 @dever_rt_v1_text_copy_utf8(ptr {message}, i64 {length}, ptr %message)\n %code = getelementptr %dever.fault, ptr %fault, i32 0, i32 0\n store i32 3, ptr %code\n %origin = getelementptr %dever.fault, ptr %fault, i32 0, i32 1\n store i32 {location}, ptr %origin\n ret i32 1\nrun:").unwrap();
|
||||
}
|
||||
self.functions
|
||||
.push_str(" %status = call i32 @dever_application_run(ptr %out, ptr %fault)\n");
|
||||
// async_root has drained every child before these immutable globals can
|
||||
// be destroyed or another entry can publish a new Adapter selection.
|
||||
for adapter in app.adapters {
|
||||
let adapter = &self.program.adapters[adapter];
|
||||
if let Some(id) = adapter.setting {
|
||||
let owner = adapter.owner;
|
||||
let ty = self.type_index(&Type::Named(id));
|
||||
writeln!(self.functions, " %live{owner} = load i1, ptr @dever_setting_live_{owner}\n br i1 %live{owner}, label %drop_setting{owner}, label %setting_done{owner}\ndrop_setting{owner}:\n call void @dever_drop_{ty}(ptr @dever_setting_{owner})\n store %T{id} zeroinitializer, ptr @dever_setting_{owner}\n store i1 false, ptr @dever_setting_live_{owner}\n br label %setting_done{owner}\nsetting_done{owner}:").unwrap();
|
||||
}
|
||||
}
|
||||
for port in app.ports {
|
||||
writeln!(self.functions, " store i64 0, ptr @dever_port_{port}").unwrap();
|
||||
}
|
||||
if self.clock_enabled() {
|
||||
self.functions.push_str(" %job_clock = load ptr, ptr @dever_job_clock\n call void @dever_rt_v1_job_clock_release(ptr %job_clock)\n store ptr null, ptr @dever_job_clock\n");
|
||||
}
|
||||
if self.jobs_enabled() {
|
||||
self.functions.push_str(" %jobs = load ptr, ptr @dever_job_session\n call void @dever_rt_v1_job_session_release(ptr %jobs)\n store ptr null, ptr @dever_job_session\n");
|
||||
}
|
||||
if self.database_enabled() {
|
||||
for index in 0..self.program.models.len() {
|
||||
writeln!(self.functions, " %database{index} = load ptr, ptr @dever_database_{index}\n call void @dever_rt_v1_db_release(ptr %database{index})\n store ptr null, ptr @dever_database_{index}").unwrap();
|
||||
}
|
||||
self.functions.push_str(" %session = load ptr, ptr @dever_database_session\n call void @dever_rt_v1_db_session_release(ptr %session)\n store ptr null, ptr @dever_database_session\n");
|
||||
}
|
||||
if self.api_enabled() {
|
||||
self.functions.push_str(" %api_session = load ptr, ptr @dever_api_session\n call void @dever_rt_v1_api_session_release(ptr %api_session)\n store ptr null, ptr @dever_api_session\n");
|
||||
}
|
||||
if gated {
|
||||
self.functions
|
||||
.push_str(" store atomic i8 0, ptr @dever_application_active release, align 1\n");
|
||||
}
|
||||
self.functions.push_str(" ret i32 %status\n}\n");
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn application_text(&mut self, text: &str, span: Span) -> String {
|
||||
let (pointer, length) = self.module.text_literal(text);
|
||||
let value = self.runtime_call(
|
||||
"dever_rt_v1_text_new",
|
||||
vec![format!("ptr {pointer}"), format!("i64 {length}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &value, false);
|
||||
value
|
||||
}
|
||||
|
||||
fn application_concat(&mut self, left: &str, right: &str, span: Span) -> String {
|
||||
let value = self.runtime_call(
|
||||
"dever_rt_v1_text_concat",
|
||||
vec![format!("ptr {left}"), format!("ptr {right}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &value, false);
|
||||
value
|
||||
}
|
||||
|
||||
pub(super) fn application_fault(&mut self, message: &str, span: Span) {
|
||||
let message = self.application_text(message, span);
|
||||
self.application_fault_text(&message, span);
|
||||
}
|
||||
|
||||
pub(super) fn application_fault_text(&mut self, message: &str, span: Span) {
|
||||
let buffer = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let status = self.temp();
|
||||
self.line(format!(
|
||||
"{status} = call i32 @dever_rt_v1_text_export(ptr {message}, ptr {buffer})"
|
||||
));
|
||||
self.check_runtime_status(&status, &buffer, span);
|
||||
self.check_runtime_status("1", &buffer, span);
|
||||
self.line("unreachable");
|
||||
}
|
||||
|
||||
pub(super) fn application_argument(&mut self, cursor: &str, span: Span) -> String {
|
||||
let value = self.runtime_call(
|
||||
"dever_rt_v1_list_cursor_next",
|
||||
vec![format!("ptr {cursor}")],
|
||||
&Type::Nullable(Box::new(Type::Text)),
|
||||
true,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let argument = self.temp();
|
||||
self.line(format!(
|
||||
"{argument} = extractvalue {{ i1, ptr }} {value}, 1"
|
||||
));
|
||||
self.own_value(&Type::Text, &argument, false);
|
||||
argument
|
||||
}
|
||||
|
||||
fn emit_command(
|
||||
&mut self,
|
||||
command: &crate::hir::ApiCommand,
|
||||
index: usize,
|
||||
body: &str,
|
||||
) -> Result<(), String> {
|
||||
let instance = Specialization {
|
||||
function: command.function,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let function = self.module.program.functions[command.function].clone();
|
||||
let span = function.span;
|
||||
self.application_require_components(&command.components, span);
|
||||
let input_ty = asynchronous::input_type(&function);
|
||||
let mut fields = Vec::new();
|
||||
for (name, parameter) in command.inputs.iter().zip(&function.parameters) {
|
||||
let ty = parameter
|
||||
.value_type()
|
||||
.expect("checked CMD value parameter")
|
||||
.clone();
|
||||
let callback = self
|
||||
.module
|
||||
.wire_callback(&ty, crate::wire::Policy::ApiInput, false);
|
||||
fields.push((name.clone(), ty, callback));
|
||||
}
|
||||
let callback_name = format!("dever_command_decode_{index}");
|
||||
let mut decoder = FunctionEmitter::new(self.module, &instance);
|
||||
decoder.asynchronous = false;
|
||||
decoder.start("entry");
|
||||
let row = decoder.decode_fields(&input_ty, &fields, "%inputs", wire::FieldInput::Command);
|
||||
decoder.line(format!(
|
||||
"store {} {row}, ptr %out",
|
||||
decoder.module.ty(&input_ty)
|
||||
));
|
||||
let callback =
|
||||
decoder.finish_protocol_callback(&callback_name, "ptr %inputs, ptr %out, ptr %error");
|
||||
self.module.declarations.push_str(&callback);
|
||||
let input = self.runtime_call(
|
||||
"dever_rt_v1_cmd_decode",
|
||||
vec![
|
||||
format!("ptr {body}"),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(&input_ty)),
|
||||
format!("ptr @{callback_name}"),
|
||||
],
|
||||
&input_ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&input_ty, &input, false);
|
||||
let mut args = Vec::new();
|
||||
for (index, (_, ty, _)) in fields.iter().enumerate() {
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = extractvalue {} {input}, {index}",
|
||||
self.module.ty(&input_ty)
|
||||
));
|
||||
args.push((ty.clone(), value));
|
||||
}
|
||||
let output_ty = output_type(&function.outputs);
|
||||
let transactional = self.begin_command_transaction(&instance, span);
|
||||
let (status, row) = self.invoke_values(&instance, &args, &output_ty, span);
|
||||
self.propagate_status(&status, span, false);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {row}",
|
||||
self.module.ty(&output_ty)
|
||||
));
|
||||
self.own_value(&output_ty, &value, false);
|
||||
let encoder = self
|
||||
.module
|
||||
.wire_callback(&output_ty, crate::wire::Policy::Output, true);
|
||||
let encoded = self.runtime_call(
|
||||
"dever_rt_v1_wire_encode",
|
||||
vec![
|
||||
format!("ptr {row}"),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(&output_ty)),
|
||||
format!("ptr @{encoder}"),
|
||||
],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &encoded, false);
|
||||
if transactional {
|
||||
self.commit_command_transaction();
|
||||
}
|
||||
let prefix = self.application_text("{\"code\":0,\"message\":\"ok\",\"data\":", span);
|
||||
let suffix = self.application_text("}\n", span);
|
||||
let text = self.application_concat(&prefix, &encoded, span);
|
||||
let text = self.application_concat(&text, &suffix, span);
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_stdout_write",
|
||||
vec![format!("ptr {text}")],
|
||||
&Type::Bool,
|
||||
false,
|
||||
Some("i8"),
|
||||
span,
|
||||
);
|
||||
self.exit("0");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
//! Normalize the existing application CLI into one typed CMD dispatch path.
|
||||
use super::*;
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn application_command_arguments(
|
||||
&mut self,
|
||||
first: &str,
|
||||
cursor: &str,
|
||||
length: &str,
|
||||
migration: Option<&database_schema::Migration>,
|
||||
span: Span,
|
||||
) -> Result<(String, String, String), String> {
|
||||
let name_slot = self.entry_slot(&Type::Text);
|
||||
let body_slot = self.entry_slot(&Type::Text);
|
||||
let tenant_slot = self.entry_slot(&Type::Int);
|
||||
self.line(format!("store i64 0, ptr {tenant_slot}"));
|
||||
if self.module.api_enabled() {
|
||||
self.application_management(first, cursor, length, migration, span)?;
|
||||
let flag = self.application_text("--dever-tenant-cmd", span);
|
||||
let matches = self.equals(&Type::Text, first, &flag)?;
|
||||
self.line(format!(
|
||||
"br i1 {matches}, label %command_tenant, label %command_plain"
|
||||
));
|
||||
self.start("command_tenant");
|
||||
self.application_arity(length, 4, "expected --dever-tenant-cmd, one positive tenant id, CMD name, and one JSON object", span);
|
||||
let tenant = self.application_positive_argument(
|
||||
cursor,
|
||||
"tenant id must be a positive integer",
|
||||
span,
|
||||
);
|
||||
let name = self.application_argument(cursor, span);
|
||||
let body = self.application_argument(cursor, span);
|
||||
self.line(format!("store ptr {name}, ptr {name_slot}"));
|
||||
self.line(format!("store ptr {body}, ptr {body_slot}"));
|
||||
self.line(format!("store i64 {tenant}, ptr {tenant_slot}"));
|
||||
self.line("br label %command_dispatch");
|
||||
self.start("command_plain");
|
||||
}
|
||||
self.application_arity(length, 2, "expected CMD name and one JSON object", span);
|
||||
let body = self.application_argument(cursor, span);
|
||||
self.line(format!("store ptr {first}, ptr {name_slot}"));
|
||||
self.line(format!("store ptr {body}, ptr {body_slot}"));
|
||||
self.line("br label %command_dispatch");
|
||||
self.start("command_dispatch");
|
||||
let name = self.temp();
|
||||
let body = self.temp();
|
||||
let tenant = self.temp();
|
||||
self.line(format!("{name} = load ptr, ptr {name_slot}"));
|
||||
self.line(format!("{body} = load ptr, ptr {body_slot}"));
|
||||
self.line(format!("{tenant} = load i64, ptr {tenant_slot}"));
|
||||
Ok((name, body, tenant))
|
||||
}
|
||||
|
||||
fn application_management(
|
||||
&mut self,
|
||||
first: &str,
|
||||
cursor: &str,
|
||||
length: &str,
|
||||
migration: Option<&database_schema::Migration>,
|
||||
span: Span,
|
||||
) -> Result<(), String> {
|
||||
for (flag, arity) in [
|
||||
("--dever-tenant-migrate", 2),
|
||||
("--dever-tenant-owner", 4),
|
||||
("--dever-tenant-component", 4),
|
||||
] {
|
||||
if flag == "--dever-tenant-owner" && self.module.program.permissions.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let expected = self.application_text(flag, span);
|
||||
let matches = self.equals(&Type::Text, first, &expected)?;
|
||||
let matched = self.label("management");
|
||||
let next = self.label("management_next");
|
||||
self.line(format!("br i1 {matches}, label %{matched}, label %{next}"));
|
||||
self.start(&matched);
|
||||
let usage = match flag {
|
||||
"--dever-tenant-migrate" => {
|
||||
"expected --dever-tenant-migrate and one positive tenant id"
|
||||
}
|
||||
"--dever-tenant-owner" => {
|
||||
"expected --dever-tenant-owner, one positive tenant id, one site, and one positive user id"
|
||||
}
|
||||
_ => {
|
||||
"expected --dever-tenant-component, one positive tenant id, enable or disable, and one component"
|
||||
}
|
||||
};
|
||||
self.application_arity(length, arity, usage, span);
|
||||
let tenant = self.application_positive_argument(
|
||||
cursor,
|
||||
"tenant id must be a positive integer",
|
||||
span,
|
||||
);
|
||||
let session = self.application_session();
|
||||
match flag {
|
||||
"--dever-tenant-migrate" => {
|
||||
if let Some(migration) = migration {
|
||||
let mut arguments = vec![
|
||||
format!("ptr {session}"),
|
||||
format!("ptr {}", migration.bindings),
|
||||
format!("ptr {}", migration.models),
|
||||
format!("ptr {}", migration.errors),
|
||||
format!("i64 {}", migration.count),
|
||||
];
|
||||
let symbol = if self.module.jobs_enabled() {
|
||||
let errors = self.database_errors(span);
|
||||
arguments.push(format!("ptr {errors}"));
|
||||
"api_migrate_application"
|
||||
} else {
|
||||
"api_migrate_models"
|
||||
};
|
||||
arguments.push(format!("i64 {tenant}"));
|
||||
let operation = self.async_operation(symbol, arguments, span);
|
||||
self.await_operation(&operation, &Type::Unit, span, false);
|
||||
self.exit("0");
|
||||
} else {
|
||||
self.application_fault("application has no database runtime", span);
|
||||
}
|
||||
}
|
||||
"--dever-tenant-owner" => {
|
||||
let site = self.application_argument(cursor, span);
|
||||
let user = self.application_positive_argument(
|
||||
cursor,
|
||||
"user id must be a positive integer",
|
||||
span,
|
||||
);
|
||||
let operation = self.async_operation(
|
||||
"api_tenant_owner",
|
||||
vec![
|
||||
format!("ptr {session}"),
|
||||
format!("i64 {tenant}"),
|
||||
format!("ptr {site}"),
|
||||
format!("i64 {user}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Unit, span, false);
|
||||
self.exit("0");
|
||||
}
|
||||
_ => {
|
||||
if self.module.program.tenant_components.is_empty() {
|
||||
self.application_fault("application has no tenant components", span);
|
||||
} else {
|
||||
let action = self.application_argument(cursor, span);
|
||||
let component = self.application_argument(cursor, span);
|
||||
let enable = self.application_text("enable", span);
|
||||
let disable = self.application_text("disable", span);
|
||||
let enabled = self.equals(&Type::Text, &action, &enable)?;
|
||||
let disabled = self.equals(&Type::Text, &action, &disable)?;
|
||||
let valid = self.temp();
|
||||
self.line(format!("{valid} = or i1 {enabled}, {disabled}"));
|
||||
self.application_require(
|
||||
&valid,
|
||||
"operation must be enable or disable",
|
||||
span,
|
||||
);
|
||||
let byte = self.temp();
|
||||
self.line(format!("{byte} = zext i1 {enabled} to i8"));
|
||||
let manifest = self
|
||||
.module
|
||||
.wire_names(&self.module.program.tenant_components.clone());
|
||||
let operation = self.async_operation(
|
||||
"api_tenant_component",
|
||||
vec![
|
||||
format!("ptr {session}"),
|
||||
format!("i64 {tenant}"),
|
||||
format!("ptr {component}"),
|
||||
format!("i8 {byte}"),
|
||||
format!("ptr {manifest}"),
|
||||
format!("i64 {}", self.module.program.tenant_components.len()),
|
||||
],
|
||||
span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Unit, span, false);
|
||||
self.exit("0");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.start(&next);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn application_invoke_command(
|
||||
&mut self,
|
||||
command: &crate::hir::ApiCommand,
|
||||
target: &Specialization,
|
||||
body: &str,
|
||||
tenant: &str,
|
||||
span: Span,
|
||||
) {
|
||||
if self.module.api_enabled() {
|
||||
let selected = self.temp();
|
||||
self.line(format!("{selected} = icmp sgt i64 {tenant}, 0"));
|
||||
let scoped = self.label("command_scoped");
|
||||
let plain = self.label("command_unscoped");
|
||||
self.line(format!("br i1 {selected}, label %{scoped}, label %{plain}"));
|
||||
self.start(&scoped);
|
||||
if command.components.is_empty() {
|
||||
self.application_fault(
|
||||
"selected CMD does not use tenant storage; remove --tenant",
|
||||
span,
|
||||
);
|
||||
} else {
|
||||
let configured = self.application_has_tenant(span);
|
||||
self.application_require(&configured, "tenant storage is not configured", span);
|
||||
let input =
|
||||
asynchronous::input_type(&self.module.program.functions[target.function]);
|
||||
let row = self.temp();
|
||||
self.line(format!(
|
||||
"{row} = insertvalue {} zeroinitializer, ptr {body}, 0",
|
||||
self.module.ty(&input)
|
||||
));
|
||||
let pointer = self.row_pointer(&input, &row);
|
||||
if self.module.jobs_enabled() {
|
||||
self.invoke_system(target, &pointer, tenant, span);
|
||||
} else {
|
||||
let function = self.module.names[target].clone();
|
||||
let operation = self.async_operation(
|
||||
"api_tenant_scope",
|
||||
vec![
|
||||
format!("i64 {tenant}"),
|
||||
format!("ptr @dever_async_{function}"),
|
||||
format!("ptr {pointer}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Unit, span, false);
|
||||
}
|
||||
self.exit("0");
|
||||
}
|
||||
self.start(&plain);
|
||||
if !command.components.is_empty() {
|
||||
let configured = self.application_has_tenant(span);
|
||||
let ordinary = self.temp();
|
||||
self.line(format!("{ordinary} = xor i1 {configured}, true"));
|
||||
self.application_require(
|
||||
&ordinary,
|
||||
"selected CMD requires an explicit tenant; use dever run --tenant",
|
||||
span,
|
||||
);
|
||||
}
|
||||
}
|
||||
if self.module.jobs_enabled() {
|
||||
let input = asynchronous::input_type(&self.module.program.functions[target.function]);
|
||||
let row = self.temp();
|
||||
self.line(format!(
|
||||
"{row} = insertvalue {} zeroinitializer, ptr {body}, 0",
|
||||
self.module.ty(&input)
|
||||
));
|
||||
let pointer = self.row_pointer(&input, &row);
|
||||
self.invoke_system(target, &pointer, "0", span);
|
||||
} else {
|
||||
let (status, _) =
|
||||
self.invoke_values(target, &[(Type::Text, body.into())], &Type::Unit, span);
|
||||
self.propagate_status(&status, span, false);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn application_require_components(&mut self, components: &[String], span: Span) {
|
||||
if !self.module.api_enabled() || components.is_empty() {
|
||||
return;
|
||||
}
|
||||
let session = self.application_session();
|
||||
let names = self.module.wire_names(components);
|
||||
let manifest = self
|
||||
.module
|
||||
.wire_names(&self.module.program.tenant_components.clone());
|
||||
let operation = self.async_operation(
|
||||
"api_require_components",
|
||||
vec![
|
||||
format!("ptr {session}"),
|
||||
format!("ptr {names}"),
|
||||
format!("i64 {}", components.len()),
|
||||
format!("ptr {manifest}"),
|
||||
format!("i64 {}", self.module.program.tenant_components.len()),
|
||||
],
|
||||
span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Unit, span, false);
|
||||
}
|
||||
|
||||
fn application_session(&mut self) -> String {
|
||||
let session = self.temp();
|
||||
self.line(format!("{session} = load ptr, ptr @dever_api_session"));
|
||||
session
|
||||
}
|
||||
|
||||
fn application_has_tenant(&mut self, span: Span) -> String {
|
||||
let session = self.application_session();
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_api_has_tenant",
|
||||
vec![format!("ptr {session}")],
|
||||
&Type::Bool,
|
||||
false,
|
||||
Some("i8"),
|
||||
span,
|
||||
)
|
||||
}
|
||||
|
||||
fn application_positive_argument(&mut self, cursor: &str, message: &str, span: Span) -> String {
|
||||
let text = self.application_argument(cursor, span);
|
||||
let optional = self.runtime_call(
|
||||
"dever_rt_v1_int_parse",
|
||||
vec![format!("ptr {text}")],
|
||||
&Type::Nullable(Box::new(Type::Int)),
|
||||
true,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let present = self.temp();
|
||||
let number = self.temp();
|
||||
let positive = self.temp();
|
||||
let valid = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {{ i1, i64 }} {optional}, 0"
|
||||
));
|
||||
self.line(format!(
|
||||
"{number} = extractvalue {{ i1, i64 }} {optional}, 1"
|
||||
));
|
||||
self.line(format!("{positive} = icmp sgt i64 {number}, 0"));
|
||||
self.line(format!("{valid} = and i1 {present}, {positive}"));
|
||||
self.application_require(&valid, message, span);
|
||||
number
|
||||
}
|
||||
|
||||
fn application_arity(&mut self, length: &str, expected: i64, message: &str, span: Span) {
|
||||
let valid = self.temp();
|
||||
self.line(format!("{valid} = icmp eq i64 {length}, {expected}"));
|
||||
self.application_require(&valid, message, span);
|
||||
}
|
||||
|
||||
pub(super) fn application_require(&mut self, valid: &str, message: &str, span: Span) {
|
||||
let success = self.label("argument_valid");
|
||||
let invalid = self.label("argument_invalid");
|
||||
self.line(format!("br i1 {valid}, label %{success}, label %{invalid}"));
|
||||
self.start(&invalid);
|
||||
self.application_fault(message, span);
|
||||
self.start(&success);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,677 @@
|
||||
//! Coroutine frames, typed async descriptors, and polling at actual suspension points.
|
||||
use super::*;
|
||||
|
||||
pub(super) fn input_type(function: &crate::hir::Function) -> Type {
|
||||
Type::Outputs(
|
||||
function
|
||||
.parameters
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, parameter)| {
|
||||
parameter.value_type().map(|ty| crate::types::Field {
|
||||
name: format!("p{index}"),
|
||||
ty: ty.clone(),
|
||||
private: false,
|
||||
bounds: Vec::new(),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
const DECLARATIONS: &str = "\
|
||||
%dever.owned_type = type { i64, i64, ptr, ptr, i8 }
|
||||
%dever.async_function = type { ptr, ptr, ptr, ptr, ptr, ptr, i8 }
|
||||
%dever.sync_function = type { ptr, ptr, ptr, ptr, i8 }
|
||||
declare token @llvm.coro.id(i32, ptr, ptr, ptr)
|
||||
declare i64 @llvm.coro.size.i64()
|
||||
declare i64 @llvm.coro.align.i64()
|
||||
declare ptr @llvm.coro.begin(token, ptr)
|
||||
declare i8 @llvm.coro.suspend(token, i1)
|
||||
declare ptr @llvm.coro.free(token, ptr)
|
||||
declare i1 @llvm.coro.end(ptr, i1, token)
|
||||
declare void @llvm.coro.resume(ptr)
|
||||
declare void @llvm.coro.destroy(ptr)
|
||||
declare i1 @llvm.coro.done(ptr)
|
||||
declare ptr @dever_rt_v1_async_frame_alloc(i64, i64)
|
||||
declare void @dever_rt_v1_async_frame_free(ptr, i64, i64)
|
||||
declare i32 @dever_rt_v1_async_root(ptr, ptr, ptr, ptr, ptr)
|
||||
declare void @dever_rt_v1_async_complete(ptr, i32)
|
||||
declare i32 @dever_rt_v1_async_op_poll(ptr, ptr, ptr, ptr, ptr, ptr)
|
||||
declare void @dever_rt_v1_async_op_release(ptr)
|
||||
declare ptr @dever_rt_v1_async_call(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_sleep(i64)
|
||||
declare ptr @dever_rt_v1_async_blocking(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_parallel(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_task_run(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_task_run_sync(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_task_wait(ptr)
|
||||
declare ptr @dever_rt_v1_async_task_stop(ptr)
|
||||
declare ptr @dever_rt_v1_async_task_retain(ptr)
|
||||
declare void @dever_rt_v1_async_task_release(ptr)
|
||||
declare i32 @dever_rt_v1_async_group_new(i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_group_run(ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_group_run_sync(ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_group_wait(ptr)
|
||||
declare ptr @dever_rt_v1_async_group_stop(ptr)
|
||||
declare ptr @dever_rt_v1_async_group_retain(ptr)
|
||||
declare void @dever_rt_v1_async_group_release(ptr)
|
||||
declare i32 @dever_rt_v1_async_channel_new(i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_channel_retain(ptr)
|
||||
declare void @dever_rt_v1_async_channel_release(ptr)
|
||||
declare ptr @dever_rt_v1_async_channel_send(ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_channel_receive(ptr)
|
||||
declare ptr @dever_rt_v1_async_channel_close(ptr)
|
||||
";
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn validate_async_boundaries(&self, sources: &SourceMap) -> Result<(), String> {
|
||||
for instance in &self.instances {
|
||||
let function = &self.program.functions[instance.function];
|
||||
if matches!(function.kind, FunctionKind::Job { .. }) && !self.send_safe(instance) {
|
||||
return Err(located(
|
||||
sources,
|
||||
function.span,
|
||||
"LLVM Job boundary requires transferable input, frame, and typed failures",
|
||||
));
|
||||
}
|
||||
let bindings = specialize::handler_bindings(function, instance);
|
||||
let mut rejected = None;
|
||||
crate::check::visit(function, |expression| {
|
||||
let target = match &expression.kind {
|
||||
ExpressionKind::RunCall {
|
||||
target, arguments, ..
|
||||
}
|
||||
| ExpressionKind::ParallelCall { target, arguments }
|
||||
| ExpressionKind::BlockingCall { target, arguments } => match target {
|
||||
CallTarget::Function(function) => Some(specialize::call_specialization(
|
||||
*function, arguments, &bindings,
|
||||
)),
|
||||
CallTarget::Handler(handler) => Some(Specialization {
|
||||
function: specialize::resolve_handler(*handler, &bindings),
|
||||
handlers: Vec::new(),
|
||||
}),
|
||||
},
|
||||
ExpressionKind::Collection {
|
||||
operation: CollectionOp::ParallelEach,
|
||||
handler: Some(handler),
|
||||
..
|
||||
} => Some(Specialization {
|
||||
function: specialize::resolve_handler(*handler, &bindings),
|
||||
handlers: Vec::new(),
|
||||
}),
|
||||
ExpressionKind::Intrinsic {
|
||||
operation:
|
||||
Intrinsic::HttpServe
|
||||
| Intrinsic::HttpServeTls
|
||||
| Intrinsic::HttpServeLive
|
||||
| Intrinsic::HttpServeLiveTls,
|
||||
handler: Some(handler),
|
||||
..
|
||||
} => Some(Specialization {
|
||||
function: specialize::resolve_handler(*handler, &bindings),
|
||||
handlers: Vec::new(),
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(target) = target
|
||||
&& !self.send_safe(&target)
|
||||
{
|
||||
rejected.get_or_insert(expression.span);
|
||||
}
|
||||
});
|
||||
if let Some(span) = rejected {
|
||||
return Err(located(
|
||||
sources,
|
||||
span,
|
||||
"LLVM task or blocking boundary requires a transferable frame, outputs, and typed failures",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn emit_async_types(&mut self) {
|
||||
let parallel = self.instances.iter().any(|instance| {
|
||||
let mut found = false;
|
||||
crate::check::visit(&self.program.functions[instance.function], |expression| {
|
||||
found |= matches!(
|
||||
expression.kind,
|
||||
ExpressionKind::Collection {
|
||||
operation: CollectionOp::ParallelEach,
|
||||
..
|
||||
}
|
||||
);
|
||||
});
|
||||
found
|
||||
});
|
||||
if !self
|
||||
.instances
|
||||
.iter()
|
||||
.any(|instance| specialize::suspends(self.program, instance))
|
||||
&& !self.value_types.iter().any(|ty| {
|
||||
matches!(
|
||||
ty,
|
||||
Type::Task(_)
|
||||
| Type::Group
|
||||
| Type::Channel(_)
|
||||
| Type::AsyncStream(_)
|
||||
| Type::Socket
|
||||
| Type::Listener
|
||||
)
|
||||
})
|
||||
&& !parallel
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.declarations.push_str(DECLARATIONS);
|
||||
self.declarations.push_str(super::network::DECLARATIONS);
|
||||
self.declarations.push_str(super::parallel::DECLARATIONS);
|
||||
self.declarations.push_str(concat!(
|
||||
"define internal void @dever_coro_resume(ptr %frame) {\nentry:\n call void @llvm.coro.resume(ptr %frame)\n ret void\n}\n",
|
||||
"define internal void @dever_coro_destroy(ptr %frame) {\nentry:\n call void @llvm.coro.destroy(ptr %frame)\n ret void\n}\n",
|
||||
"define internal i8 @dever_coro_done(ptr %frame) {\nentry:\n %done = call i1 @llvm.coro.done(ptr %frame)\n %result = zext i1 %done to i8\n ret i8 %result\n}\n",
|
||||
));
|
||||
for (index, ty) in self.value_types.iter().enumerate() {
|
||||
let llvm = self.ty(ty);
|
||||
writeln!(self.declarations, "define internal void @dever_move_{index}(ptr %source, ptr %destination) {{\nentry:\n %value = load {llvm}, ptr %source\n store {llvm} %value, ptr %destination\n store {llvm} zeroinitializer, ptr %source\n ret void\n}}").unwrap();
|
||||
writeln!(self.declarations, "@dever_owned_{index} = private constant %dever.owned_type {{ i64 {}, i64 {}, ptr @dever_move_{index}, ptr @dever_drop_{index}, i8 {} }}", self.bytes(ty), self.alignment(ty), u8::from(self.boundary_transferable(ty))).unwrap();
|
||||
}
|
||||
self.emit_fault_move();
|
||||
for instance in self.instances.clone() {
|
||||
self.emit_async_descriptor(&instance);
|
||||
}
|
||||
}
|
||||
|
||||
// Fault payloads retain their checked nominal type. Never memcpy an erased
|
||||
// payload or treat a business error as its diagnostic text.
|
||||
fn emit_fault_move(&mut self) {
|
||||
self.declarations.push_str("define internal void @dever_fault_move(ptr %source, ptr %destination) {\nentry:\n store %dever.fault zeroinitializer, ptr %destination\n");
|
||||
let capacity = self.instances.len() + 1;
|
||||
for (index, ty) in [
|
||||
"i32".into(),
|
||||
"i32".into(),
|
||||
"i32".into(),
|
||||
format!("[{capacity} x i32]"),
|
||||
"{ ptr, i64 }".into(),
|
||||
"i32".into(),
|
||||
"i32".into(),
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
writeln!(self.declarations, " %s{index} = getelementptr %dever.fault, ptr %source, i32 0, i32 {index}\n %d{index} = getelementptr %dever.fault, ptr %destination, i32 0, i32 {index}\n %v{index} = load {ty}, ptr %s{index}\n store {ty} %v{index}, ptr %d{index}").unwrap();
|
||||
}
|
||||
self.declarations.push_str(" %scause = getelementptr %dever.fault, ptr %source, i32 0, i32 8\n %dcause = getelementptr %dever.fault, ptr %destination, i32 0, i32 8\n %cause = load ptr, ptr %scause\n store ptr %cause, ptr %dcause\n");
|
||||
let failures = self
|
||||
.instances
|
||||
.iter()
|
||||
.flat_map(|instance| specialize::failures(self.program, instance))
|
||||
.map(|failure| failure.ty)
|
||||
.collect::<BTreeSet<_>>();
|
||||
let arms = failures
|
||||
.iter()
|
||||
.map(|id| format!("i32 {}, label %payload_{id}", id + 1))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
writeln!(self.declarations, " switch i32 %v5, label %moved [{arms}]").unwrap();
|
||||
for id in failures {
|
||||
writeln!(self.declarations, "payload_{id}:\n %sp{id} = getelementptr %dever.fault, ptr %source, i32 0, i32 7\n %dp{id} = getelementptr %dever.fault, ptr %destination, i32 0, i32 7\n %p{id} = load %T{id}, ptr %sp{id}\n store %T{id} %p{id}, ptr %dp{id}\n br label %moved").unwrap();
|
||||
}
|
||||
self.declarations
|
||||
.push_str("moved:\n store %dever.fault zeroinitializer, ptr %source\n ret void\n}\n");
|
||||
}
|
||||
|
||||
fn fault_transferable(&self, instance: &Specialization) -> bool {
|
||||
specialize::failures(self.program, instance)
|
||||
.iter()
|
||||
.all(|failure| {
|
||||
let Shape::Choice(variants) = &self.program.types[failure.ty].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
variants[failure.variant]
|
||||
.fields
|
||||
.iter()
|
||||
.all(|field| field.ty.transferable(&self.program.types))
|
||||
})
|
||||
}
|
||||
|
||||
fn frame_send(&self, ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Unit
|
||||
| Type::Task(_)
|
||||
| Type::Group
|
||||
| Type::Socket
|
||||
| Type::Listener
|
||||
| Type::Upload => true,
|
||||
Type::Stream(_) => false,
|
||||
Type::Related(_) | Type::RowStream(_) => ty.transferable(&self.program.types),
|
||||
Type::List(inner)
|
||||
| Type::Nullable(inner)
|
||||
| Type::Channel(inner)
|
||||
| Type::AsyncStream(inner) => self.frame_send(inner),
|
||||
Type::Map(key, value) | Type::MapEntry(key, value) => {
|
||||
self.frame_send(key) && self.frame_send(value)
|
||||
}
|
||||
Type::Outputs(fields) => fields.iter().all(|field| self.frame_send(&field.ty)),
|
||||
Type::Named(id) => match &self.program.types[*id].shape {
|
||||
Shape::Record(fields) => fields.iter().all(|field| self.frame_send(&field.ty)),
|
||||
Shape::Choice(variants) => variants
|
||||
.iter()
|
||||
.flat_map(|variant| &variant.fields)
|
||||
.all(|field| self.frame_send(&field.ty)),
|
||||
},
|
||||
_ => ty.transferable(&self.program.types),
|
||||
}
|
||||
}
|
||||
|
||||
fn boundary_transferable(&self, ty: &Type) -> bool {
|
||||
// Unit and Outputs are compiler-internal ABI rows. HTTP may transfer
|
||||
// its affine Upload owner; source spawn checks still forbid Upload.
|
||||
match ty {
|
||||
Type::Unit | Type::Upload => true,
|
||||
Type::Outputs(fields) => fields
|
||||
.iter()
|
||||
.all(|field| self.boundary_transferable(&field.ty)),
|
||||
_ => ty.transferable(&self.program.types),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_safe(&self, instance: &Specialization) -> bool {
|
||||
let function = &self.program.functions[instance.function];
|
||||
let boundary = function
|
||||
.parameters
|
||||
.iter()
|
||||
.filter_map(Parameter::value_type)
|
||||
.all(|ty| self.boundary_transferable(ty))
|
||||
&& function
|
||||
.outputs
|
||||
.iter()
|
||||
.all(|field| field.ty.transferable(&self.program.types))
|
||||
&& self.fault_transferable(instance);
|
||||
if !boundary || !specialize::suspends(self.program, instance) {
|
||||
return boundary;
|
||||
}
|
||||
// A Send caller also carries the futures of ordinary async callees.
|
||||
specialize::reachable(&self.program.functions, [instance.clone()])
|
||||
.expect("validated acyclic calls")
|
||||
.iter()
|
||||
.all(|callee| {
|
||||
if !specialize::suspends(self.program, callee) {
|
||||
return true;
|
||||
}
|
||||
let function = &self.program.functions[callee.function];
|
||||
let mut expressions_send = true;
|
||||
crate::check::visit(function, |expression| {
|
||||
expressions_send &= self.frame_send(&expression.ty)
|
||||
});
|
||||
expressions_send
|
||||
&& function
|
||||
.parameters
|
||||
.iter()
|
||||
.filter_map(Parameter::value_type)
|
||||
.all(|ty| self.frame_send(ty))
|
||||
&& function
|
||||
.clauses
|
||||
.iter()
|
||||
.flat_map(|clause| &clause.locals)
|
||||
.all(|ty| self.frame_send(ty))
|
||||
&& function
|
||||
.outputs
|
||||
.iter()
|
||||
.all(|field| self.frame_send(&field.ty))
|
||||
&& self.fault_transferable(callee)
|
||||
})
|
||||
}
|
||||
|
||||
fn emit_async_descriptor(&mut self, instance: &Specialization) {
|
||||
let function = &self.program.functions[instance.function];
|
||||
let name = self.names[instance].clone();
|
||||
let input = input_type(function);
|
||||
let input_id = self.type_index(&input);
|
||||
let output_id = self.type_index(&output_type(&function.outputs));
|
||||
writeln!(self.declarations, "@dever_fault_owned_{name} = private constant %dever.owned_type {{ i64 ptrtoint (ptr getelementptr (%dever.fault, ptr null, i32 1) to i64), i64 8, ptr @dever_fault_move, ptr @dever_fault_release, i8 {} }}", u8::from(self.fault_transferable(instance))).unwrap();
|
||||
let asynchronous = specialize::suspends(self.program, instance);
|
||||
let mut wrapper = format!(
|
||||
"define internal {} @dever_{}_create_{name}({}) {{\nentry:\n",
|
||||
if asynchronous { "ptr" } else { "i32" },
|
||||
if asynchronous { "async" } else { "sync" },
|
||||
if asynchronous {
|
||||
"ptr %state, ptr %input, ptr %out, ptr %fault"
|
||||
} else {
|
||||
"ptr %input, ptr %out, ptr %fault"
|
||||
}
|
||||
);
|
||||
wrapper.push_str(" store %dever.fault zeroinitializer, ptr %fault\n");
|
||||
let mut args = Vec::new();
|
||||
for (position, (index, ty)) in function
|
||||
.parameters
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, parameter)| parameter.value_type().map(|ty| (index, ty)))
|
||||
.enumerate()
|
||||
{
|
||||
writeln!(wrapper, " %a{index} = getelementptr {}, ptr %input, i32 0, i32 {position}\n %p{index} = load {}, ptr %a{index}", self.ty(&input), self.ty(ty)).unwrap();
|
||||
args.push(format!("{} %p{index}", self.ty(ty)));
|
||||
}
|
||||
let database = self.database_function(instance);
|
||||
let direct_arguments = args.clone();
|
||||
if database {
|
||||
args.push("ptr null".into());
|
||||
}
|
||||
args.extend(["ptr %out".into(), "ptr %fault".into()]);
|
||||
if asynchronous {
|
||||
args.push("ptr %state".into());
|
||||
}
|
||||
writeln!(
|
||||
wrapper,
|
||||
" %result = call {} @{name}({})",
|
||||
if asynchronous { "ptr" } else { "i32" },
|
||||
args.join(", ")
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
wrapper,
|
||||
" ret {} %result\n}}",
|
||||
if asynchronous { "ptr" } else { "i32" }
|
||||
)
|
||||
.unwrap();
|
||||
self.declarations.push_str(&wrapper);
|
||||
let send_safe = u8::from(self.send_safe(instance));
|
||||
if asynchronous {
|
||||
writeln!(self.declarations, "@dever_async_{name} = private constant %dever.async_function {{ ptr @dever_owned_{output_id}, ptr @dever_fault_owned_{name}, ptr @dever_async_create_{name}, ptr @dever_coro_resume, ptr @dever_coro_destroy, ptr @dever_coro_done, i8 {send_safe} }}").unwrap();
|
||||
if database {
|
||||
let layout = format!("{{ {}, ptr }}", self.ty(&input));
|
||||
writeln!(self.declarations, "define internal ptr @dever_async_context_create_{name}(ptr %state, ptr %input, ptr %out, ptr %fault) {{\nentry:\n %context_ptr = getelementptr {layout}, ptr %input, i32 0, i32 1\n %context = load ptr, ptr %context_ptr\n store %dever.fault zeroinitializer, ptr %fault").unwrap();
|
||||
for (position, (index, ty)) in function
|
||||
.parameters
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, parameter)| parameter.value_type().map(|ty| (index, ty)))
|
||||
.enumerate()
|
||||
{
|
||||
writeln!(self.declarations, " %a{index} = getelementptr {}, ptr %input, i32 0, i32 {position}\n %p{index} = load {}, ptr %a{index}", self.ty(&input), self.ty(ty)).unwrap();
|
||||
}
|
||||
let mut arguments = direct_arguments;
|
||||
arguments.extend([
|
||||
"ptr %context".into(),
|
||||
"ptr %out".into(),
|
||||
"ptr %fault".into(),
|
||||
"ptr %state".into(),
|
||||
]);
|
||||
writeln!(self.declarations, " %result = call ptr @{name}({})\n ret ptr %result\n}}\n@dever_async_context_{name} = private constant %dever.async_function {{ ptr @dever_owned_{output_id}, ptr @dever_fault_owned_{name}, ptr @dever_async_context_create_{name}, ptr @dever_coro_resume, ptr @dever_coro_destroy, ptr @dever_coro_done, i8 {send_safe} }}", arguments.join(", ")).unwrap();
|
||||
}
|
||||
} else {
|
||||
writeln!(self.declarations, "@dever_sync_{name} = private constant %dever.sync_function {{ ptr @dever_owned_{input_id}, ptr @dever_owned_{output_id}, ptr @dever_fault_owned_{name}, ptr @dever_sync_create_{name}, i8 {send_safe} }}").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn emit_async_entry(&mut self, root: &Specialization) {
|
||||
let name = self.names[root].clone();
|
||||
let location = self.location(self.program.functions[root.function].span);
|
||||
let (symbol, extra) = if self.api_enabled() {
|
||||
(
|
||||
"api_application_root",
|
||||
", ptr @dever_api_session, ptr @dever_api_append_cause".into(),
|
||||
)
|
||||
} else if self.database_enabled() {
|
||||
let errors = self.database_error(root, self.program.functions[root.function].span);
|
||||
(
|
||||
"db_application_root",
|
||||
format!(", ptr @dever_database_session, ptr {errors}"),
|
||||
)
|
||||
} else if self.external_enabled() {
|
||||
("external_root", ", ptr @dever_external_append_cause".into())
|
||||
} else {
|
||||
("async_root", String::new())
|
||||
};
|
||||
writeln!(self.functions, " %error = alloca {{ ptr, i64 }}\n %input = alloca {{}}\n %async_status = call i32 @dever_rt_v1_{symbol}(ptr @dever_async_{name}, ptr %input, ptr %out, ptr %fault{extra}, ptr %error)\n switch i32 %async_status, label %root_error [i32 1, label %root_success i32 2, label %root_fault]\nroot_error:\n %message = load {{ ptr, i64 }}, ptr %error\n %message_ptr = getelementptr %dever.fault, ptr %fault, i32 0, i32 4\n store {{ ptr, i64 }} %message, ptr %message_ptr\n %code = getelementptr %dever.fault, ptr %fault, i32 0, i32 0\n store i32 3, ptr %code\n %origin = getelementptr %dever.fault, ptr %fault, i32 0, i32 1\n store i32 {location}, ptr %origin\n br label %root_fault\nroot_success:\n br label %root_return\nroot_fault:\n br label %root_return\nroot_return:\n %status = phi i32 [0, %root_success], [1, %root_fault]").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn parameter(&self, index: usize) -> String {
|
||||
self.parameters
|
||||
.get(&index)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("%p{index}"))
|
||||
}
|
||||
|
||||
pub(super) fn begin_coroutine(&mut self, function: &crate::hir::Function) {
|
||||
self.prologue.push_str(concat!(
|
||||
" %cancelling = alloca i1\n store i1 0, ptr %cancelling\n",
|
||||
" %coro_id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)\n",
|
||||
" %coro_size = call i64 @llvm.coro.size.i64()\n %coro_align = call i64 @llvm.coro.align.i64()\n",
|
||||
" %coro_memory = call ptr @dever_rt_v1_async_frame_alloc(i64 %coro_size, i64 %coro_align)\n",
|
||||
" %coro_frame = call ptr @llvm.coro.begin(token %coro_id, ptr %coro_memory)\n",
|
||||
));
|
||||
// create() receives borrowed arguments. Acquire frame ownership before
|
||||
// the initial suspend returns to its caller, not on the first poll.
|
||||
for (index, parameter) in function.parameters.iter().enumerate() {
|
||||
let Some(ty) = parameter.value_type() else {
|
||||
continue;
|
||||
};
|
||||
if contains_owned(self.module.program, ty) {
|
||||
let guard = self.own_value(ty, &format!("%p{index}"), true);
|
||||
let owned = self.load_owned_value(ty, guard);
|
||||
self.parameters.insert(index, owned);
|
||||
}
|
||||
}
|
||||
if self.database_context != "null" {
|
||||
let retained = self.temp();
|
||||
self.line(format!(
|
||||
"{retained} = call ptr @dever_rt_v1_db_transaction_retain(ptr {})",
|
||||
self.database_context
|
||||
));
|
||||
self.protocol_owner("db_transaction", &retained);
|
||||
self.database_context = retained;
|
||||
}
|
||||
self.suspend("async_start", false);
|
||||
self.start("async_start");
|
||||
}
|
||||
|
||||
fn suspend(&mut self, resume: &str, final_suspend: bool) {
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = call i8 @llvm.coro.suspend(token none, i1 {})",
|
||||
u8::from(final_suspend)
|
||||
));
|
||||
let arms = if final_suspend {
|
||||
"i8 1, label %free_frame".into()
|
||||
} else {
|
||||
format!("i8 0, label %{resume} i8 1, label %destroy_frame")
|
||||
};
|
||||
self.line(format!(
|
||||
"switch i8 {result}, label %suspend_return [{arms}]"
|
||||
));
|
||||
}
|
||||
|
||||
pub(super) fn end_coroutine(&mut self, status: &str) {
|
||||
let cancelling = self.temp();
|
||||
self.line(format!("{cancelling} = load i1, ptr %cancelling"));
|
||||
self.line(format!(
|
||||
"br i1 {cancelling}, label %free_frame, label %complete_frame"
|
||||
));
|
||||
self.start("complete_frame");
|
||||
let okay = self.temp();
|
||||
self.line(format!("{okay} = icmp eq i32 {status}, 0"));
|
||||
let completion = self.temp();
|
||||
self.line(format!("{completion} = select i1 {okay}, i32 1, i32 2"));
|
||||
self.line(format!(
|
||||
"call void @dever_rt_v1_async_complete(ptr %state, i32 {completion})"
|
||||
));
|
||||
self.suspend("", true);
|
||||
self.start("destroy_frame");
|
||||
self.line("store i1 1, ptr %cancelling");
|
||||
self.line("br label %cleanup");
|
||||
self.start("free_frame");
|
||||
self.line("%allocation = call ptr @llvm.coro.free(token %coro_id, ptr %coro_frame)");
|
||||
self.line("call void @dever_rt_v1_async_frame_free(ptr %allocation, i64 %coro_size, i64 %coro_align)");
|
||||
self.line("br label %suspend_return");
|
||||
self.start("suspend_return");
|
||||
self.line("call i1 @llvm.coro.end(ptr %coro_frame, i1 false, token none)");
|
||||
self.line("ret ptr %coro_frame");
|
||||
}
|
||||
|
||||
pub(super) fn pack_input(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
values: &[(Type, String)],
|
||||
owned: bool,
|
||||
) -> (String, Option<usize>) {
|
||||
let ty = input_type(&self.module.program.functions[instance.function]);
|
||||
let source = self.entry_slot(&ty);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {source}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
for (index, (field, value)) in values.iter().enumerate() {
|
||||
let pointer = self.temp();
|
||||
self.line(format!(
|
||||
"{pointer} = getelementptr {}, ptr {source}, i32 0, i32 {index}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {pointer}",
|
||||
self.module.ty(field)
|
||||
));
|
||||
}
|
||||
if !owned {
|
||||
return (source, None);
|
||||
}
|
||||
let destination = self.entry_slot(&ty);
|
||||
self.line(format!(
|
||||
"call void @dever_clone_{}(ptr {source}, ptr {destination})",
|
||||
self.module.type_index(&ty)
|
||||
));
|
||||
let guard = self.register_guard(&ty, destination.clone());
|
||||
self.mark_live(guard);
|
||||
(destination, Some(guard))
|
||||
}
|
||||
|
||||
pub(super) fn async_operation(
|
||||
&mut self,
|
||||
symbol: &str,
|
||||
mut arguments: Vec<String>,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
arguments.push(format!("ptr {error}"));
|
||||
let operation = self.temp();
|
||||
self.line(format!(
|
||||
"{operation} = call ptr @dever_rt_v1_{symbol}({})",
|
||||
arguments.join(", ")
|
||||
));
|
||||
let missing = self.temp();
|
||||
self.line(format!("{missing} = icmp eq ptr {operation}, null"));
|
||||
let failed = self.label("operation_error");
|
||||
let ready = self.label("operation_created");
|
||||
self.line(format!("br i1 {missing}, label %{failed}, label %{ready}"));
|
||||
self.start(&failed);
|
||||
self.check_runtime_status("1", &error, span);
|
||||
self.line("unreachable");
|
||||
self.start(&ready);
|
||||
operation
|
||||
}
|
||||
|
||||
pub(super) fn poll_raw(
|
||||
&mut self,
|
||||
operation: &str,
|
||||
row: &Type,
|
||||
) -> (String, String, String, String) {
|
||||
self.poll_raw_layout(operation, &self.module.ty(row))
|
||||
}
|
||||
|
||||
pub(super) fn poll_raw_layout(
|
||||
&mut self,
|
||||
operation: &str,
|
||||
layout: &str,
|
||||
) -> (String, String, String, String) {
|
||||
debug_assert!(
|
||||
self.asynchronous,
|
||||
"only checked suspension points poll operations"
|
||||
);
|
||||
let operation_slot = self.entry_slot_ir("ptr");
|
||||
let guard = self.register_owned(OwnedKind::AsyncOp, operation_slot.clone());
|
||||
self.line(format!("store ptr {operation}, ptr {operation_slot}"));
|
||||
self.mark_live(guard);
|
||||
let output = self.entry_slot_ir(layout);
|
||||
self.line(format!("store {layout} zeroinitializer, ptr {output}"));
|
||||
let present = self.entry_slot_ir("i8");
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let poll = self.label("poll_operation");
|
||||
let waiting = self.label("pending_operation");
|
||||
let ready = self.label("ready_operation");
|
||||
self.line(format!("br label %{poll}"));
|
||||
self.start(&poll);
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_async_op_poll(ptr %state, ptr {operation}, ptr {output}, ptr {present}, ptr %fault, ptr {error})"));
|
||||
let pending = self.temp();
|
||||
self.line(format!("{pending} = icmp eq i32 {status}, 0"));
|
||||
self.line(format!("br i1 {pending}, label %{waiting}, label %{ready}"));
|
||||
self.start(&waiting);
|
||||
self.suspend(&poll, false);
|
||||
self.start(&ready);
|
||||
self.release_guard(guard);
|
||||
(status, output, present, error)
|
||||
}
|
||||
|
||||
pub(super) fn poll_operation(
|
||||
&mut self,
|
||||
operation: &str,
|
||||
output: &Type,
|
||||
span: Span,
|
||||
) -> (String, String) {
|
||||
let (status, value, _, error) = self.poll_raw(operation, output);
|
||||
let legacy = self.poll_status(&status, &error, span);
|
||||
(legacy, value)
|
||||
}
|
||||
|
||||
pub(super) fn poll_status(&mut self, status: &str, error: &str, span: Span) -> String {
|
||||
let error_status = self.temp();
|
||||
self.line(format!("{error_status} = icmp ugt i32 {status}, 2"));
|
||||
let failed = self.label("poll_error");
|
||||
let done = self.label("poll_done");
|
||||
self.line(format!(
|
||||
"br i1 {error_status}, label %{failed}, label %{done}"
|
||||
));
|
||||
self.start(&failed);
|
||||
self.check_runtime_status(status, error, span);
|
||||
self.line("unreachable");
|
||||
self.start(&done);
|
||||
let legacy = self.temp();
|
||||
self.line(format!("{legacy} = sub i32 {status}, 1"));
|
||||
legacy
|
||||
}
|
||||
|
||||
pub(super) fn await_operation(
|
||||
&mut self,
|
||||
operation: &str,
|
||||
output: &Type,
|
||||
span: Span,
|
||||
trace: bool,
|
||||
) -> String {
|
||||
let (status, value) = self.poll_operation(operation, output, span);
|
||||
self.propagate_status(&status, span, trace);
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = load {}, ptr {value}",
|
||||
self.module.ty(output)
|
||||
));
|
||||
result
|
||||
}
|
||||
|
||||
pub(super) fn propagate_status(&mut self, status: &str, span: Span, trace: bool) {
|
||||
let failed = self.temp();
|
||||
self.line(format!("{failed} = icmp ne i32 {status}, 0"));
|
||||
let error = self.label("await_fault");
|
||||
let done = self.label("await_done");
|
||||
self.line(format!("br i1 {failed}, label %{error}, label %{done}"));
|
||||
self.start(&error);
|
||||
if trace {
|
||||
self.append_frame(span);
|
||||
}
|
||||
self.exit(status);
|
||||
self.start(&done);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn supported(operation: CollectionOp, sequence: Option<SequenceKind>) -> bool {
|
||||
let direct = matches!(
|
||||
operation,
|
||||
CollectionOp::Append
|
||||
| CollectionOp::First
|
||||
| CollectionOp::Get
|
||||
| CollectionOp::Put
|
||||
| CollectionOp::Remove
|
||||
| CollectionOp::Length
|
||||
| CollectionOp::Entries
|
||||
| CollectionOp::Close
|
||||
);
|
||||
let traversal = matches!(
|
||||
operation,
|
||||
CollectionOp::Each
|
||||
| CollectionOp::ParallelEach
|
||||
| CollectionOp::Filter
|
||||
| CollectionOp::Find
|
||||
| CollectionOp::Reduce
|
||||
| CollectionOp::ReduceUntil
|
||||
| CollectionOp::Sum
|
||||
) && matches!(
|
||||
sequence,
|
||||
Some(
|
||||
SequenceKind::List
|
||||
| SequenceKind::Bytes
|
||||
| SequenceKind::Stream
|
||||
| SequenceKind::AsyncStream
|
||||
| SequenceKind::RowStream
|
||||
)
|
||||
);
|
||||
direct || traversal
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn row_pointer(&mut self, ty: &Type, value: &str) -> String {
|
||||
let pointer = self.entry_slot(ty);
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {pointer}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
pointer
|
||||
}
|
||||
|
||||
pub(super) fn pointer_array(&mut self, pointers: &[String]) -> String {
|
||||
if pointers.is_empty() {
|
||||
return "null".into();
|
||||
}
|
||||
let array = self.entry_slot_ir(&format!("[{} x ptr]", pointers.len()));
|
||||
for (index, pointer) in pointers.iter().enumerate() {
|
||||
let element = self.temp();
|
||||
self.line(format!(
|
||||
"{element} = getelementptr [{} x ptr], ptr {array}, i32 0, i32 {index}",
|
||||
pointers.len()
|
||||
));
|
||||
self.line(format!("store ptr {pointer}, ptr {element}"));
|
||||
}
|
||||
array
|
||||
}
|
||||
|
||||
pub(super) fn list_literal(&mut self, values: &[Expression], result: &Expression) -> String {
|
||||
let Type::List(element) = &result.ty else {
|
||||
unreachable!("checked List literal")
|
||||
};
|
||||
let mut pointers = Vec::with_capacity(values.len());
|
||||
for value in values {
|
||||
let row = self.expression(value).expect("validated List element");
|
||||
pointers.push(self.row_pointer(element, &row));
|
||||
}
|
||||
let array = self.pointer_array(&pointers);
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_list_new",
|
||||
vec![
|
||||
format!("ptr @dever_type_{}", self.module.type_index(element)),
|
||||
format!("ptr {array}"),
|
||||
format!("i64 {}", values.len()),
|
||||
],
|
||||
&result.ty,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn map_literal(
|
||||
&mut self,
|
||||
entries: &[(Expression, Expression)],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
let Type::Map(key_ty, value_ty) = &result.ty else {
|
||||
unreachable!("checked Map literal")
|
||||
};
|
||||
let mut keys = Vec::with_capacity(entries.len());
|
||||
let mut values = Vec::with_capacity(entries.len());
|
||||
for (key, value) in entries {
|
||||
let key_value = self.expression(key).expect("validated Map key");
|
||||
keys.push(self.row_pointer(key_ty, &key_value));
|
||||
let mapped = self.expression(value).expect("validated Map value");
|
||||
values.push(self.row_pointer(value_ty, &mapped));
|
||||
}
|
||||
let key_array = self.pointer_array(&keys);
|
||||
let value_array = self.pointer_array(&values);
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_map_new",
|
||||
vec![
|
||||
format!("ptr @dever_type_{}", self.module.type_index(key_ty)),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(value_ty)),
|
||||
format!("ptr {key_array}"),
|
||||
format!("ptr {value_array}"),
|
||||
format!("i64 {}", entries.len()),
|
||||
],
|
||||
&result.ty,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn collection(
|
||||
&mut self,
|
||||
operation: CollectionOp,
|
||||
_handler: Option<crate::hir::HandlerTarget>,
|
||||
_sequence: Option<SequenceKind>,
|
||||
arguments: &[Expression],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
if operation == CollectionOp::ParallelEach {
|
||||
return self.parallel_each(
|
||||
_handler.expect("checked parallel handler"),
|
||||
_sequence.expect("checked parallel sequence"),
|
||||
arguments,
|
||||
result,
|
||||
);
|
||||
}
|
||||
if matches!(
|
||||
operation,
|
||||
CollectionOp::Each
|
||||
| CollectionOp::Filter
|
||||
| CollectionOp::Find
|
||||
| CollectionOp::Reduce
|
||||
| CollectionOp::ReduceUntil
|
||||
| CollectionOp::Sum
|
||||
) {
|
||||
return self.traverse(
|
||||
operation,
|
||||
_handler,
|
||||
_sequence.expect("checked sequence"),
|
||||
arguments,
|
||||
result,
|
||||
);
|
||||
}
|
||||
if operation == CollectionOp::Entries {
|
||||
return self.map_entries(&arguments[0], result);
|
||||
}
|
||||
let receiver = self
|
||||
.expression(&arguments[0])
|
||||
.expect("validated collection input");
|
||||
let receiver_guard =
|
||||
contains_owned(self.module.program, &arguments[0].ty).then(|| self.guards.len() - 1);
|
||||
let mut values = vec![receiver.clone()];
|
||||
for argument in &arguments[1..] {
|
||||
values.push(
|
||||
self.expression(argument)
|
||||
.expect("validated collection argument"),
|
||||
);
|
||||
}
|
||||
let receiver_ty = &arguments[0].ty;
|
||||
if operation == CollectionOp::Close {
|
||||
let symbol = if matches!(receiver_ty, Type::AsyncStream(_)) {
|
||||
"async_stream_close"
|
||||
} else if matches!(receiver_ty, Type::RowStream(_)) {
|
||||
"db_stream_close"
|
||||
} else {
|
||||
"stream_close"
|
||||
};
|
||||
self.runtime_call(
|
||||
&format!("dever_rt_v1_{symbol}"),
|
||||
vec![format!("ptr {receiver}")],
|
||||
&Type::Unit,
|
||||
false,
|
||||
Some("i8"),
|
||||
result.span,
|
||||
);
|
||||
return "zeroinitializer".into();
|
||||
}
|
||||
let mut typed_args = Vec::new();
|
||||
if matches!(
|
||||
operation,
|
||||
CollectionOp::Append
|
||||
| CollectionOp::First
|
||||
| CollectionOp::Get
|
||||
| CollectionOp::Put
|
||||
| CollectionOp::Remove
|
||||
) && let Some(guard) = receiver_guard
|
||||
{
|
||||
self.disarm(guard);
|
||||
}
|
||||
typed_args.push(format!("ptr {receiver}"));
|
||||
let symbol = match operation {
|
||||
CollectionOp::Append => {
|
||||
typed_args.push(format!(
|
||||
"ptr {}",
|
||||
self.row_pointer(&arguments[1].ty, &values[1])
|
||||
));
|
||||
"list_append_take"
|
||||
}
|
||||
CollectionOp::First => "list_first_take",
|
||||
CollectionOp::Get | CollectionOp::Put | CollectionOp::Remove => {
|
||||
typed_args.push(format!(
|
||||
"ptr {}",
|
||||
self.row_pointer(&arguments[1].ty, &values[1])
|
||||
));
|
||||
if operation == CollectionOp::Put {
|
||||
typed_args.push(format!(
|
||||
"ptr {}",
|
||||
self.row_pointer(&arguments[2].ty, &values[2])
|
||||
));
|
||||
}
|
||||
match operation {
|
||||
CollectionOp::Get => "map_get_take",
|
||||
CollectionOp::Put => "map_put_take",
|
||||
CollectionOp::Remove => "map_remove_take",
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
CollectionOp::Length => match receiver_ty {
|
||||
Type::Text => "text_length",
|
||||
Type::Bytes => "bytes_length",
|
||||
Type::List(_) => "list_length",
|
||||
Type::Map(_, _) => "map_length",
|
||||
_ => unreachable!("checked Length receiver"),
|
||||
},
|
||||
_ => unreachable!("preflighted collection"),
|
||||
};
|
||||
let symbol = format!("dever_rt_v1_{symbol}");
|
||||
let row_type = match (operation, receiver_ty) {
|
||||
(CollectionOp::First, Type::List(element)) => Some(element.as_ref()),
|
||||
(CollectionOp::Get, Type::Map(_, value)) => Some(value.as_ref()),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(row_type) = row_type {
|
||||
self.runtime_optional_row(&symbol, typed_args, row_type, &result.ty, result.span)
|
||||
} else {
|
||||
self.runtime_call(&symbol, typed_args, &result.ty, false, None, result.span)
|
||||
}
|
||||
}
|
||||
|
||||
fn map_entries(&mut self, map: &Expression, result: &Expression) -> String {
|
||||
let Type::Map(key_ty, value_ty) = &map.ty else {
|
||||
unreachable!("checked entries input")
|
||||
};
|
||||
let Type::List(entry_ty) = &result.ty else {
|
||||
unreachable!("checked entries output")
|
||||
};
|
||||
let input = self.expression(map).expect("validated Map input");
|
||||
self.disarm(self.guards.len() - 1);
|
||||
let cursor = self.runtime_call(
|
||||
"dever_rt_v1_map_cursor_take",
|
||||
vec![format!("ptr {input}")],
|
||||
&Type::Unit,
|
||||
false,
|
||||
Some("ptr"),
|
||||
result.span,
|
||||
);
|
||||
let cursor_pointer = self.entry_slot_ir("ptr");
|
||||
self.line(format!("store ptr {cursor}, ptr {cursor_pointer}"));
|
||||
let cursor_guard = self.register_owned(OwnedKind::MapCursor, cursor_pointer);
|
||||
self.mark_live(cursor_guard);
|
||||
|
||||
let list = self.runtime_call(
|
||||
"dever_rt_v1_list_new",
|
||||
vec![
|
||||
format!("ptr @dever_type_{}", self.module.type_index(entry_ty)),
|
||||
"ptr null".into(),
|
||||
"i64 0".into(),
|
||||
],
|
||||
&result.ty,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
);
|
||||
let list_pointer = self.entry_slot(&result.ty);
|
||||
self.line(format!("store ptr {list}, ptr {list_pointer}"));
|
||||
let list_guard = self.register_guard(&result.ty, list_pointer.clone());
|
||||
self.mark_live(list_guard);
|
||||
|
||||
let key_pointer = self.entry_slot(key_ty);
|
||||
let value_pointer = self.entry_slot(value_ty);
|
||||
let key_guard = contains_owned(self.module.program, key_ty)
|
||||
.then(|| self.register_guard(key_ty, key_pointer.clone()));
|
||||
let value_guard = contains_owned(self.module.program, value_ty)
|
||||
.then(|| self.register_guard(value_ty, value_pointer.clone()));
|
||||
let present_pointer = self.entry_slot_ir("i8");
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let loop_label = self.label("entries_next");
|
||||
let body = self.label("entries_body");
|
||||
let done = self.label("entries_done");
|
||||
self.line(format!("br label %{loop_label}"));
|
||||
self.start(&loop_label);
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_map_cursor_next(ptr {cursor}, ptr {key_pointer}, ptr {value_pointer}, ptr {present_pointer}, ptr {error})"));
|
||||
self.check_runtime_status(&status, &error, result.span);
|
||||
let present = self.temp();
|
||||
self.line(format!("{present} = load i8, ptr {present_pointer}"));
|
||||
let has_entry = self.temp();
|
||||
self.line(format!("{has_entry} = icmp ne i8 {present}, 0"));
|
||||
self.line(format!("br i1 {has_entry}, label %{body}, label %{done}"));
|
||||
self.start(&body);
|
||||
if let Some(guard) = key_guard {
|
||||
self.mark_live(guard);
|
||||
}
|
||||
if let Some(guard) = value_guard {
|
||||
self.mark_live(guard);
|
||||
}
|
||||
let key = self.temp();
|
||||
self.line(format!(
|
||||
"{key} = load {}, ptr {key_pointer}",
|
||||
self.module.ty(key_ty)
|
||||
));
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {value_pointer}",
|
||||
self.module.ty(value_ty)
|
||||
));
|
||||
let first = self.temp();
|
||||
self.line(format!(
|
||||
"{first} = insertvalue {} undef, {} {key}, 0",
|
||||
self.module.ty(entry_ty),
|
||||
self.module.ty(key_ty)
|
||||
));
|
||||
let row = self.temp();
|
||||
self.line(format!(
|
||||
"{row} = insertvalue {} {first}, {} {value}, 1",
|
||||
self.module.ty(entry_ty),
|
||||
self.module.ty(value_ty)
|
||||
));
|
||||
let row_pointer = self.row_pointer(entry_ty, &row);
|
||||
let current = self.temp();
|
||||
self.line(format!("{current} = load ptr, ptr {list_pointer}"));
|
||||
self.disarm(list_guard);
|
||||
let appended = self.runtime_call(
|
||||
"dever_rt_v1_list_append_take",
|
||||
vec![format!("ptr {current}"), format!("ptr {row_pointer}")],
|
||||
&result.ty,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
);
|
||||
self.line(format!("store ptr {appended}, ptr {list_pointer}"));
|
||||
self.mark_live(list_guard);
|
||||
if let Some(guard) = value_guard {
|
||||
self.release_guard(guard);
|
||||
}
|
||||
if let Some(guard) = key_guard {
|
||||
self.release_guard(guard);
|
||||
}
|
||||
self.line(format!("br label %{loop_label}"));
|
||||
self.start(&done);
|
||||
self.release_guard(cursor_guard);
|
||||
let output = self.temp();
|
||||
self.line(format!("{output} = load ptr, ptr {list_pointer}"));
|
||||
self.disarm(list_guard);
|
||||
output
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
//! Checked Task/Group/Channel and blocking boundaries share coroutine polling.
|
||||
use super::*;
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn task_selection(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
handler: Option<crate::hir::HandlerTarget>,
|
||||
arguments: &[Expression],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
let mut values = Vec::with_capacity(arguments.len());
|
||||
let mut task_guards = Vec::new();
|
||||
for argument in arguments {
|
||||
values.push(
|
||||
self.expression(argument)
|
||||
.expect("checked task selection argument"),
|
||||
);
|
||||
if matches!(argument.ty, Type::Task(_)) {
|
||||
task_guards.push(self.guards.len() - 1);
|
||||
}
|
||||
}
|
||||
if operation == Intrinsic::TaskRace {
|
||||
let tasks = self.pointer_array(&values);
|
||||
let operation = self.async_operation(
|
||||
"async_task_race",
|
||||
vec![format!("ptr {tasks}"), format!("i64 {}", values.len())],
|
||||
result.span,
|
||||
);
|
||||
// A rejected race leaves every wrapper caller-owned.
|
||||
for guard in task_guards {
|
||||
self.disarm(guard);
|
||||
}
|
||||
return self.await_operation(&operation, &result.ty, result.span, true);
|
||||
}
|
||||
// Argument evaluation may fail; transfer only after every argument exists.
|
||||
for guard in task_guards {
|
||||
self.disarm(guard);
|
||||
}
|
||||
let operation = self.temp();
|
||||
self.line(format!(
|
||||
"{operation} = call ptr @dever_rt_v1_async_task_wait_timeout(ptr {}, i64 {})",
|
||||
values[0], values[1]
|
||||
));
|
||||
let (status, output, present, error) = self.poll_raw(&operation, &result.ty);
|
||||
let legacy = self.poll_status(&status, &error, result.span);
|
||||
self.propagate_status(&legacy, result.span, true);
|
||||
let flag = self.temp();
|
||||
self.line(format!("{flag} = load i8, ptr {present}"));
|
||||
let completed = self.temp();
|
||||
self.line(format!("{completed} = icmp ne i8 {flag}, 0"));
|
||||
let fallback = self.label("timeout_fallback");
|
||||
let done = self.label("timeout_done");
|
||||
self.line(format!(
|
||||
"br i1 {completed}, label %{done}, label %{fallback}"
|
||||
));
|
||||
self.start(&fallback);
|
||||
let (value, ty) =
|
||||
self.invoke_handler(handler.expect("checked timeout fallback"), &[], result.span);
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {output}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {output}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
value
|
||||
}
|
||||
|
||||
fn concurrency_arguments(
|
||||
&mut self,
|
||||
arguments: &[CallArgument],
|
||||
) -> Result<Vec<(Type, String)>, String> {
|
||||
arguments
|
||||
.iter()
|
||||
.filter_map(|argument| match argument {
|
||||
CallArgument::Value(value) => Some(
|
||||
self.expression(value)
|
||||
.map(|result| (value.ty.clone(), result)),
|
||||
),
|
||||
CallArgument::Handler(_) => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(super) fn concurrency(&mut self, expression: &Expression) -> Result<String, String> {
|
||||
let span = expression.span;
|
||||
let result = match &expression.kind {
|
||||
ExpressionKind::RunCall {
|
||||
group,
|
||||
target,
|
||||
arguments,
|
||||
} => {
|
||||
let group = group
|
||||
.as_ref()
|
||||
.map(|group| self.expression(group))
|
||||
.transpose()?;
|
||||
let instance = self.call_instance(*target, arguments);
|
||||
let asynchronous = specialize::suspends(self.module.program, &instance);
|
||||
let values = self.concurrency_arguments(arguments)?;
|
||||
let (input, guard) = self.pack_input(&instance, &values, !asynchronous);
|
||||
let mut args = Vec::new();
|
||||
if let Some(group) = &group {
|
||||
args.push(format!("ptr {group}"));
|
||||
}
|
||||
let descriptor = if asynchronous { "async" } else { "sync" };
|
||||
args.extend([
|
||||
format!("ptr @dever_{descriptor}_{}", self.module.names[&instance]),
|
||||
format!("ptr {input}"),
|
||||
]);
|
||||
let symbol = match (group.is_some(), asynchronous) {
|
||||
(false, true) => "async_task_run",
|
||||
(false, false) => "async_task_run_sync",
|
||||
(true, true) => "async_group_run",
|
||||
(true, false) => "async_group_run_sync",
|
||||
};
|
||||
let operation = self.async_operation(symbol, args, span);
|
||||
if let Some(guard) = guard {
|
||||
self.disarm(guard);
|
||||
}
|
||||
self.await_operation(&operation, &expression.ty, span, group.is_some())
|
||||
}
|
||||
ExpressionKind::ParallelCall { target, arguments }
|
||||
| ExpressionKind::BlockingCall { target, arguments } => {
|
||||
let instance = self.call_instance(*target, arguments);
|
||||
let values = self.concurrency_arguments(arguments)?;
|
||||
let symbol = if matches!(expression.kind, ExpressionKind::ParallelCall { .. }) {
|
||||
"async_parallel"
|
||||
} else {
|
||||
"async_blocking"
|
||||
};
|
||||
self.invoke_sync_boundary(&instance, &values, symbol, &expression.ty, span)
|
||||
}
|
||||
ExpressionKind::AwaitTask(value)
|
||||
| ExpressionKind::StopTask(value)
|
||||
| ExpressionKind::AwaitGroup(value)
|
||||
| ExpressionKind::StopGroup(value) => {
|
||||
let handle = self.expression(value)?;
|
||||
// The ABI consumes this one wrapper even when polling fails.
|
||||
self.disarm(self.guards.len() - 1);
|
||||
let symbol = match expression.kind {
|
||||
ExpressionKind::AwaitTask(_) => "async_task_wait",
|
||||
ExpressionKind::StopTask(_) => "async_task_stop",
|
||||
ExpressionKind::AwaitGroup(_) => "async_group_wait",
|
||||
_ => "async_group_stop",
|
||||
};
|
||||
let operation = self.temp();
|
||||
self.line(format!(
|
||||
"{operation} = call ptr @dever_rt_v1_{symbol}(ptr {handle})"
|
||||
));
|
||||
self.await_operation(&operation, &expression.ty, span, true)
|
||||
}
|
||||
ExpressionKind::Group(limit) => {
|
||||
let limit = self.expression(limit)?;
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_async_group_new",
|
||||
vec![format!("i64 {limit}")],
|
||||
&Type::Group,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
)
|
||||
}
|
||||
ExpressionKind::Channel { capacity, .. } => {
|
||||
let capacity = self.expression(capacity)?;
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_async_channel_new",
|
||||
vec![format!("i64 {capacity}")],
|
||||
&expression.ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
)
|
||||
}
|
||||
ExpressionKind::ChannelSend { channel, value } => {
|
||||
let channel = self.expression(channel)?;
|
||||
let value_ir = self.expression(value)?;
|
||||
let guard = self.own_value(
|
||||
&value.ty,
|
||||
&value_ir,
|
||||
contains_owned(self.module.program, &value.ty),
|
||||
);
|
||||
let input = self.guards[guard].pointer.clone();
|
||||
let operation = self.async_operation(
|
||||
"async_channel_send",
|
||||
vec![
|
||||
format!("ptr {channel}"),
|
||||
format!("ptr @dever_owned_{}", self.module.type_index(&value.ty)),
|
||||
format!("ptr {input}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
self.disarm(guard);
|
||||
self.await_operation(&operation, &Type::Unit, span, false)
|
||||
}
|
||||
ExpressionKind::ChannelReceive(channel) => {
|
||||
let Type::Channel(element) = &channel.ty else {
|
||||
unreachable!("checked receive")
|
||||
};
|
||||
let handle = self.expression(channel)?;
|
||||
let operation = self.temp();
|
||||
self.line(format!(
|
||||
"{operation} = call ptr @dever_rt_v1_async_channel_receive(ptr {handle})"
|
||||
));
|
||||
let (status, row, present, error) = self.poll_raw(&operation, element);
|
||||
let legacy = self.poll_status(&status, &error, span);
|
||||
self.propagate_status(&legacy, span, false);
|
||||
self.optional_row(&expression.ty, element, &row, &present)
|
||||
}
|
||||
ExpressionKind::ChannelClose(channel) => {
|
||||
let channel = self.expression(channel)?;
|
||||
let operation = self.temp();
|
||||
self.line(format!(
|
||||
"{operation} = call ptr @dever_rt_v1_async_channel_close(ptr {channel})"
|
||||
));
|
||||
self.await_operation(&operation, &Type::Unit, span, false)
|
||||
}
|
||||
_ => unreachable!("checked concurrency expression"),
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(super) fn task_sleep(&mut self, duration: &str, expression: &Expression) -> String {
|
||||
let operation = self.temp();
|
||||
self.line(format!(
|
||||
"{operation} = call ptr @dever_rt_v1_async_sleep(i64 {duration})"
|
||||
));
|
||||
let (status, _, _, error) = self.poll_raw(&operation, &Type::Unit);
|
||||
let Type::Named(id) = expression.ty else {
|
||||
unreachable!("checked sleep result")
|
||||
};
|
||||
let Shape::Choice(variants) = &self.module.program.types[id].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let done_tag = variants
|
||||
.iter()
|
||||
.position(|variant| variant.name == "Done")
|
||||
.expect("sleep Done");
|
||||
let failed_tag = variants
|
||||
.iter()
|
||||
.position(|variant| variant.name == "Failed")
|
||||
.expect("sleep Failed");
|
||||
let slot = self.entry_slot(&expression.ty);
|
||||
let done = self.label("sleep_done");
|
||||
let failed = self.label("sleep_failed");
|
||||
let invalid = self.label("sleep_invalid");
|
||||
let result = self.label("sleep_result");
|
||||
self.line(format!(
|
||||
"switch i32 {status}, label %{invalid} [i32 1, label %{done} i32 3, label %{failed}]"
|
||||
));
|
||||
self.start(&done);
|
||||
self.write_variant(id, done_tag, &[], &slot);
|
||||
self.line(format!("br label %{result}"));
|
||||
self.start(&failed);
|
||||
let message = self.error_text(&error, expression.span);
|
||||
self.write_variant(id, failed_tag, &[(Type::Text, message)], &slot);
|
||||
self.line(format!("br label %{result}"));
|
||||
self.start(&invalid);
|
||||
let legacy = self.poll_status(&status, &error, expression.span);
|
||||
self.propagate_status(&legacy, expression.span, false);
|
||||
self.line("unreachable");
|
||||
self.start(&result);
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load %T{id}, ptr {slot}"));
|
||||
value
|
||||
}
|
||||
|
||||
pub(super) fn invoke_sync_boundary(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
values: &[(Type, String)],
|
||||
symbol: &str,
|
||||
output: &Type,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let (input, guard) = self.pack_input(instance, values, true);
|
||||
let operation = self.async_operation(
|
||||
symbol,
|
||||
vec![
|
||||
format!("ptr @dever_sync_{}", self.module.names[instance]),
|
||||
format!("ptr {input}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
self.disarm(guard.expect("owned worker input"));
|
||||
self.await_operation(&operation, output, span, true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
//! Typed database boundary. SQL values never enter source-language evaluation.
|
||||
use super::*;
|
||||
|
||||
pub(super) const DECLARATIONS: &str = "\
|
||||
%dever.db_error = type { ptr, ptr, ptr }
|
||||
%dever.db_value = type { i32, i32, i32, i64, double, { i64, i64 }, ptr }
|
||||
%dever.db_sql = type { { ptr, i64 }, { ptr, i64 } }
|
||||
declare void @dever_rt_v1_db_cause_append(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_application_root(ptr, ptr, ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_session_new(ptr, i64, ptr, i64, ptr, ptr)
|
||||
declare void @dever_rt_v1_db_session_release(ptr)
|
||||
declare ptr @dever_rt_v1_db_prepare(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_resolve_scoped(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_select(ptr, ptr, ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_retain(ptr)
|
||||
declare void @dever_rt_v1_db_release(ptr)
|
||||
declare i32 @dever_rt_v1_db_max_page_size(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_begin(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_transaction_retain(ptr)
|
||||
declare void @dever_rt_v1_db_transaction_release(ptr)
|
||||
declare ptr @dever_rt_v1_db_finish(ptr, i8, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_rollback_take(ptr, ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_execute(ptr, ptr, ptr, ptr, i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_query(ptr, ptr, ptr, ptr, i64, i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_relation_query(ptr, ptr, ptr, ptr, ptr, ptr, i64, i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_stream(ptr, ptr, ptr, ptr, i64, i64, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_stream_retain(ptr)
|
||||
declare void @dever_rt_v1_db_stream_release(ptr)
|
||||
declare i32 @dever_rt_v1_db_stream_close(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_stream_pull(ptr, ptr, ptr)
|
||||
declare void @dever_rt_v1_db_rows_release(ptr)
|
||||
declare i32 @dever_rt_v1_db_rows_len(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_rows_at(ptr, i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_required_row(ptr, ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_optional_row(ptr, ptr, ptr, ptr, ptr, ptr, ptr)
|
||||
declare void @dever_rt_v1_db_row_release(ptr)
|
||||
declare i32 @dever_rt_v1_db_row_len(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_related_new(ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_db_related_retain(ptr)
|
||||
declare void @dever_rt_v1_db_related_release(ptr)
|
||||
declare i8 @dever_rt_v1_db_related_equal(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_related_get(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_row_null(ptr, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_row_bool(ptr, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_row_int(ptr, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_row_float(ptr, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_row_decimal(ptr, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_row_text(ptr, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_row_bytes(ptr, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_row_uuid(ptr, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_uuid_new(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_error(i32, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_pagination(i64, i64, i64, ptr, ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_cursor_size(i64, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_stream_capacity(i64, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_db_relation_limit(i64, ptr, ptr, ptr, ptr)
|
||||
";
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn database_enabled(&self) -> bool {
|
||||
self.application.as_ref().is_some_and(|app| match app.test {
|
||||
Some(index) => self.program.tests[index].uses_database(),
|
||||
None => !self.program.models.is_empty() || !self.program.jobs.is_empty(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn database_function(&self, instance: &Specialization) -> bool {
|
||||
!specialize::database_effects(self.program, instance).is_empty()
|
||||
}
|
||||
|
||||
pub(super) fn database_error(&mut self, instance: &Specialization, span: Span) -> String {
|
||||
let location = self.location(span);
|
||||
let name = self.names[instance].clone();
|
||||
let symbol = format!("@dever_db_error_{name}_{location}");
|
||||
if !self.database_errors.insert((name.clone(), location)) {
|
||||
return symbol;
|
||||
}
|
||||
let id = crate::capture::database_errors(&self.program.types)[0].ty;
|
||||
let Shape::Choice(variants) = &self.program.types[id].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let kinds = [
|
||||
"Pool",
|
||||
"PoolExhausted",
|
||||
"Connection",
|
||||
"Timeout",
|
||||
"Cancelled",
|
||||
"Database",
|
||||
"Constraint",
|
||||
"NotFound",
|
||||
"InvalidData",
|
||||
"Migration",
|
||||
];
|
||||
let variants = kinds
|
||||
.iter()
|
||||
.map(|name| {
|
||||
variants
|
||||
.iter()
|
||||
.position(|variant| variant.name == *name)
|
||||
.expect("bundled database error kind")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
writeln!(self.declarations, "{symbol} = private constant %dever.db_error {{ ptr @dever_fault_owned_{name}, ptr @dever_db_pack_{name}_{location}, ptr @dever_db_append_cause }}").unwrap();
|
||||
writeln!(self.declarations, "define internal void @dever_db_pack_{name}_{location}(i32 %kind, ptr %message, ptr %fault) {{\nentry:\n store %dever.fault zeroinitializer, ptr %fault\n %owned = call ptr @dever_rt_v1_text_retain(ptr %message)\n %payload = getelementptr %dever.fault, ptr %fault, i32 0, i32 7").unwrap();
|
||||
for (index, value) in [(0, 4), (1, location), (5, id + 1)] {
|
||||
writeln!(self.declarations, " %f{index} = getelementptr %dever.fault, ptr %fault, i32 0, i32 {index}\n store i32 {value}, ptr %f{index}").unwrap();
|
||||
}
|
||||
let arms = variants
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(kind, variant)| format!("i32 {kind}, label %kind{variant}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
writeln!(
|
||||
self.declarations,
|
||||
" switch i32 %kind, label %invalid [{arms}]\ninvalid:\n unreachable"
|
||||
)
|
||||
.unwrap();
|
||||
for variant in variants {
|
||||
writeln!(self.declarations, "kind{variant}:\n %tag{variant} = getelementptr %T{id}, ptr %payload, i32 0, i32 0\n store i32 {variant}, ptr %tag{variant}\n %body{variant} = getelementptr %T{id}, ptr %payload, i32 0, i32 1\n store ptr %owned, ptr %body{variant}\n %variant{variant} = getelementptr %dever.fault, ptr %fault, i32 0, i32 6\n store i32 {variant}, ptr %variant{variant}\n ret void").unwrap();
|
||||
}
|
||||
self.declarations.push_str("}\n");
|
||||
symbol
|
||||
}
|
||||
|
||||
pub(super) fn database_sql(&mut self, sqlite: &str, postgres: &str) -> String {
|
||||
let (sqlite, slen) = self.text_literal(sqlite);
|
||||
let (postgres, plen) = self.text_literal(postgres);
|
||||
let name = format!("@dever_db_sql_{}", self.next_literal);
|
||||
self.next_literal += 1;
|
||||
writeln!(self.declarations, "{name} = private constant %dever.db_sql {{ {{ ptr, i64 }} {{ ptr {sqlite}, i64 {slen} }}, {{ ptr, i64 }} {{ ptr {postgres}, i64 {plen} }} }}").unwrap();
|
||||
name
|
||||
}
|
||||
|
||||
pub(super) fn planned_sql(&mut self, sql: &str) -> String {
|
||||
let (sqlite, postgres) = crate::native::orm::sql_dialects(sql, sql);
|
||||
self.database_sql(&sqlite, &postgres)
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn await_database_handle(&mut self, operation: &str, span: Span) -> String {
|
||||
let (status, output, _, error) = self.poll_raw_layout(operation, "ptr");
|
||||
let status = self.poll_status(&status, &error, span);
|
||||
self.propagate_status(&status, span, false);
|
||||
let handle = self.temp();
|
||||
self.line(format!("{handle} = load ptr, ptr {output}"));
|
||||
handle
|
||||
}
|
||||
pub(super) fn database_errors(&mut self, span: Span) -> String {
|
||||
self.database_errors_override
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.module.database_error(self.instance, span))
|
||||
}
|
||||
pub(super) fn database_status(&mut self, status: &str, error: &str, span: Span) {
|
||||
let typed = self.temp();
|
||||
let failed = self.label("database_fault");
|
||||
let checked = self.label("database_status");
|
||||
self.line(format!("{typed} = icmp eq i32 {status}, 1"));
|
||||
self.line(format!("br i1 {typed}, label %{failed}, label %{checked}"));
|
||||
self.start(&failed);
|
||||
self.exit("1");
|
||||
self.start(&checked);
|
||||
self.check_runtime_status(status, error, span);
|
||||
}
|
||||
|
||||
pub(super) fn database_call(
|
||||
&mut self,
|
||||
symbol: &str,
|
||||
mut arguments: Vec<String>,
|
||||
layout: &str,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let output = self.entry_slot_ir(layout);
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let errors = self.database_errors(span);
|
||||
arguments.extend([
|
||||
format!("ptr {output}"),
|
||||
format!("ptr {errors}"),
|
||||
"ptr %fault".into(),
|
||||
format!("ptr {error}"),
|
||||
]);
|
||||
let status = self.temp();
|
||||
self.line(format!(
|
||||
"{status} = call i32 @dever_rt_v1_{symbol}({})",
|
||||
arguments.join(", ")
|
||||
));
|
||||
self.database_status(&status, &error, span);
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load {layout}, ptr {output}"));
|
||||
value
|
||||
}
|
||||
|
||||
pub(super) fn database_operation(
|
||||
&mut self,
|
||||
symbol: &str,
|
||||
mut arguments: Vec<String>,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let errors = self.database_errors(span);
|
||||
arguments.push(format!("ptr {errors}"));
|
||||
self.async_operation(symbol, arguments, span)
|
||||
}
|
||||
|
||||
pub(super) fn database_fault(&mut self, kind: u32, message: &str, span: Span) {
|
||||
let text = self.application_text(message, span);
|
||||
let errors = self.database_errors(span);
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_db_error(i32 {kind}, ptr {text}, ptr {errors}, ptr %fault, ptr {error})"));
|
||||
self.database_status(&status, &error, span);
|
||||
self.line("unreachable");
|
||||
}
|
||||
|
||||
pub(super) fn model_database(&mut self, model: usize) -> String {
|
||||
if self.module.api_enabled() {
|
||||
let span = self.module.program.functions[self.instance.function].span;
|
||||
let session = self.temp();
|
||||
self.line(format!("{session} = load ptr, ptr @dever_database_session"));
|
||||
let binding = self.module.database_binding(model);
|
||||
let slot = self.entry_slot_ir("%dever.db_binding");
|
||||
self.line(format!("store %dever.db_binding {binding}, ptr {slot}"));
|
||||
let operation = self.database_operation(
|
||||
"db_resolve_scoped",
|
||||
vec![format!("ptr {session}"), format!("ptr {slot}")],
|
||||
span,
|
||||
);
|
||||
let database = self.await_database_handle(&operation, span);
|
||||
self.protocol_owner("db", &database);
|
||||
return database;
|
||||
}
|
||||
let database = self.temp();
|
||||
self.line(format!(
|
||||
"{database} = load ptr, ptr @dever_database_{model}"
|
||||
));
|
||||
database
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
//! Concrete Model/SQL row codecs and borrowed persistence parameter arrays.
|
||||
use super::*;
|
||||
use crate::model::ModelFieldType;
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn database_parameters(
|
||||
&mut self,
|
||||
values: &[(Type, ModelFieldType, String)],
|
||||
span: Span,
|
||||
) -> String {
|
||||
let count = values.len();
|
||||
let pointer = self.entry_slot_ir(&format!("[{count} x %dever.db_value]"));
|
||||
for (index, (ty, storage, value)) in values.iter().enumerate() {
|
||||
let encoded = self.database_parameter(ty, storage, value, span);
|
||||
let destination = self.temp();
|
||||
self.line(format!("{destination} = getelementptr [{count} x %dever.db_value], ptr {pointer}, i32 0, i64 {index}"));
|
||||
self.line(format!(
|
||||
"store %dever.db_value {encoded}, ptr {destination}"
|
||||
));
|
||||
}
|
||||
pointer
|
||||
}
|
||||
|
||||
fn database_parameter(
|
||||
&mut self,
|
||||
ty: &Type,
|
||||
storage: &ModelFieldType,
|
||||
value: &str,
|
||||
span: Span,
|
||||
) -> String {
|
||||
if let Type::Nullable(inner) = ty {
|
||||
let result = self.entry_slot_ir("%dever.db_value");
|
||||
self.line(format!(
|
||||
"store %dever.db_value zeroinitializer, ptr {result}"
|
||||
));
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let some = self.label("database_parameter_some");
|
||||
let done = self.label("database_parameter_done");
|
||||
self.line(format!("br i1 {present}, label %{some}, label %{done}"));
|
||||
self.start(&some);
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = extractvalue {} {value}, 1",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let encoded = self.database_parameter(inner, storage, &payload, span);
|
||||
self.line(format!("store %dever.db_value {encoded}, ptr {result}"));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let encoded = self.temp();
|
||||
self.line(format!("{encoded} = load %dever.db_value, ptr {result}"));
|
||||
return encoded;
|
||||
}
|
||||
let (kind, field, layout, value) = match ty {
|
||||
Type::Bool => {
|
||||
let integer = self.temp();
|
||||
self.line(format!("{integer} = zext i1 {value} to i64"));
|
||||
(1, 3, "i64", integer)
|
||||
}
|
||||
Type::Float => (3, 4, "double", value.into()),
|
||||
Type::Decimal => (4, 5, "{ i64, i64 }", value.into()),
|
||||
Type::Text | Type::Json => (5, 6, "ptr", value.into()),
|
||||
Type::Bytes => (6, 6, "ptr", value.into()),
|
||||
Type::Uuid => (7, 6, "ptr", value.into()),
|
||||
Type::Named(id) if self.module.program.types[*id].kind != DefinitionKind::ModelId => {
|
||||
let Shape::Choice(variants) = self.module.program.types[*id].shape.clone() else {
|
||||
unreachable!()
|
||||
};
|
||||
let slot = self.entry_slot(&Type::Text);
|
||||
let tag = self.temp();
|
||||
self.line(format!("{tag} = extractvalue %T{id} {value}, 0"));
|
||||
let done = self.label("database_choice_done");
|
||||
let arms = variants
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, _)| (index, self.label("database_choice")))
|
||||
.collect::<Vec<_>>();
|
||||
let cases = arms
|
||||
.iter()
|
||||
.map(|(index, block)| format!("i32 {index}, label %{block}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let invalid = self.label("database_choice_invalid");
|
||||
self.line(format!("switch i32 {tag}, label %{invalid} [{cases}]"));
|
||||
self.start(&invalid);
|
||||
self.line("unreachable");
|
||||
for (index, block) in arms {
|
||||
self.start(&block);
|
||||
let text = self.application_text(&variants[index].name, span);
|
||||
self.line(format!("store ptr {text}, ptr {slot}"));
|
||||
self.line(format!("br label %{done}"));
|
||||
}
|
||||
self.start(&done);
|
||||
let text = self.temp();
|
||||
self.line(format!("{text} = load ptr, ptr {slot}"));
|
||||
(5, 6, "ptr", text)
|
||||
}
|
||||
_ => (2, 3, "i64", value.into()),
|
||||
};
|
||||
let tagged = self.temp();
|
||||
self.line(format!(
|
||||
"{tagged} = insertvalue %dever.db_value zeroinitializer, i32 {kind}, 0"
|
||||
));
|
||||
let mut result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = insertvalue %dever.db_value {tagged}, {layout} {value}, {field}"
|
||||
));
|
||||
if let ModelFieldType::Decimal { precision, scale } = storage {
|
||||
for (field, value) in [(1, precision), (2, scale)] {
|
||||
let next = self.temp();
|
||||
self.line(format!(
|
||||
"{next} = insertvalue %dever.db_value {result}, i32 {value}, {field}"
|
||||
));
|
||||
result = next;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub(super) fn database_scalar(
|
||||
&mut self,
|
||||
row: &str,
|
||||
index: usize,
|
||||
ty: &Type,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let arguments = vec![format!("ptr {row}"), format!("i64 {index}")];
|
||||
if let Type::Nullable(inner) = ty {
|
||||
let null = self.database_call("db_row_null", arguments, "i8", span);
|
||||
let absent = self.temp();
|
||||
self.line(format!("{absent} = icmp ne i8 {null}, 0"));
|
||||
let slot = self.entry_slot(ty);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {slot}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let some = self.label("database_column_some");
|
||||
let done = self.label("database_column_done");
|
||||
self.line(format!("br i1 {absent}, label %{done}, label %{some}"));
|
||||
self.start(&some);
|
||||
let value = self.database_scalar(row, index, inner, span);
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = insertvalue {} zeroinitializer, i1 1, 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let wrapped = self.temp();
|
||||
self.line(format!(
|
||||
"{wrapped} = insertvalue {} {present}, {} {value}, 1",
|
||||
self.module.ty(ty),
|
||||
self.module.ty(inner)
|
||||
));
|
||||
self.line(format!(
|
||||
"store {} {wrapped}, ptr {slot}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load {}, ptr {slot}", self.module.ty(ty)));
|
||||
return value;
|
||||
}
|
||||
let kind = match ty {
|
||||
Type::Bool => "bool",
|
||||
Type::Float => "float",
|
||||
Type::Decimal => "decimal",
|
||||
Type::Text | Type::Json => "text",
|
||||
Type::Bytes => "bytes",
|
||||
Type::Uuid => "uuid",
|
||||
Type::Named(id) if self.module.program.types[*id].kind != DefinitionKind::ModelId => {
|
||||
"text"
|
||||
}
|
||||
_ => "int",
|
||||
};
|
||||
let choice = matches!(ty, Type::Named(id) if self.module.program.types[*id].kind != DefinitionKind::ModelId);
|
||||
let layout = if *ty == Type::Bool {
|
||||
"i8".into()
|
||||
} else if choice {
|
||||
"ptr".into()
|
||||
} else {
|
||||
self.module.ty(ty)
|
||||
};
|
||||
let value = self.database_call(&format!("db_row_{kind}"), arguments, &layout, span);
|
||||
if *ty == Type::Bool {
|
||||
let boolean = self.temp();
|
||||
self.line(format!("{boolean} = icmp ne i8 {value}, 0"));
|
||||
return boolean;
|
||||
}
|
||||
if choice {
|
||||
let Type::Named(id) = ty else { unreachable!() };
|
||||
let Shape::Choice(variants) = self.module.program.types[*id].shape.clone() else {
|
||||
unreachable!()
|
||||
};
|
||||
let owner = self.own_value(&Type::Text, &value, false);
|
||||
let output = self.entry_slot(ty);
|
||||
let done = self.label("database_choice_read");
|
||||
for (index, variant) in variants.iter().enumerate() {
|
||||
let expected = self.application_text(&variant.name, span);
|
||||
let equal = self.temp();
|
||||
self.line(format!(
|
||||
"{equal} = call i8 @dever_rt_v1_text_equal(ptr {value}, ptr {expected})"
|
||||
));
|
||||
let matched = self.temp();
|
||||
self.line(format!("{matched} = icmp ne i8 {equal}, 0"));
|
||||
let success = self.label("database_choice_found");
|
||||
let next = self.label("database_choice_next");
|
||||
self.line(format!("br i1 {matched}, label %{success}, label %{next}"));
|
||||
self.start(&success);
|
||||
self.write_variant(*id, index, &[], &output);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&next);
|
||||
}
|
||||
let prefix = self.application_text("invalid database choice '", span);
|
||||
let text = self.runtime_call(
|
||||
"dever_rt_v1_text_concat",
|
||||
vec![format!("ptr {prefix}"), format!("ptr {value}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &text, false);
|
||||
let suffix = self.application_text("'", span);
|
||||
let message = self.runtime_call(
|
||||
"dever_rt_v1_text_concat",
|
||||
vec![format!("ptr {text}"), format!("ptr {suffix}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &message, false);
|
||||
let errors = self.database_errors(span);
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_db_error(i32 8, ptr {message}, ptr {errors}, ptr %fault, ptr {error})"));
|
||||
self.database_status(&status, &error, span);
|
||||
self.line("unreachable");
|
||||
self.start(&done);
|
||||
self.release_guard(owner);
|
||||
let decoded = self.temp();
|
||||
self.line(format!("{decoded} = load %T{id}, ptr {output}"));
|
||||
return decoded;
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
pub(super) fn database_record(
|
||||
&mut self,
|
||||
row: &str,
|
||||
id: usize,
|
||||
offset: usize,
|
||||
exact: bool,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let Shape::Record(fields) = self.module.program.types[id].shape.clone() else {
|
||||
unreachable!()
|
||||
};
|
||||
let slot = self.entry_slot(&Type::Named(id));
|
||||
self.line(format!("store %T{id} zeroinitializer, ptr {slot}"));
|
||||
let guard = self.register_guard(&Type::Named(id), slot.clone());
|
||||
self.mark_live(guard);
|
||||
let actual = self.protocol_call(
|
||||
"dever_rt_v1_db_row_len",
|
||||
vec![format!("ptr {row}")],
|
||||
"i64",
|
||||
span,
|
||||
);
|
||||
let record_name = self
|
||||
.module
|
||||
.program
|
||||
.models
|
||||
.iter()
|
||||
.find(|model| model.record == id)
|
||||
.map(|model| model.package.clone())
|
||||
.unwrap_or_else(|| self.module.program.types[id].name.clone());
|
||||
let mut column = offset;
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
if matches!(field.ty, Type::Related(_)) {
|
||||
continue;
|
||||
}
|
||||
self.database_column_present(
|
||||
&actual,
|
||||
column,
|
||||
&format!("{record_name}.{}", field.name),
|
||||
span,
|
||||
);
|
||||
let value = self.database_scalar(row, column, &field.ty, span);
|
||||
let destination = self.temp();
|
||||
self.line(format!(
|
||||
"{destination} = getelementptr %T{id}, ptr {slot}, i32 0, i32 {index}"
|
||||
));
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {destination}",
|
||||
self.module.ty(&field.ty)
|
||||
));
|
||||
column += 1;
|
||||
}
|
||||
if exact {
|
||||
self.database_row_length(
|
||||
row,
|
||||
column,
|
||||
&format!("database row for {record_name} has extra columns"),
|
||||
span,
|
||||
);
|
||||
}
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load %T{id}, ptr {slot}"));
|
||||
self.disarm(guard);
|
||||
value
|
||||
}
|
||||
|
||||
pub(super) fn database_column_present(
|
||||
&mut self,
|
||||
actual: &str,
|
||||
index: usize,
|
||||
name: &str,
|
||||
span: Span,
|
||||
) {
|
||||
let present = self.temp();
|
||||
self.line(format!("{present} = icmp sgt i64 {actual}, {index}"));
|
||||
let found = self.label("database_column_found");
|
||||
let missing = self.label("database_column_missing");
|
||||
self.line(format!("br i1 {present}, label %{found}, label %{missing}"));
|
||||
self.start(&missing);
|
||||
self.database_fault(8, &format!("database row is missing column '{name}'"), span);
|
||||
self.start(&found);
|
||||
}
|
||||
|
||||
pub(super) fn database_row_length(
|
||||
&mut self,
|
||||
row: &str,
|
||||
length: usize,
|
||||
message: &str,
|
||||
span: Span,
|
||||
) {
|
||||
let actual = self.protocol_call(
|
||||
"dever_rt_v1_db_row_len",
|
||||
vec![format!("ptr {row}")],
|
||||
"i64",
|
||||
span,
|
||||
);
|
||||
let equal = self.temp();
|
||||
self.line(format!("{equal} = icmp eq i64 {actual}, {length}"));
|
||||
let okay = self.label("database_row_exact");
|
||||
let invalid = self.label("database_row_extra");
|
||||
self.line(format!("br i1 {equal}, label %{okay}, label %{invalid}"));
|
||||
self.start(&invalid);
|
||||
self.database_fault(8, message, span);
|
||||
self.start(&okay);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
//! Static schema metadata shares the native backend's SQL/DDL owner.
|
||||
use super::*;
|
||||
use crate::model::{DatabaseOwner, ModelFieldType, ModelSchema, ModelScope, ModelValue};
|
||||
use crate::native::orm as sql;
|
||||
|
||||
pub(super) struct Migration {
|
||||
pub bindings: String,
|
||||
pub models: String,
|
||||
pub errors: String,
|
||||
pub count: usize,
|
||||
}
|
||||
|
||||
pub(super) const DECLARATIONS: &str = "\
|
||||
%dever.db_binding = type { ptr, { ptr, i64 }, i8 }
|
||||
%dever.db_field = type { { ptr, i64 }, { ptr, i64 }, i8, i8, ptr, ptr }
|
||||
%dever.db_index = type { { ptr, i64 }, ptr, i64, i8 }
|
||||
%dever.db_migration = type { { ptr, i64 }, { ptr, i64 }, ptr, i64 }
|
||||
%dever.db_seed = type { %dever.db_sql, ptr, i64 }
|
||||
%dever.db_data_migration = type { { ptr, i64 }, i32, %dever.db_seed }
|
||||
%dever.db_pg_column = type { { ptr, i64 }, { ptr, i64 }, { ptr, i64 }, ptr }
|
||||
%dever.db_pg_constraint = type { { ptr, i64 }, { ptr, i64 }, i8 }
|
||||
%dever.db_model = type { { ptr, i64 }, { ptr, i64 }, { ptr, i64 }, { ptr, i64 }, ptr, i64, ptr, i64, ptr, i64, %dever.db_sql, { ptr, i64 }, ptr, i64, ptr, i64, ptr, i64, ptr, i64, ptr, i64 }
|
||||
declare ptr @dever_rt_v1_db_migrate_models(ptr, ptr, ptr, i64, ptr)
|
||||
declare ptr @dever_rt_v1_api_migrate_models(ptr, ptr, ptr, ptr, i64, i64, ptr)
|
||||
";
|
||||
|
||||
impl Module<'_> {
|
||||
fn database_name(&mut self, name: &str) -> String {
|
||||
let (pointer, length) = self.text_literal(name);
|
||||
format!("{{ ptr, i64 }} {{ ptr {pointer}, i64 {length} }}")
|
||||
}
|
||||
|
||||
fn database_optional_name(&mut self, name: Option<&str>) -> String {
|
||||
name.map(|name| self.wire_names(&[name.into()]))
|
||||
.unwrap_or_else(|| "null".into())
|
||||
}
|
||||
|
||||
fn database_array(&mut self, ty: &str, values: &[String]) -> String {
|
||||
if values.is_empty() {
|
||||
return "null".into();
|
||||
}
|
||||
let name = format!("@dever_db_metadata_{}", self.next_literal);
|
||||
self.next_literal += 1;
|
||||
let body = values
|
||||
.iter()
|
||||
.map(|value| format!("{ty} {value}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"{name} = private constant [{} x {ty}] [{body}]",
|
||||
values.len()
|
||||
)
|
||||
.unwrap();
|
||||
name
|
||||
}
|
||||
|
||||
pub(super) fn database_binding(&mut self, index: usize) -> String {
|
||||
let (explicit, root) = self.program.database_selector(DatabaseOwner::Model(index));
|
||||
let explicit = explicit.map(str::to_owned);
|
||||
let root = root.to_owned();
|
||||
let explicit = self.database_optional_name(explicit.as_deref());
|
||||
let root = self.database_name(&root);
|
||||
let tenant = u8::from(self.program.models[index].scope == ModelScope::Tenant);
|
||||
format!("{{ ptr {explicit}, {root}, i8 {tenant} }}")
|
||||
}
|
||||
|
||||
pub(super) fn emit_database_slots(&mut self) {
|
||||
self.declarations.push_str(DECLARATIONS);
|
||||
self.declarations.push_str("@dever_database_session = private global ptr null\ndefine internal void @dever_db_append_cause(ptr %fault, ptr %message) {\nentry:\n %cause = getelementptr %dever.fault, ptr %fault, i32 0, i32 8\n call void @dever_rt_v1_db_cause_append(ptr %cause, ptr %message)\n ret void\n}\n");
|
||||
for index in 0..self.program.models.len() {
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@dever_database_{index} = private global ptr null"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn database_model(&mut self, model: &ModelSchema) -> String {
|
||||
let fields = model
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = self.database_name(&field.name);
|
||||
let ty = self.database_name(&crate::model::field_type(&field.ty));
|
||||
let default = field.default.as_ref().map(crate::model::value);
|
||||
let default = self.database_optional_name(default.as_deref());
|
||||
let rename = self.database_optional_name(field.rename_from.as_deref());
|
||||
format!(
|
||||
"{{ {name}, {ty}, i8 {}, i8 {}, ptr {default}, ptr {rename} }}",
|
||||
u8::from(field.nullable),
|
||||
u8::from(field.generated)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let fields = self.database_array("%dever.db_field", &fields);
|
||||
let indexes = model
|
||||
.indexes
|
||||
.iter()
|
||||
.filter(|index| index.fields.as_slice() != ["id"])
|
||||
.map(|index| {
|
||||
let name = self.database_name(&sql::index_name(model, index));
|
||||
let fields = self.wire_names(&index.fields);
|
||||
format!(
|
||||
"{{ {name}, ptr {fields}, i64 {}, i8 {} }}",
|
||||
index.fields.len(),
|
||||
u8::from(index.unique)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let index_count = indexes.len();
|
||||
let indexes = self.database_array("%dever.db_index", &indexes);
|
||||
let migrations = model
|
||||
.migrations
|
||||
.iter()
|
||||
.map(|migration| {
|
||||
let name = self.database_name(&migration.name);
|
||||
let revision = self.database_name(&migration.revision());
|
||||
let drops = self.wire_names(&migration.drops);
|
||||
format!(
|
||||
"{{ {name}, {revision}, ptr {drops}, i64 {} }}",
|
||||
migration.drops.len()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let migrations = self.database_array("%dever.db_migration", &migrations);
|
||||
let constraints = sql::postgres_constraints(model, &self.program.models);
|
||||
let pg_table = sql::postgres_table(model, &constraints, &model.table);
|
||||
let sqlite_table = sql::sqlite_table(model, &self.program.models, &model.table);
|
||||
let sqlite_table = self.database_name(&sqlite_table);
|
||||
let pg_table = self.database_name(&pg_table);
|
||||
let temporary = format!("_dever_{}_{}", model.table, &model.revision[..8]);
|
||||
let temporary = sql::sqlite_table(model, &self.program.models, &temporary);
|
||||
let temporary = self.database_name(&temporary);
|
||||
let create_indexes = sql::sqlite_indexes(model)
|
||||
.iter()
|
||||
.map(|query| {
|
||||
let name = self.database_name(query);
|
||||
format!("{{ {name}, {name} }}")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let create_index_count = create_indexes.len();
|
||||
let create_indexes = self.database_array("%dever.db_sql", &create_indexes);
|
||||
let columns = model
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = self.database_name(&field.name);
|
||||
let definition = self.database_name(&sql::postgres_column(field));
|
||||
let ty = self.database_name(&sql::postgres_type(&field.ty));
|
||||
let default = sql::postgres_field_default(field);
|
||||
let default = self.database_optional_name(default.as_deref());
|
||||
format!("{{ {name}, {definition}, {ty}, ptr {default} }}")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let columns = self.database_array("%dever.db_pg_column", &columns);
|
||||
let constraints = constraints
|
||||
.iter()
|
||||
.map(|(name, definition, foreign)| {
|
||||
let name = self.database_name(name);
|
||||
let definition = self.database_name(definition);
|
||||
format!("{{ {name}, {definition}, i8 {} }}", u8::from(*foreign))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let constraint_count = constraints.len();
|
||||
let constraints = self.database_array("%dever.db_pg_constraint", &constraints);
|
||||
let name = self.database_name(&model.package);
|
||||
let table = self.database_name(&model.table);
|
||||
let revision = self.database_name(&model.revision);
|
||||
let seed_revision = self.database_name(&model.seed_revision);
|
||||
format!(
|
||||
"{{ {name}, {table}, {revision}, {seed_revision}, ptr {fields}, i64 {}, ptr {indexes}, i64 {index_count}, ptr {migrations}, i64 {}, %dever.db_sql {{ {sqlite_table}, {pg_table} }}, {temporary}, ptr {create_indexes}, i64 {create_index_count}, ptr {columns}, i64 {}, ptr {constraints}, i64 {constraint_count}, ptr null, i64 0, ptr null, i64 0 }}",
|
||||
model.fields.len(),
|
||||
model.migrations.len(),
|
||||
model.fields.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn initialize_database(
|
||||
&mut self,
|
||||
span: Span,
|
||||
service_start: &str,
|
||||
) -> Option<Migration> {
|
||||
if !self.module.database_enabled() {
|
||||
if self.module.api_enabled() {
|
||||
self.initialize_api("null", 0, "null", 0, service_start, span);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
let models = self.module.program.models.clone();
|
||||
let mut bindings = (0..models.len())
|
||||
.map(|index| self.module.database_binding(index))
|
||||
.collect::<Vec<_>>();
|
||||
for job in self.module.program.jobs.clone() {
|
||||
bindings.push(self.module.owner_binding(DatabaseOwner::Job(job.function)));
|
||||
}
|
||||
let binding_count = bindings.len();
|
||||
let bindings = self.module.database_array("%dever.db_binding", &bindings);
|
||||
let mut transactions = self
|
||||
.module
|
||||
.instances
|
||||
.iter()
|
||||
.filter(|instance| {
|
||||
self.module.program.functions[instance.function].kind == FunctionKind::Transaction
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
transactions.extend(
|
||||
self.module
|
||||
.program
|
||||
.api_commands
|
||||
.iter()
|
||||
.map(|command| Specialization {
|
||||
function: command.function,
|
||||
handlers: Vec::new(),
|
||||
})
|
||||
.filter(|instance| specialize::writes_database(self.module.program, instance)),
|
||||
);
|
||||
transactions.extend(
|
||||
self.module
|
||||
.program
|
||||
.api_routes
|
||||
.iter()
|
||||
.filter(|route| matches!(route.method, "POST" | "PUT" | "DELETE"))
|
||||
.map(|route| Specialization {
|
||||
function: route.function,
|
||||
handlers: Vec::new(),
|
||||
})
|
||||
.filter(|instance| specialize::writes_database(self.module.program, instance)),
|
||||
);
|
||||
if let Some(application) = &self.module.application {
|
||||
transactions.extend(
|
||||
application
|
||||
.rest_handlers
|
||||
.iter()
|
||||
.filter(|handler| handler.method != "GET")
|
||||
.map(|handler| Specialization {
|
||||
function: handler.function,
|
||||
handlers: Vec::new(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
transactions.extend(
|
||||
self.module
|
||||
.program
|
||||
.jobs
|
||||
.iter()
|
||||
.map(|job| Specialization {
|
||||
function: job.function,
|
||||
handlers: Vec::new(),
|
||||
})
|
||||
.filter(|instance| specialize::writes_database(self.module.program, instance)),
|
||||
);
|
||||
let groups = transactions
|
||||
.iter()
|
||||
.map(|instance| {
|
||||
let owners = specialize::database_effects(self.module.program, instance);
|
||||
let values = owners
|
||||
.iter()
|
||||
.map(|owner| self.module.owner_binding(*owner))
|
||||
.collect::<Vec<_>>();
|
||||
let pointer = self.module.database_array("%dever.db_binding", &values);
|
||||
format!("{{ ptr {pointer}, i64 {} }}", values.len())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let transactions = self.module.database_array("{ ptr, i64 }", &groups);
|
||||
let session = if self.module.api_enabled() {
|
||||
let api_session = self.initialize_api(
|
||||
&bindings,
|
||||
binding_count,
|
||||
&transactions,
|
||||
groups.len(),
|
||||
service_start,
|
||||
span,
|
||||
);
|
||||
self.protocol_call(
|
||||
"dever_rt_v1_api_session_database",
|
||||
vec![format!("ptr {api_session}")],
|
||||
"ptr",
|
||||
span,
|
||||
)
|
||||
} else {
|
||||
self.protocol_call(
|
||||
"dever_rt_v1_db_session_new",
|
||||
vec![
|
||||
format!("ptr {bindings}"),
|
||||
format!("i64 {binding_count}"),
|
||||
format!("ptr {transactions}"),
|
||||
format!("i64 {}", groups.len()),
|
||||
],
|
||||
"ptr",
|
||||
span,
|
||||
)
|
||||
};
|
||||
self.line(format!("store ptr {session}, ptr @dever_database_session"));
|
||||
self.initialize_jobs(span);
|
||||
if !self.module.api_enabled() {
|
||||
let prepare =
|
||||
self.database_operation("db_prepare", vec![format!("ptr {session}")], span);
|
||||
self.await_operation(&prepare, &Type::Unit, span, false);
|
||||
}
|
||||
let database_array = self.entry_slot_ir(&format!("[{} x ptr]", models.len()));
|
||||
let model_array = self.entry_slot_ir(&format!("[{} x %dever.db_model]", models.len()));
|
||||
let error_array = self.entry_slot_ir(&format!("[{} x ptr]", models.len()));
|
||||
for (index, model) in models.iter().enumerate() {
|
||||
let span = self.module.program.types[model.record].span;
|
||||
let binding = self.temp();
|
||||
self.line(format!("{binding} = getelementptr [{} x %dever.db_binding], ptr {bindings}, i32 0, i64 {index}", models.len()));
|
||||
let database = if self.module.api_enabled() {
|
||||
"null".into()
|
||||
} else {
|
||||
self.database_call(
|
||||
"db_select",
|
||||
vec![format!("ptr {session}"), format!("ptr {binding}")],
|
||||
"ptr",
|
||||
span,
|
||||
)
|
||||
};
|
||||
self.line(format!("store ptr {database}, ptr @dever_database_{index}"));
|
||||
let database_slot = self.temp();
|
||||
self.line(format!("{database_slot} = getelementptr [{} x ptr], ptr {database_array}, i32 0, i64 {index}", models.len()));
|
||||
self.line(format!("store ptr {database}, ptr {database_slot}"));
|
||||
let model_slot = self.temp();
|
||||
self.line(format!("{model_slot} = getelementptr [{} x %dever.db_model], ptr {model_array}, i32 0, i64 {index}", models.len()));
|
||||
let value = self.module.database_model(model);
|
||||
self.line(format!("store %dever.db_model {value}, ptr {model_slot}"));
|
||||
self.database_seeds(model, &model_slot, span);
|
||||
let errors = self.database_errors(span);
|
||||
let error_slot = self.temp();
|
||||
self.line(format!(
|
||||
"{error_slot} = getelementptr [{} x ptr], ptr {error_array}, i32 0, i64 {index}",
|
||||
models.len()
|
||||
));
|
||||
self.line(format!("store ptr {errors}, ptr {error_slot}"));
|
||||
}
|
||||
let operation = if self.module.api_enabled() {
|
||||
let api_session = self.temp();
|
||||
self.line(format!("{api_session} = load ptr, ptr @dever_api_session"));
|
||||
let mut arguments = vec![
|
||||
format!("ptr {api_session}"),
|
||||
format!("ptr {bindings}"),
|
||||
format!("ptr {model_array}"),
|
||||
format!("ptr {error_array}"),
|
||||
format!("i64 {}", models.len()),
|
||||
];
|
||||
let symbol = if self.module.jobs_enabled() {
|
||||
let errors = self.database_errors(span);
|
||||
arguments.push(format!("ptr {errors}"));
|
||||
"api_migrate_application"
|
||||
} else {
|
||||
"api_migrate_models"
|
||||
};
|
||||
arguments.push("i64 0".into());
|
||||
self.async_operation(symbol, arguments, span)
|
||||
} else {
|
||||
self.async_operation(
|
||||
"db_migrate_models",
|
||||
vec![
|
||||
format!("ptr {database_array}"),
|
||||
format!("ptr {model_array}"),
|
||||
format!("ptr {error_array}"),
|
||||
format!("i64 {}", models.len()),
|
||||
],
|
||||
span,
|
||||
)
|
||||
};
|
||||
self.await_operation(&operation, &Type::Unit, span, false);
|
||||
Some(Migration {
|
||||
bindings,
|
||||
models: model_array,
|
||||
errors: error_array,
|
||||
count: models.len(),
|
||||
})
|
||||
}
|
||||
|
||||
fn database_seed_value(
|
||||
&mut self,
|
||||
storage: &ModelFieldType,
|
||||
value: &ModelValue,
|
||||
span: Span,
|
||||
) -> (Type, ModelFieldType, String) {
|
||||
let (ty, value) = match value {
|
||||
ModelValue::Null => (
|
||||
Type::Nullable(Box::new(Type::Int)),
|
||||
"zeroinitializer".into(),
|
||||
),
|
||||
ModelValue::Bool(value) => (Type::Bool, u8::from(*value).to_string()),
|
||||
ModelValue::Number(value) => match storage {
|
||||
ModelFieldType::Float => (
|
||||
Type::Float,
|
||||
format!(
|
||||
"0x{:016X}",
|
||||
value.parse::<f64>().expect("checked seed Float").to_bits()
|
||||
),
|
||||
),
|
||||
ModelFieldType::Decimal { .. } => {
|
||||
let decimal =
|
||||
dever_runtime::number::parse_decimal(value).expect("checked seed Decimal");
|
||||
let bytes = decimal.to_bytes();
|
||||
let low = i64::from_le_bytes(bytes[..8].try_into().unwrap());
|
||||
let high = i64::from_le_bytes(bytes[8..].try_into().unwrap());
|
||||
(Type::Decimal, format!("{{ i64 {low}, i64 {high} }}"))
|
||||
}
|
||||
_ => (Type::Int, value.clone()),
|
||||
},
|
||||
ModelValue::Text(value) | ModelValue::Choice(value) => {
|
||||
let value = if matches!(storage, ModelFieldType::Choice(_)) {
|
||||
value.rsplit('.').next().unwrap()
|
||||
} else {
|
||||
value.as_str()
|
||||
};
|
||||
let text = self.application_text(value, span);
|
||||
if matches!(storage, ModelFieldType::Uuid) {
|
||||
let uuid = self.uuid_from_literal_text(&text, span);
|
||||
self.own_value(&Type::Uuid, &uuid, false);
|
||||
(Type::Uuid, uuid)
|
||||
} else {
|
||||
(Type::Text, text)
|
||||
}
|
||||
}
|
||||
};
|
||||
(ty, storage.clone(), value)
|
||||
}
|
||||
|
||||
fn database_seeds(&mut self, model: &ModelSchema, model_slot: &str, span: Span) {
|
||||
let mut seeds = Vec::new();
|
||||
for seed in &model.seed {
|
||||
let mut columns = Vec::new();
|
||||
let mut values = Vec::new();
|
||||
for (name, value) in &seed.fields {
|
||||
let storage = &model
|
||||
.fields
|
||||
.iter()
|
||||
.find(|field| field.name == *name)
|
||||
.unwrap()
|
||||
.ty;
|
||||
columns.push(sql::quoted(name));
|
||||
values.push(self.database_seed_value(storage, value, span));
|
||||
}
|
||||
for field in model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|field| field.generated && field.ty == ModelFieldType::Uuid)
|
||||
{
|
||||
columns.push(sql::quoted(&field.name));
|
||||
let uuid = self.database_call("db_uuid_new", vec![], "ptr", span);
|
||||
self.own_value(&Type::Uuid, &uuid, false);
|
||||
values.push((Type::Uuid, ModelFieldType::Uuid, uuid));
|
||||
}
|
||||
let placeholders = values
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, (_, storage, _))| sql::parameter_sql(index + 1, storage))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let conflict = seed
|
||||
.identity
|
||||
.iter()
|
||||
.map(|name| sql::quoted(name))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let statement = format!(
|
||||
"INSERT INTO {} ({}) VALUES ({placeholders}) ON CONFLICT ({conflict}) DO NOTHING",
|
||||
sql::quoted(&model.table),
|
||||
columns.join(", ")
|
||||
);
|
||||
let query = self.module.planned_sql(&statement);
|
||||
seeds.push(self.database_seed(&query, &values, span));
|
||||
}
|
||||
self.database_schema_array(model_slot, 18, "%dever.db_seed", &seeds);
|
||||
let mut migrations = Vec::new();
|
||||
for migration in &model.migrations {
|
||||
for statement in &migration.statements {
|
||||
let values = statement
|
||||
.parameters
|
||||
.iter()
|
||||
.map(|(storage, value)| self.database_seed_value(storage, value, span))
|
||||
.collect::<Vec<_>>();
|
||||
let query = self
|
||||
.module
|
||||
.database_sql(&statement.sqlite, &statement.postgres);
|
||||
let seed = self.database_seed(&query, &values, span);
|
||||
let name = self.module.database_name(&migration.name);
|
||||
let phase = u8::from(matches!(
|
||||
statement.phase,
|
||||
crate::syntax::MigrationPhase::After
|
||||
));
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = insertvalue %dever.db_data_migration {{ {name}, i32 {phase}, %dever.db_seed zeroinitializer }}, %dever.db_seed {seed}, 2"));
|
||||
migrations.push(value);
|
||||
}
|
||||
}
|
||||
self.database_schema_array(model_slot, 20, "%dever.db_data_migration", &migrations);
|
||||
}
|
||||
|
||||
fn database_seed(
|
||||
&mut self,
|
||||
query: &str,
|
||||
values: &[(Type, ModelFieldType, String)],
|
||||
span: Span,
|
||||
) -> String {
|
||||
let parameters = self.database_parameters(values, span);
|
||||
let sql = self.temp();
|
||||
self.line(format!("{sql} = load %dever.db_sql, ptr {query}"));
|
||||
let mut result = "zeroinitializer".to_string();
|
||||
for (index, ty, value) in [
|
||||
(0, "%dever.db_sql", sql),
|
||||
(1, "ptr", parameters),
|
||||
(2, "i64", values.len().to_string()),
|
||||
] {
|
||||
let next = self.temp();
|
||||
self.line(format!(
|
||||
"{next} = insertvalue %dever.db_seed {result}, {ty} {value}, {index}"
|
||||
));
|
||||
result = next;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn database_schema_array(&mut self, model: &str, field: usize, ty: &str, values: &[String]) {
|
||||
if values.is_empty() {
|
||||
return;
|
||||
}
|
||||
let count = values.len();
|
||||
let array = self.entry_slot_ir(&format!("[{count} x {ty}]"));
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
let slot = self.temp();
|
||||
self.line(format!(
|
||||
"{slot} = getelementptr [{count} x {ty}], ptr {array}, i32 0, i64 {index}"
|
||||
));
|
||||
self.line(format!("store {ty} {value}, ptr {slot}"));
|
||||
}
|
||||
let pointer = self.temp();
|
||||
self.line(format!(
|
||||
"{pointer} = getelementptr %dever.db_model, ptr {model}, i32 0, i32 {field}"
|
||||
));
|
||||
self.line(format!("store ptr {array}, ptr {pointer}"));
|
||||
let length = self.temp();
|
||||
self.line(format!(
|
||||
"{length} = getelementptr %dever.db_model, ptr {model}, i32 0, i32 {}",
|
||||
field + 1
|
||||
));
|
||||
self.line(format!("store i64 {count}, ptr {length}"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,984 @@
|
||||
use super::*;
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn atom(&mut self, atom: &Atom, ty: &Type, value: &str) -> Result<String, String> {
|
||||
if matches!(atom, Atom::Null) {
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let absent = self.temp();
|
||||
self.line(format!("{absent} = xor i1 {present}, 1"));
|
||||
return Ok(absent);
|
||||
}
|
||||
if let Type::Nullable(inner) = ty {
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = extractvalue {} {value}, 1",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let matched = self.atom(atom, inner, &payload)?;
|
||||
let result = self.temp();
|
||||
self.line(format!("{result} = and i1 {present}, {matched}"));
|
||||
return Ok(result);
|
||||
}
|
||||
Ok(match atom {
|
||||
Atom::Any => "1".into(),
|
||||
Atom::Bool(expected) => {
|
||||
self.compare("icmp eq", ty, value, if *expected { "1" } else { "0" })
|
||||
}
|
||||
Atom::Variant(variant) => {
|
||||
if matches!(ty, Type::Related(_)) {
|
||||
let matched = self.temp();
|
||||
self.line(format!(
|
||||
"{matched} = icmp {} ptr {value}, null",
|
||||
if *variant == 0 { "eq" } else { "ne" }
|
||||
));
|
||||
return Ok(matched);
|
||||
}
|
||||
let tag = self.temp();
|
||||
self.line(format!(
|
||||
"{tag} = extractvalue {} {value}, 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let result = self.temp();
|
||||
self.line(format!("{result} = icmp eq i32 {tag}, {variant}"));
|
||||
result
|
||||
}
|
||||
Atom::Int(low, high) => {
|
||||
let mut tests = Vec::new();
|
||||
if *low != i64::MIN {
|
||||
tests.push(self.compare("icmp sge", ty, value, &low.to_string()));
|
||||
}
|
||||
if *high != i64::MAX {
|
||||
tests.push(self.compare("icmp sle", ty, value, &high.to_string()));
|
||||
}
|
||||
self.reduce_bool("and", tests, "1")
|
||||
}
|
||||
Atom::Literal(literal) => {
|
||||
let first = self.guards.len();
|
||||
let expected = self.atom_constant(literal, ty);
|
||||
let equal = self.equals(ty, value, &expected)?;
|
||||
self.release_from(first);
|
||||
equal
|
||||
}
|
||||
Atom::Remainder(literals) => {
|
||||
let mut tests = Vec::new();
|
||||
for literal in literals {
|
||||
let first = self.guards.len();
|
||||
let expected = self.atom_constant(literal, ty);
|
||||
let equal = self.equals(ty, value, &expected)?;
|
||||
self.release_from(first);
|
||||
let different = self.temp();
|
||||
self.line(format!("{different} = xor i1 {equal}, 1"));
|
||||
tests.push(different);
|
||||
}
|
||||
self.reduce_bool("and", tests, "1")
|
||||
}
|
||||
Atom::Decimal(low, high) => {
|
||||
let low = self.constant(&Constant::Decimal(*low));
|
||||
let high = self.constant(&Constant::Decimal(*high));
|
||||
let below = self.decimal_compare(
|
||||
value,
|
||||
&low,
|
||||
BinaryOperator::GreaterEqual,
|
||||
self.module.program.functions[self.instance.function].span,
|
||||
);
|
||||
let above = self.decimal_compare(
|
||||
value,
|
||||
&high,
|
||||
BinaryOperator::LessEqual,
|
||||
self.module.program.functions[self.instance.function].span,
|
||||
);
|
||||
self.reduce_bool("and", vec![below, above], "1")
|
||||
}
|
||||
Atom::Null => unreachable!("validated kernel domain"),
|
||||
})
|
||||
}
|
||||
|
||||
fn atom_constant(&mut self, value: &Constant, ty: &Type) -> String {
|
||||
if let Constant::Text(text) = value {
|
||||
let (literal, length) = self.module.text_literal(text);
|
||||
let result = self.runtime_call(
|
||||
"dever_rt_v1_text_new",
|
||||
vec![format!("ptr {literal}"), format!("i64 {length}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
self.module.program.functions[self.instance.function].span,
|
||||
);
|
||||
self.own_value(&Type::Text, &result, false);
|
||||
if *ty == Type::Uuid {
|
||||
let uuid = self.uuid_from_literal_text(
|
||||
&result,
|
||||
self.module.program.functions[self.instance.function].span,
|
||||
);
|
||||
self.own_value(&Type::Uuid, &uuid, false);
|
||||
uuid
|
||||
} else {
|
||||
result
|
||||
}
|
||||
} else {
|
||||
self.constant(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn constant(&self, constant: &Constant) -> String {
|
||||
match constant {
|
||||
Constant::Null => "zeroinitializer".into(),
|
||||
Constant::Bool(value) => u8::from(*value).to_string(),
|
||||
Constant::Int(value) => value.to_string(),
|
||||
Constant::Float(value) => format!("0x{:016X}", value.to_bits()),
|
||||
Constant::Decimal(value) => {
|
||||
let bytes = value.to_bytes();
|
||||
let low = i64::from_le_bytes(bytes[..8].try_into().unwrap());
|
||||
let high = i64::from_le_bytes(bytes[8..].try_into().unwrap());
|
||||
format!("{{ i64 {low}, i64 {high} }}")
|
||||
}
|
||||
Constant::Text(_) => unreachable!("Text needs an owned runtime handle"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn compare(
|
||||
&mut self,
|
||||
operation: &str,
|
||||
ty: &Type,
|
||||
left: &str,
|
||||
right: &str,
|
||||
) -> String {
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = {operation} {} {left}, {right}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
result
|
||||
}
|
||||
|
||||
pub(super) fn expression(&mut self, expression: &Expression) -> Result<String, String> {
|
||||
let first = self.guards.len();
|
||||
let mut result = self.expression_raw(expression)?;
|
||||
if contains_owned(self.module.program, &expression.ty) {
|
||||
let field_move = matches!(expression.kind, ExpressionKind::Field { .. })
|
||||
&& self.moves.contains(&(expression as *const Expression))
|
||||
&& crate::native::updates::place(expression).is_some();
|
||||
let borrowed = matches!(
|
||||
expression.kind,
|
||||
ExpressionKind::Field { .. }
|
||||
| ExpressionKind::Record { .. }
|
||||
| ExpressionKind::Variant { .. }
|
||||
| ExpressionKind::Some(_)
|
||||
) && !field_move
|
||||
|| matches!(expression.kind, ExpressionKind::Local(_))
|
||||
&& !self.moves.contains(&(expression as *const Expression));
|
||||
let owner = self.own_value(&expression.ty, &result, borrowed);
|
||||
if borrowed {
|
||||
// A retain may return a different handle. Return the owned row
|
||||
// before releasing the temporaries from which it was cloned.
|
||||
result = self.load_owned_value(&expression.ty, owner);
|
||||
}
|
||||
self.release_between(first, owner);
|
||||
} else {
|
||||
self.release_from(first);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn expression_raw(&mut self, expression: &Expression) -> Result<String, String> {
|
||||
let result = match &expression.kind {
|
||||
ExpressionKind::Constant(Constant::Text(value)) => {
|
||||
let (literal, length) = self.module.text_literal(value);
|
||||
let text = self.runtime_call(
|
||||
"dever_rt_v1_text_new",
|
||||
vec![format!("ptr {literal}"), format!("i64 {length}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
expression.span,
|
||||
);
|
||||
if expression.ty == Type::Uuid {
|
||||
self.own_value(&Type::Text, &text, false);
|
||||
self.uuid_from_literal_text(&text, expression.span)
|
||||
} else {
|
||||
text
|
||||
}
|
||||
}
|
||||
ExpressionKind::Constant(value) => self.constant(value),
|
||||
ExpressionKind::Local(slot) => {
|
||||
let value = self.load_local(*slot);
|
||||
if self.moves.contains(&(expression as *const Expression))
|
||||
&& let Some(guard) = self.local_guards[*slot]
|
||||
{
|
||||
self.disarm(guard);
|
||||
}
|
||||
value
|
||||
}
|
||||
ExpressionKind::Field { value, index } => {
|
||||
if self.moves.contains(&(expression as *const Expression))
|
||||
&& contains_owned(self.module.program, &expression.ty)
|
||||
&& let Some((slot, fields)) = crate::native::updates::place(expression)
|
||||
{
|
||||
let (pointer, ty) = self.field_pointer(slot, &fields);
|
||||
debug_assert_eq!(ty, expression.ty);
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = load {}, ptr {pointer}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {pointer}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
return Ok(result);
|
||||
}
|
||||
let parent = self.expression(value)?;
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = extractvalue {} {parent}, {index}",
|
||||
self.module.ty(&value.ty)
|
||||
));
|
||||
result
|
||||
}
|
||||
ExpressionKind::Call { target, arguments } => {
|
||||
self.call(*target, arguments, expression)?
|
||||
}
|
||||
ExpressionKind::RunCall { .. }
|
||||
| ExpressionKind::AwaitTask(_)
|
||||
| ExpressionKind::StopTask(_)
|
||||
| ExpressionKind::Group(_)
|
||||
| ExpressionKind::AwaitGroup(_)
|
||||
| ExpressionKind::StopGroup(_)
|
||||
| ExpressionKind::ParallelCall { .. }
|
||||
| ExpressionKind::BlockingCall { .. }
|
||||
| ExpressionKind::Channel { .. }
|
||||
| ExpressionKind::ChannelSend { .. }
|
||||
| ExpressionKind::ChannelReceive(_)
|
||||
| ExpressionKind::ChannelClose(_) => self.concurrency(expression)?,
|
||||
ExpressionKind::CaptureResult {
|
||||
target,
|
||||
arguments,
|
||||
choice,
|
||||
success,
|
||||
} => self.capture_result(*target, arguments, *choice, *success, expression)?,
|
||||
ExpressionKind::Fail(error) => self.fail(error, expression.span)?,
|
||||
ExpressionKind::Record { fields } => {
|
||||
let mut values = Vec::with_capacity(fields.len());
|
||||
for (index, value) in fields {
|
||||
values.push((*index, self.expression(value)?));
|
||||
}
|
||||
let Type::Named(id) = expression.ty else {
|
||||
unreachable!("checked record")
|
||||
};
|
||||
let Shape::Record(definition) = &self.module.program.types[id].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let types = definition
|
||||
.iter()
|
||||
.map(|field| field.ty.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let mut aggregate = "undef".to_owned();
|
||||
for (index, value) in values {
|
||||
let next = self.temp();
|
||||
self.line(format!(
|
||||
"{next} = insertvalue %T{id} {aggregate}, {} {value}, {index}",
|
||||
self.module.ty(&types[index])
|
||||
));
|
||||
aggregate = next;
|
||||
}
|
||||
aggregate
|
||||
}
|
||||
ExpressionKind::Variant { variant, arguments } => {
|
||||
if let Type::Related(inner) = &expression.ty {
|
||||
if *variant == 0 {
|
||||
return Ok("null".into());
|
||||
}
|
||||
let value = self.expression(&arguments[0])?;
|
||||
let input = self.entry_slot(inner);
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {input}",
|
||||
self.module.ty(inner)
|
||||
));
|
||||
let related = self.protocol_call(
|
||||
"dever_rt_v1_db_related_new",
|
||||
vec![
|
||||
format!("ptr @dever_type_{}", self.module.type_index(inner)),
|
||||
format!("ptr {input}"),
|
||||
],
|
||||
"ptr",
|
||||
expression.span,
|
||||
);
|
||||
self.own_value(&expression.ty, &related, false);
|
||||
return Ok(related);
|
||||
}
|
||||
let mut values = Vec::with_capacity(arguments.len());
|
||||
for value in arguments {
|
||||
values.push(self.expression(value)?);
|
||||
}
|
||||
let Type::Named(id) = expression.ty else {
|
||||
unreachable!("checked variant")
|
||||
};
|
||||
let Shape::Choice(variants) = &self.module.program.types[id].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let types = variants[*variant]
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| field.ty.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let alloca = self.temp();
|
||||
self.line(format!("{alloca} = alloca %T{id}"));
|
||||
self.line(format!("store %T{id} zeroinitializer, ptr {alloca}"));
|
||||
let tag = self.temp();
|
||||
self.line(format!(
|
||||
"{tag} = getelementptr %T{id}, ptr {alloca}, i32 0, i32 0"
|
||||
));
|
||||
self.line(format!("store i32 {variant}, ptr {tag}"));
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = getelementptr %T{id}, ptr {alloca}, i32 0, i32 1"
|
||||
));
|
||||
for (index, (ty, value)) in types.iter().zip(values).enumerate() {
|
||||
let field = self.temp();
|
||||
self.line(format!("{field} = getelementptr %T{id}V{variant}, ptr {payload}, i32 0, i32 {index}"));
|
||||
self.line(format!("store {} {value}, ptr {field}", self.module.ty(ty)));
|
||||
}
|
||||
let result = self.temp();
|
||||
self.line(format!("{result} = load %T{id}, ptr {alloca}"));
|
||||
result
|
||||
}
|
||||
ExpressionKind::Some(value) => {
|
||||
let inner = self.expression(value)?;
|
||||
let first = self.temp();
|
||||
self.line(format!(
|
||||
"{first} = insertvalue {} undef, i1 1, 0",
|
||||
self.module.ty(&expression.ty)
|
||||
));
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = insertvalue {} {first}, {} {inner}, 1",
|
||||
self.module.ty(&expression.ty),
|
||||
self.module.ty(&value.ty)
|
||||
));
|
||||
result
|
||||
}
|
||||
ExpressionKind::Unary { operator, value } => {
|
||||
let input = self.expression(value)?;
|
||||
match (operator, &value.ty) {
|
||||
(UnaryOperator::Not, Type::Bool) => {
|
||||
let result = self.temp();
|
||||
self.line(format!("{result} = xor i1 {input}, 1"));
|
||||
result
|
||||
}
|
||||
(UnaryOperator::Negate, Type::Float) => {
|
||||
let result = self.temp();
|
||||
self.line(format!("{result} = fneg double {input}"));
|
||||
result
|
||||
}
|
||||
(UnaryOperator::Negate, Type::Int) => {
|
||||
self.checked_integer("ssub", "0", &input, expression.span)
|
||||
}
|
||||
(UnaryOperator::Negate, Type::Decimal) => {
|
||||
let parts = self.decimal_parts(&input);
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_decimal_neg",
|
||||
parts,
|
||||
&Type::Decimal,
|
||||
false,
|
||||
None,
|
||||
expression.span,
|
||||
)
|
||||
}
|
||||
_ => unreachable!("checked unary type"),
|
||||
}
|
||||
}
|
||||
ExpressionKind::Binary {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
} => self.binary(left, *operator, right, expression.span)?,
|
||||
ExpressionKind::Promote(value) => {
|
||||
let input = self.expression(value)?;
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_decimal_from_int",
|
||||
vec![format!("i64 {input}")],
|
||||
&Type::Decimal,
|
||||
false,
|
||||
None,
|
||||
expression.span,
|
||||
)
|
||||
}
|
||||
ExpressionKind::Intrinsic {
|
||||
operation,
|
||||
handler,
|
||||
arguments,
|
||||
} => self.intrinsic(*operation, *handler, arguments, expression),
|
||||
ExpressionKind::List(values) => self.list_literal(values, expression),
|
||||
ExpressionKind::Map(entries) => self.map_literal(entries, expression),
|
||||
ExpressionKind::Collection {
|
||||
operation,
|
||||
handler,
|
||||
sequence,
|
||||
arguments,
|
||||
} => self.collection(*operation, *handler, *sequence, arguments, expression),
|
||||
ExpressionKind::ModelOperation { model, operation } => {
|
||||
self.model_operation(*model, operation, expression)?
|
||||
}
|
||||
ExpressionKind::JobEnqueue {
|
||||
target,
|
||||
arguments,
|
||||
scheduled,
|
||||
} => self.enqueue_job(*target, arguments, *scheduled, expression.span)?,
|
||||
_ => unreachable!("validated kernel expression"),
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn call(
|
||||
&mut self,
|
||||
target: CallTarget,
|
||||
arguments: &[CallArgument],
|
||||
expression: &Expression,
|
||||
) -> Result<String, String> {
|
||||
let instance = self.call_instance(target, arguments);
|
||||
let (status, result) =
|
||||
self.invoke(&instance, arguments, &expression.ty, expression.span)?;
|
||||
let failed = self.temp();
|
||||
self.line(format!("{failed} = icmp ne i32 {status}, 0"));
|
||||
let fault = self.label("call_fault");
|
||||
let success = self.label("call_ok");
|
||||
self.line(format!("br i1 {failed}, label %{fault}, label %{success}"));
|
||||
self.start(&fault);
|
||||
self.append_frame(expression.span);
|
||||
self.exit(&status);
|
||||
self.start(&success);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {result}",
|
||||
self.module.ty(&expression.ty)
|
||||
));
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub(super) fn append_frame(&mut self, span: Span) {
|
||||
let location = self.module.location(span);
|
||||
let depth_ptr = self.temp();
|
||||
self.line(format!(
|
||||
"{depth_ptr} = getelementptr %dever.fault, ptr %fault, i32 0, i32 2"
|
||||
));
|
||||
let depth = self.temp();
|
||||
self.line(format!("{depth} = load i32, ptr {depth_ptr}"));
|
||||
let frame = self.temp();
|
||||
self.line(format!(
|
||||
"{frame} = getelementptr %dever.fault, ptr %fault, i32 0, i32 3, i32 {depth}"
|
||||
));
|
||||
self.line(format!("store i32 {location}, ptr {frame}"));
|
||||
let next = self.temp();
|
||||
self.line(format!("{next} = add i32 {depth}, 1"));
|
||||
self.line(format!("store i32 {next}, ptr {depth_ptr}"));
|
||||
}
|
||||
|
||||
pub(super) fn write_fault(&mut self, code: &str, span: Span) {
|
||||
let location = self.module.location(span);
|
||||
for (index, value) in [
|
||||
(0, code.to_owned()),
|
||||
(1, location.to_string()),
|
||||
(2, "0".into()),
|
||||
] {
|
||||
let pointer = self.temp();
|
||||
self.line(format!(
|
||||
"{pointer} = getelementptr %dever.fault, ptr %fault, i32 0, i32 {index}"
|
||||
));
|
||||
self.line(format!("store i32 {value}, ptr {pointer}"));
|
||||
}
|
||||
self.exit("1");
|
||||
}
|
||||
|
||||
pub(super) fn checked_integer(
|
||||
&mut self,
|
||||
operation: &str,
|
||||
left: &str,
|
||||
right: &str,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let pair = self.temp();
|
||||
self.line(format!("{pair} = call {{ i64, i1 }} @llvm.{operation}.with.overflow.i64(i64 {left}, i64 {right})"));
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = extractvalue {{ i64, i1 }} {pair}, 0"));
|
||||
let overflow = self.temp();
|
||||
self.line(format!("{overflow} = extractvalue {{ i64, i1 }} {pair}, 1"));
|
||||
let fault = self.label("overflow");
|
||||
let success = self.label("arithmetic_ok");
|
||||
self.line(format!(
|
||||
"br i1 {overflow}, label %{fault}, label %{success}"
|
||||
));
|
||||
self.start(&fault);
|
||||
self.write_fault(&(FaultCode::Overflow as i32).to_string(), span);
|
||||
self.start(&success);
|
||||
value
|
||||
}
|
||||
|
||||
fn integer_division(&mut self, operation: &str, left: &str, right: &str, span: Span) -> String {
|
||||
let zero = self.compare("icmp eq", &Type::Int, right, "0");
|
||||
let minimum = self.compare("icmp eq", &Type::Int, left, &i64::MIN.to_string());
|
||||
let negative_one = self.compare("icmp eq", &Type::Int, right, "-1");
|
||||
let overflow = self.temp();
|
||||
self.line(format!("{overflow} = and i1 {minimum}, {negative_one}"));
|
||||
let invalid = self.temp();
|
||||
self.line(format!("{invalid} = or i1 {zero}, {overflow}"));
|
||||
let fault = self.label("division_fault");
|
||||
let success = self.label("division_ok");
|
||||
self.line(format!("br i1 {invalid}, label %{fault}, label %{success}"));
|
||||
self.start(&fault);
|
||||
let code = self.temp();
|
||||
self.line(format!(
|
||||
"{code} = select i1 {zero}, i32 {}, i32 {}",
|
||||
FaultCode::DivisionByZero as i32,
|
||||
FaultCode::Overflow as i32
|
||||
));
|
||||
self.write_fault(&code, span);
|
||||
self.start(&success);
|
||||
let result = self.temp();
|
||||
self.line(format!("{result} = {operation} i64 {left}, {right}"));
|
||||
result
|
||||
}
|
||||
|
||||
fn decimal_compare(
|
||||
&mut self,
|
||||
left: &str,
|
||||
right: &str,
|
||||
operation: BinaryOperator,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let mut arguments = self.decimal_parts(left);
|
||||
arguments.extend(self.decimal_parts(right));
|
||||
let order = self.runtime_call(
|
||||
"dever_rt_v1_decimal_compare",
|
||||
arguments,
|
||||
&Type::Int,
|
||||
false,
|
||||
Some("i32"),
|
||||
span,
|
||||
);
|
||||
let comparison = match operation {
|
||||
BinaryOperator::Less => "slt",
|
||||
BinaryOperator::LessEqual => "sle",
|
||||
BinaryOperator::Greater => "sgt",
|
||||
BinaryOperator::GreaterEqual => "sge",
|
||||
_ => unreachable!("ordered Decimal comparison"),
|
||||
};
|
||||
let result = self.temp();
|
||||
self.line(format!("{result} = icmp {comparison} i32 {order}, 0"));
|
||||
result
|
||||
}
|
||||
|
||||
fn binary(
|
||||
&mut self,
|
||||
left: &Expression,
|
||||
operation: BinaryOperator,
|
||||
right: &Expression,
|
||||
span: Span,
|
||||
) -> Result<String, String> {
|
||||
let lhs = self.expression(left)?;
|
||||
if matches!(operation, BinaryOperator::And | BinaryOperator::Or) {
|
||||
let first = self.block.clone();
|
||||
let rhs_block = self.label("short_rhs");
|
||||
let done = self.label("short_done");
|
||||
if operation == BinaryOperator::And {
|
||||
self.line(format!("br i1 {lhs}, label %{rhs_block}, label %{done}"));
|
||||
} else {
|
||||
self.line(format!("br i1 {lhs}, label %{done}, label %{rhs_block}"));
|
||||
}
|
||||
self.start(&rhs_block);
|
||||
let rhs = self.expression(right)?;
|
||||
let last = self.block.clone();
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = phi i1 [{lhs}, %{first}], [{rhs}, %{last}]"
|
||||
));
|
||||
return Ok(result);
|
||||
}
|
||||
let rhs = self.expression(right)?;
|
||||
let result = match operation {
|
||||
BinaryOperator::Equal | BinaryOperator::NotEqual => {
|
||||
let equal = self.equals(&left.ty, &lhs, &rhs)?;
|
||||
if operation == BinaryOperator::Equal {
|
||||
equal
|
||||
} else {
|
||||
let different = self.temp();
|
||||
self.line(format!("{different} = xor i1 {equal}, 1"));
|
||||
different
|
||||
}
|
||||
}
|
||||
BinaryOperator::Less
|
||||
| BinaryOperator::LessEqual
|
||||
| BinaryOperator::Greater
|
||||
| BinaryOperator::GreaterEqual => {
|
||||
if matches!(left.ty, Type::Decimal) {
|
||||
return Ok(self.decimal_compare(&lhs, &rhs, operation, span));
|
||||
}
|
||||
if matches!(left.ty, Type::Text) {
|
||||
let order = self.runtime_call(
|
||||
"dever_rt_v1_text_compare",
|
||||
vec![format!("ptr {lhs}"), format!("ptr {rhs}")],
|
||||
&Type::Int,
|
||||
false,
|
||||
Some("i32"),
|
||||
span,
|
||||
);
|
||||
let comparison = match operation {
|
||||
BinaryOperator::Less => "slt",
|
||||
BinaryOperator::LessEqual => "sle",
|
||||
BinaryOperator::Greater => "sgt",
|
||||
BinaryOperator::GreaterEqual => "sge",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let result = self.temp();
|
||||
self.line(format!("{result} = icmp {comparison} i32 {order}, 0"));
|
||||
return Ok(result);
|
||||
}
|
||||
let comparison = match (&left.ty, operation) {
|
||||
(
|
||||
Type::Int | Type::DateTime | Type::Date | Type::Time | Type::Duration,
|
||||
BinaryOperator::Less,
|
||||
) => "icmp slt",
|
||||
(
|
||||
Type::Int | Type::DateTime | Type::Date | Type::Time | Type::Duration,
|
||||
BinaryOperator::LessEqual,
|
||||
) => "icmp sle",
|
||||
(
|
||||
Type::Int | Type::DateTime | Type::Date | Type::Time | Type::Duration,
|
||||
BinaryOperator::Greater,
|
||||
) => "icmp sgt",
|
||||
(
|
||||
Type::Int | Type::DateTime | Type::Date | Type::Time | Type::Duration,
|
||||
BinaryOperator::GreaterEqual,
|
||||
) => "icmp sge",
|
||||
(Type::Float, BinaryOperator::Less) => "fcmp olt",
|
||||
(Type::Float, BinaryOperator::LessEqual) => "fcmp ole",
|
||||
(Type::Float, BinaryOperator::Greater) => "fcmp ogt",
|
||||
(Type::Float, BinaryOperator::GreaterEqual) => "fcmp oge",
|
||||
_ => unreachable!("checked ordered comparison"),
|
||||
};
|
||||
self.compare(comparison, &left.ty, &lhs, &rhs)
|
||||
}
|
||||
BinaryOperator::Add
|
||||
| BinaryOperator::Subtract
|
||||
| BinaryOperator::Multiply
|
||||
| BinaryOperator::Divide
|
||||
| BinaryOperator::IntegerDivide
|
||||
| BinaryOperator::Remainder => match &left.ty {
|
||||
Type::Int => match operation {
|
||||
BinaryOperator::Add => self.checked_integer("sadd", &lhs, &rhs, span),
|
||||
BinaryOperator::Subtract => self.checked_integer("ssub", &lhs, &rhs, span),
|
||||
BinaryOperator::Multiply => self.checked_integer("smul", &lhs, &rhs, span),
|
||||
BinaryOperator::IntegerDivide => {
|
||||
self.integer_division("sdiv", &lhs, &rhs, span)
|
||||
}
|
||||
BinaryOperator::Remainder => self.integer_division("srem", &lhs, &rhs, span),
|
||||
_ => unreachable!("checked Int operation"),
|
||||
},
|
||||
Type::Float => {
|
||||
let operation = match operation {
|
||||
BinaryOperator::Add => "fadd",
|
||||
BinaryOperator::Subtract => "fsub",
|
||||
BinaryOperator::Multiply => "fmul",
|
||||
BinaryOperator::Divide => "fdiv",
|
||||
BinaryOperator::Remainder => "frem",
|
||||
_ => unreachable!("checked Float operation"),
|
||||
};
|
||||
let result = self.temp();
|
||||
self.line(format!("{result} = {operation} double {lhs}, {rhs}"));
|
||||
result
|
||||
}
|
||||
Type::Decimal => {
|
||||
let symbol = match operation {
|
||||
BinaryOperator::Add => "decimal_add",
|
||||
BinaryOperator::Subtract => "decimal_sub",
|
||||
BinaryOperator::Multiply => "decimal_mul",
|
||||
BinaryOperator::Divide => "decimal_div",
|
||||
_ => unreachable!("checked Decimal operation"),
|
||||
};
|
||||
let mut arguments = self.decimal_parts(&lhs);
|
||||
arguments.extend(self.decimal_parts(&rhs));
|
||||
self.runtime_call(
|
||||
&format!("dever_rt_v1_{symbol}"),
|
||||
arguments,
|
||||
&Type::Decimal,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
)
|
||||
}
|
||||
Type::Text => self.runtime_call(
|
||||
"dever_rt_v1_text_concat",
|
||||
vec![format!("ptr {lhs}"), format!("ptr {rhs}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
),
|
||||
_ => unreachable!("checked arithmetic type"),
|
||||
},
|
||||
BinaryOperator::And | BinaryOperator::Or => unreachable!(),
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(super) fn equals(&mut self, ty: &Type, left: &str, right: &str) -> Result<String, String> {
|
||||
match ty {
|
||||
Type::Bool | Type::Int | Type::DateTime | Type::Date | Type::Time | Type::Duration => {
|
||||
Ok(self.compare("icmp eq", ty, left, right))
|
||||
}
|
||||
Type::Float => Ok(self.compare("fcmp oeq", ty, left, right)),
|
||||
Type::Decimal => {
|
||||
let mut arguments = self.decimal_parts(left);
|
||||
arguments.extend(self.decimal_parts(right));
|
||||
let raw = self.temp();
|
||||
self.line(format!(
|
||||
"{raw} = call i8 @dever_rt_v1_decimal_equal({})",
|
||||
arguments.join(", ")
|
||||
));
|
||||
let equal = self.temp();
|
||||
self.line(format!("{equal} = icmp ne i8 {raw}, 0"));
|
||||
Ok(equal)
|
||||
}
|
||||
Type::Text
|
||||
| Type::Json
|
||||
| Type::Id
|
||||
| Type::Uuid
|
||||
| Type::Bytes
|
||||
| Type::List(_)
|
||||
| Type::Related(_)
|
||||
| Type::Map(_, _) => {
|
||||
let kind = match ty {
|
||||
Type::Text | Type::Json | Type::Id => "text",
|
||||
Type::Uuid => "uuid",
|
||||
Type::Bytes => "bytes",
|
||||
Type::List(_) => "list",
|
||||
Type::Related(_) => "db_related",
|
||||
Type::Map(_, _) => "map",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let raw = self.temp();
|
||||
self.line(format!(
|
||||
"{raw} = call i8 @dever_rt_v1_{kind}_equal(ptr {left}, ptr {right})"
|
||||
));
|
||||
let equal = self.temp();
|
||||
self.line(format!("{equal} = icmp ne i8 {raw}, 0"));
|
||||
Ok(equal)
|
||||
}
|
||||
Type::Nullable(inner) => {
|
||||
let left_present = self.temp();
|
||||
self.line(format!(
|
||||
"{left_present} = extractvalue {} {left}, 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let right_present = self.temp();
|
||||
self.line(format!(
|
||||
"{right_present} = extractvalue {} {right}, 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let same_presence =
|
||||
self.compare("icmp eq", &Type::Bool, &left_present, &right_present);
|
||||
let both = self.temp();
|
||||
self.line(format!("{both} = and i1 {left_present}, {right_present}"));
|
||||
let skip = self.block.clone();
|
||||
let compare = self.label("some_eq");
|
||||
let done = self.label("nullable_eq");
|
||||
self.line(format!("br i1 {both}, label %{compare}, label %{done}"));
|
||||
self.start(&compare);
|
||||
let lhs = self.temp();
|
||||
self.line(format!(
|
||||
"{lhs} = extractvalue {} {left}, 1",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let rhs = self.temp();
|
||||
self.line(format!(
|
||||
"{rhs} = extractvalue {} {right}, 1",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let equal = self.equals(inner, &lhs, &rhs)?;
|
||||
let compared = self.block.clone();
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = phi i1 [{same_presence}, %{skip}], [{equal}, %{compared}]"
|
||||
));
|
||||
Ok(result)
|
||||
}
|
||||
Type::Named(id) if self.module.program.types[*id].kind == DefinitionKind::ModelId => {
|
||||
Ok(self.compare("icmp eq", ty, left, right))
|
||||
}
|
||||
Type::Named(id) => match &self.module.program.types[*id].shape {
|
||||
Shape::Record(fields) => {
|
||||
let types = fields
|
||||
.iter()
|
||||
.map(|field| field.ty.clone())
|
||||
.collect::<Vec<_>>();
|
||||
self.equals_fields(&types, ty, left, right)
|
||||
}
|
||||
Shape::Choice(variants) => {
|
||||
let types = variants
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| field.ty.clone())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.equals_choice(*id, &types, left, right)
|
||||
}
|
||||
},
|
||||
Type::Outputs(fields) => {
|
||||
let types = fields
|
||||
.iter()
|
||||
.map(|field| field.ty.clone())
|
||||
.collect::<Vec<_>>();
|
||||
self.equals_fields(&types, ty, left, right)
|
||||
}
|
||||
Type::MapEntry(key, value) => self.equals_fields(
|
||||
&[key.as_ref().clone(), value.as_ref().clone()],
|
||||
ty,
|
||||
left,
|
||||
right,
|
||||
),
|
||||
Type::Unit => Ok("1".into()),
|
||||
// Resources are not source-comparable. Internal collection descriptors
|
||||
// still need a total callback for their concrete handle storage.
|
||||
Type::File
|
||||
| Type::Secret
|
||||
| Type::ClientTls
|
||||
| Type::ServerTls
|
||||
| Type::HttpClient
|
||||
| Type::HttpReply
|
||||
| Type::WebSocket
|
||||
| Type::Upload
|
||||
| Type::Stream(_)
|
||||
| Type::AsyncStream(_)
|
||||
| Type::RowStream(_)
|
||||
| Type::Socket
|
||||
| Type::Listener
|
||||
| Type::Task(_)
|
||||
| Type::Group
|
||||
| Type::Channel(_) => Ok(self.compare("icmp eq", ty, left, right)),
|
||||
}
|
||||
}
|
||||
|
||||
fn equals_fields(
|
||||
&mut self,
|
||||
fields: &[Type],
|
||||
ty: &Type,
|
||||
left: &str,
|
||||
right: &str,
|
||||
) -> Result<String, String> {
|
||||
let mut checks = Vec::new();
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
let lhs = self.temp();
|
||||
self.line(format!(
|
||||
"{lhs} = extractvalue {} {left}, {index}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let rhs = self.temp();
|
||||
self.line(format!(
|
||||
"{rhs} = extractvalue {} {right}, {index}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
checks.push(self.equals(field, &lhs, &rhs)?);
|
||||
}
|
||||
Ok(self.reduce_bool("and", checks, "1"))
|
||||
}
|
||||
|
||||
fn equals_choice(
|
||||
&mut self,
|
||||
id: usize,
|
||||
variants: &[Vec<Type>],
|
||||
left: &str,
|
||||
right: &str,
|
||||
) -> Result<String, String> {
|
||||
let left_tag = self.temp();
|
||||
self.line(format!("{left_tag} = extractvalue %T{id} {left}, 0"));
|
||||
let right_tag = self.temp();
|
||||
self.line(format!("{right_tag} = extractvalue %T{id} {right}, 0"));
|
||||
let same = self.temp();
|
||||
self.line(format!("{same} = icmp eq i32 {left_tag}, {right_tag}"));
|
||||
let mismatch = self.label("choice_mismatch");
|
||||
let dispatch = self.label("choice_dispatch");
|
||||
let done = self.label("choice_done");
|
||||
self.line(format!(
|
||||
"br i1 {same}, label %{dispatch}, label %{mismatch}"
|
||||
));
|
||||
self.start(&mismatch);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&dispatch);
|
||||
let arms = variants
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, _)| format!("i32 {index}, label %choice{id}_v{index}_{}", self.next))
|
||||
.collect::<Vec<_>>();
|
||||
let base = self.next;
|
||||
self.next += 1;
|
||||
self.line(format!(
|
||||
"switch i32 {left_tag}, label %{mismatch} [{}]",
|
||||
arms.join(" ")
|
||||
));
|
||||
let mut results = vec![("0".to_owned(), mismatch)];
|
||||
for (variant, fields) in variants.iter().enumerate() {
|
||||
self.start(&format!("choice{id}_v{variant}_{base}"));
|
||||
let lhs = self.temp();
|
||||
self.line(format!("{lhs} = alloca %T{id}"));
|
||||
self.line(format!("store %T{id} {left}, ptr {lhs}"));
|
||||
let rhs = self.temp();
|
||||
self.line(format!("{rhs} = alloca %T{id}"));
|
||||
self.line(format!("store %T{id} {right}, ptr {rhs}"));
|
||||
let lhs_payload = self.temp();
|
||||
self.line(format!(
|
||||
"{lhs_payload} = getelementptr %T{id}, ptr {lhs}, i32 0, i32 1"
|
||||
));
|
||||
let rhs_payload = self.temp();
|
||||
self.line(format!(
|
||||
"{rhs_payload} = getelementptr %T{id}, ptr {rhs}, i32 0, i32 1"
|
||||
));
|
||||
let mut checks = Vec::new();
|
||||
for (index, ty) in fields.iter().enumerate() {
|
||||
let left_ptr = self.temp();
|
||||
self.line(format!("{left_ptr} = getelementptr %T{id}V{variant}, ptr {lhs_payload}, i32 0, i32 {index}"));
|
||||
let right_ptr = self.temp();
|
||||
self.line(format!("{right_ptr} = getelementptr %T{id}V{variant}, ptr {rhs_payload}, i32 0, i32 {index}"));
|
||||
let left_value = self.temp();
|
||||
self.line(format!(
|
||||
"{left_value} = load {}, ptr {left_ptr}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let right_value = self.temp();
|
||||
self.line(format!(
|
||||
"{right_value} = load {}, ptr {right_ptr}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
checks.push(self.equals(ty, &left_value, &right_value)?);
|
||||
}
|
||||
let equal = self.reduce_bool("and", checks, "1");
|
||||
let end = self.block.clone();
|
||||
self.line(format!("br label %{done}"));
|
||||
results.push((equal, end));
|
||||
}
|
||||
self.start(&done);
|
||||
let result = self.temp();
|
||||
let incoming = results
|
||||
.iter()
|
||||
.map(|(value, block)| format!("[{value}, %{block}]"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
self.line(format!("{result} = phi i1 {incoming}"));
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
//! Typed external operation envelopes over the shared invocation-owned supervisor.
|
||||
use super::*;
|
||||
use crate::hir::AdapterImplementation;
|
||||
use crate::native::EmbeddedResource;
|
||||
use crate::types::Field;
|
||||
|
||||
const DECLARATIONS: &str = "\
|
||||
%dever.external_resource = type { { ptr, i64 }, ptr, i64, { ptr, i64 }, i8 }
|
||||
%dever.external_definition = type { { ptr, i64 }, { ptr, i64 }, { ptr, i64 }, { ptr, i64 }, { ptr, i64 }, { ptr, i64 }, ptr, i64, ptr, i64, i64 }
|
||||
declare i32 @dever_rt_v1_external_prepare(ptr, ptr, i64, ptr)
|
||||
declare ptr @dever_rt_v1_external_start(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_external_call(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_external_reply_take(ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_external_root(ptr, ptr, ptr, ptr, ptr, ptr)
|
||||
declare void @dever_rt_v1_external_cause_append(ptr, ptr)
|
||||
";
|
||||
|
||||
fn input_fields(program: &Program, function: usize) -> Vec<Field> {
|
||||
let function = &program.functions[function];
|
||||
let external = function.external.expect("external operation");
|
||||
let contract = program.functions[external.contract].port.as_ref().unwrap();
|
||||
contract
|
||||
.input_names
|
||||
.iter()
|
||||
.zip(&function.parameters)
|
||||
.map(|(name, parameter)| Field {
|
||||
name: name.clone(),
|
||||
ty: parameter
|
||||
.value_type()
|
||||
.expect("checked Port value input")
|
||||
.clone(),
|
||||
private: false,
|
||||
bounds: Vec::new(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn external_enabled(&self) -> bool {
|
||||
self.application
|
||||
.as_ref()
|
||||
.is_some_and(|app| app.resources.is_some())
|
||||
|| self
|
||||
.instances
|
||||
.iter()
|
||||
.any(|instance| self.program.functions[instance.function].external.is_some())
|
||||
}
|
||||
|
||||
pub(super) fn collect_external_types(&mut self) {
|
||||
for instance in self.instances.clone() {
|
||||
let function = &self.program.functions[instance.function];
|
||||
let Some(external) = function.external else {
|
||||
continue;
|
||||
};
|
||||
let mut rows = vec![
|
||||
input_fields(self.program, instance.function),
|
||||
function.outputs.clone(),
|
||||
];
|
||||
let contract = self.program.functions[external.contract]
|
||||
.port
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
for failure in &contract.failures {
|
||||
let Shape::Choice(variants) = &self.program.types[failure.ty].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
rows.push(variants[failure.variant].fields.clone());
|
||||
}
|
||||
for fields in rows {
|
||||
collect_owned_types(self.program, &Type::Outputs(fields), &mut self.value_types);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn emit_external_descriptors(
|
||||
&mut self,
|
||||
resources: &[EmbeddedResource],
|
||||
indices: &[usize],
|
||||
attachments: &[BinaryResource<'_>],
|
||||
) {
|
||||
if !self.external_enabled() && resources.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.declarations.push_str(DECLARATIONS);
|
||||
self.declarations.push_str("define internal void @dever_external_append_cause(ptr %fault, ptr %message) {\nentry:\n %cause = getelementptr %dever.fault, ptr %fault, i32 0, i32 8\n call void @dever_rt_v1_external_cause_append(ptr %cause, ptr %message)\n ret void\n}\n");
|
||||
let adapters = self.application.as_ref().unwrap().adapters.clone();
|
||||
for id in adapters {
|
||||
let adapter = self.program.adapters[id].clone();
|
||||
let AdapterImplementation::External(external) = &adapter.implementation else {
|
||||
continue;
|
||||
};
|
||||
let names = [
|
||||
format!("{}:{}", adapter.identity, adapter.name),
|
||||
external.ecosystem.keyword().to_owned(),
|
||||
external.entry.clone(),
|
||||
adapter.identity.clone(),
|
||||
adapter.name.clone(),
|
||||
external.schema.clone(),
|
||||
];
|
||||
let metadata = names
|
||||
.iter()
|
||||
.map(|name| self.api_name(name))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let capabilities = self.wire_names(
|
||||
&external
|
||||
.capabilities
|
||||
.iter()
|
||||
.map(|value| (*value).to_owned())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
let operations = adapter
|
||||
.operations
|
||||
.keys()
|
||||
.map(|port| {
|
||||
self.program.functions[*port]
|
||||
.port
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.operation
|
||||
.clone()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let operation_names = self.wire_names(&operations);
|
||||
writeln!(self.declarations, "@dever_external_{id} = private constant %dever.external_definition {{ {metadata}, ptr {capabilities}, i64 {}, ptr {operation_names}, i64 {}, i64 30000 }}", external.capabilities.len(), operations.len()).unwrap();
|
||||
}
|
||||
if resources.is_empty() {
|
||||
return;
|
||||
}
|
||||
for resource in attachments {
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@{} = external constant [{} x i8]",
|
||||
resource.symbol,
|
||||
resource.bytes.len()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let mut entries = Vec::new();
|
||||
for (resource, index) in resources.iter().zip(indices) {
|
||||
let path = self.api_name(&resource.path);
|
||||
let digest = self.api_name(&resource.sha256);
|
||||
entries.push(format!("%dever.external_resource {{ {path}, ptr @dever_external_bytes_{index}, i64 {}, {digest}, i8 {} }}", resource.bytes.len(), u8::from(resource.executable)));
|
||||
}
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@dever_external_resources = private constant [{} x %dever.external_resource] [{}]",
|
||||
resources.len(),
|
||||
entries.join(", ")
|
||||
)
|
||||
.unwrap();
|
||||
let digest = self.wire_names(&[crate::native::embedded_bundle_digest(resources)]);
|
||||
self.application.as_mut().unwrap().resources = Some((digest, resources.len()));
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
fn external_wire_policy(&self) -> crate::wire::Policy {
|
||||
let function = &self.module.program.functions[self.instance.function];
|
||||
let operation = function.external.expect("external operation");
|
||||
let AdapterImplementation::External(external) =
|
||||
&self.module.program.adapters[operation.adapter].implementation
|
||||
else {
|
||||
unreachable!("external operation Adapter")
|
||||
};
|
||||
crate::wire::Policy::external(external.ecosystem)
|
||||
}
|
||||
|
||||
pub(super) fn initialize_external_resources(&mut self, span: Span) {
|
||||
let Some((digest, count)) = self
|
||||
.module
|
||||
.application
|
||||
.as_ref()
|
||||
.and_then(|app| app.resources.clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.wire_write_at(
|
||||
"dever_rt_v1_external_prepare",
|
||||
vec![
|
||||
format!("ptr {digest}"),
|
||||
"ptr @dever_external_resources".into(),
|
||||
format!("i64 {count}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn start_external_adapter(
|
||||
&mut self,
|
||||
adapter: &crate::hir::Adapter,
|
||||
setting: &str,
|
||||
span: Span,
|
||||
) {
|
||||
if !matches!(adapter.implementation, AdapterImplementation::External(_)) {
|
||||
return;
|
||||
}
|
||||
let id = self
|
||||
.module
|
||||
.program
|
||||
.adapters
|
||||
.iter()
|
||||
.position(|value| value.owner == adapter.owner)
|
||||
.unwrap();
|
||||
let operation = self.async_operation(
|
||||
"external_start",
|
||||
vec![
|
||||
format!("ptr @dever_external_{id}"),
|
||||
format!("ptr {setting}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Unit, span, false);
|
||||
}
|
||||
|
||||
pub(super) fn emit_external_body(
|
||||
&mut self,
|
||||
function: &crate::hir::Function,
|
||||
) -> Result<bool, String> {
|
||||
let Some(external) = function.external else {
|
||||
return Ok(false);
|
||||
};
|
||||
let span = function.span;
|
||||
let adapter = self.module.program.adapters[external.adapter].clone();
|
||||
let contract = self.module.program.functions[external.contract]
|
||||
.port
|
||||
.clone()
|
||||
.unwrap();
|
||||
let payload = self.encode_external_inputs(span);
|
||||
let key = self.application_text(&format!("{}:{}", adapter.identity, adapter.name), span);
|
||||
let operation = self.application_text(&contract.operation, span);
|
||||
let (tag, identity, body) = self.call_external(&key, &operation, &payload, span);
|
||||
let success = self.label("external_result");
|
||||
let failure = self.label("external_failure");
|
||||
let ok = self.temp();
|
||||
self.line(format!("{ok} = icmp eq i8 {tag}, 0"));
|
||||
self.line(format!("br i1 {ok}, label %{success}, label %{failure}"));
|
||||
self.start(&success);
|
||||
self.return_external_result(&function.outputs, &body, span);
|
||||
self.start(&failure);
|
||||
self.fail_external(&contract.failures, &identity, &body, span)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn encode_external_inputs(&mut self, span: Span) -> String {
|
||||
let fields = input_fields(self.module.program, self.instance.function);
|
||||
let input_ty = Type::Outputs(fields.clone());
|
||||
let mut input = "zeroinitializer".to_owned();
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = insertvalue {} {input}, {} {}, {index}",
|
||||
self.module.ty(&input_ty),
|
||||
self.module.ty(&field.ty),
|
||||
self.parameter(index)
|
||||
));
|
||||
input = value;
|
||||
}
|
||||
let row = self.row_pointer(&input_ty, &input);
|
||||
let callback = self.module.wire_fields_callback(
|
||||
&fields,
|
||||
&format!("dever_external_inputs_{}", self.instance.function),
|
||||
self.external_wire_policy(),
|
||||
true,
|
||||
);
|
||||
let payload = self.runtime_call(
|
||||
"dever_rt_v1_wire_encode",
|
||||
vec![
|
||||
format!("ptr {row}"),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(&input_ty)),
|
||||
format!("ptr @{callback}"),
|
||||
],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &payload, false);
|
||||
payload
|
||||
}
|
||||
|
||||
fn call_external(
|
||||
&mut self,
|
||||
key: &str,
|
||||
operation_name: &str,
|
||||
payload: &str,
|
||||
span: Span,
|
||||
) -> (String, String, String) {
|
||||
let operation = self.async_operation(
|
||||
"external_call",
|
||||
vec![
|
||||
format!("ptr {key}"),
|
||||
format!("ptr {operation_name}"),
|
||||
format!("ptr {payload}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
let (status, reply_row, _, error) = self.poll_raw_layout(&operation, "ptr");
|
||||
let status = self.poll_status(&status, &error, span);
|
||||
self.propagate_status(&status, span, false);
|
||||
let reply = self.temp();
|
||||
self.line(format!("{reply} = load ptr, ptr {reply_row}"));
|
||||
let kind = self.entry_slot_ir("i8");
|
||||
let identity = self.entry_slot(&Type::Text);
|
||||
let body = self.entry_slot(&Type::Text);
|
||||
self.wire_write_at(
|
||||
"dever_rt_v1_external_reply_take",
|
||||
vec![
|
||||
format!("ptr {reply}"),
|
||||
format!("ptr {kind}"),
|
||||
format!("ptr {identity}"),
|
||||
format!("ptr {body}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
let identity_value = self.temp();
|
||||
let body_value = self.temp();
|
||||
self.line(format!("{identity_value} = load ptr, ptr {identity}"));
|
||||
self.line(format!("{body_value} = load ptr, ptr {body}"));
|
||||
self.own_value(&Type::Text, &identity_value, false);
|
||||
self.own_value(&Type::Text, &body_value, false);
|
||||
let tag = self.temp();
|
||||
self.line(format!("{tag} = load i8, ptr {kind}"));
|
||||
(tag, identity_value, body_value)
|
||||
}
|
||||
|
||||
fn return_external_result(&mut self, fields: &[Field], body: &str, span: Span) {
|
||||
let value = self.decode_external_fields(fields, body, "outputs", span);
|
||||
let output = output_type(fields);
|
||||
let result = match fields.len() {
|
||||
0 => "zeroinitializer".into(),
|
||||
1 => {
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = extractvalue {} {value}, 0",
|
||||
self.module.ty(&Type::Outputs(fields.to_vec()))
|
||||
));
|
||||
result
|
||||
}
|
||||
_ => value,
|
||||
};
|
||||
self.line(format!(
|
||||
"store {} {result}, ptr %out",
|
||||
self.module.ty(&output)
|
||||
));
|
||||
self.exit("0");
|
||||
}
|
||||
|
||||
fn fail_external(
|
||||
&mut self,
|
||||
failures: &BTreeSet<crate::hir::Failure>,
|
||||
identity: &str,
|
||||
body: &str,
|
||||
span: Span,
|
||||
) -> Result<(), String> {
|
||||
for declared in failures {
|
||||
let definition = self.module.program.types[declared.ty].clone();
|
||||
let Shape::Choice(variants) = &definition.shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let variant = &variants[declared.variant];
|
||||
let expected =
|
||||
self.application_text(&format!("{}.{}", definition.name, variant.name), span);
|
||||
let matches = self.equals(&Type::Text, identity, &expected)?;
|
||||
let matched = self.label("external_declared_error");
|
||||
let next = self.label("external_next_error");
|
||||
self.line(format!("br i1 {matches}, label %{matched}, label %{next}"));
|
||||
self.start(&matched);
|
||||
let values = self.decode_external_fields(
|
||||
&variant.fields,
|
||||
body,
|
||||
&format!("error_{}_{}", declared.ty, declared.variant),
|
||||
span,
|
||||
);
|
||||
let row_ty = Type::Outputs(variant.fields.clone());
|
||||
let mut fields = Vec::new();
|
||||
for (index, field) in variant.fields.iter().enumerate() {
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = extractvalue {} {values}, {index}",
|
||||
self.module.ty(&row_ty)
|
||||
));
|
||||
fields.push((field.ty.clone(), value));
|
||||
}
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = getelementptr %dever.fault, ptr %fault, i32 0, i32 7"
|
||||
));
|
||||
self.write_variant(declared.ty, declared.variant, &fields, &payload);
|
||||
for (index, value) in [(5, declared.ty + 1), (6, declared.variant)] {
|
||||
let pointer = self.temp();
|
||||
self.line(format!(
|
||||
"{pointer} = getelementptr %dever.fault, ptr %fault, i32 0, i32 {index}"
|
||||
));
|
||||
self.line(format!("store i32 {value}, ptr {pointer}"));
|
||||
}
|
||||
self.write_fault(&(FaultCode::Business as i32).to_string(), span);
|
||||
self.start(&next);
|
||||
}
|
||||
self.application_fault(
|
||||
"external Adapter returned an undeclared business error",
|
||||
span,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The wire callback transfers a complete row; callers immediately move it
|
||||
/// into their output or fault, with no intervening fallible operation.
|
||||
fn decode_external_fields(
|
||||
&mut self,
|
||||
fields: &[Field],
|
||||
body: &str,
|
||||
suffix: &str,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let ty = Type::Outputs(fields.to_vec());
|
||||
let callback = self.module.wire_fields_callback(
|
||||
fields,
|
||||
&format!("dever_external_{suffix}_{}", self.instance.function),
|
||||
self.external_wire_policy(),
|
||||
false,
|
||||
);
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_wire_decode",
|
||||
vec![
|
||||
format!("ptr {body}"),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(&ty)),
|
||||
format!("ptr @{callback}"),
|
||||
],
|
||||
&ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
use super::*;
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn call_instance(
|
||||
&self,
|
||||
target: CallTarget,
|
||||
arguments: &[CallArgument],
|
||||
) -> Specialization {
|
||||
match target {
|
||||
CallTarget::Function(function) => {
|
||||
specialize::call_specialization(function, arguments, &self.bindings)
|
||||
}
|
||||
CallTarget::Handler(target) => Specialization {
|
||||
function: specialize::resolve_handler(target, &self.bindings),
|
||||
handlers: Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn invoke(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
arguments: &[CallArgument],
|
||||
output: &Type,
|
||||
span: Span,
|
||||
) -> Result<(String, String), String> {
|
||||
let mut values = Vec::new();
|
||||
for argument in arguments {
|
||||
if let CallArgument::Value(value) = argument {
|
||||
let result = self.expression(value)?;
|
||||
values.push((value.ty.clone(), result));
|
||||
}
|
||||
}
|
||||
Ok(self.invoke_values(instance, &values, output, span))
|
||||
}
|
||||
|
||||
pub(super) fn invoke_values(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
arguments: &[(Type, String)],
|
||||
output: &Type,
|
||||
span: Span,
|
||||
) -> (String, String) {
|
||||
if specialize::suspends(self.module.program, instance) {
|
||||
let (input, _) = self.pack_input(instance, arguments, false);
|
||||
let (input, descriptor) = if self.module.database_function(instance) {
|
||||
let ty =
|
||||
asynchronous::input_type(&self.module.program.functions[instance.function]);
|
||||
let layout = format!("{{ {}, ptr }}", self.module.ty(&ty));
|
||||
let extended = self.entry_slot_ir(&layout);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {input}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {extended}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
let context = self.temp();
|
||||
self.line(format!(
|
||||
"{context} = getelementptr {layout}, ptr {extended}, i32 0, i32 1"
|
||||
));
|
||||
self.line(format!(
|
||||
"store ptr {}, ptr {context}",
|
||||
self.database_context
|
||||
));
|
||||
(
|
||||
extended,
|
||||
format!("@dever_async_context_{}", self.module.names[instance]),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
input,
|
||||
format!("@dever_async_{}", self.module.names[instance]),
|
||||
)
|
||||
};
|
||||
let operation = self.async_operation(
|
||||
"async_call",
|
||||
vec![format!("ptr {descriptor}"), format!("ptr {input}")],
|
||||
span,
|
||||
);
|
||||
return self.poll_operation(&operation, output, span);
|
||||
}
|
||||
let result = self.entry_slot(output);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {result}",
|
||||
self.module.ty(output)
|
||||
));
|
||||
let mut values = arguments
|
||||
.iter()
|
||||
.map(|(ty, value)| format!("{} {value}", self.module.ty(ty)))
|
||||
.collect::<Vec<_>>();
|
||||
if self.module.database_function(instance) {
|
||||
values.push(format!("ptr {}", self.database_context));
|
||||
}
|
||||
values.push(format!("ptr {result}"));
|
||||
values.push("ptr %fault".into());
|
||||
let status = self.temp();
|
||||
self.line(format!(
|
||||
"{status} = call i32 @{}({})",
|
||||
self.module.names[instance],
|
||||
values.join(", ")
|
||||
));
|
||||
(status, result)
|
||||
}
|
||||
|
||||
pub(super) fn fail(&mut self, error: &Expression, span: Span) -> Result<String, String> {
|
||||
let Type::Named(id) = error.ty else {
|
||||
unreachable!("checked fail choice")
|
||||
};
|
||||
let ExpressionKind::Variant { variant, .. } = error.kind else {
|
||||
unreachable!("checked direct failure variant")
|
||||
};
|
||||
let value = self.expression(error)?;
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = getelementptr %dever.fault, ptr %fault, i32 0, i32 7"
|
||||
));
|
||||
self.line(format!("store %T{id} {value}, ptr {payload}"));
|
||||
if contains_owned(self.module.program, &error.ty) {
|
||||
self.disarm(self.guards.len() - 1);
|
||||
}
|
||||
for (index, number) in [(5, id + 1), (6, variant)] {
|
||||
let pointer = self.temp();
|
||||
self.line(format!(
|
||||
"{pointer} = getelementptr %dever.fault, ptr %fault, i32 0, i32 {index}"
|
||||
));
|
||||
self.line(format!("store i32 {number}, ptr {pointer}"));
|
||||
}
|
||||
self.write_fault(&(FaultCode::Business as i32).to_string(), span);
|
||||
let dead = self.label("after_fail");
|
||||
self.start(&dead);
|
||||
Ok("zeroinitializer".into())
|
||||
}
|
||||
|
||||
pub(super) fn capture_result(
|
||||
&mut self,
|
||||
target: CallTarget,
|
||||
arguments: &[CallArgument],
|
||||
choice: usize,
|
||||
success: usize,
|
||||
expression: &Expression,
|
||||
) -> Result<String, String> {
|
||||
let instance = self.call_instance(target, arguments);
|
||||
let function = &self.module.program.functions[instance.function];
|
||||
let output = output_type(&function.outputs);
|
||||
let failures = specialize::failures(self.module.program, &instance);
|
||||
let targets = crate::capture::targets(&self.module.program.types, choice, &failures)
|
||||
.expect("checked result capture mapping");
|
||||
let (status, output_slot) = self.invoke(&instance, arguments, &output, expression.span)?;
|
||||
let result_slot = self.entry_slot(&expression.ty);
|
||||
let success_block = self.label("capture_success");
|
||||
let failure_block = self.label("capture_failure");
|
||||
let done = self.label("capture_done");
|
||||
let ok = self.temp();
|
||||
self.line(format!("{ok} = icmp eq i32 {status}, 0"));
|
||||
self.line(format!(
|
||||
"br i1 {ok}, label %{success_block}, label %{failure_block}"
|
||||
));
|
||||
|
||||
self.start(&success_block);
|
||||
let success_fields = match &self.module.program.types[choice].shape {
|
||||
Shape::Choice(variants) => variants[success].fields.clone(),
|
||||
_ => unreachable!("checked capture choice"),
|
||||
};
|
||||
let values = success_fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, field)| {
|
||||
let value = self.temp();
|
||||
if success_fields.len() == 1 {
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {output_slot}",
|
||||
self.module.ty(&field.ty)
|
||||
));
|
||||
} else {
|
||||
let aggregate = self.temp();
|
||||
self.line(format!(
|
||||
"{aggregate} = load {}, ptr {output_slot}",
|
||||
self.module.ty(&output)
|
||||
));
|
||||
self.line(format!(
|
||||
"{value} = extractvalue {} {aggregate}, {index}",
|
||||
self.module.ty(&output)
|
||||
));
|
||||
}
|
||||
(field.ty.clone(), value)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.write_variant(choice, success, &values, &result_slot);
|
||||
self.line(format!("br label %{done}"));
|
||||
|
||||
self.start(&failure_block);
|
||||
let kind_ptr = self.temp();
|
||||
self.line(format!(
|
||||
"{kind_ptr} = getelementptr %dever.fault, ptr %fault, i32 0, i32 0"
|
||||
));
|
||||
let kind = self.temp();
|
||||
self.line(format!("{kind} = load i32, ptr {kind_ptr}"));
|
||||
let business = self.temp();
|
||||
self.line(format!(
|
||||
"{business} = icmp eq i32 {kind}, {}",
|
||||
FaultCode::Business as i32
|
||||
));
|
||||
let dispatch = self.label("capture_dispatch");
|
||||
let propagate = self.label("capture_propagate");
|
||||
self.line(format!(
|
||||
"br i1 {business}, label %{dispatch}, label %{propagate}"
|
||||
));
|
||||
self.start(&dispatch);
|
||||
let type_ptr = self.temp();
|
||||
self.line(format!(
|
||||
"{type_ptr} = getelementptr %dever.fault, ptr %fault, i32 0, i32 5"
|
||||
));
|
||||
let type_id = self.temp();
|
||||
self.line(format!("{type_id} = load i32, ptr {type_ptr}"));
|
||||
let variant_ptr = self.temp();
|
||||
self.line(format!(
|
||||
"{variant_ptr} = getelementptr %dever.fault, ptr %fault, i32 0, i32 6"
|
||||
));
|
||||
let variant_id = self.temp();
|
||||
self.line(format!("{variant_id} = load i32, ptr {variant_ptr}"));
|
||||
for (failure, target) in targets {
|
||||
if !failures.contains(&failure) {
|
||||
continue;
|
||||
}
|
||||
let matches_type = self.temp();
|
||||
self.line(format!(
|
||||
"{matches_type} = icmp eq i32 {type_id}, {}",
|
||||
failure.ty + 1
|
||||
));
|
||||
let matches_variant = self.temp();
|
||||
self.line(format!(
|
||||
"{matches_variant} = icmp eq i32 {variant_id}, {}",
|
||||
failure.variant
|
||||
));
|
||||
let matches = self.temp();
|
||||
self.line(format!(
|
||||
"{matches} = and i1 {matches_type}, {matches_variant}"
|
||||
));
|
||||
let captured = self.label("capture_match");
|
||||
let next = self.label("capture_next");
|
||||
self.line(format!("br i1 {matches}, label %{captured}, label %{next}"));
|
||||
self.start(&captured);
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = getelementptr %dever.fault, ptr %fault, i32 0, i32 7"
|
||||
));
|
||||
let inner = self.temp();
|
||||
self.line(format!("{inner} = load %T{}, ptr {payload}", failure.ty));
|
||||
if target.wrapped {
|
||||
self.write_variant(
|
||||
choice,
|
||||
target.variant,
|
||||
&[(Type::Named(failure.ty), inner)],
|
||||
&result_slot,
|
||||
);
|
||||
} else {
|
||||
self.line(format!("store %T{choice} {inner}, ptr {result_slot}"));
|
||||
}
|
||||
if self.module.uses_runtime() {
|
||||
let cause_ptr = self.temp();
|
||||
self.line(format!(
|
||||
"{cause_ptr} = getelementptr %dever.fault, ptr %fault, i32 0, i32 8"
|
||||
));
|
||||
let cause = self.temp();
|
||||
self.line(format!("{cause} = load ptr, ptr {cause_ptr}"));
|
||||
self.line(format!("call void @dever_rt_v1_text_release(ptr {cause})"));
|
||||
}
|
||||
self.line("store %dever.fault zeroinitializer, ptr %fault");
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&next);
|
||||
}
|
||||
self.line(format!("br label %{propagate}"));
|
||||
self.start(&propagate);
|
||||
self.append_frame(expression.span);
|
||||
self.exit(&status);
|
||||
self.start(&done);
|
||||
let result = self.temp();
|
||||
self.line(format!("{result} = load %T{choice}, ptr {result_slot}"));
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(super) fn write_variant(
|
||||
&mut self,
|
||||
id: usize,
|
||||
variant: usize,
|
||||
fields: &[(Type, String)],
|
||||
slot: &str,
|
||||
) {
|
||||
self.line(format!("store %T{id} zeroinitializer, ptr {slot}"));
|
||||
let tag = self.temp();
|
||||
self.line(format!(
|
||||
"{tag} = getelementptr %T{id}, ptr {slot}, i32 0, i32 0"
|
||||
));
|
||||
self.line(format!("store i32 {variant}, ptr {tag}"));
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = getelementptr %T{id}, ptr {slot}, i32 0, i32 1"
|
||||
));
|
||||
for (index, (ty, value)) in fields.iter().enumerate() {
|
||||
let field = self.temp();
|
||||
self.line(format!(
|
||||
"{field} = getelementptr %T{id}V{variant}, ptr {payload}, i32 0, i32 {index}"
|
||||
));
|
||||
self.line(format!("store {} {value}, ptr {field}", self.module.ty(ty)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
//! Protocol ABI conversion is field-name based, independent of source layout.
|
||||
use super::*;
|
||||
|
||||
pub(super) const DECLARATIONS: &str = "\
|
||||
%dever.http_handler = type { ptr, ptr, ptr, ptr, ptr, ptr, ptr }
|
||||
declare ptr @dever_rt_v1_http_headers_retain(ptr)
|
||||
declare void @dever_rt_v1_http_headers_release(ptr)
|
||||
declare void @dever_rt_v1_http_request_release(ptr)
|
||||
declare ptr @dever_rt_v1_http_request_retain(ptr)
|
||||
declare void @dever_rt_v1_http_response_release(ptr)
|
||||
declare void @dever_rt_v1_http_stream_request_release(ptr)
|
||||
declare void @dever_rt_v1_http_stream_response_release(ptr)
|
||||
declare void @dever_rt_v1_ws_message_release(ptr)
|
||||
declare i32 @dever_rt_v1_http_headers_new(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_http_headers_push(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_http_headers_length(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_http_headers_get(ptr, i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_http_request_new(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_http_request_fields(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_http_response_new(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_http_response_fields(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_http_stream_request_new(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_http_stream_response_fields(ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_tls_system(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_tls_client(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_tls_server(ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_http_send(ptr, i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_http_client(ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_http_request(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_http_open(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_http_open_stream(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_http_close_client(ptr)
|
||||
declare ptr @dever_rt_v1_async_http_serve(ptr, ptr, ptr, ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_http_respond(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_http_start(ptr, i64, ptr)
|
||||
declare ptr @dever_rt_v1_async_http_write(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_http_finish(ptr)
|
||||
declare ptr @dever_rt_v1_async_sse_start(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_sse_send(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_ws_accept(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_ws_connect(ptr, i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_ws_open(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_ws_message_new(i32, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_ws_message_fields(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_ws_send(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_ws_receive(ptr)
|
||||
declare i32 @dever_rt_v1_ws_messages(ptr, ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_ws_close(ptr, i64, ptr)
|
||||
";
|
||||
|
||||
pub(super) fn supported(operation: Intrinsic) -> bool {
|
||||
use Intrinsic::*;
|
||||
matches!(
|
||||
operation,
|
||||
TlsSystem
|
||||
| TlsClient
|
||||
| TlsServer
|
||||
| HttpSend
|
||||
| HttpClientNew
|
||||
| HttpRequest
|
||||
| HttpOpen
|
||||
| HttpOpenStream
|
||||
| HttpCloseClient
|
||||
| HttpServe
|
||||
| HttpServeTls
|
||||
| HttpServeLive
|
||||
| HttpServeLiveTls
|
||||
| HttpRespond
|
||||
| HttpStart
|
||||
| HttpWrite
|
||||
| HttpFinish
|
||||
| SseStart
|
||||
| SseSend
|
||||
| WsAccept
|
||||
| WsConnect
|
||||
| WsOpen
|
||||
| WsSend
|
||||
| WsReceive
|
||||
| WsMessages
|
||||
| WsClose
|
||||
)
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn http_intrinsic(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
handler: Option<crate::hir::HandlerTarget>,
|
||||
arguments: &[Expression],
|
||||
values: &[String],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
use Intrinsic::*;
|
||||
let mut args = arguments
|
||||
.iter()
|
||||
.zip(values)
|
||||
.map(|(argument, value)| format!("{} {value}", self.module.ty(&argument.ty)))
|
||||
.collect::<Vec<_>>();
|
||||
match operation {
|
||||
TlsSystem => {
|
||||
return self.runtime_call(
|
||||
"dever_rt_v1_tls_system",
|
||||
args,
|
||||
&result.ty,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
);
|
||||
}
|
||||
TlsClient | TlsServer => {
|
||||
return self.recoverable_call(
|
||||
if operation == TlsClient {
|
||||
"dever_rt_v1_tls_client"
|
||||
} else {
|
||||
"dever_rt_v1_tls_server"
|
||||
},
|
||||
args,
|
||||
result,
|
||||
"Ready",
|
||||
None,
|
||||
);
|
||||
}
|
||||
HttpServe | HttpServeTls | HttpServeLive | HttpServeLiveTls => {
|
||||
return self.http_serve(
|
||||
operation,
|
||||
handler.expect("checked HTTP handler"),
|
||||
arguments,
|
||||
values,
|
||||
result,
|
||||
);
|
||||
}
|
||||
HttpSend => {
|
||||
args[2] = format!(
|
||||
"ptr {}",
|
||||
self.protocol_to_runtime(&arguments[2].ty, &values[2], result.span)
|
||||
);
|
||||
args[3] = format!("ptr {}", self.http_limits(&arguments[3].ty, &values[3]));
|
||||
}
|
||||
HttpClientNew => {
|
||||
args[2] = format!("ptr {}", self.http_limits(&arguments[2].ty, &values[2]));
|
||||
args[3] = format!(
|
||||
"ptr {}",
|
||||
self.protocol_limits(
|
||||
&arguments[3].ty,
|
||||
&values[3],
|
||||
&["idle_ms", "chunk_bytes", "read_ms"]
|
||||
)
|
||||
);
|
||||
}
|
||||
HttpRequest | HttpOpen | HttpOpenStream | HttpRespond | WsSend => {
|
||||
args[1] = format!(
|
||||
"ptr {}",
|
||||
self.protocol_to_runtime(&arguments[1].ty, &values[1], result.span)
|
||||
);
|
||||
}
|
||||
HttpStart | SseStart => {
|
||||
let index = if operation == HttpStart { 2 } else { 1 };
|
||||
args[index] = format!(
|
||||
"ptr {}",
|
||||
self.headers_to_runtime(&arguments[index].ty, &values[index], result.span)
|
||||
);
|
||||
}
|
||||
SseSend => args[1] = format!("ptr {}", self.sse_event(&arguments[1].ty, &values[1])),
|
||||
WsAccept | WsConnect | WsOpen => {
|
||||
let index = args.len() - 1;
|
||||
args[index] = format!(
|
||||
"ptr {}",
|
||||
self.protocol_limits(
|
||||
&arguments[index].ty,
|
||||
&values[index],
|
||||
&["message_bytes", "idle_ms", "write_ms"]
|
||||
)
|
||||
);
|
||||
}
|
||||
WsMessages => {
|
||||
let Type::AsyncStream(event) = &result.ty else {
|
||||
unreachable!()
|
||||
};
|
||||
let Type::Named(id) = event.as_ref() else {
|
||||
unreachable!()
|
||||
};
|
||||
let descriptor = self.module.websocket_event(*id);
|
||||
args.push(format!("ptr {descriptor}"));
|
||||
args.push(format!(
|
||||
"ptr @dever_owned_{}",
|
||||
self.module.type_index(event)
|
||||
));
|
||||
return self.runtime_call(
|
||||
"dever_rt_v1_ws_messages",
|
||||
args,
|
||||
&result.ty,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let (symbol, success, end) = match operation {
|
||||
HttpSend => ("http_send", "Read", None),
|
||||
HttpClientNew => ("http_client", "Ready", None),
|
||||
HttpRequest => ("http_request", "Read", None),
|
||||
HttpOpen => ("http_open", "Read", None),
|
||||
HttpOpenStream => ("http_open_stream", "Read", None),
|
||||
HttpCloseClient => ("http_close_client", "Closed", None),
|
||||
HttpRespond => ("http_respond", "Done", None),
|
||||
HttpStart => ("http_start", "Done", None),
|
||||
HttpWrite => ("http_write", "Done", None),
|
||||
HttpFinish => ("http_finish", "Closed", None),
|
||||
SseStart => ("sse_start", "Done", None),
|
||||
SseSend => ("sse_send", "Done", None),
|
||||
WsAccept => ("ws_accept", "Connected", None),
|
||||
WsConnect => ("ws_connect", "Connected", None),
|
||||
WsOpen => ("ws_open", "Connected", None),
|
||||
WsSend => ("ws_send", "Done", None),
|
||||
WsReceive => ("ws_receive", "Read", Some("End")),
|
||||
WsClose => ("ws_close", "Closed", None),
|
||||
_ => unreachable!("checked HTTP operation"),
|
||||
};
|
||||
let operation = self.temp();
|
||||
self.line(format!(
|
||||
"{operation} = call ptr @dever_rt_v1_async_{symbol}({})",
|
||||
args.join(", ")
|
||||
));
|
||||
self.async_resource_result(&operation, result, success, end)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
//! Static HTTP handlers and typed upload/download callbacks.
|
||||
use super::*;
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn upload_decoder(&mut self, id: usize) -> String {
|
||||
let name = format!("@dever_upload_decode_{id}");
|
||||
if !self.upload_decoders.insert(id) {
|
||||
return name;
|
||||
}
|
||||
let Shape::Choice(variants) = &self.program.types[id].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let mut callback = format!(
|
||||
"define internal i32 {name}(ptr %event, ptr %out) {{\nentry:\n %tag_ptr = getelementptr %T{id}, ptr %event, i32 0, i32 0\n %tag = load i32, ptr %tag_ptr\n %payload_ptr = getelementptr %T{id}, ptr %event, i32 0, i32 1\n %payload = load ptr, ptr %payload_ptr\n switch i32 %tag, label %invalid ["
|
||||
);
|
||||
for (index, _) in variants.iter().enumerate() {
|
||||
write!(callback, "i32 {index}, label %variant{index} ").unwrap();
|
||||
}
|
||||
callback.push_str("]\ninvalid:\n unreachable\n");
|
||||
for (index, variant) in variants.iter().enumerate() {
|
||||
let (kind, status) = match variant.name.as_str() {
|
||||
"Chunk" => ("bytes", 0),
|
||||
"Failed" => ("text", 1),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
writeln!(callback, "variant{index}:\n %owned{index} = call ptr @dever_rt_v1_{kind}_retain(ptr %payload)\n store ptr %owned{index}, ptr %out\n ret i32 {status}").unwrap();
|
||||
}
|
||||
callback.push_str("}\n");
|
||||
self.declarations.push_str(&callback);
|
||||
name
|
||||
}
|
||||
|
||||
pub(super) fn websocket_event(&mut self, id: usize) -> String {
|
||||
let descriptor = format!("@dever_ws_event_{id}");
|
||||
if !self.websocket_events.insert(id) {
|
||||
return descriptor;
|
||||
}
|
||||
let Shape::Choice(variants) = self.program.types[id].shape.clone() else {
|
||||
unreachable!()
|
||||
};
|
||||
let read = variants
|
||||
.iter()
|
||||
.position(|variant| variant.name == "Read")
|
||||
.unwrap();
|
||||
let failed = variants
|
||||
.iter()
|
||||
.position(|variant| variant.name == "Failed")
|
||||
.unwrap();
|
||||
let Type::Named(message) = variants[read].fields[0].ty else {
|
||||
unreachable!()
|
||||
};
|
||||
let Shape::Choice(messages) = self.program.types[message].shape.clone() else {
|
||||
unreachable!()
|
||||
};
|
||||
let instance = self.instances.first().unwrap().clone();
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.start("entry");
|
||||
emitter.line("switch i32 %kind, label %invalid [i32 0, label %kind0 i32 1, label %kind1 i32 2, label %kind2 i32 3, label %kind3]");
|
||||
emitter.start("invalid");
|
||||
emitter.line("unreachable");
|
||||
for (kind, name) in ["Text", "Binary", "Ping", "Pong"].iter().enumerate() {
|
||||
emitter.start(&format!("kind{kind}"));
|
||||
let (variant, definition) = messages
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, variant)| variant.name == *name)
|
||||
.unwrap();
|
||||
let symbol = if kind == 0 { "text" } else { "bytes" };
|
||||
let owned = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{owned} = call ptr @dever_rt_v1_{symbol}_retain(ptr %payload)"
|
||||
));
|
||||
let slot = emitter.entry_slot(&Type::Named(message));
|
||||
emitter.write_variant(
|
||||
message,
|
||||
variant,
|
||||
&[(definition.fields[0].ty.clone(), owned)],
|
||||
&slot,
|
||||
);
|
||||
let value = emitter.temp();
|
||||
emitter.line(format!("{value} = load %T{message}, ptr {slot}"));
|
||||
emitter.write_variant(id, read, &[(Type::Named(message), value)], "%out");
|
||||
emitter.line("ret void");
|
||||
}
|
||||
let callback = format!(
|
||||
"define internal void @dever_ws_read_{id}(i32 %kind, ptr %payload, ptr %out) {{\nentry:\n{}{} }}\n",
|
||||
emitter.prologue, emitter.body
|
||||
);
|
||||
self.declarations.push_str(&callback);
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.start("entry");
|
||||
let text = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{text} = call ptr @dever_rt_v1_text_retain(ptr %payload)"
|
||||
));
|
||||
emitter.write_variant(id, failed, &[(Type::Text, text)], "%out");
|
||||
emitter.line("ret void");
|
||||
let callback = format!(
|
||||
"define internal void @dever_ws_failed_{id}(ptr %payload, ptr %out) {{\nentry:\n{}{} }}\n",
|
||||
emitter.prologue, emitter.body
|
||||
);
|
||||
self.declarations.push_str(&callback);
|
||||
writeln!(self.declarations, "{descriptor} = private constant {{ ptr, ptr, ptr }} {{ ptr @dever_type_{}, ptr @dever_ws_read_{id}, ptr @dever_ws_failed_{id} }}", self.type_index(&Type::Named(id))).unwrap();
|
||||
descriptor
|
||||
}
|
||||
|
||||
fn http_handler(&mut self, instance: &Specialization, live: bool) -> String {
|
||||
let name = self.names[instance].clone();
|
||||
let descriptor = format!("@dever_http_handler_{name}");
|
||||
if !self.http_handlers.insert(instance.clone()) {
|
||||
return descriptor;
|
||||
}
|
||||
let function = self.program.functions[instance.function].clone();
|
||||
let input = asynchronous::input_type(&function);
|
||||
let Type::Outputs(fields) = &input else {
|
||||
unreachable!()
|
||||
};
|
||||
let context_index = if live { 2 } else { 1 };
|
||||
let context = fields.get(context_index).map_or("null".into(), |field| {
|
||||
format!("@dever_type_{}", self.type_index(&field.ty))
|
||||
});
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.start("entry");
|
||||
let request = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{request} = call ptr @dever_rt_v1_http_request_retain(ptr %request)"
|
||||
));
|
||||
let value = emitter.protocol_to_language(&fields[0].ty, &request, function.span);
|
||||
let request_guard = emitter.own_value(&fields[0].ty, &value, false);
|
||||
let request_row = emitter.guards[request_guard].pointer.clone();
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
let target = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{target} = getelementptr {}, ptr %out, i32 0, i32 {index}",
|
||||
emitter.module.ty(&input)
|
||||
));
|
||||
if index == 0 {
|
||||
emitter.line(format!(
|
||||
"call void @dever_clone_{}(ptr {request_row}, ptr {target})",
|
||||
emitter.module.type_index(&field.ty)
|
||||
));
|
||||
} else if live && index == 1 {
|
||||
let reply = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{reply} = call ptr @dever_rt_v1_http_reply_retain(ptr %reply)"
|
||||
));
|
||||
emitter.line(format!("store ptr {reply}, ptr {target}"));
|
||||
} else {
|
||||
emitter.line(format!(
|
||||
"call void @dever_clone_{}(ptr %context, ptr {target})",
|
||||
emitter.module.type_index(&field.ty)
|
||||
));
|
||||
}
|
||||
}
|
||||
let callback = emitter.finish_protocol_callback(
|
||||
&format!("dever_http_pack_{name}"),
|
||||
"ptr %request, ptr %reply, ptr %context, ptr %out, ptr %error",
|
||||
);
|
||||
self.declarations.push_str(&callback);
|
||||
let response = if live {
|
||||
"null".into()
|
||||
} else {
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.start("entry");
|
||||
let output_ty = &function.outputs[0].ty;
|
||||
let output = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{output} = load {}, ptr %input",
|
||||
emitter.module.ty(output_ty)
|
||||
));
|
||||
let handle = emitter.protocol_to_runtime(output_ty, &output, function.span);
|
||||
emitter.line(format!("store ptr {handle}, ptr %out"));
|
||||
emitter.disarm(emitter.guards.len() - 1);
|
||||
let callback = emitter.finish_protocol_callback(
|
||||
&format!("dever_http_response_{name}"),
|
||||
"ptr %input, ptr %out, ptr %error",
|
||||
);
|
||||
self.declarations.push_str(&callback);
|
||||
format!("@dever_http_response_{name}")
|
||||
};
|
||||
let message = self.http_fault_message(instance);
|
||||
let (asynchronous, synchronous) = if specialize::suspends(self.program, instance) {
|
||||
(format!("@dever_async_{name}"), "null".into())
|
||||
} else {
|
||||
("null".into(), format!("@dever_sync_{name}"))
|
||||
};
|
||||
writeln!(self.declarations, "{descriptor} = private constant %dever.http_handler {{ ptr {asynchronous}, ptr {synchronous}, ptr @dever_owned_{}, ptr {context}, ptr @dever_http_pack_{name}, ptr {response}, ptr {message} }}", self.type_index(&input)).unwrap();
|
||||
descriptor
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn finish_protocol_callback(mut self, name: &str, arguments: &str) -> String {
|
||||
self.exit("0");
|
||||
self.start("cleanup");
|
||||
self.release_from(0);
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = load i32, ptr %exit_status"));
|
||||
let message = self.temp();
|
||||
self.line(format!(
|
||||
"{message} = getelementptr %dever.fault, ptr %fault, i32 0, i32 4"
|
||||
));
|
||||
let buffer = self.temp();
|
||||
self.line(format!("{buffer} = load {{ ptr, i64 }}, ptr {message}"));
|
||||
self.line(format!("store {{ ptr, i64 }} {buffer}, ptr %error"));
|
||||
self.line(format!("ret i32 {status}"));
|
||||
format!(
|
||||
"define internal i32 @{name}({arguments}) {{\nentry:\n %exit_status = alloca i32\n store i32 0, ptr %exit_status\n %fault = alloca %dever.fault\n store %dever.fault zeroinitializer, ptr %fault\n{}{} }}\n",
|
||||
self.prologue, self.body
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn http_serve(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
handler: crate::hir::HandlerTarget,
|
||||
arguments: &[Expression],
|
||||
values: &[String],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
let live = matches!(
|
||||
operation,
|
||||
Intrinsic::HttpServeLive | Intrinsic::HttpServeLiveTls
|
||||
);
|
||||
let tls = matches!(
|
||||
operation,
|
||||
Intrinsic::HttpServeTls | Intrinsic::HttpServeLiveTls
|
||||
);
|
||||
let instance = Specialization {
|
||||
function: specialize::resolve_handler(handler, &self.bindings),
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let descriptor = self.module.http_handler(&instance, live);
|
||||
let limits = self.http_limits(&arguments[1].ty, &values[1]);
|
||||
let live_limits = if live {
|
||||
self.protocol_limits(
|
||||
&arguments[2].ty,
|
||||
&values[2],
|
||||
&["chunk_bytes", "idle_ms", "heartbeat_ms"],
|
||||
)
|
||||
} else {
|
||||
"null".into()
|
||||
};
|
||||
let tls_index = if live { 3 } else { 2 };
|
||||
let tls_value = if tls {
|
||||
values[tls_index].clone()
|
||||
} else {
|
||||
"null".into()
|
||||
};
|
||||
let context_index = tls_index + usize::from(tls);
|
||||
let context = arguments
|
||||
.get(context_index)
|
||||
.map_or("null".into(), |argument| {
|
||||
self.row_pointer(&argument.ty, &values[context_index])
|
||||
});
|
||||
let operation = self.async_operation(
|
||||
"async_http_serve",
|
||||
vec![
|
||||
format!("ptr {}", values[0]),
|
||||
format!("ptr {limits}"),
|
||||
format!("ptr {live_limits}"),
|
||||
format!("ptr {tls_value}"),
|
||||
format!("ptr {descriptor}"),
|
||||
format!("ptr {context}"),
|
||||
],
|
||||
result.span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Unit, result.span, true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
//! Owned protocol records and canonical ABI fields.
|
||||
use super::*;
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn protocol_to_runtime(&mut self, ty: &Type, value: &str, span: Span) -> String {
|
||||
let Type::Named(id) = ty else { unreachable!() };
|
||||
let name = self.module.program.types[*id].name.clone();
|
||||
if name == "dever.websocket.Message" {
|
||||
return self.websocket_to_runtime(*id, value, span);
|
||||
}
|
||||
let request = matches!(
|
||||
name.as_str(),
|
||||
"dever.http.Request" | "dever.http.StreamRequest"
|
||||
);
|
||||
let stream = name == "dever.http.StreamRequest";
|
||||
let mut fields = Vec::new();
|
||||
for name in if request {
|
||||
&["method", "target"][..]
|
||||
} else {
|
||||
&["status"][..]
|
||||
} {
|
||||
fields.push((
|
||||
if request { "ptr" } else { "i64" },
|
||||
self.record_field(ty, value, name).1,
|
||||
));
|
||||
}
|
||||
let (headers_ty, headers) = self.record_field(ty, value, "headers");
|
||||
fields.push(("ptr", self.headers_to_runtime(&headers_ty, &headers, span)));
|
||||
let (body_ty, body) = self.record_field(ty, value, "body");
|
||||
fields.push(("ptr", body));
|
||||
let fields = self.protocol_struct(&fields);
|
||||
let kind = if stream {
|
||||
"http_stream_request"
|
||||
} else if request {
|
||||
"http_request"
|
||||
} else {
|
||||
"http_response"
|
||||
};
|
||||
let mut args = vec![format!("ptr {fields}")];
|
||||
if stream {
|
||||
let Type::AsyncStream(event) = body_ty else {
|
||||
unreachable!()
|
||||
};
|
||||
let Type::Named(id) = event.as_ref() else {
|
||||
unreachable!()
|
||||
};
|
||||
args.push(format!("ptr {}", self.module.upload_decoder(*id)));
|
||||
}
|
||||
let handle = self.protocol_call(&format!("dever_rt_v1_{kind}_new"), args, "ptr", span);
|
||||
self.protocol_owner(kind, &handle);
|
||||
handle
|
||||
}
|
||||
|
||||
pub(super) fn protocol_to_language(&mut self, ty: &Type, handle: &str, span: Span) -> String {
|
||||
let Type::Named(id) = ty else { unreachable!() };
|
||||
let definition = self.module.program.types[*id].clone();
|
||||
if definition.name == "dever.websocket.Message" {
|
||||
self.protocol_owner("ws_message", handle);
|
||||
return self.websocket_to_language(*id, handle, span);
|
||||
}
|
||||
let Shape::Record(fields) = definition.shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let request = definition.name == "dever.http.Request";
|
||||
let stream = definition.name == "dever.http.StreamResponse";
|
||||
let kind = if request {
|
||||
"http_request"
|
||||
} else if stream {
|
||||
"http_stream_response"
|
||||
} else {
|
||||
"http_response"
|
||||
};
|
||||
self.protocol_owner(kind, handle);
|
||||
let mut args = vec![format!("ptr {handle}")];
|
||||
if stream {
|
||||
let Type::AsyncStream(event) =
|
||||
&fields.iter().find(|field| field.name == "body").unwrap().ty
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
let Type::Named(id) = event.as_ref() else {
|
||||
unreachable!()
|
||||
};
|
||||
args.push(format!("ptr {}", self.module.read_event(*id)));
|
||||
args.push(format!(
|
||||
"ptr @dever_owned_{}",
|
||||
self.module.type_index(event)
|
||||
));
|
||||
}
|
||||
let layout = if request {
|
||||
"{ ptr, ptr, ptr, ptr }"
|
||||
} else {
|
||||
"{ i64, ptr, ptr }"
|
||||
};
|
||||
let decoded = self.protocol_call(&format!("dever_rt_v1_{kind}_fields"), args, layout, span);
|
||||
let names = if request {
|
||||
&["method", "target", "headers", "body"][..]
|
||||
} else {
|
||||
&["status", "headers", "body"][..]
|
||||
};
|
||||
let mut values = Vec::new();
|
||||
let mut transfers = Vec::new();
|
||||
for (index, name) in names.iter().enumerate() {
|
||||
let field_ty = &fields.iter().find(|field| field.name == *name).unwrap().ty;
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = extractvalue {layout} {decoded}, {index}"
|
||||
));
|
||||
if *name == "headers" {
|
||||
self.protocol_owner("http_headers", &value);
|
||||
values.push((*name, value));
|
||||
} else {
|
||||
if contains_owned(self.module.program, field_ty) {
|
||||
transfers.push(self.own_value(field_ty, &value, false));
|
||||
}
|
||||
values.push((*name, value));
|
||||
}
|
||||
}
|
||||
// Every getter field is owned before a subsequent conversion can fail.
|
||||
let header_index = values
|
||||
.iter()
|
||||
.position(|(name, _)| *name == "headers")
|
||||
.unwrap();
|
||||
let header_ty = &fields
|
||||
.iter()
|
||||
.find(|field| field.name == "headers")
|
||||
.unwrap()
|
||||
.ty;
|
||||
let headers = self.headers_to_language(header_ty, &values[header_index].1, span);
|
||||
transfers.push(self.own_value(header_ty, &headers, false));
|
||||
values[header_index].1 = headers;
|
||||
let result = self.record_value(ty, &values);
|
||||
for guard in transfers {
|
||||
self.disarm(guard);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub(super) fn sse_event(&mut self, ty: &Type, value: &str) -> String {
|
||||
let event = self.record_field(ty, value, "event").1;
|
||||
let data = self.record_field(ty, value, "data").1;
|
||||
let (id_ty, id) = self.record_field(ty, value, "id");
|
||||
let id = self.optional_payload(&id_ty, &id, "null").1;
|
||||
let (retry_ty, retry) = self.record_field(ty, value, "retry_ms");
|
||||
let (present, retry) = self.optional_payload(&retry_ty, &retry, "0");
|
||||
let flag = self.temp();
|
||||
self.line(format!("{flag} = zext i1 {present} to i8"));
|
||||
self.protocol_struct(&[
|
||||
("ptr", event),
|
||||
("ptr", data),
|
||||
("ptr", id),
|
||||
("i64", retry),
|
||||
("i8", flag),
|
||||
])
|
||||
}
|
||||
|
||||
fn optional_payload(&mut self, ty: &Type, value: &str, absent: &str) -> (String, String) {
|
||||
let Type::Nullable(inner) = ty else {
|
||||
unreachable!()
|
||||
};
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = extractvalue {} {value}, 1",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let selected = self.temp();
|
||||
self.line(format!(
|
||||
"{selected} = select i1 {present}, {} {payload}, {} {absent}",
|
||||
self.module.ty(inner),
|
||||
self.module.ty(inner)
|
||||
));
|
||||
(present, selected)
|
||||
}
|
||||
|
||||
fn websocket_to_runtime(&mut self, id: usize, value: &str, span: Span) -> String {
|
||||
let Shape::Choice(variants) = self.module.program.types[id].shape.clone() else {
|
||||
unreachable!()
|
||||
};
|
||||
let source = self.row_pointer(&Type::Named(id), value);
|
||||
let tag = self.temp();
|
||||
self.line(format!("{tag} = extractvalue %T{id} {value}, 0"));
|
||||
let output = self.entry_slot_ir("ptr");
|
||||
let done = self.label("ws_encoded");
|
||||
let invalid = self.label("ws_invalid_tag");
|
||||
let labels = variants
|
||||
.iter()
|
||||
.map(|_| self.label("ws_encode"))
|
||||
.collect::<Vec<_>>();
|
||||
self.line(format!(
|
||||
"switch i32 {tag}, label %{invalid} [{}]",
|
||||
labels
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, label)| format!("i32 {i}, label %{label}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
));
|
||||
self.start(&invalid);
|
||||
self.line("unreachable");
|
||||
for (index, variant) in variants.iter().enumerate() {
|
||||
self.start(&labels[index]);
|
||||
let pointer = self.temp();
|
||||
self.line(format!(
|
||||
"{pointer} = getelementptr %T{id}, ptr {source}, i32 0, i32 1"
|
||||
));
|
||||
let payload = self.temp();
|
||||
self.line(format!("{payload} = load ptr, ptr {pointer}"));
|
||||
let kind = ["Text", "Binary", "Ping", "Pong"]
|
||||
.iter()
|
||||
.position(|name| *name == variant.name)
|
||||
.unwrap();
|
||||
let handle = self.protocol_call(
|
||||
"dever_rt_v1_ws_message_new",
|
||||
vec![format!("i32 {kind}"), format!("ptr {payload}")],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
self.line(format!("store ptr {handle}, ptr {output}"));
|
||||
self.line(format!("br label %{done}"));
|
||||
}
|
||||
self.start(&done);
|
||||
let handle = self.temp();
|
||||
self.line(format!("{handle} = load ptr, ptr {output}"));
|
||||
self.protocol_owner("ws_message", &handle);
|
||||
handle
|
||||
}
|
||||
|
||||
fn websocket_to_language(&mut self, id: usize, handle: &str, span: Span) -> String {
|
||||
let fields = self.protocol_call(
|
||||
"dever_rt_v1_ws_message_fields",
|
||||
vec![format!("ptr {handle}")],
|
||||
"{ i32, ptr }",
|
||||
span,
|
||||
);
|
||||
let tag = self.temp();
|
||||
self.line(format!("{tag} = extractvalue {{ i32, ptr }} {fields}, 0"));
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = extractvalue {{ i32, ptr }} {fields}, 1"
|
||||
));
|
||||
let result = self.entry_slot(&Type::Named(id));
|
||||
let Shape::Choice(variants) = self.module.program.types[id].shape.clone() else {
|
||||
unreachable!()
|
||||
};
|
||||
let done = self.label("ws_decoded");
|
||||
let invalid = self.label("ws_invalid_kind");
|
||||
let labels = (0..4).map(|_| self.label("ws_decode")).collect::<Vec<_>>();
|
||||
self.line(format!(
|
||||
"switch i32 {tag}, label %{invalid} [{}]",
|
||||
labels
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, label)| format!("i32 {i}, label %{label}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
));
|
||||
self.start(&invalid);
|
||||
self.line("unreachable");
|
||||
for (kind, name) in ["Text", "Binary", "Ping", "Pong"].iter().enumerate() {
|
||||
self.start(&labels[kind]);
|
||||
let (variant, definition) = variants
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, variant)| variant.name == *name)
|
||||
.unwrap();
|
||||
self.write_variant(
|
||||
id,
|
||||
variant,
|
||||
&[(definition.fields[0].ty.clone(), payload.clone())],
|
||||
&result,
|
||||
);
|
||||
self.line(format!("br label %{done}"));
|
||||
}
|
||||
self.start(&done);
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load %T{id}, ptr {result}"));
|
||||
value
|
||||
}
|
||||
pub(super) fn protocol_owner(&mut self, kind: &'static str, handle: &str) -> usize {
|
||||
let slot = self.entry_slot_ir("ptr");
|
||||
self.line(format!("store ptr {handle}, ptr {slot}"));
|
||||
let guard = self.register_owned(OwnedKind::Protocol(kind), slot);
|
||||
self.mark_live(guard);
|
||||
guard
|
||||
}
|
||||
|
||||
pub(super) fn protocol_call(
|
||||
&mut self,
|
||||
symbol: &str,
|
||||
args: Vec<String>,
|
||||
layout: &str,
|
||||
span: Span,
|
||||
) -> String {
|
||||
self.runtime_call(symbol, args, &Type::Unit, false, Some(layout), span)
|
||||
}
|
||||
|
||||
fn record_field(&mut self, ty: &Type, value: &str, name: &str) -> (Type, String) {
|
||||
let Type::Named(id) = ty else {
|
||||
unreachable!("checked protocol record")
|
||||
};
|
||||
let Shape::Record(fields) = &self.module.program.types[*id].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let (index, field) = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, field)| field.name == name)
|
||||
.expect("checked protocol field");
|
||||
let field_ty = field.ty.clone();
|
||||
let extracted = self.temp();
|
||||
self.line(format!(
|
||||
"{extracted} = extractvalue {} {value}, {index}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
(field_ty, extracted)
|
||||
}
|
||||
|
||||
fn record_value(&mut self, ty: &Type, fields: &[(&str, String)]) -> String {
|
||||
let Type::Named(id) = ty else { unreachable!() };
|
||||
let Shape::Record(definition) = &self.module.program.types[*id].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let definition = definition.clone();
|
||||
let mut value = "zeroinitializer".to_string();
|
||||
for (index, field) in definition.iter().enumerate() {
|
||||
let (_, content) = fields
|
||||
.iter()
|
||||
.find(|(name, _)| *name == field.name)
|
||||
.expect("complete protocol fields");
|
||||
let next = self.temp();
|
||||
self.line(format!(
|
||||
"{next} = insertvalue {} {value}, {} {content}, {index}",
|
||||
self.module.ty(ty),
|
||||
self.module.ty(&field.ty)
|
||||
));
|
||||
value = next;
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
fn protocol_struct(&mut self, fields: &[(&str, String)]) -> String {
|
||||
let layout = format!(
|
||||
"{{ {} }}",
|
||||
fields
|
||||
.iter()
|
||||
.map(|(ty, _)| *ty)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
let slot = self.entry_slot_ir(&layout);
|
||||
let mut value = "zeroinitializer".to_string();
|
||||
for (index, (ty, content)) in fields.iter().enumerate() {
|
||||
let next = self.temp();
|
||||
self.line(format!(
|
||||
"{next} = insertvalue {layout} {value}, {ty} {content}, {index}"
|
||||
));
|
||||
value = next;
|
||||
}
|
||||
self.line(format!("store {layout} {value}, ptr {slot}"));
|
||||
slot
|
||||
}
|
||||
|
||||
pub(super) fn protocol_limits(&mut self, ty: &Type, value: &str, names: &[&str]) -> String {
|
||||
let mut fields = Vec::new();
|
||||
for name in names {
|
||||
fields.push(("i64", self.record_field(ty, value, name).1));
|
||||
}
|
||||
self.protocol_struct(&fields)
|
||||
}
|
||||
|
||||
pub(super) fn http_limits(&mut self, ty: &Type, value: &str) -> String {
|
||||
let mut fields = ["header_bytes", "body_bytes", "timeout_ms", "connections"]
|
||||
.iter()
|
||||
.map(|name| ("i64", self.record_field(ty, value, name).1))
|
||||
.collect::<Vec<_>>();
|
||||
let (http2_ty, http2) = self.record_field(ty, value, "http2");
|
||||
let Type::Nullable(inner) = &http2_ty else {
|
||||
unreachable!()
|
||||
};
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {http2}, 0",
|
||||
self.module.ty(&http2_ty)
|
||||
));
|
||||
let pointer = self.entry_slot_ir("ptr");
|
||||
self.line(format!("store ptr null, ptr {pointer}"));
|
||||
let some = self.label("http2_limits");
|
||||
let done = self.label("http_limits");
|
||||
self.line(format!("br i1 {present}, label %{some}, label %{done}"));
|
||||
self.start(&some);
|
||||
let content = self.temp();
|
||||
self.line(format!(
|
||||
"{content} = extractvalue {} {http2}, 1",
|
||||
self.module.ty(&http2_ty)
|
||||
));
|
||||
let limits = self.protocol_limits(
|
||||
inner,
|
||||
&content,
|
||||
&["streams", "stream_window_bytes", "connection_window_bytes"],
|
||||
);
|
||||
self.line(format!("store ptr {limits}, ptr {pointer}"));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let http2 = self.temp();
|
||||
self.line(format!("{http2} = load ptr, ptr {pointer}"));
|
||||
fields.push(("ptr", http2));
|
||||
self.protocol_struct(&fields)
|
||||
}
|
||||
|
||||
pub(super) fn headers_to_runtime(&mut self, ty: &Type, value: &str, span: Span) -> String {
|
||||
let Type::List(element) = ty else {
|
||||
unreachable!()
|
||||
};
|
||||
let headers = self.protocol_call("dever_rt_v1_http_headers_new", vec![], "ptr", span);
|
||||
self.protocol_owner("http_headers", &headers);
|
||||
let retained = self.temp();
|
||||
self.line(format!(
|
||||
"{retained} = call ptr @dever_rt_v1_list_retain(ptr {value})"
|
||||
));
|
||||
let cursor = self.protocol_call(
|
||||
"dever_rt_v1_list_cursor_take",
|
||||
vec![format!("ptr {retained}")],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
let cursor_slot = self.entry_slot_ir("ptr");
|
||||
self.line(format!("store ptr {cursor}, ptr {cursor_slot}"));
|
||||
let cursor_guard = self.register_owned(OwnedKind::ListCursor, cursor_slot);
|
||||
self.mark_live(cursor_guard);
|
||||
let row = self.entry_slot(element);
|
||||
let guard = self.register_guard(element, row.clone());
|
||||
let present = self.entry_slot_ir("i8");
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let next = self.label("header_next");
|
||||
let body = self.label("header_value");
|
||||
let done = self.label("headers_ready");
|
||||
self.line(format!("br label %{next}"));
|
||||
self.start(&next);
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_list_cursor_next(ptr {cursor}, ptr {row}, ptr {present}, ptr {error})"));
|
||||
self.check_runtime_status(&status, &error, span);
|
||||
let flag = self.temp();
|
||||
self.line(format!("{flag} = load i8, ptr {present}"));
|
||||
let exists = self.temp();
|
||||
self.line(format!("{exists} = icmp ne i8 {flag}, 0"));
|
||||
self.line(format!("br i1 {exists}, label %{body}, label %{done}"));
|
||||
self.start(&body);
|
||||
self.mark_live(guard);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {row}",
|
||||
self.module.ty(element)
|
||||
));
|
||||
let name = self.record_field(element, &value, "name").1;
|
||||
let value = self.record_field(element, &value, "value").1;
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_http_headers_push(ptr {headers}, ptr {name}, ptr {value}, ptr {error})"));
|
||||
self.check_runtime_status(&status, &error, span);
|
||||
self.release_guard(guard);
|
||||
self.line(format!("br label %{next}"));
|
||||
self.start(&done);
|
||||
self.release_guard(cursor_guard);
|
||||
headers
|
||||
}
|
||||
|
||||
fn headers_to_language(&mut self, ty: &Type, headers: &str, span: Span) -> String {
|
||||
let Type::List(element) = ty else {
|
||||
unreachable!()
|
||||
};
|
||||
let list = self.runtime_call(
|
||||
"dever_rt_v1_list_new",
|
||||
vec![
|
||||
format!("ptr @dever_type_{}", self.module.type_index(element)),
|
||||
"ptr null".into(),
|
||||
"i64 0".into(),
|
||||
],
|
||||
ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let list_guard = self.own_value(ty, &list, false);
|
||||
let list_slot = self.guards[list_guard].pointer.clone();
|
||||
let length = self.runtime_call(
|
||||
"dever_rt_v1_http_headers_length",
|
||||
vec![format!("ptr {headers}")],
|
||||
&Type::Int,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let index = self.entry_slot_ir("i64");
|
||||
self.line(format!("store i64 0, ptr {index}"));
|
||||
let next = self.label("header_decode_next");
|
||||
let body = self.label("header_decode");
|
||||
let done = self.label("headers_decoded");
|
||||
self.line(format!("br label %{next}"));
|
||||
self.start(&next);
|
||||
let position = self.temp();
|
||||
self.line(format!("{position} = load i64, ptr {index}"));
|
||||
let exists = self.temp();
|
||||
self.line(format!("{exists} = icmp slt i64 {position}, {length}"));
|
||||
self.line(format!("br i1 {exists}, label %{body}, label %{done}"));
|
||||
self.start(&body);
|
||||
let first = self.guards.len();
|
||||
let fields = self.protocol_call(
|
||||
"dever_rt_v1_http_headers_get",
|
||||
vec![format!("ptr {headers}"), format!("i64 {position}")],
|
||||
"{ ptr, ptr }",
|
||||
span,
|
||||
);
|
||||
let name = self.temp();
|
||||
self.line(format!("{name} = extractvalue {{ ptr, ptr }} {fields}, 0"));
|
||||
self.own_value(&Type::Text, &name, false);
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = extractvalue {{ ptr, ptr }} {fields}, 1"));
|
||||
self.own_value(&Type::Bytes, &value, false);
|
||||
let header = self.record_value(element, &[("name", name), ("value", value)]);
|
||||
let row = self.row_pointer(element, &header);
|
||||
let previous = self.temp();
|
||||
self.line(format!("{previous} = load ptr, ptr {list_slot}"));
|
||||
self.disarm(list_guard);
|
||||
let appended = self.runtime_call(
|
||||
"dever_rt_v1_list_append_take",
|
||||
vec![format!("ptr {previous}"), format!("ptr {row}")],
|
||||
ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.line(format!("store ptr {appended}, ptr {list_slot}"));
|
||||
self.mark_live(list_guard);
|
||||
self.release_from(first);
|
||||
let incremented = self.temp();
|
||||
self.line(format!("{incremented} = add i64 {position}, 1"));
|
||||
self.line(format!("store i64 {incremented}, ptr {index}"));
|
||||
self.line(format!("br label %{next}"));
|
||||
self.start(&done);
|
||||
let list = self.temp();
|
||||
self.line(format!("{list} = load ptr, ptr {list_slot}"));
|
||||
self.disarm(list_guard);
|
||||
list
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn supported(operation: Intrinsic) -> bool {
|
||||
use Intrinsic::*;
|
||||
super::system::supported(operation)
|
||||
|| super::testing::supported(operation)
|
||||
|| super::http::supported(operation)
|
||||
|| super::api_context::supported(operation)
|
||||
|| matches!(
|
||||
operation,
|
||||
TextTrim
|
||||
| TextLower
|
||||
| TextUpper
|
||||
| TextCodepoint
|
||||
| TextFromCodepoint
|
||||
| TextAt
|
||||
| TextSlice
|
||||
| TextIndexOf
|
||||
| TextContains
|
||||
| TextStartsWith
|
||||
| TextEndsWith
|
||||
| TextReplace
|
||||
| TextSplit
|
||||
| IntParse
|
||||
| IntToText
|
||||
| DecimalParse
|
||||
| DecimalToText
|
||||
| DecimalFromInt
|
||||
| DecimalRound
|
||||
| FloatParse
|
||||
| FloatToText
|
||||
| FloatFromInt
|
||||
| FloatSqrt
|
||||
| FloatSin
|
||||
| FloatCos
|
||||
| FloatLog
|
||||
| FloatPow
|
||||
| FloatIsNan
|
||||
| FloatIsFinite
|
||||
| FloatIsInfinite
|
||||
| IdFromText
|
||||
| IdToText
|
||||
| BytesFromText
|
||||
| BytesLength
|
||||
| BytesAt
|
||||
| BytesConcat
|
||||
| BytesToText
|
||||
| BytesFromInts
|
||||
| BytesSlice
|
||||
| FileOpen
|
||||
| FileCreate
|
||||
| FileRead
|
||||
| FileWrite
|
||||
| FileClose
|
||||
| FileChunks
|
||||
| TimeSleep
|
||||
| TaskSleep
|
||||
| TaskTicks
|
||||
| TaskTimeout
|
||||
| TaskRace
|
||||
| StreamOf
|
||||
| TcpConnect
|
||||
| TcpConnectTimeout
|
||||
| TcpListen
|
||||
| TcpAccept
|
||||
| TcpPort
|
||||
| TcpRead
|
||||
| TcpWrite
|
||||
| TcpTimeout
|
||||
| TcpClose
|
||||
| TcpCloseListener
|
||||
| TcpChunks
|
||||
| TcpConnections
|
||||
)
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn decimal_parts(&mut self, value: &str) -> Vec<String> {
|
||||
(0..2)
|
||||
.map(|index| {
|
||||
let part = self.temp();
|
||||
self.line(format!(
|
||||
"{part} = extractvalue {{ i64, i64 }} {value}, {index}"
|
||||
));
|
||||
format!("i64 {part}")
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(super) fn intrinsic(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
handler: Option<crate::hir::HandlerTarget>,
|
||||
arguments: &[Expression],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
use Intrinsic::*;
|
||||
if matches!(operation, TaskTimeout | TaskRace) {
|
||||
return self.task_selection(operation, handler, arguments, result);
|
||||
}
|
||||
let values = arguments
|
||||
.iter()
|
||||
.map(|argument| {
|
||||
self.expression(argument)
|
||||
.expect("validated intrinsic argument")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let args = arguments
|
||||
.iter()
|
||||
.zip(&values)
|
||||
.map(|(argument, value)| format!("{} {value}", self.module.ty(&argument.ty)))
|
||||
.collect::<Vec<_>>();
|
||||
let nullable = matches!(result.ty, Type::Nullable(_));
|
||||
let span = result.span;
|
||||
if super::testing::supported(operation) {
|
||||
return self.test_intrinsic(operation, arguments, &values, span);
|
||||
}
|
||||
if super::api_context::supported(operation) {
|
||||
return self.api_intrinsic(operation, arguments, &values, result);
|
||||
}
|
||||
if super::http::supported(operation) {
|
||||
return self.http_intrinsic(operation, handler, arguments, &values, result);
|
||||
}
|
||||
if super::system::supported(operation) {
|
||||
return self.system_intrinsic(operation, args, &values, result);
|
||||
}
|
||||
if matches!(
|
||||
operation,
|
||||
StreamOf
|
||||
| TaskTicks
|
||||
| TcpConnect
|
||||
| TcpConnectTimeout
|
||||
| TcpListen
|
||||
| TcpAccept
|
||||
| TcpPort
|
||||
| TcpRead
|
||||
| TcpWrite
|
||||
| TcpTimeout
|
||||
| TcpClose
|
||||
| TcpCloseListener
|
||||
| TcpChunks
|
||||
| TcpConnections
|
||||
) {
|
||||
return self.network_intrinsic(operation, arguments, args, result);
|
||||
}
|
||||
if matches!(operation, BytesToText | BytesFromInts | BytesSlice) {
|
||||
return self.bytes_result(operation, args, result);
|
||||
}
|
||||
if matches!(
|
||||
operation,
|
||||
FileOpen | FileCreate | FileRead | FileWrite | FileClose | FileChunks
|
||||
) {
|
||||
return self.file_result(operation, args, result);
|
||||
}
|
||||
if operation == TaskSleep {
|
||||
return self.task_sleep(&values[0], result);
|
||||
}
|
||||
if operation == TimeSleep {
|
||||
return self.recoverable_call("dever_rt_v1_time_sleep", args, result, "Done", None);
|
||||
}
|
||||
let (symbol, args, physical) = match operation {
|
||||
TextTrim => ("text_trim", args, None),
|
||||
TextLower => ("text_lower", args, None),
|
||||
TextUpper => ("text_upper", args, None),
|
||||
TextCodepoint => ("text_codepoint", args, None),
|
||||
TextFromCodepoint => ("text_from_codepoint", args, None),
|
||||
TextAt => ("text_at", args, None),
|
||||
TextSlice => ("text_slice", args, None),
|
||||
TextIndexOf => ("text_index_of", args, None),
|
||||
TextContains => ("text_contains", args, Some("i8")),
|
||||
TextStartsWith => ("text_starts_with", args, Some("i8")),
|
||||
TextEndsWith => ("text_ends_with", args, Some("i8")),
|
||||
TextReplace => ("text_replace", args, None),
|
||||
TextSplit => {
|
||||
let mut args = args;
|
||||
args.push(format!(
|
||||
"ptr @dever_type_{}",
|
||||
self.module.type_index(&Type::Text)
|
||||
));
|
||||
("text_split", args, None)
|
||||
}
|
||||
IntParse => ("int_parse", args, None),
|
||||
DecimalParse => ("decimal_parse", args, None),
|
||||
FloatParse => ("float_parse", args, None),
|
||||
DecimalFromInt => ("decimal_from_int", args, None),
|
||||
DecimalRound => {
|
||||
let mut parts = self.decimal_parts(&values[0]);
|
||||
parts.push(format!("i64 {}", values[1]));
|
||||
("decimal_round", parts, None)
|
||||
}
|
||||
BytesFromText => ("bytes_from_text", args, None),
|
||||
BytesLength => ("bytes_length", args, None),
|
||||
BytesAt => ("bytes_at", args, None),
|
||||
BytesConcat => ("bytes_concat", args, None),
|
||||
IntToText => return self.render_text("dever_rt_v1_int_to_text", args, span),
|
||||
FloatToText => return self.render_text("dever_rt_v1_float_to_text", args, span),
|
||||
DecimalToText => {
|
||||
let parts = self.decimal_parts(&values[0]);
|
||||
return self.render_text("dever_rt_v1_decimal_to_text", parts, span);
|
||||
}
|
||||
FloatFromInt => {
|
||||
let converted = self.temp();
|
||||
self.line(format!("{converted} = sitofp i64 {} to double", values[0]));
|
||||
return converted;
|
||||
}
|
||||
FloatSqrt | FloatSin | FloatCos | FloatLog | FloatPow => {
|
||||
let name = match operation {
|
||||
FloatSqrt => "sqrt",
|
||||
FloatSin => "sin",
|
||||
FloatCos => "cos",
|
||||
FloatLog => "log",
|
||||
FloatPow => "pow",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let converted = self.temp();
|
||||
self.line(format!(
|
||||
"{converted} = call double @llvm.{name}.f64({})",
|
||||
args.join(", ")
|
||||
));
|
||||
return converted;
|
||||
}
|
||||
FloatIsNan => {
|
||||
let converted = self.temp();
|
||||
self.line(format!(
|
||||
"{converted} = fcmp uno double {}, {}",
|
||||
values[0], values[0]
|
||||
));
|
||||
return converted;
|
||||
}
|
||||
FloatIsFinite | FloatIsInfinite => {
|
||||
let bits = self.temp();
|
||||
self.line(format!("{bits} = bitcast double {} to i64", values[0]));
|
||||
let exponent = self.temp();
|
||||
self.line(format!("{exponent} = and i64 {bits}, 9218868437227405312"));
|
||||
let all_ones = self.temp();
|
||||
self.line(format!(
|
||||
"{all_ones} = icmp eq i64 {exponent}, 9218868437227405312"
|
||||
));
|
||||
if operation == FloatIsFinite {
|
||||
let finite = self.temp();
|
||||
self.line(format!("{finite} = xor i1 {all_ones}, 1"));
|
||||
return finite;
|
||||
}
|
||||
let fraction = self.temp();
|
||||
self.line(format!("{fraction} = and i64 {bits}, 4503599627370495"));
|
||||
let no_fraction = self.temp();
|
||||
self.line(format!("{no_fraction} = icmp eq i64 {fraction}, 0"));
|
||||
let infinite = self.temp();
|
||||
self.line(format!("{infinite} = and i1 {all_ones}, {no_fraction}"));
|
||||
return infinite;
|
||||
}
|
||||
IdFromText | IdToText => {
|
||||
let retained = self.temp();
|
||||
self.line(format!(
|
||||
"{retained} = call ptr @dever_rt_v1_text_retain(ptr {})",
|
||||
values[0]
|
||||
));
|
||||
return retained;
|
||||
}
|
||||
_ => unreachable!("preflighted intrinsic"),
|
||||
};
|
||||
self.runtime_call(
|
||||
&format!("dever_rt_v1_{symbol}"),
|
||||
args,
|
||||
&result.ty,
|
||||
nullable,
|
||||
physical,
|
||||
span,
|
||||
)
|
||||
}
|
||||
|
||||
fn bytes_result(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
args: Vec<String>,
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
let success_name = if operation == Intrinsic::BytesToText {
|
||||
"Decoded"
|
||||
} else {
|
||||
"Ready"
|
||||
};
|
||||
let symbol = match operation {
|
||||
Intrinsic::BytesToText => "dever_rt_v1_bytes_to_text_handle",
|
||||
Intrinsic::BytesFromInts => "dever_rt_v1_bytes_from_ints",
|
||||
Intrinsic::BytesSlice => "dever_rt_v1_bytes_slice",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.recoverable_call(symbol, args, result, success_name, None)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
//! Durable Job metadata and concrete payload/transaction adapters.
|
||||
use super::*;
|
||||
use crate::model::{DatabaseOwner, ModelScope};
|
||||
|
||||
const CLOCK_DECLARATIONS: &str = "\
|
||||
declare i32 @dever_rt_v1_job_clock_new(i8, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_job_clock_now(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_job_clock_advance(ptr, i64, ptr)
|
||||
declare void @dever_rt_v1_job_clock_release(ptr)
|
||||
@dever_job_clock = private global ptr null
|
||||
";
|
||||
|
||||
const DECLARATIONS: &str = "\
|
||||
%dever.job = type { { ptr, i64 }, { ptr, i64 }, i32, i32, ptr, ptr, ptr, i64, ptr }
|
||||
%dever.job_auth = type { { ptr, i64 }, ptr }
|
||||
declare i32 @dever_rt_v1_job_session_new(ptr, ptr, ptr, i64, ptr, i64, ptr, i64, ptr, i64, ptr, ptr)
|
||||
declare void @dever_rt_v1_job_session_release(ptr)
|
||||
declare i32 @dever_rt_v1_job_configure(ptr, i8, i8, ptr)
|
||||
declare i32 @dever_rt_v1_job_decode_unit(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_job_initialize(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_job_enqueue(ptr, i64, ptr, ptr, ptr, ptr, i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_job_drain(ptr, i64, ptr)
|
||||
declare ptr @dever_rt_v1_job_system_scope(i64, i8, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_api_serve_with_jobs(ptr, ptr, i64, ptr, i64, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_api_migrate_application(ptr, ptr, ptr, ptr, i64, ptr, i64, ptr)
|
||||
@dever_job_session = private global ptr null
|
||||
";
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn jobs_enabled(&self) -> bool {
|
||||
self.application.is_some() && !self.program.jobs.is_empty()
|
||||
}
|
||||
|
||||
pub(super) fn clock_enabled(&self) -> bool {
|
||||
self.jobs_enabled()
|
||||
|| self
|
||||
.application
|
||||
.as_ref()
|
||||
.is_some_and(|app| app.test.is_some())
|
||||
}
|
||||
|
||||
pub(super) fn owner_binding(&mut self, owner: DatabaseOwner) -> String {
|
||||
if let DatabaseOwner::Model(model) = owner {
|
||||
return self.database_binding(model);
|
||||
}
|
||||
let DatabaseOwner::Job(target) = owner else {
|
||||
unreachable!()
|
||||
};
|
||||
let (explicit, root) = self.program.database_selector(owner);
|
||||
let explicit = explicit.map(str::to_owned);
|
||||
let root = root.to_owned();
|
||||
let explicit = explicit
|
||||
.map(|value| self.wire_names(&[value]))
|
||||
.unwrap_or_else(|| "null".into());
|
||||
let root = self.api_name(&root);
|
||||
let tenant =
|
||||
u8::from(crate::native::jobs::scope(self.program, target) == ModelScope::Tenant);
|
||||
format!("{{ ptr {explicit}, {root}, i8 {tenant} }}")
|
||||
}
|
||||
|
||||
pub(super) fn emit_job_descriptors(&mut self) {
|
||||
if self.clock_enabled() {
|
||||
self.declarations.push_str(CLOCK_DECLARATIONS);
|
||||
}
|
||||
if !self.jobs_enabled() {
|
||||
return;
|
||||
}
|
||||
self.declarations.push_str(DECLARATIONS);
|
||||
let jobs = self.program.jobs.clone();
|
||||
let mut descriptors = Vec::new();
|
||||
for (index, job) in jobs.iter().enumerate() {
|
||||
let binding = self.owner_binding(DatabaseOwner::Job(job.function));
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@dever_job_binding_{index} = private constant %dever.db_binding {binding}"
|
||||
)
|
||||
.unwrap();
|
||||
let target = self.api_name(&self.program.functions[job.function].name.clone());
|
||||
let schema = self.api_name(
|
||||
job.payload
|
||||
.as_ref()
|
||||
.map(|schema| schema.fingerprint())
|
||||
.unwrap_or("wire-v1:Job:Unit"),
|
||||
);
|
||||
let schedule = job
|
||||
.schedule
|
||||
.as_ref()
|
||||
.map(|cron| self.wire_names(std::slice::from_ref(cron)))
|
||||
.unwrap_or_else(|| "null".into());
|
||||
let components = self.wire_names(&job.components);
|
||||
let handler = self.job_handler(index);
|
||||
descriptors.push(format!("%dever.job {{ {target}, {schema}, i32 {}, i32 {}, ptr {schedule}, ptr @dever_job_binding_{index}, ptr {components}, i64 {}, ptr {handler} }}", job.attempts, job.timeout_ms, job.components.len()));
|
||||
}
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@dever_jobs = private constant [{} x %dever.job] [{}]",
|
||||
jobs.len(),
|
||||
descriptors.join(", ")
|
||||
)
|
||||
.unwrap();
|
||||
let mut hooks = Vec::new();
|
||||
for (index, hook) in self.program.auth.clone().iter().enumerate() {
|
||||
let provider = self.api_name(&hook.provider);
|
||||
hooks.push(format!(
|
||||
"%dever.job_auth {{ {provider}, ptr @dever_api_auth_{index} }}"
|
||||
));
|
||||
}
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@dever_job_auth = private constant [{} x %dever.job_auth] [{}]",
|
||||
hooks.len(),
|
||||
hooks.join(", ")
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn job_handler(&mut self, index: usize) -> String {
|
||||
let instance = Specialization {
|
||||
function: self.application.as_ref().unwrap().job_handlers[index],
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let function = self.program.functions[instance.function].clone();
|
||||
let input = asynchronous::input_type(&function);
|
||||
let span = function.span;
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.protocol_callback = true;
|
||||
emitter.start("entry");
|
||||
if let Some(parameter) = function.parameters.first() {
|
||||
let ty = parameter.value_type().unwrap();
|
||||
let decoder = emitter
|
||||
.module
|
||||
.wire_callback(ty, crate::wire::Policy::Job, false);
|
||||
let payload = emitter.runtime_call(
|
||||
"dever_rt_v1_wire_decode",
|
||||
vec![
|
||||
"ptr %payload".into(),
|
||||
format!("ptr @dever_type_{}", emitter.module.type_index(ty)),
|
||||
format!("ptr @{decoder}"),
|
||||
],
|
||||
ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let row = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{row} = insertvalue {} zeroinitializer, {} {payload}, 0",
|
||||
emitter.module.ty(&input),
|
||||
emitter.module.ty(ty)
|
||||
));
|
||||
emitter.line(format!(
|
||||
"store {} {row}, ptr %out",
|
||||
emitter.module.ty(&input)
|
||||
));
|
||||
} else {
|
||||
emitter.wire_write("dever_rt_v1_job_decode_unit", vec!["ptr %payload".into()]);
|
||||
}
|
||||
let decode = format!("dever_job_decode_{index}");
|
||||
let body = emitter
|
||||
.finish_protocol_callback(&decode, "ptr %payload, i64 %unused, ptr %out, ptr %error");
|
||||
self.declarations.push_str(&body);
|
||||
let descriptor = format!("@dever_job_handler_{index}");
|
||||
writeln!(self.declarations, "{descriptor} = private constant %dever.api_handler {{ ptr @dever_async_{}, ptr null, ptr @dever_owned_{}, ptr @{decode}, ptr @dever_api_fault_status, ptr null }}", self.names[&instance], self.type_index(&input)).unwrap();
|
||||
descriptor
|
||||
}
|
||||
|
||||
pub(super) fn emit_job_body(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
index: usize,
|
||||
) -> Result<(), String> {
|
||||
let function = self.program.functions[instance.function].clone();
|
||||
let target = Specialization {
|
||||
function: self.program.jobs[index].function,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
let parameters = emitter.begin_generated_function(&function);
|
||||
let arguments = function
|
||||
.parameters
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, parameter)| {
|
||||
(
|
||||
parameter.value_type().unwrap().clone(),
|
||||
emitter.parameter(index),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let transaction = emitter.begin_command_transaction(&target, function.span);
|
||||
if specialize::suspends(emitter.module.program, &target) {
|
||||
let (status, _) =
|
||||
emitter.invoke_values(&target, &arguments, &Type::Unit, function.span);
|
||||
emitter.propagate_status(&status, function.span, false);
|
||||
} else {
|
||||
emitter.invoke_sync_boundary(
|
||||
&target,
|
||||
&arguments,
|
||||
"async_blocking",
|
||||
&Type::Unit,
|
||||
function.span,
|
||||
);
|
||||
}
|
||||
if transaction {
|
||||
emitter.commit_command_transaction();
|
||||
}
|
||||
emitter.exit("0");
|
||||
let name = emitter.module.names[instance].clone();
|
||||
let body = emitter.finish_function(&name, ¶meters);
|
||||
self.functions.push_str(&body);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn initialize_clock(&mut self, span: Span) {
|
||||
if !self.module.clock_enabled() {
|
||||
return;
|
||||
}
|
||||
let test = u8::from(self.module.application.as_ref().unwrap().test.is_some());
|
||||
let clock = self.protocol_call(
|
||||
"dever_rt_v1_job_clock_new",
|
||||
vec![format!("i8 {test}")],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
self.line(format!("store ptr {clock}, ptr @dever_job_clock"));
|
||||
}
|
||||
|
||||
pub(super) fn register_jobs(&mut self, span: Span) {
|
||||
if !self.module.jobs_enabled() {
|
||||
return;
|
||||
}
|
||||
let session = self.temp();
|
||||
self.line(format!("{session} = load ptr, ptr @dever_api_session"));
|
||||
let clock = self.temp();
|
||||
self.line(format!("{clock} = load ptr, ptr @dever_job_clock"));
|
||||
let manifest = self
|
||||
.module
|
||||
.wire_names(&self.module.program.tenant_components.clone());
|
||||
let jobs = self.protocol_call(
|
||||
"dever_rt_v1_job_session_new",
|
||||
vec![
|
||||
format!("ptr {session}"),
|
||||
format!("ptr {clock}"),
|
||||
"ptr @dever_jobs".into(),
|
||||
format!("i64 {}", self.module.program.jobs.len()),
|
||||
"ptr @dever_job_auth".into(),
|
||||
format!("i64 {}", self.module.program.auth.len()),
|
||||
"ptr @dever_permissions".into(),
|
||||
format!("i64 {}", self.module.program.permissions.len()),
|
||||
format!("ptr {manifest}"),
|
||||
format!("i64 {}", self.module.program.tenant_components.len()),
|
||||
],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
self.line(format!("store ptr {jobs}, ptr @dever_job_session"));
|
||||
}
|
||||
|
||||
pub(super) fn initialize_jobs(&mut self, span: Span) {
|
||||
if !self.module.jobs_enabled() {
|
||||
return;
|
||||
}
|
||||
let jobs = self.temp();
|
||||
self.line(format!("{jobs} = load ptr, ptr @dever_job_session"));
|
||||
let operation =
|
||||
self.database_operation("job_initialize", vec![format!("ptr {jobs}")], span);
|
||||
self.await_operation(&operation, &Type::Unit, span, false);
|
||||
}
|
||||
|
||||
pub(super) fn owner_database(&mut self, owner: DatabaseOwner, span: Span) -> String {
|
||||
if let DatabaseOwner::Model(model) = owner {
|
||||
return self.model_database(model);
|
||||
}
|
||||
let session = self.temp();
|
||||
self.line(format!("{session} = load ptr, ptr @dever_database_session"));
|
||||
let binding = self.module.owner_binding(owner);
|
||||
let slot = self.entry_slot_ir("%dever.db_binding");
|
||||
self.line(format!("store %dever.db_binding {binding}, ptr {slot}"));
|
||||
let operation = self.database_operation(
|
||||
"db_resolve_scoped",
|
||||
vec![format!("ptr {session}"), format!("ptr {slot}")],
|
||||
span,
|
||||
);
|
||||
let database = self.await_database_handle(&operation, span);
|
||||
self.protocol_owner("db", &database);
|
||||
database
|
||||
}
|
||||
|
||||
pub(super) fn enqueue_job(
|
||||
&mut self,
|
||||
target: usize,
|
||||
arguments: &[Expression],
|
||||
scheduled: bool,
|
||||
span: Span,
|
||||
) -> Result<String, String> {
|
||||
let index = self
|
||||
.module
|
||||
.program
|
||||
.jobs
|
||||
.iter()
|
||||
.position(|job| job.function == target)
|
||||
.expect("reachable Job");
|
||||
let has_payload = self.module.program.jobs[index].payload.is_some();
|
||||
let values = arguments
|
||||
.iter()
|
||||
.map(|argument| self.expression(argument))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let payload = if has_payload {
|
||||
let ty = &arguments[0].ty;
|
||||
let encoder = self
|
||||
.module
|
||||
.wire_callback(ty, crate::wire::Policy::Job, true);
|
||||
let row = self.row_pointer(ty, &values[0]);
|
||||
let payload = self.runtime_call(
|
||||
"dever_rt_v1_wire_encode",
|
||||
vec![
|
||||
format!("ptr {row}"),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(ty)),
|
||||
format!("ptr @{encoder}"),
|
||||
],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &payload, false);
|
||||
payload
|
||||
} else {
|
||||
self.application_text("null", span)
|
||||
};
|
||||
let key = usize::from(has_payload);
|
||||
let run_at = if scheduled {
|
||||
values[key + 1].clone()
|
||||
} else {
|
||||
let clock = self.temp();
|
||||
self.line(format!("{clock} = load ptr, ptr @dever_job_clock"));
|
||||
self.protocol_call(
|
||||
"dever_rt_v1_job_clock_now",
|
||||
vec![format!("ptr {clock}")],
|
||||
"i64",
|
||||
span,
|
||||
)
|
||||
};
|
||||
let database = self.owner_database(DatabaseOwner::Job(target), span);
|
||||
let jobs = self.temp();
|
||||
self.line(format!("{jobs} = load ptr, ptr @dever_job_session"));
|
||||
let operation = self.database_operation(
|
||||
"job_enqueue",
|
||||
vec![
|
||||
format!("ptr {jobs}"),
|
||||
format!("i64 {index}"),
|
||||
format!("ptr {database}"),
|
||||
format!("ptr {}", self.database_context),
|
||||
format!("ptr {payload}"),
|
||||
format!("ptr {}", values[key]),
|
||||
format!("i64 {run_at}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
Ok(self.await_operation(&operation, &Type::Id, span, false))
|
||||
}
|
||||
|
||||
pub(super) fn invoke_system(
|
||||
&mut self,
|
||||
target: &Specialization,
|
||||
input: &str,
|
||||
tenant: &str,
|
||||
span: Span,
|
||||
) {
|
||||
let present = self.temp();
|
||||
self.line(format!("{present} = icmp sgt i64 {tenant}, 0"));
|
||||
let flag = self.temp();
|
||||
self.line(format!("{flag} = zext i1 {present} to i8"));
|
||||
let name = self.module.names[target].clone();
|
||||
let operation = self.async_operation(
|
||||
"job_system_scope",
|
||||
vec![
|
||||
format!("i64 {tenant}"),
|
||||
format!("i8 {flag}"),
|
||||
format!("ptr @dever_async_{name}"),
|
||||
format!("ptr {input}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Unit, span, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
//! Typed stream and TCP operations reuse coroutine polling and resource choices.
|
||||
use super::resources::ResourceRow;
|
||||
use super::*;
|
||||
|
||||
pub(super) const DECLARATIONS: &str = "\
|
||||
declare i32 @dever_rt_v1_async_stream_from_list(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_async_stream_from_bytes(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_async_channel_stream(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_async_ticks(i64, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_stream_retain(ptr)
|
||||
declare void @dever_rt_v1_async_stream_release(ptr)
|
||||
declare ptr @dever_rt_v1_async_stream_pull(ptr)
|
||||
declare i32 @dever_rt_v1_async_stream_close(ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_net_connect(ptr, i64)
|
||||
declare ptr @dever_rt_v1_async_net_connect_timeout(ptr, i64, i64)
|
||||
declare ptr @dever_rt_v1_async_net_listen(ptr, i64)
|
||||
declare ptr @dever_rt_v1_async_net_accept(ptr)
|
||||
declare ptr @dever_rt_v1_async_net_read(ptr, i64)
|
||||
declare ptr @dever_rt_v1_async_net_write(ptr, ptr)
|
||||
declare ptr @dever_rt_v1_socket_retain(ptr)
|
||||
declare void @dever_rt_v1_socket_release(ptr)
|
||||
declare ptr @dever_rt_v1_listener_retain(ptr)
|
||||
declare void @dever_rt_v1_listener_release(ptr)
|
||||
declare i32 @dever_rt_v1_net_port(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_net_timeout(ptr, i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_socket_close(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_listener_close(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_net_chunks(ptr, i64, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_net_connections(ptr, ptr, ptr, ptr, ptr)
|
||||
declare ptr @dever_rt_v1_async_task_wait_timeout(ptr, i64)
|
||||
declare ptr @dever_rt_v1_async_task_race(ptr, i64, ptr)
|
||||
";
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn network_intrinsic(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
arguments: &[Expression],
|
||||
mut values: Vec<String>,
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
use Intrinsic::*;
|
||||
if matches!(operation, StreamOf | TaskTicks) {
|
||||
let Type::AsyncStream(element) = &result.ty else {
|
||||
unreachable!("checked async stream")
|
||||
};
|
||||
let symbol = match operation {
|
||||
TaskTicks => "async_ticks",
|
||||
StreamOf => match arguments[0].ty {
|
||||
Type::List(_) => "async_stream_from_list",
|
||||
Type::Bytes => "async_stream_from_bytes",
|
||||
Type::Channel(_) => "async_channel_stream",
|
||||
_ => unreachable!("checked stream source"),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if !matches!(arguments[0].ty, Type::Channel(_)) {
|
||||
values.push(format!(
|
||||
"ptr @dever_owned_{}",
|
||||
self.module.type_index(element)
|
||||
));
|
||||
}
|
||||
return self.runtime_call(
|
||||
&format!("dever_rt_v1_{symbol}"),
|
||||
values,
|
||||
&result.ty,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
);
|
||||
}
|
||||
if matches!(operation, TcpChunks | TcpConnections) {
|
||||
let stream_type = if operation == TcpChunks {
|
||||
let Type::Named(id) = result.ty else {
|
||||
unreachable!()
|
||||
};
|
||||
let Shape::Choice(variants) = &self.module.program.types[id].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
variants
|
||||
.iter()
|
||||
.find(|variant| variant.name == "Streaming")
|
||||
.expect("checked Streaming")
|
||||
.fields[0]
|
||||
.ty
|
||||
.clone()
|
||||
} else {
|
||||
result.ty.clone()
|
||||
};
|
||||
let Type::AsyncStream(element) = stream_type else {
|
||||
unreachable!()
|
||||
};
|
||||
let Type::Named(event) = element.as_ref() else {
|
||||
unreachable!()
|
||||
};
|
||||
let descriptor = self.module.read_event(*event);
|
||||
values.push(format!("ptr {descriptor}"));
|
||||
values.push(format!(
|
||||
"ptr @dever_owned_{}",
|
||||
self.module.type_index(&element)
|
||||
));
|
||||
return if operation == TcpChunks {
|
||||
self.recoverable_call("dever_rt_v1_net_chunks", values, result, "Streaming", None)
|
||||
} else {
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_net_connections",
|
||||
values,
|
||||
&result.ty,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
)
|
||||
};
|
||||
}
|
||||
let (symbol, success, end, asynchronous) = match operation {
|
||||
TcpConnect => ("async_net_connect", "Connected", None, true),
|
||||
TcpConnectTimeout => ("async_net_connect_timeout", "Connected", None, true),
|
||||
TcpListen => ("async_net_listen", "Listening", None, true),
|
||||
TcpAccept => ("async_net_accept", "Connected", None, true),
|
||||
TcpRead => ("async_net_read", "Read", Some("End"), true),
|
||||
TcpWrite => ("async_net_write", "Done", None, true),
|
||||
TcpPort => ("net_port", "Bound", None, false),
|
||||
TcpTimeout => ("net_timeout", "Done", None, false),
|
||||
TcpClose => ("socket_close", "Closed", None, false),
|
||||
TcpCloseListener => ("listener_close", "Closed", None, false),
|
||||
_ => unreachable!("checked network operation"),
|
||||
};
|
||||
if !asynchronous {
|
||||
return self.recoverable_call(
|
||||
&format!("dever_rt_v1_{symbol}"),
|
||||
values,
|
||||
result,
|
||||
success,
|
||||
end,
|
||||
);
|
||||
}
|
||||
let operation = self.temp();
|
||||
self.line(format!(
|
||||
"{operation} = call ptr @dever_rt_v1_{symbol}({})",
|
||||
values.join(", ")
|
||||
));
|
||||
self.async_resource_result(&operation, result, success, end)
|
||||
}
|
||||
|
||||
pub(super) fn async_resource_result(
|
||||
&mut self,
|
||||
operation: &str,
|
||||
result: &Expression,
|
||||
success: &str,
|
||||
end: Option<&str>,
|
||||
) -> String {
|
||||
let Type::Named(id) = result.ty else {
|
||||
unreachable!("checked resource result")
|
||||
};
|
||||
let Shape::Choice(variants) = &self.module.program.types[id].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let mut row = variants
|
||||
.iter()
|
||||
.find(|variant| variant.name == success)
|
||||
.expect("checked success")
|
||||
.fields
|
||||
.first()
|
||||
.map_or(Type::Bool, |field| field.ty.clone());
|
||||
let protocol_output = matches!(&row, Type::Named(id) if matches!(self.module.program.types[*id].name.as_str(), "dever.http.Response" | "dever.http.StreamResponse" | "dever.websocket.Message"));
|
||||
if protocol_output {
|
||||
row = Type::Text;
|
||||
}
|
||||
let (status, output, present, error) = self.poll_raw(operation, &row);
|
||||
let typed_fault = self.temp();
|
||||
self.line(format!("{typed_fault} = icmp eq i32 {status}, 2"));
|
||||
let fault = self.label("resource_typed_fault");
|
||||
let ready = self.label("resource_polled");
|
||||
self.line(format!(
|
||||
"br i1 {typed_fault}, label %{fault}, label %{ready}"
|
||||
));
|
||||
self.start(&fault);
|
||||
self.append_frame(result.span);
|
||||
self.exit("1");
|
||||
self.start(&ready);
|
||||
// Poll and synchronous ABI statuses have different numeric contracts.
|
||||
let okay = self.temp();
|
||||
self.line(format!("{okay} = icmp eq i32 {status}, 1"));
|
||||
let failed = self.temp();
|
||||
self.line(format!("{failed} = icmp eq i32 {status}, 3"));
|
||||
let failure_status = self.temp();
|
||||
self.line(format!(
|
||||
"{failure_status} = select i1 {failed}, i32 1, i32 2"
|
||||
));
|
||||
let status = self.temp();
|
||||
self.line(format!(
|
||||
"{status} = select i1 {okay}, i32 0, i32 {failure_status}"
|
||||
));
|
||||
self.resource_row(
|
||||
result,
|
||||
success,
|
||||
end,
|
||||
ResourceRow {
|
||||
status,
|
||||
output,
|
||||
present: end.map(|_| present),
|
||||
error,
|
||||
protocol_output,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
//! Checked Model operations lower to static SQL and concrete parameter/row code.
|
||||
use super::*;
|
||||
use crate::hir::{ModelOperation, QueryPlan};
|
||||
use crate::model::{ModelFieldType, ModelSchema, ModelSqlCardinality};
|
||||
use crate::native::orm as sql;
|
||||
|
||||
pub(super) type DatabaseValues = Vec<(Type, ModelFieldType, String)>;
|
||||
type ModelWrite = [(usize, Expression)];
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn model_operation(
|
||||
&mut self,
|
||||
index: usize,
|
||||
operation: &ModelOperation,
|
||||
result: &Expression,
|
||||
) -> Result<String, String> {
|
||||
let model = self.module.program.models[index].clone();
|
||||
match operation {
|
||||
ModelOperation::Create { values } => {
|
||||
self.model_insert(index, &model, values, None, result)
|
||||
}
|
||||
ModelOperation::Upsert {
|
||||
key,
|
||||
create,
|
||||
update,
|
||||
} => {
|
||||
let insert = key.iter().chain(create).cloned().collect::<Vec<_>>();
|
||||
self.model_insert(index, &model, &insert, Some((key, update)), result)
|
||||
}
|
||||
ModelOperation::CreateMany { rows } => {
|
||||
self.model_insert_many(index, &model, rows, result)
|
||||
}
|
||||
ModelOperation::Get { id } => {
|
||||
let value = self.expression(id)?;
|
||||
let values = vec![(id.ty.clone(), model.fields[0].ty.clone(), value)];
|
||||
let query = self.module.planned_sql(&format!(
|
||||
"SELECT {} FROM {} WHERE {} = ?1 LIMIT 1",
|
||||
sql::select_columns(&model),
|
||||
sql::quoted(&model.table),
|
||||
sql::quoted("id")
|
||||
));
|
||||
let rows = self.model_query(index, &query, &values, "0", result.span);
|
||||
Ok(self.model_first(
|
||||
&rows,
|
||||
&model,
|
||||
None,
|
||||
Some((7, format!("{} was not found", model.package))),
|
||||
result.span,
|
||||
))
|
||||
}
|
||||
ModelOperation::Update { query, values } => {
|
||||
let mut expressions = values
|
||||
.iter()
|
||||
.map(|(field, value)| (value, &model.fields[*field].ty))
|
||||
.collect::<Vec<_>>();
|
||||
let assignments =
|
||||
sql::update_assignments(&model, values.iter().map(|(field, _)| *field), 1);
|
||||
let condition =
|
||||
sql::condition_sql(&model, query.condition.as_ref(), &mut expressions);
|
||||
let values = self.model_bind(&expressions)?;
|
||||
let statement = self.module.planned_sql(&format!(
|
||||
"UPDATE {} SET {assignments}{condition}",
|
||||
sql::quoted(&model.table)
|
||||
));
|
||||
Ok(self.model_execute(index, &statement, &values, result.span))
|
||||
}
|
||||
ModelOperation::Delete { query } => {
|
||||
let mut expressions = Vec::new();
|
||||
let condition =
|
||||
sql::condition_sql(&model, query.condition.as_ref(), &mut expressions);
|
||||
let values = self.model_bind(&expressions)?;
|
||||
let statement = self.module.planned_sql(&format!(
|
||||
"DELETE FROM {}{condition}",
|
||||
sql::quoted(&model.table)
|
||||
));
|
||||
Ok(self.model_execute(index, &statement, &values, result.span))
|
||||
}
|
||||
ModelOperation::Count { query } | ModelOperation::Exists { query } => {
|
||||
let mut expressions = Vec::new();
|
||||
let condition =
|
||||
sql::condition_sql(&model, query.condition.as_ref(), &mut expressions);
|
||||
let values = self.model_bind(&expressions)?;
|
||||
let statement = if matches!(operation, ModelOperation::Count { .. }) {
|
||||
format!(
|
||||
"SELECT COUNT(*) FROM {}{condition}",
|
||||
sql::quoted(&model.table)
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"SELECT EXISTS(SELECT 1 FROM {}{condition})",
|
||||
sql::quoted(&model.table)
|
||||
)
|
||||
};
|
||||
let statement = self.module.planned_sql(&statement);
|
||||
let rows = self.model_query(index, &statement, &values, "0", result.span);
|
||||
Ok(self.model_scalar_rows(&rows, &result.ty, result.span))
|
||||
}
|
||||
ModelOperation::Sql {
|
||||
operation,
|
||||
arguments,
|
||||
..
|
||||
} => {
|
||||
let operation = &model.sql[*operation];
|
||||
let expressions = arguments
|
||||
.iter()
|
||||
.zip(&operation.parameters)
|
||||
.map(|(argument, parameter)| (argument, ¶meter.ty))
|
||||
.collect::<Vec<_>>();
|
||||
let values = self.model_bind(&expressions)?;
|
||||
let statement = self
|
||||
.module
|
||||
.database_sql(&operation.sqlite, &operation.postgres);
|
||||
let maximum = if operation.result.cardinality == ModelSqlCardinality::Many {
|
||||
self.model_maximum(index, result.span)
|
||||
} else {
|
||||
"1".into()
|
||||
};
|
||||
let rows = self.model_query(index, &statement, &values, &maximum, result.span);
|
||||
if operation.result.cardinality == ModelSqlCardinality::Many {
|
||||
return Ok(self.model_rows_list(
|
||||
index,
|
||||
&rows,
|
||||
operation.result.record,
|
||||
None,
|
||||
None,
|
||||
result.span,
|
||||
));
|
||||
}
|
||||
let name = self
|
||||
.module
|
||||
.wire_names(&[format!("{}.{}", model.package, operation.name)]);
|
||||
let nullable = operation.result.cardinality == ModelSqlCardinality::Optional;
|
||||
let slot = self.entry_slot_ir("ptr");
|
||||
let present = self.entry_slot_ir("i8");
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let errors = self.database_errors(result.span);
|
||||
let status = self.temp();
|
||||
let symbol = if nullable { "optional" } else { "required" };
|
||||
let extra = if nullable {
|
||||
format!(", ptr {present}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_db_{symbol}_row(ptr {rows}, ptr {name}, ptr {slot}{extra}, ptr {errors}, ptr %fault, ptr {error})"));
|
||||
self.database_status(&status, &error, result.span);
|
||||
let output = self.entry_slot(&result.ty);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {output}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
let done = self.label("database_sql_done");
|
||||
if nullable {
|
||||
let found = self.temp();
|
||||
self.line(format!("{found} = load i8, ptr {present}"));
|
||||
let has_row = self.temp();
|
||||
self.line(format!("{has_row} = icmp ne i8 {found}, 0"));
|
||||
let decode = self.label("database_sql_decode");
|
||||
self.line(format!("br i1 {has_row}, label %{decode}, label %{done}"));
|
||||
self.start(&decode);
|
||||
}
|
||||
let row = self.temp();
|
||||
self.line(format!("{row} = load ptr, ptr {slot}"));
|
||||
let owner = self.protocol_owner("db_row", &row);
|
||||
let value =
|
||||
self.database_record(&row, operation.result.record, 0, true, result.span);
|
||||
let value = if nullable {
|
||||
self.model_some(&Type::Named(operation.result.record), &value)
|
||||
} else {
|
||||
value
|
||||
};
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {output}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
self.release_guard(owner);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {output}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
Ok(value)
|
||||
}
|
||||
ModelOperation::First { query } => {
|
||||
let (statement, values) = self.model_select(&model, query, " LIMIT 1")?;
|
||||
let rows = self.model_query(index, &statement, &values, "0", result.span);
|
||||
Ok(self.model_first(&rows, &model, Some(query), None, result.span))
|
||||
}
|
||||
ModelOperation::List { query } => self.model_page(index, &model, query, result),
|
||||
ModelOperation::Cursor { query } => self.model_cursor(index, &model, query, result),
|
||||
ModelOperation::Stream { query } => self.model_stream(index, &model, query, result),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn model_bind(
|
||||
&mut self,
|
||||
expressions: &[(&Expression, &ModelFieldType)],
|
||||
) -> Result<DatabaseValues, String> {
|
||||
expressions
|
||||
.iter()
|
||||
.map(|(expression, storage)| {
|
||||
Ok((
|
||||
expression.ty.clone(),
|
||||
(*storage).clone(),
|
||||
self.expression(expression)?,
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(super) fn model_maximum(&mut self, model: usize, span: Span) -> String {
|
||||
let database = self.model_database(model);
|
||||
self.protocol_call(
|
||||
"dever_rt_v1_db_max_page_size",
|
||||
vec![format!("ptr {database}")],
|
||||
"i64",
|
||||
span,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn model_query(
|
||||
&mut self,
|
||||
model: usize,
|
||||
query: &str,
|
||||
values: &[(Type, ModelFieldType, String)],
|
||||
maximum: &str,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let database = self.model_database(model);
|
||||
let parameters = self.database_parameters(values, span);
|
||||
let operation = self.database_operation(
|
||||
"db_query",
|
||||
vec![
|
||||
format!("ptr {database}"),
|
||||
format!("ptr {}", self.database_context),
|
||||
format!("ptr {query}"),
|
||||
format!("ptr {parameters}"),
|
||||
format!("i64 {}", values.len()),
|
||||
format!("i64 {maximum}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
let rows = self.await_database_handle(&operation, span);
|
||||
self.protocol_owner("db_rows", &rows);
|
||||
rows
|
||||
}
|
||||
|
||||
fn model_execute(
|
||||
&mut self,
|
||||
model: usize,
|
||||
query: &str,
|
||||
values: &[(Type, ModelFieldType, String)],
|
||||
span: Span,
|
||||
) -> String {
|
||||
let database = self.model_database(model);
|
||||
let parameters = self.database_parameters(values, span);
|
||||
let operation = self.database_operation(
|
||||
"db_execute",
|
||||
vec![
|
||||
format!("ptr {database}"),
|
||||
format!("ptr {}", self.database_context),
|
||||
format!("ptr {query}"),
|
||||
format!("ptr {parameters}"),
|
||||
format!("i64 {}", values.len()),
|
||||
],
|
||||
span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Int, span, false)
|
||||
}
|
||||
|
||||
fn model_insert(
|
||||
&mut self,
|
||||
index: usize,
|
||||
model: &ModelSchema,
|
||||
insert: &[(usize, Expression)],
|
||||
upsert: Option<(&ModelWrite, &ModelWrite)>,
|
||||
result: &Expression,
|
||||
) -> Result<String, String> {
|
||||
let mut expressions = insert
|
||||
.iter()
|
||||
.map(|(field, expression)| (expression, &model.fields[*field].ty))
|
||||
.collect::<Vec<_>>();
|
||||
if let Some((_, update)) = upsert {
|
||||
expressions.extend(
|
||||
update
|
||||
.iter()
|
||||
.map(|(field, expression)| (expression, &model.fields[*field].ty)),
|
||||
);
|
||||
}
|
||||
let mut values = self.model_bind(&expressions)?;
|
||||
let mut columns = insert
|
||||
.iter()
|
||||
.map(|(field, _)| sql::quoted(&model.fields[*field].name))
|
||||
.collect::<Vec<_>>();
|
||||
let generated = model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|field| field.generated && field.ty == ModelFieldType::Uuid)
|
||||
.collect::<Vec<_>>();
|
||||
for (offset, field) in generated.iter().enumerate() {
|
||||
let uuid = self.database_call("db_uuid_new", vec![], "ptr", result.span);
|
||||
self.own_value(&Type::Uuid, &uuid, false);
|
||||
values.insert(
|
||||
insert.len() + offset,
|
||||
(Type::Uuid, ModelFieldType::Uuid, uuid),
|
||||
);
|
||||
columns.push(sql::quoted(&field.name));
|
||||
}
|
||||
let insert_count = insert.len() + generated.len();
|
||||
let placeholders = values[..insert_count]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, (_, storage, _))| sql::parameter_sql(index + 1, storage))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let body = if columns.is_empty() {
|
||||
"DEFAULT VALUES".into()
|
||||
} else {
|
||||
format!("({}) VALUES ({placeholders})", columns.join(", "))
|
||||
};
|
||||
let conflict = if let Some((key, update)) = upsert {
|
||||
let keys = key
|
||||
.iter()
|
||||
.map(|(field, _)| sql::quoted(&model.fields[*field].name))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let assignments = sql::update_assignments(
|
||||
model,
|
||||
update.iter().map(|(field, _)| *field),
|
||||
insert_count + 1,
|
||||
);
|
||||
format!(" ON CONFLICT ({keys}) DO UPDATE SET {assignments}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let query = self.module.planned_sql(&format!(
|
||||
"INSERT INTO {} {body}{conflict} RETURNING {}",
|
||||
sql::quoted(&model.table),
|
||||
sql::select_columns(model)
|
||||
));
|
||||
let rows = self.model_query(index, &query, &values, "0", result.span);
|
||||
let message = if upsert.is_some() {
|
||||
"database UPSERT RETURNING produced no row"
|
||||
} else {
|
||||
"database INSERT RETURNING produced no row"
|
||||
};
|
||||
Ok(self.model_first(&rows, model, None, Some((8, message.into())), result.span))
|
||||
}
|
||||
|
||||
fn model_insert_many(
|
||||
&mut self,
|
||||
index: usize,
|
||||
model: &ModelSchema,
|
||||
rows: &[Vec<(usize, Expression)>],
|
||||
result: &Expression,
|
||||
) -> Result<String, String> {
|
||||
let expressions = rows
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|(field, value)| (value, &model.fields[*field].ty))
|
||||
.collect::<Vec<_>>();
|
||||
let mut values = self.model_bind(&expressions)?;
|
||||
let generated = model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|field| field.generated && field.ty == ModelFieldType::Uuid)
|
||||
.collect::<Vec<_>>();
|
||||
for _ in 0..rows.len() * generated.len() {
|
||||
let uuid = self.database_call("db_uuid_new", vec![], "ptr", result.span);
|
||||
self.own_value(&Type::Uuid, &uuid, false);
|
||||
values.push((Type::Uuid, ModelFieldType::Uuid, uuid));
|
||||
}
|
||||
let columns = rows[0]
|
||||
.iter()
|
||||
.map(|(field, _)| sql::quoted(&model.fields[*field].name))
|
||||
.chain(generated.iter().map(|field| sql::quoted(&field.name)))
|
||||
.collect::<Vec<_>>();
|
||||
let table = sql::quoted(&model.table);
|
||||
let (sqlite, postgres) = if columns.is_empty() {
|
||||
(
|
||||
format!(
|
||||
"INSERT INTO {table} ({}) VALUES {}",
|
||||
sql::quoted("id"),
|
||||
vec!["(NULL)"; rows.len()].join(", ")
|
||||
),
|
||||
format!(
|
||||
"INSERT INTO {table} ({}) VALUES {}",
|
||||
sql::quoted("created_at"),
|
||||
vec!["(DEFAULT)"; rows.len()].join(", ")
|
||||
),
|
||||
)
|
||||
} else {
|
||||
let explicit = expressions.len();
|
||||
let groups = rows
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(row, fields)| {
|
||||
let placeholders = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(column, (field, _))| {
|
||||
sql::parameter_sql(
|
||||
row * fields.len() + column + 1,
|
||||
&model.fields[*field].ty,
|
||||
)
|
||||
})
|
||||
.chain(generated.iter().enumerate().map(|(column, field)| {
|
||||
sql::parameter_sql(
|
||||
explicit + row * generated.len() + column + 1,
|
||||
&field.ty,
|
||||
)
|
||||
}))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
format!("({placeholders})")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let query = format!(
|
||||
"INSERT INTO {table} ({}) VALUES {groups}",
|
||||
columns.join(", ")
|
||||
);
|
||||
(query.clone(), query)
|
||||
};
|
||||
let (sqlite, postgres) = sql::sql_dialects(&sqlite, &postgres);
|
||||
let query = self.module.database_sql(&sqlite, &postgres);
|
||||
Ok(self.model_execute(index, &query, &values, result.span))
|
||||
}
|
||||
|
||||
pub(super) fn model_select(
|
||||
&mut self,
|
||||
model: &ModelSchema,
|
||||
query: &QueryPlan,
|
||||
suffix: &str,
|
||||
) -> Result<(String, DatabaseValues), String> {
|
||||
let mut expressions = Vec::new();
|
||||
let alias = sql::has_to_one(model, query).then_some("_dever_parent");
|
||||
let condition =
|
||||
sql::condition_sql_qualified(model, query.condition.as_ref(), &mut expressions, alias);
|
||||
let (projection, source) = sql::select_source(model, query, &self.module.program.models);
|
||||
let order = sql::order_sql_qualified(model, &query.order, alias);
|
||||
let statement = self.module.planned_sql(&format!(
|
||||
"SELECT {projection} FROM {source}{condition} ORDER BY {order}{suffix}"
|
||||
));
|
||||
Ok((statement, self.model_bind(&expressions)?))
|
||||
}
|
||||
|
||||
pub(super) fn model_some(&mut self, ty: &Type, value: &str) -> String {
|
||||
let optional = self.module.ty(&Type::Nullable(Box::new(ty.clone())));
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = insertvalue {optional} zeroinitializer, i1 1, 0"
|
||||
));
|
||||
let wrapped = self.temp();
|
||||
self.line(format!(
|
||||
"{wrapped} = insertvalue {optional} {present}, {} {value}, 1",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
wrapped
|
||||
}
|
||||
|
||||
pub(super) fn model_scalar_rows(&mut self, rows: &str, ty: &Type, span: Span) -> String {
|
||||
let length = self.protocol_call(
|
||||
"dever_rt_v1_db_rows_len",
|
||||
vec![format!("ptr {rows}")],
|
||||
"i64",
|
||||
span,
|
||||
);
|
||||
self.model_require_nonempty(&length, 8, "database scalar query returned no row", span);
|
||||
let row = self.protocol_call(
|
||||
"dever_rt_v1_db_rows_at",
|
||||
vec![format!("ptr {rows}"), "i64 0".into()],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
let owner = self.protocol_owner("db_row", &row);
|
||||
let actual = self.protocol_call(
|
||||
"dever_rt_v1_db_row_len",
|
||||
vec![format!("ptr {row}")],
|
||||
"i64",
|
||||
span,
|
||||
);
|
||||
self.database_column_present(&actual, 0, "scalar", span);
|
||||
let value = self.database_scalar(&row, 0, ty, span);
|
||||
self.database_row_length(
|
||||
&row,
|
||||
1,
|
||||
"database scalar query returned more than one column",
|
||||
span,
|
||||
);
|
||||
self.release_guard(owner);
|
||||
value
|
||||
}
|
||||
|
||||
pub(super) fn model_require_nonempty(
|
||||
&mut self,
|
||||
length: &str,
|
||||
kind: u32,
|
||||
message: &str,
|
||||
span: Span,
|
||||
) {
|
||||
let present = self.temp();
|
||||
self.line(format!("{present} = icmp sgt i64 {length}, 0"));
|
||||
let found = self.label("database_rows_present");
|
||||
let missing = self.label("database_rows_empty");
|
||||
self.line(format!("br i1 {present}, label %{found}, label %{missing}"));
|
||||
self.start(&missing);
|
||||
self.database_fault(kind, message, span);
|
||||
self.start(&found);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,534 @@
|
||||
//! Query result assembly, pagination, and association loading use concrete rows.
|
||||
use super::*;
|
||||
use crate::hir::QueryPlan;
|
||||
use crate::model::{ModelFieldType, ModelRelationKind, ModelSchema};
|
||||
use crate::native::orm as sql;
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn model_first(
|
||||
&mut self,
|
||||
rows: &str,
|
||||
model: &ModelSchema,
|
||||
query: Option<&QueryPlan>,
|
||||
required: Option<(u32, String)>,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let index = self
|
||||
.module
|
||||
.program
|
||||
.models
|
||||
.iter()
|
||||
.position(|candidate| candidate.record == model.record)
|
||||
.unwrap();
|
||||
let list = self.model_rows_list(index, rows, model.record, query, None, span);
|
||||
let ty = Type::Nullable(Box::new(Type::Named(model.record)));
|
||||
let value = self.runtime_call(
|
||||
"dever_rt_v1_list_first_take",
|
||||
vec![format!("ptr {list}")],
|
||||
&ty,
|
||||
true,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let Some((kind, message)) = required else {
|
||||
return value;
|
||||
};
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
let found = self.label("database_first_found");
|
||||
let missing = self.label("database_first_missing");
|
||||
self.line(format!("br i1 {present}, label %{found}, label %{missing}"));
|
||||
self.start(&missing);
|
||||
self.database_fault(kind, &message, span);
|
||||
self.start(&found);
|
||||
let record = self.temp();
|
||||
self.line(format!(
|
||||
"{record} = extractvalue {} {value}, 1",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
record
|
||||
}
|
||||
|
||||
pub(super) fn model_rows_list(
|
||||
&mut self,
|
||||
model: usize,
|
||||
rows: &str,
|
||||
record: usize,
|
||||
query: Option<&QueryPlan>,
|
||||
limit: Option<&str>,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let ty = Type::List(Box::new(Type::Named(record)));
|
||||
let list = self.runtime_call(
|
||||
"dever_rt_v1_list_new",
|
||||
vec![
|
||||
format!(
|
||||
"ptr @dever_type_{}",
|
||||
self.module.type_index(&Type::Named(record))
|
||||
),
|
||||
"ptr null".into(),
|
||||
"i64 0".into(),
|
||||
],
|
||||
&ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let owner = self.own_value(&ty, &list, false);
|
||||
let list_slot = self.guards[owner].pointer.clone();
|
||||
let length = self.protocol_call(
|
||||
"dever_rt_v1_db_rows_len",
|
||||
vec![format!("ptr {rows}")],
|
||||
"i64",
|
||||
span,
|
||||
);
|
||||
let index_slot = self.entry_slot(&Type::Int);
|
||||
self.line(format!("store i64 0, ptr {index_slot}"));
|
||||
let check = self.label("database_rows_check");
|
||||
let body = self.label("database_rows_decode");
|
||||
let done = self.label("database_rows_done");
|
||||
self.line(format!("br label %{check}"));
|
||||
self.start(&check);
|
||||
let index = self.temp();
|
||||
self.line(format!("{index} = load i64, ptr {index_slot}"));
|
||||
let more = self.temp();
|
||||
self.line(format!("{more} = icmp slt i64 {index}, {length}"));
|
||||
self.line(format!("br i1 {more}, label %{body}, label %{done}"));
|
||||
self.start(&body);
|
||||
let first = self.guards.len();
|
||||
let row = self.protocol_call(
|
||||
"dever_rt_v1_db_rows_at",
|
||||
vec![format!("ptr {rows}"), format!("i64 {index}")],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
self.protocol_owner("db_row", &row);
|
||||
let value = if let Some(query) = query {
|
||||
self.model_included_row(&row, model, query, span)
|
||||
} else {
|
||||
self.database_record(&row, record, 0, true, span)
|
||||
};
|
||||
self.own_value(&Type::Named(record), &value, false);
|
||||
let advance = self.label("database_rows_advance");
|
||||
if let Some(limit) = limit {
|
||||
let keep = self.temp();
|
||||
self.line(format!("{keep} = icmp slt i64 {index}, {limit}"));
|
||||
let append = self.label("database_rows_append");
|
||||
self.line(format!("br i1 {keep}, label %{append}, label %{advance}"));
|
||||
self.start(&append);
|
||||
}
|
||||
let pointer = self.row_pointer(&Type::Named(record), &value);
|
||||
let list = self.temp();
|
||||
self.line(format!("{list} = load ptr, ptr {list_slot}"));
|
||||
self.disarm(owner);
|
||||
let appended = self.runtime_call(
|
||||
"dever_rt_v1_list_append_take",
|
||||
vec![format!("ptr {list}"), format!("ptr {pointer}")],
|
||||
&ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.line(format!("store ptr {appended}, ptr {list_slot}"));
|
||||
self.mark_live(owner);
|
||||
self.line(format!("br label %{advance}"));
|
||||
self.start(&advance);
|
||||
self.release_from(first);
|
||||
let next = self.temp();
|
||||
self.line(format!("{next} = add i64 {index}, 1"));
|
||||
self.line(format!("store i64 {next}, ptr {index_slot}"));
|
||||
self.line(format!("br label %{check}"));
|
||||
self.start(&done);
|
||||
if let Some(query) = query {
|
||||
self.model_load_many(model, query, &list_slot, span);
|
||||
}
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load ptr, ptr {list_slot}"));
|
||||
self.disarm(owner);
|
||||
value
|
||||
}
|
||||
|
||||
fn model_included_row(
|
||||
&mut self,
|
||||
row: &str,
|
||||
index: usize,
|
||||
query: &QueryPlan,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let model = self.module.program.models[index].clone();
|
||||
let mut value = self.database_record(row, model.record, 0, false, span);
|
||||
let owner = self.own_value(&Type::Named(model.record), &value, false);
|
||||
let record_slot = self.guards[owner].pointer.clone();
|
||||
let mut offset = model.fields.len();
|
||||
let Shape::Record(fields) = self.module.program.types[model.record].shape.clone() else {
|
||||
unreachable!()
|
||||
};
|
||||
for relation_index in &query.relations {
|
||||
let relation = &model.relations[*relation_index];
|
||||
if relation.kind != ModelRelationKind::ToOne {
|
||||
continue;
|
||||
}
|
||||
let target = self
|
||||
.module
|
||||
.program
|
||||
.models
|
||||
.iter()
|
||||
.find(|target| target.package == relation.model)
|
||||
.unwrap()
|
||||
.clone();
|
||||
let field = model.fields.len() + relation_index;
|
||||
let Type::Related(inner) = &fields[field].ty else {
|
||||
unreachable!()
|
||||
};
|
||||
let payload = self.entry_slot(inner);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {payload}",
|
||||
self.module.ty(inner)
|
||||
));
|
||||
let done = self.label("database_relation_decoded");
|
||||
if matches!(inner.as_ref(), Type::Nullable(_)) {
|
||||
let actual = self.protocol_call(
|
||||
"dever_rt_v1_db_row_len",
|
||||
vec![format!("ptr {row}")],
|
||||
"i64",
|
||||
span,
|
||||
);
|
||||
self.database_column_present(
|
||||
&actual,
|
||||
offset,
|
||||
&format!("{}.{}", target.package, target.fields[0].name),
|
||||
span,
|
||||
);
|
||||
let absent = self.database_call(
|
||||
"db_row_null",
|
||||
vec![format!("ptr {row}"), format!("i64 {offset}")],
|
||||
"i8",
|
||||
span,
|
||||
);
|
||||
let absent_bool = self.temp();
|
||||
self.line(format!("{absent_bool} = icmp ne i8 {absent}, 0"));
|
||||
let decode = self.label("database_relation_decode");
|
||||
let null = self.label("database_relation_null");
|
||||
self.line(format!(
|
||||
"br i1 {absent_bool}, label %{null}, label %{decode}"
|
||||
));
|
||||
self.start(&null);
|
||||
self.database_column_present(
|
||||
&actual,
|
||||
offset + target.fields.len() - 1,
|
||||
&format!("{}.{}", target.package, relation.name),
|
||||
span,
|
||||
);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&decode);
|
||||
}
|
||||
let decoded = self.database_record(row, target.record, offset, false, span);
|
||||
let decoded = if matches!(inner.as_ref(), Type::Nullable(_)) {
|
||||
self.model_some(&Type::Named(target.record), &decoded)
|
||||
} else {
|
||||
decoded
|
||||
};
|
||||
self.line(format!(
|
||||
"store {} {decoded}, ptr {payload}",
|
||||
self.module.ty(inner)
|
||||
));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let temporary = self.register_guard(inner, payload.clone());
|
||||
self.mark_live(temporary);
|
||||
let related = self.protocol_call(
|
||||
"dever_rt_v1_db_related_new",
|
||||
vec![
|
||||
format!("ptr @dever_type_{}", self.module.type_index(inner)),
|
||||
format!("ptr {payload}"),
|
||||
],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
let destination = self.temp();
|
||||
self.line(format!(
|
||||
"{destination} = getelementptr %T{}, ptr {record_slot}, i32 0, i32 {field}",
|
||||
model.record
|
||||
));
|
||||
self.line(format!("store ptr {related}, ptr {destination}"));
|
||||
self.release_guard(temporary);
|
||||
offset += target.fields.len();
|
||||
}
|
||||
self.database_row_length(
|
||||
row,
|
||||
offset,
|
||||
&format!("database row for {} has extra columns", model.package),
|
||||
span,
|
||||
);
|
||||
value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load %T{}, ptr {record_slot}",
|
||||
model.record
|
||||
));
|
||||
self.disarm(owner);
|
||||
value
|
||||
}
|
||||
|
||||
pub(super) fn model_page(
|
||||
&mut self,
|
||||
index: usize,
|
||||
model: &ModelSchema,
|
||||
query: &QueryPlan,
|
||||
result: &Expression,
|
||||
) -> Result<String, String> {
|
||||
let mut expressions = Vec::new();
|
||||
let condition = sql::condition_sql(model, query.condition.as_ref(), &mut expressions);
|
||||
let mut values = self.model_bind(&expressions)?;
|
||||
let page = self.expression(&query.page)?;
|
||||
let size = self.expression(&query.size)?;
|
||||
let maximum = self.model_maximum(index, result.span);
|
||||
let page_slot = self.entry_slot(&Type::Int);
|
||||
let size_slot = self.entry_slot(&Type::Int);
|
||||
let offset_slot = self.entry_slot(&Type::Int);
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let errors = self.database_errors(result.span);
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_db_pagination(i64 {page}, i64 {size}, i64 {maximum}, ptr {page_slot}, ptr {size_slot}, ptr {offset_slot}, ptr {errors}, ptr %fault, ptr {error})"));
|
||||
self.database_status(&status, &error, result.span);
|
||||
let offset = self.temp();
|
||||
self.line(format!("{offset} = load i64, ptr {offset_slot}"));
|
||||
let count_sql = self.module.planned_sql(&format!(
|
||||
"SELECT COUNT(*) FROM {}{condition}",
|
||||
sql::quoted(&model.table)
|
||||
));
|
||||
let count_rows = self.model_query(index, &count_sql, &values, "0", result.span);
|
||||
let total = self.model_scalar_rows(&count_rows, &Type::Int, result.span);
|
||||
let alias = sql::has_to_one(model, query).then_some("_dever_parent");
|
||||
let mut ignored = Vec::new();
|
||||
let condition =
|
||||
sql::condition_sql_qualified(model, query.condition.as_ref(), &mut ignored, alias);
|
||||
let (projection, source) = sql::select_source(model, query, &self.module.program.models);
|
||||
let order = sql::order_sql_qualified(model, &query.order, alias);
|
||||
let statement = self.module.planned_sql(&format!(
|
||||
"SELECT {projection} FROM {source}{condition} ORDER BY {order} LIMIT ?{} OFFSET ?{}",
|
||||
values.len() + 1,
|
||||
values.len() + 2
|
||||
));
|
||||
values.extend([
|
||||
(Type::Int, ModelFieldType::Int, size.clone()),
|
||||
(Type::Int, ModelFieldType::Int, offset),
|
||||
]);
|
||||
let rows = self.model_query(index, &statement, &values, "0", result.span);
|
||||
let items =
|
||||
self.model_rows_list(index, &rows, model.record, Some(query), None, result.span);
|
||||
let quotient = self.temp();
|
||||
let remainder = self.temp();
|
||||
let extra = self.temp();
|
||||
let extra_integer = self.temp();
|
||||
let pages = self.temp();
|
||||
self.line(format!("{quotient} = sdiv i64 {total}, {size}"));
|
||||
self.line(format!("{remainder} = srem i64 {total}, {size}"));
|
||||
self.line(format!("{extra} = icmp ne i64 {remainder}, 0"));
|
||||
self.line(format!("{extra_integer} = zext i1 {extra} to i64"));
|
||||
self.line(format!("{pages} = add i64 {quotient}, {extra_integer}"));
|
||||
Ok(self.model_aggregate(
|
||||
model.page,
|
||||
&[
|
||||
("ptr", items),
|
||||
("i64", page),
|
||||
("i64", size),
|
||||
("i64", total),
|
||||
("i64", pages),
|
||||
],
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn model_cursor(
|
||||
&mut self,
|
||||
index: usize,
|
||||
model: &ModelSchema,
|
||||
query: &QueryPlan,
|
||||
result: &Expression,
|
||||
) -> Result<String, String> {
|
||||
let mut expressions = Vec::new();
|
||||
let alias = sql::has_to_one(model, query).then_some("_dever_parent");
|
||||
let mut condition =
|
||||
sql::condition_sql_qualified(model, query.condition.as_ref(), &mut expressions, alias);
|
||||
let mut values = self.model_bind(&expressions)?;
|
||||
if let Some(after) = &query.after {
|
||||
let cursor = sql::cursor_condition(model, &query.order, values.len() + 1, alias);
|
||||
condition.push_str(if condition.is_empty() {
|
||||
" WHERE "
|
||||
} else {
|
||||
" AND "
|
||||
});
|
||||
condition.push_str(&cursor);
|
||||
let after_value = self.expression(after)?;
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {after_value}, 0",
|
||||
self.module.ty(&after.ty)
|
||||
));
|
||||
let Shape::Record(fields) = self.module.program.types[model.record].shape.clone()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
for order in &query.order {
|
||||
let field = &fields[order.field];
|
||||
let ty = Type::Nullable(Box::new(field.ty.clone()));
|
||||
let slot = self.entry_slot(&ty);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {slot}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
let some = self.label("database_cursor_after");
|
||||
let done = self.label("database_cursor_bound");
|
||||
self.line(format!("br i1 {present}, label %{some}, label %{done}"));
|
||||
self.start(&some);
|
||||
let record = self.temp();
|
||||
self.line(format!(
|
||||
"{record} = extractvalue {} {after_value}, 1",
|
||||
self.module.ty(&after.ty)
|
||||
));
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = extractvalue %T{} {record}, {}",
|
||||
model.record, order.field
|
||||
));
|
||||
let wrapped = self.model_some(&field.ty, &value);
|
||||
self.line(format!(
|
||||
"store {} {wrapped}, ptr {slot}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {slot}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
values.push((ty, model.fields[order.field].ty.clone(), value));
|
||||
}
|
||||
}
|
||||
let size = self.expression(&query.size)?;
|
||||
let maximum = self.model_maximum(index, result.span);
|
||||
let size = self.database_call(
|
||||
"db_cursor_size",
|
||||
vec![format!("i64 {size}"), format!("i64 {maximum}")],
|
||||
"i64",
|
||||
result.span,
|
||||
);
|
||||
let limit = self.temp();
|
||||
self.line(format!("{limit} = add i64 {size}, 1"));
|
||||
let (projection, source) = sql::select_source(model, query, &self.module.program.models);
|
||||
let order = sql::order_sql_qualified(model, &query.order, alias);
|
||||
let statement = self.module.planned_sql(&format!(
|
||||
"SELECT {projection} FROM {source}{condition} ORDER BY {order} LIMIT ?{}",
|
||||
values.len() + 1
|
||||
));
|
||||
values.push((Type::Int, ModelFieldType::Int, limit));
|
||||
let rows = self.model_query(index, &statement, &values, "0", result.span);
|
||||
let length = self.protocol_call(
|
||||
"dever_rt_v1_db_rows_len",
|
||||
vec![format!("ptr {rows}")],
|
||||
"i64",
|
||||
result.span,
|
||||
);
|
||||
let more = self.temp();
|
||||
self.line(format!("{more} = icmp sgt i64 {length}, {size}"));
|
||||
let items = self.model_rows_list(
|
||||
index,
|
||||
&rows,
|
||||
model.record,
|
||||
Some(query),
|
||||
Some(&size),
|
||||
result.span,
|
||||
);
|
||||
let owner = self.own_value(
|
||||
&Type::List(Box::new(Type::Named(model.record))),
|
||||
&items,
|
||||
false,
|
||||
);
|
||||
let next_ty = Type::Nullable(Box::new(Type::Named(model.record)));
|
||||
let next = self.entry_slot(&next_ty);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {next}",
|
||||
self.module.ty(&next_ty)
|
||||
));
|
||||
let some = self.label("database_cursor_next");
|
||||
let done = self.label("database_cursor_ready");
|
||||
self.line(format!("br i1 {more}, label %{some}, label %{done}"));
|
||||
self.start(&some);
|
||||
self.model_last(&items, model.record, &next, result.span);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let next_value = self.temp();
|
||||
self.line(format!(
|
||||
"{next_value} = load {}, ptr {next}",
|
||||
self.module.ty(&next_ty)
|
||||
));
|
||||
self.disarm(owner);
|
||||
let layout = self.module.ty(&next_ty);
|
||||
Ok(self.model_aggregate(
|
||||
model.cursor,
|
||||
&[("ptr", items), (&layout, next_value), ("i1", more)],
|
||||
))
|
||||
}
|
||||
|
||||
fn model_last(&mut self, list: &str, record: usize, output: &str, span: Span) {
|
||||
let retained = self.temp();
|
||||
self.line(format!(
|
||||
"{retained} = call ptr @dever_rt_v1_list_retain(ptr {list})"
|
||||
));
|
||||
let cursor = self.protocol_call(
|
||||
"dever_rt_v1_list_cursor_take",
|
||||
vec![format!("ptr {retained}")],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
let slot = self.entry_slot_ir("ptr");
|
||||
self.line(format!("store ptr {cursor}, ptr {slot}"));
|
||||
let owner = self.register_owned(OwnedKind::ListCursor, slot);
|
||||
self.mark_live(owner);
|
||||
let ty = Type::Nullable(Box::new(Type::Named(record)));
|
||||
let last_owner = self.register_guard(&ty, output.into());
|
||||
self.mark_live(last_owner);
|
||||
let check = self.label("database_cursor_last");
|
||||
let found = self.label("database_cursor_last_found");
|
||||
let done = self.label("database_cursor_last_done");
|
||||
self.line(format!("br label %{check}"));
|
||||
self.start(&check);
|
||||
let row = self.runtime_call(
|
||||
"dever_rt_v1_list_cursor_next",
|
||||
vec![format!("ptr {cursor}")],
|
||||
&ty,
|
||||
true,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {row}, 0",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
self.line(format!("br i1 {present}, label %{found}, label %{done}"));
|
||||
self.start(&found);
|
||||
self.release_guard(last_owner);
|
||||
self.line(format!("store {} {row}, ptr {output}", self.module.ty(&ty)));
|
||||
self.mark_live(last_owner);
|
||||
self.line(format!("br label %{check}"));
|
||||
self.start(&done);
|
||||
self.release_guard(owner);
|
||||
self.disarm(last_owner);
|
||||
}
|
||||
|
||||
fn model_aggregate(&mut self, id: usize, fields: &[(&str, String)]) -> String {
|
||||
let mut value = "zeroinitializer".to_string();
|
||||
for (index, (ty, field)) in fields.iter().enumerate() {
|
||||
let next = self.temp();
|
||||
self.line(format!(
|
||||
"{next} = insertvalue %T{id} {value}, {ty} {field}, {index}"
|
||||
));
|
||||
value = next;
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
//! To-many loading batches 998 parent IDs and groups concrete child rows.
|
||||
use super::*;
|
||||
use crate::hir::QueryPlan;
|
||||
use crate::model::{ModelRelationKind, ModelSchema};
|
||||
use crate::native::orm as sql;
|
||||
|
||||
struct RelationBatch<'a> {
|
||||
ids: &'a str,
|
||||
count: &'a str,
|
||||
limit: &'a str,
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
fn database_empty_list(&mut self, record: usize, span: Span) -> String {
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_list_new",
|
||||
vec![
|
||||
format!(
|
||||
"ptr @dever_type_{}",
|
||||
self.module.type_index(&Type::Named(record))
|
||||
),
|
||||
"ptr null".into(),
|
||||
"i64 0".into(),
|
||||
],
|
||||
&Type::List(Box::new(Type::Named(record))),
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
)
|
||||
}
|
||||
|
||||
fn database_list_cursor(&mut self, list: &str, span: Span) -> (String, usize) {
|
||||
let retained = self.temp();
|
||||
self.line(format!(
|
||||
"{retained} = call ptr @dever_rt_v1_list_retain(ptr {list})"
|
||||
));
|
||||
let cursor = self.protocol_call(
|
||||
"dever_rt_v1_list_cursor_take",
|
||||
vec![format!("ptr {retained}")],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
let slot = self.entry_slot_ir("ptr");
|
||||
self.line(format!("store ptr {cursor}, ptr {slot}"));
|
||||
let owner = self.register_owned(OwnedKind::ListCursor, slot);
|
||||
self.mark_live(owner);
|
||||
(cursor, owner)
|
||||
}
|
||||
|
||||
fn database_for_each(
|
||||
&mut self,
|
||||
list: &str,
|
||||
record: usize,
|
||||
span: Span,
|
||||
mut apply: impl FnMut(&mut Self, &str),
|
||||
) {
|
||||
let (cursor, owner) = self.database_list_cursor(list, span);
|
||||
let check = self.label("relation_rows_next");
|
||||
let body = self.label("relation_rows_body");
|
||||
let done = self.label("relation_rows_done");
|
||||
self.line(format!("br label %{check}"));
|
||||
self.start(&check);
|
||||
let ty = Type::Nullable(Box::new(Type::Named(record)));
|
||||
let value = self.runtime_call(
|
||||
"dever_rt_v1_list_cursor_next",
|
||||
vec![format!("ptr {cursor}")],
|
||||
&ty,
|
||||
true,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
self.line(format!("br i1 {present}, label %{body}, label %{done}"));
|
||||
self.start(&body);
|
||||
let first = self.guards.len();
|
||||
let row = self.temp();
|
||||
self.line(format!(
|
||||
"{row} = extractvalue {} {value}, 1",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
self.own_value(&Type::Named(record), &row, false);
|
||||
apply(self, &row);
|
||||
self.release_from(first);
|
||||
self.line(format!("br label %{check}"));
|
||||
self.start(&done);
|
||||
self.release_guard(owner);
|
||||
}
|
||||
|
||||
fn database_append(&mut self, list_slot: &str, record: usize, row: &str, span: Span) {
|
||||
let list = self.temp();
|
||||
self.line(format!("{list} = load ptr, ptr {list_slot}"));
|
||||
let owner = self
|
||||
.guards
|
||||
.iter()
|
||||
.position(|guard| guard.pointer == list_slot)
|
||||
.expect("owned relation list slot");
|
||||
let pointer = self.row_pointer(&Type::Named(record), row);
|
||||
self.disarm(owner);
|
||||
let next = self.runtime_call(
|
||||
"dever_rt_v1_list_append_take",
|
||||
vec![format!("ptr {list}"), format!("ptr {pointer}")],
|
||||
&Type::List(Box::new(Type::Named(record))),
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.line(format!("store ptr {next}, ptr {list_slot}"));
|
||||
self.mark_live(owner);
|
||||
}
|
||||
|
||||
pub(super) fn model_load_many(
|
||||
&mut self,
|
||||
index: usize,
|
||||
query: &QueryPlan,
|
||||
list_slot: &str,
|
||||
span: Span,
|
||||
) {
|
||||
let model = self.module.program.models[index].clone();
|
||||
for relation_index in &query.relations {
|
||||
let relation = &model.relations[*relation_index];
|
||||
if relation.kind != ModelRelationKind::ToMany {
|
||||
continue;
|
||||
}
|
||||
let target_index = self
|
||||
.module
|
||||
.program
|
||||
.models
|
||||
.iter()
|
||||
.position(|candidate| candidate.package == relation.model)
|
||||
.unwrap();
|
||||
let target = self.module.program.models[target_index].clone();
|
||||
let foreign_key = target
|
||||
.fields
|
||||
.iter()
|
||||
.position(|field| field.name == relation.field)
|
||||
.unwrap();
|
||||
let field = model.fields.len() + relation_index;
|
||||
let parents = self.temp();
|
||||
self.line(format!("{parents} = load ptr, ptr {list_slot}"));
|
||||
let (cursor, cursor_owner) = self.database_list_cursor(&parents, span);
|
||||
let output = self.database_empty_list(model.record, span);
|
||||
let output_ty = Type::List(Box::new(Type::Named(model.record)));
|
||||
let output_owner = self.own_value(&output_ty, &output, false);
|
||||
let output_slot = self.guards[output_owner].pointer.clone();
|
||||
let maximum = self.model_maximum(index, span);
|
||||
let limit = self.database_call(
|
||||
"db_relation_limit",
|
||||
vec![format!("i64 {maximum}")],
|
||||
"i64",
|
||||
span,
|
||||
);
|
||||
let ids = self.entry_slot_ir("[998 x i64]");
|
||||
let count_slot = self.entry_slot(&Type::Int);
|
||||
let done_slot = self.entry_slot_ir("i1");
|
||||
self.line(format!("store i1 0, ptr {done_slot}"));
|
||||
let batch_start = self.label("relation_batch_start");
|
||||
let read = self.label("relation_batch_read");
|
||||
let add = self.label("relation_batch_add");
|
||||
let exhausted = self.label("relation_batch_exhausted");
|
||||
let query_block = self.label("relation_batch_query");
|
||||
let queried = self.label("relation_batch_queried");
|
||||
let finish = self.label("relation_batches_done");
|
||||
self.line(format!("br label %{batch_start}"));
|
||||
self.start(&batch_start);
|
||||
let batch_first = self.guards.len();
|
||||
let batch = self.database_empty_list(model.record, span);
|
||||
let batch_owner = self.own_value(&output_ty, &batch, false);
|
||||
let batch_slot = self.guards[batch_owner].pointer.clone();
|
||||
self.line(format!("store i64 0, ptr {count_slot}"));
|
||||
self.line(format!("br label %{read}"));
|
||||
self.start(&read);
|
||||
let next_ty = Type::Nullable(Box::new(Type::Named(model.record)));
|
||||
let parent = self.runtime_call(
|
||||
"dever_rt_v1_list_cursor_next",
|
||||
vec![format!("ptr {cursor}")],
|
||||
&next_ty,
|
||||
true,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {parent}, 0",
|
||||
self.module.ty(&next_ty)
|
||||
));
|
||||
self.line(format!("br i1 {present}, label %{add}, label %{exhausted}"));
|
||||
self.start(&add);
|
||||
let parent_row = self.temp();
|
||||
self.line(format!(
|
||||
"{parent_row} = extractvalue {} {parent}, 1",
|
||||
self.module.ty(&next_ty)
|
||||
));
|
||||
let parent_owner = self.own_value(&Type::Named(model.record), &parent_row, false);
|
||||
let id = self.temp();
|
||||
self.line(format!(
|
||||
"{id} = extractvalue %T{} {parent_row}, 0",
|
||||
model.record
|
||||
));
|
||||
let count = self.temp();
|
||||
self.line(format!("{count} = load i64, ptr {count_slot}"));
|
||||
let id_slot = self.temp();
|
||||
self.line(format!(
|
||||
"{id_slot} = getelementptr [998 x i64], ptr {ids}, i32 0, i64 {count}"
|
||||
));
|
||||
self.line(format!("store i64 {id}, ptr {id_slot}"));
|
||||
self.database_append(&batch_slot, model.record, &parent_row, span);
|
||||
self.release_guard(parent_owner);
|
||||
let next = self.temp();
|
||||
self.line(format!("{next} = add i64 {count}, 1"));
|
||||
self.line(format!("store i64 {next}, ptr {count_slot}"));
|
||||
let full = self.temp();
|
||||
self.line(format!("{full} = icmp eq i64 {next}, 998"));
|
||||
self.line(format!("br i1 {full}, label %{query_block}, label %{read}"));
|
||||
self.start(&exhausted);
|
||||
self.line(format!("store i1 1, ptr {done_slot}"));
|
||||
let count = self.temp();
|
||||
self.line(format!("{count} = load i64, ptr {count_slot}"));
|
||||
let empty = self.temp();
|
||||
self.line(format!("{empty} = icmp eq i64 {count}, 0"));
|
||||
self.line(format!(
|
||||
"br i1 {empty}, label %{queried}, label %{query_block}"
|
||||
));
|
||||
self.start(&query_block);
|
||||
let count = self.temp();
|
||||
self.line(format!("{count} = load i64, ptr {count_slot}"));
|
||||
let batch = RelationBatch {
|
||||
ids: &ids,
|
||||
count: &count,
|
||||
limit: &limit,
|
||||
};
|
||||
let rows = self.relation_query(index, &target, &relation.field, batch, span);
|
||||
let groups = self.relation_group_rows(&rows, target_index, foreign_key, span);
|
||||
let batch = self.temp();
|
||||
self.line(format!("{batch} = load ptr, ptr {batch_slot}"));
|
||||
self.database_for_each(&batch, model.record, span, |emitter, parent| {
|
||||
let id = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{id} = extractvalue %T{} {parent}, 0",
|
||||
model.record
|
||||
));
|
||||
let children = emitter.relation_children(&groups, &id, target.record, span);
|
||||
let child_ty = Type::List(Box::new(Type::Named(target.record)));
|
||||
let child_owner = emitter.own_value(&child_ty, &children, false);
|
||||
let pointer = emitter.row_pointer(&child_ty, &children);
|
||||
let related = emitter.protocol_call(
|
||||
"dever_rt_v1_db_related_new",
|
||||
vec![
|
||||
format!("ptr @dever_type_{}", emitter.module.type_index(&child_ty)),
|
||||
format!("ptr {pointer}"),
|
||||
],
|
||||
"ptr",
|
||||
span,
|
||||
);
|
||||
emitter.protocol_owner("db_related", &related);
|
||||
let updated = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{updated} = insertvalue %T{} {parent}, ptr {related}, {field}",
|
||||
model.record
|
||||
));
|
||||
emitter.database_append(&output_slot, model.record, &updated, span);
|
||||
emitter.release_guard(child_owner);
|
||||
});
|
||||
self.line(format!("br label %{queried}"));
|
||||
self.start(&queried);
|
||||
self.release_from(batch_first);
|
||||
let exhausted = self.temp();
|
||||
self.line(format!("{exhausted} = load i1, ptr {done_slot}"));
|
||||
self.line(format!(
|
||||
"br i1 {exhausted}, label %{finish}, label %{batch_start}"
|
||||
));
|
||||
self.start(&finish);
|
||||
self.release_guard(cursor_owner);
|
||||
let rebuilt = self.temp();
|
||||
self.line(format!("{rebuilt} = load ptr, ptr {output_slot}"));
|
||||
self.line(format!(
|
||||
"call void @dever_rt_v1_list_release(ptr {parents})"
|
||||
));
|
||||
self.line(format!("store ptr {rebuilt}, ptr {list_slot}"));
|
||||
self.disarm(output_owner);
|
||||
}
|
||||
}
|
||||
|
||||
fn relation_query(
|
||||
&mut self,
|
||||
model: usize,
|
||||
target: &ModelSchema,
|
||||
field: &str,
|
||||
batch: RelationBatch<'_>,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let RelationBatch { ids, count, limit } = batch;
|
||||
let prefix = format!(
|
||||
"SELECT {} FROM (SELECT {}, ROW_NUMBER() OVER (PARTITION BY {} ORDER BY {} ASC) AS {} FROM {} WHERE {} IN (",
|
||||
sql::select_columns(target),
|
||||
sql::select_columns(target),
|
||||
sql::quoted(field),
|
||||
sql::quoted("id"),
|
||||
sql::quoted("_dever_rank"),
|
||||
sql::quoted(&target.table),
|
||||
sql::quoted(field)
|
||||
);
|
||||
let before = format!(
|
||||
")) AS {} WHERE {} <= ",
|
||||
sql::quoted("_dever_children"),
|
||||
sql::quoted("_dever_rank")
|
||||
);
|
||||
let suffix = format!(
|
||||
" ORDER BY {} ASC, {} ASC",
|
||||
sql::quoted(field),
|
||||
sql::quoted("id")
|
||||
);
|
||||
let names = [prefix, before, suffix].map(|name| self.module.wire_names(&[name]));
|
||||
let database = self.model_database(model);
|
||||
let operation = self.database_operation(
|
||||
"db_relation_query",
|
||||
vec![
|
||||
format!("ptr {database}"),
|
||||
format!("ptr {}", self.database_context),
|
||||
format!("ptr {}", names[0]),
|
||||
format!("ptr {}", names[1]),
|
||||
format!("ptr {}", names[2]),
|
||||
format!("ptr {ids}"),
|
||||
format!("i64 {count}"),
|
||||
format!("i64 {limit}"),
|
||||
],
|
||||
span,
|
||||
);
|
||||
let rows = self.await_database_handle(&operation, span);
|
||||
self.protocol_owner("db_rows", &rows);
|
||||
rows
|
||||
}
|
||||
|
||||
fn relation_children(&mut self, groups: &str, id: &str, record: usize, span: Span) -> String {
|
||||
let retained = self.temp();
|
||||
self.line(format!(
|
||||
"{retained} = call ptr @dever_rt_v1_map_retain(ptr {groups})"
|
||||
));
|
||||
let key = self.row_pointer(&Type::Int, id);
|
||||
let list_ty = Type::List(Box::new(Type::Named(record)));
|
||||
let optional_ty = Type::Nullable(Box::new(list_ty.clone()));
|
||||
let found = self.runtime_call(
|
||||
"dever_rt_v1_map_get_take",
|
||||
vec![format!("ptr {retained}"), format!("ptr {key}")],
|
||||
&optional_ty,
|
||||
true,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {found}, 0",
|
||||
self.module.ty(&optional_ty)
|
||||
));
|
||||
let slot = self.entry_slot(&list_ty);
|
||||
let some = self.label("relation_group_found");
|
||||
let missing = self.label("relation_group_empty");
|
||||
let done = self.label("relation_group_ready");
|
||||
self.line(format!("br i1 {present}, label %{some}, label %{missing}"));
|
||||
self.start(&some);
|
||||
let list = self.temp();
|
||||
self.line(format!(
|
||||
"{list} = extractvalue {} {found}, 1",
|
||||
self.module.ty(&optional_ty)
|
||||
));
|
||||
self.line(format!("store ptr {list}, ptr {slot}"));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&missing);
|
||||
let empty = self.database_empty_list(record, span);
|
||||
self.line(format!("store ptr {empty}, ptr {slot}"));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let list = self.temp();
|
||||
self.line(format!("{list} = load ptr, ptr {slot}"));
|
||||
list
|
||||
}
|
||||
|
||||
fn relation_group_rows(
|
||||
&mut self,
|
||||
rows: &str,
|
||||
model: usize,
|
||||
foreign_key: usize,
|
||||
span: Span,
|
||||
) -> String {
|
||||
let target = self.module.program.models[model].clone();
|
||||
let list_ty = Type::List(Box::new(Type::Named(target.record)));
|
||||
let map_ty = Type::Map(Box::new(Type::Int), Box::new(list_ty.clone()));
|
||||
let map = self.runtime_call(
|
||||
"dever_rt_v1_map_new",
|
||||
vec![
|
||||
format!("ptr @dever_type_{}", self.module.type_index(&Type::Int)),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(&list_ty)),
|
||||
"ptr null".into(),
|
||||
"ptr null".into(),
|
||||
"i64 0".into(),
|
||||
],
|
||||
&map_ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let owner = self.own_value(&map_ty, &map, false);
|
||||
let slot = self.guards[owner].pointer.clone();
|
||||
let list = self.model_rows_list(model, rows, target.record, None, None, span);
|
||||
let list_owner = self.own_value(&list_ty, &list, false);
|
||||
self.database_for_each(&list, target.record, span, |emitter, row| {
|
||||
let id = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{id} = extractvalue %T{} {row}, {foreign_key}",
|
||||
target.record
|
||||
));
|
||||
let done = emitter.label("relation_child_grouped");
|
||||
let id = if target.fields[foreign_key].nullable {
|
||||
let present = emitter.temp();
|
||||
emitter.line(format!("{present} = extractvalue {{ i1, i64 }} {id}, 0"));
|
||||
let group = emitter.label("relation_child_parent");
|
||||
emitter.line(format!("br i1 {present}, label %{group}, label %{done}"));
|
||||
emitter.start(&group);
|
||||
let value = emitter.temp();
|
||||
emitter.line(format!("{value} = extractvalue {{ i1, i64 }} {id}, 1"));
|
||||
value
|
||||
} else {
|
||||
id
|
||||
};
|
||||
let map = emitter.temp();
|
||||
emitter.line(format!("{map} = load ptr, ptr {slot}"));
|
||||
let children = emitter.relation_children(&map, &id, target.record, span);
|
||||
let children_owner = emitter.own_value(&list_ty, &children, false);
|
||||
let child_slot = emitter.guards[children_owner].pointer.clone();
|
||||
let key = emitter.row_pointer(&Type::Int, &id);
|
||||
emitter.disarm(owner);
|
||||
let map = emitter.runtime_call(
|
||||
"dever_rt_v1_map_remove_take",
|
||||
vec![format!("ptr {map}"), format!("ptr {key}")],
|
||||
&map_ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
emitter.line(format!("store ptr {map}, ptr {slot}"));
|
||||
emitter.mark_live(owner);
|
||||
emitter.database_append(&child_slot, target.record, row, span);
|
||||
emitter.disarm(owner);
|
||||
let changed = emitter.runtime_call(
|
||||
"dever_rt_v1_map_put_take",
|
||||
vec![
|
||||
format!("ptr {map}"),
|
||||
format!("ptr {key}"),
|
||||
format!("ptr {child_slot}"),
|
||||
],
|
||||
&map_ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
emitter.line(format!("store ptr {changed}, ptr {slot}"));
|
||||
emitter.mark_live(owner);
|
||||
emitter.release_guard(children_owner);
|
||||
emitter.line(format!("br label %{done}"));
|
||||
emitter.start(&done);
|
||||
});
|
||||
self.release_guard(list_owner);
|
||||
let groups = self.temp();
|
||||
self.line(format!("{groups} = load ptr, ptr {slot}"));
|
||||
groups
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//! RowStream pulls decode at the consumer's error location and own no borrowed row.
|
||||
use super::*;
|
||||
use crate::hir::QueryPlan;
|
||||
use crate::model::ModelSchema;
|
||||
use crate::native::orm as sql;
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn database_row_decoder(
|
||||
&mut self,
|
||||
record: usize,
|
||||
instance: &Specialization,
|
||||
) -> String {
|
||||
let symbol = format!("@dever_db_decoder_{record}");
|
||||
if !self.database_decoders.insert(record) {
|
||||
return symbol;
|
||||
}
|
||||
let index = self.type_index(&Type::Named(record));
|
||||
writeln!(self.declarations, "{symbol} = private constant {{ ptr, ptr }} {{ ptr @dever_owned_{index}, ptr @dever_db_decode_{record} }}").unwrap();
|
||||
let span = self.program.types[record].span;
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.database_errors_override = Some("%errors".into());
|
||||
emitter.start("entry");
|
||||
emitter
|
||||
.prologue
|
||||
.push_str(" %exit_status = alloca i32\n store i32 0, ptr %exit_status\n");
|
||||
let value = emitter.database_record("%row", record, 0, true, span);
|
||||
emitter.line(format!("store %T{record} {value}, ptr %out"));
|
||||
emitter.exit("0");
|
||||
emitter.start("cleanup");
|
||||
emitter.release_from(0);
|
||||
let status = emitter.temp();
|
||||
emitter.line(format!("{status} = load i32, ptr %exit_status"));
|
||||
emitter.line(format!("ret i32 {status}"));
|
||||
let body = format!(
|
||||
"define internal i32 @dever_db_decode_{record}(ptr %row, ptr %out, ptr %errors, ptr %fault, ptr %error) {{\nentry:\n{}{} }}\n",
|
||||
emitter.prologue, emitter.body
|
||||
);
|
||||
self.declarations.push_str(&body);
|
||||
symbol
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn model_stream(
|
||||
&mut self,
|
||||
index: usize,
|
||||
model: &ModelSchema,
|
||||
query: &QueryPlan,
|
||||
result: &Expression,
|
||||
) -> Result<String, String> {
|
||||
let mut expressions = Vec::new();
|
||||
let condition = sql::condition_sql(model, query.condition.as_ref(), &mut expressions);
|
||||
let values = self.model_bind(&expressions)?;
|
||||
let capacity = self.expression(&query.size)?;
|
||||
let maximum = self.model_maximum(index, result.span);
|
||||
let capacity = self.database_call(
|
||||
"db_stream_capacity",
|
||||
vec![format!("i64 {capacity}"), format!("i64 {maximum}")],
|
||||
"i64",
|
||||
result.span,
|
||||
);
|
||||
let statement = self.module.planned_sql(&format!(
|
||||
"SELECT {} FROM {}{condition} ORDER BY {}",
|
||||
sql::select_columns(model),
|
||||
sql::quoted(&model.table),
|
||||
sql::order_sql(model, &query.order)
|
||||
));
|
||||
let parameters = self.database_parameters(&values, result.span);
|
||||
let database = self.model_database(index);
|
||||
let decoder = self
|
||||
.module
|
||||
.database_row_decoder(model.record, self.instance);
|
||||
let operation = self.database_operation(
|
||||
"db_stream",
|
||||
vec![
|
||||
format!("ptr {database}"),
|
||||
format!("ptr {}", self.database_context),
|
||||
format!("ptr {statement}"),
|
||||
format!("ptr {parameters}"),
|
||||
format!("i64 {}", values.len()),
|
||||
format!("i64 {capacity}"),
|
||||
format!("ptr {decoder}"),
|
||||
],
|
||||
result.span,
|
||||
);
|
||||
Ok(self.await_database_handle(&operation, result.span))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn contains_owned(program: &Program, ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Text
|
||||
| Type::Json
|
||||
| Type::Id
|
||||
| Type::Secret
|
||||
| Type::Uuid
|
||||
| Type::ClientTls
|
||||
| Type::ServerTls
|
||||
| Type::HttpClient
|
||||
| Type::HttpReply
|
||||
| Type::WebSocket
|
||||
| Type::Upload
|
||||
| Type::Bytes
|
||||
| Type::List(_)
|
||||
| Type::Map(_, _)
|
||||
| Type::File
|
||||
| Type::Stream(_)
|
||||
| Type::AsyncStream(_)
|
||||
| Type::RowStream(_)
|
||||
| Type::Related(_)
|
||||
| Type::Socket
|
||||
| Type::Listener
|
||||
| Type::Task(_)
|
||||
| Type::Group
|
||||
| Type::Channel(_) => true,
|
||||
Type::Nullable(inner) => contains_owned(program, inner),
|
||||
Type::MapEntry(key, value) => {
|
||||
contains_owned(program, key) || contains_owned(program, value)
|
||||
}
|
||||
Type::Outputs(fields) => fields
|
||||
.iter()
|
||||
.any(|field| contains_owned(program, &field.ty)),
|
||||
Type::Named(id) => match &program.types[*id].shape {
|
||||
Shape::Record(fields) => fields
|
||||
.iter()
|
||||
.any(|field| contains_owned(program, &field.ty)),
|
||||
Shape::Choice(variants) => variants
|
||||
.iter()
|
||||
.flat_map(|variant| &variant.fields)
|
||||
.any(|field| contains_owned(program, &field.ty)),
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn hashable_key(program: &Program, ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Bool | Type::Int | Type::Text | Type::Id => true,
|
||||
Type::Named(id) if program.types[*id].kind == DefinitionKind::ModelId => true,
|
||||
Type::Named(id) => matches!(
|
||||
&program.types[*id].shape,
|
||||
Shape::Choice(variants) if variants.iter().all(|variant| variant.fields.is_empty())
|
||||
),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn emit_type_equal(&mut self, index: usize, ty: &Type) -> Result<String, String> {
|
||||
let instance = self
|
||||
.instances
|
||||
.iter()
|
||||
.next()
|
||||
.expect("reachable entry")
|
||||
.clone();
|
||||
let llvm_type = self.ty(ty);
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.start("entry");
|
||||
let left = emitter.temp();
|
||||
emitter.line(format!("{left} = load {llvm_type}, ptr %left"));
|
||||
let right = emitter.temp();
|
||||
emitter.line(format!("{right} = load {llvm_type}, ptr %right"));
|
||||
let equal = emitter.equals(ty, &left, &right)?;
|
||||
let value = emitter.temp();
|
||||
emitter.line(format!("{value} = zext i1 {equal} to i8"));
|
||||
emitter.line(format!("ret i8 {value}"));
|
||||
Ok(format!(
|
||||
"define internal i8 @dever_equal_{index}(ptr %left, ptr %right) {{\nentry:\n{} }}\n",
|
||||
emitter.body
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn emit_type_hash(&self, index: usize, ty: &Type) -> String {
|
||||
if !hashable_key(self.program, ty) {
|
||||
return String::new();
|
||||
}
|
||||
let value = match ty {
|
||||
Type::Bool => " %value = load i1, ptr %source\n %hash = zext i1 %value to i64\n".into(),
|
||||
Type::Int => " %hash = load i64, ptr %source\n".into(),
|
||||
Type::Named(id) if self.program.types[*id].kind == DefinitionKind::ModelId => " %hash = load i64, ptr %source\n".into(),
|
||||
Type::Text | Type::Id => " %value = load ptr, ptr %source\n %hash = call i64 @dever_rt_v1_text_hash(ptr %value)\n".into(),
|
||||
Type::Named(id) => format!(
|
||||
" %tag = getelementptr %T{id}, ptr %source, i32 0, i32 0\n %value = load i32, ptr %tag\n %hash = zext i32 %value to i64\n"
|
||||
),
|
||||
_ => unreachable!("checked Map key"),
|
||||
};
|
||||
format!(
|
||||
"define internal i64 @dever_hash_{index}(ptr %source) {{\nentry:\n{value} ret i64 %hash\n}}\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn collect_owned_types(program: &Program, ty: &Type, types: &mut Vec<Type>) {
|
||||
if types.contains(ty) {
|
||||
return;
|
||||
}
|
||||
types.push(ty.clone());
|
||||
match ty {
|
||||
Type::Uuid => collect_owned_types(program, &Type::Text, types),
|
||||
Type::Nullable(inner)
|
||||
| Type::List(inner)
|
||||
| Type::Stream(inner)
|
||||
| Type::AsyncStream(inner)
|
||||
| Type::RowStream(inner)
|
||||
| Type::Related(inner)
|
||||
| Type::Channel(inner) => collect_owned_types(program, inner, types),
|
||||
Type::Map(key, value) | Type::MapEntry(key, value) => {
|
||||
collect_owned_types(program, key, types);
|
||||
collect_owned_types(program, value, types);
|
||||
}
|
||||
Type::Outputs(fields) | Type::Task(fields) => {
|
||||
for field in fields {
|
||||
collect_owned_types(program, &field.ty, types);
|
||||
}
|
||||
if matches!(ty, Type::Task(_)) {
|
||||
collect_owned_types(program, &output_type(fields), types);
|
||||
}
|
||||
}
|
||||
Type::Named(id) => match &program.types[*id].shape {
|
||||
Shape::Record(fields) => {
|
||||
for field in fields {
|
||||
collect_owned_types(program, &field.ty, types);
|
||||
}
|
||||
}
|
||||
Shape::Choice(variants) => {
|
||||
for field in variants.iter().flat_map(|variant| &variant.fields) {
|
||||
collect_owned_types(program, &field.ty, types);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct TypeOwner<'a> {
|
||||
module: &'a Module<'a>,
|
||||
ir: String,
|
||||
next: usize,
|
||||
}
|
||||
|
||||
impl<'a> TypeOwner<'a> {
|
||||
pub(super) fn new(module: &'a Module<'a>) -> Self {
|
||||
Self {
|
||||
module,
|
||||
ir: String::new(),
|
||||
next: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn temp(&mut self) -> String {
|
||||
let next = self.next;
|
||||
self.next += 1;
|
||||
format!("%o{next}")
|
||||
}
|
||||
|
||||
fn label(&mut self, name: &str) -> String {
|
||||
let next = self.next;
|
||||
self.next += 1;
|
||||
format!("{name}{next}")
|
||||
}
|
||||
|
||||
fn line(&mut self, line: impl AsRef<str>) {
|
||||
writeln!(self.ir, " {}", line.as_ref()).unwrap();
|
||||
}
|
||||
|
||||
fn start(&mut self, label: &str) {
|
||||
writeln!(self.ir, "{label}:").unwrap();
|
||||
}
|
||||
|
||||
pub(super) fn emit(mut self, index: usize, ty: &Type) -> String {
|
||||
let llvm_ty = self.module.ty(ty);
|
||||
writeln!(
|
||||
self.ir,
|
||||
"define internal void @dever_clone_{index}(ptr %src, ptr %dst) {{\nentry:"
|
||||
)
|
||||
.unwrap();
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load {llvm_ty}, ptr %src"));
|
||||
self.line(format!("store {llvm_ty} {value}, ptr %dst"));
|
||||
self.adjust(ty, "%dst", true);
|
||||
self.line("ret void");
|
||||
self.ir.push_str("}\n");
|
||||
|
||||
writeln!(
|
||||
self.ir,
|
||||
"define internal void @dever_drop_{index}(ptr %value) {{\nentry:"
|
||||
)
|
||||
.unwrap();
|
||||
self.adjust(ty, "%value", false);
|
||||
self.line("ret void");
|
||||
self.ir.push_str("}\n");
|
||||
self.ir
|
||||
}
|
||||
|
||||
fn adjust(&mut self, ty: &Type, address: &str, retain: bool) {
|
||||
match ty {
|
||||
Type::Text
|
||||
| Type::Json
|
||||
| Type::Id
|
||||
| Type::Secret
|
||||
| Type::Uuid
|
||||
| Type::ClientTls
|
||||
| Type::ServerTls
|
||||
| Type::HttpClient
|
||||
| Type::HttpReply
|
||||
| Type::WebSocket
|
||||
| Type::Upload
|
||||
| Type::Bytes
|
||||
| Type::List(_)
|
||||
| Type::Map(_, _)
|
||||
| Type::File
|
||||
| Type::Stream(_)
|
||||
| Type::AsyncStream(_)
|
||||
| Type::RowStream(_)
|
||||
| Type::Related(_)
|
||||
| Type::Socket
|
||||
| Type::Listener
|
||||
| Type::Task(_)
|
||||
| Type::Group
|
||||
| Type::Channel(_) => {
|
||||
let kind = match ty {
|
||||
Type::Text | Type::Json | Type::Id => "text",
|
||||
Type::Secret => "secret",
|
||||
Type::Uuid => "uuid",
|
||||
Type::ClientTls => "client_tls",
|
||||
Type::ServerTls => "server_tls",
|
||||
Type::HttpClient => "http_client",
|
||||
Type::HttpReply => "http_reply",
|
||||
Type::WebSocket => "websocket",
|
||||
Type::Upload => "upload",
|
||||
Type::Bytes => "bytes",
|
||||
Type::List(_) => "list",
|
||||
Type::Map(_, _) => "map",
|
||||
Type::File => "file",
|
||||
Type::Stream(_) => "stream",
|
||||
Type::AsyncStream(_) => "async_stream",
|
||||
Type::RowStream(_) => "db_stream",
|
||||
Type::Related(_) => "db_related",
|
||||
Type::Socket => "socket",
|
||||
Type::Listener => "listener",
|
||||
Type::Task(_) => "async_task",
|
||||
Type::Group => "async_group",
|
||||
Type::Channel(_) => "async_channel",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let handle = self.temp();
|
||||
self.line(format!("{handle} = load ptr, ptr {address}"));
|
||||
if retain {
|
||||
let owned = self.temp();
|
||||
self.line(format!(
|
||||
"{owned} = call ptr @dever_rt_v1_{kind}_retain(ptr {handle})"
|
||||
));
|
||||
self.line(format!("store ptr {owned}, ptr {address}"));
|
||||
} else {
|
||||
self.line(format!(
|
||||
"call void @dever_rt_v1_{kind}_release(ptr {handle})"
|
||||
));
|
||||
}
|
||||
}
|
||||
Type::Nullable(inner) => {
|
||||
let present_ptr = self.temp();
|
||||
self.line(format!(
|
||||
"{present_ptr} = getelementptr {}, ptr {address}, i32 0, i32 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let present = self.temp();
|
||||
self.line(format!("{present} = load i1, ptr {present_ptr}"));
|
||||
let some = self.label("owned_some");
|
||||
let done = self.label("owned_done");
|
||||
self.line(format!("br i1 {present}, label %{some}, label %{done}"));
|
||||
self.start(&some);
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = getelementptr {}, ptr {address}, i32 0, i32 1",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
self.adjust(inner, &payload, retain);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
}
|
||||
Type::Named(id) => match &self.module.program.types[*id].shape {
|
||||
Shape::Record(fields) => {
|
||||
let fields = fields
|
||||
.iter()
|
||||
.map(|field| field.ty.clone())
|
||||
.collect::<Vec<_>>();
|
||||
self.fields(ty, address, &fields, retain);
|
||||
}
|
||||
Shape::Choice(variants) => {
|
||||
let variants = variants
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| field.ty.clone())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<Vec<Type>>>();
|
||||
self.choice(*id, address, &variants, retain);
|
||||
}
|
||||
},
|
||||
Type::MapEntry(key, value) => {
|
||||
self.fields(
|
||||
ty,
|
||||
address,
|
||||
&[key.as_ref().clone(), value.as_ref().clone()],
|
||||
retain,
|
||||
);
|
||||
}
|
||||
Type::Outputs(fields) => {
|
||||
let fields = fields
|
||||
.iter()
|
||||
.map(|field| field.ty.clone())
|
||||
.collect::<Vec<_>>();
|
||||
self.fields(ty, address, &fields, retain);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn fields(&mut self, ty: &Type, address: &str, fields: &[Type], retain: bool) {
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
if !contains_owned(self.module.program, field) {
|
||||
continue;
|
||||
}
|
||||
let pointer = self.temp();
|
||||
self.line(format!(
|
||||
"{pointer} = getelementptr {}, ptr {address}, i32 0, i32 {index}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
self.adjust(field, &pointer, retain);
|
||||
}
|
||||
}
|
||||
|
||||
fn choice(&mut self, id: usize, address: &str, variants: &[Vec<Type>], retain: bool) {
|
||||
let tag_ptr = self.temp();
|
||||
self.line(format!(
|
||||
"{tag_ptr} = getelementptr %T{id}, ptr {address}, i32 0, i32 0"
|
||||
));
|
||||
let tag = self.temp();
|
||||
self.line(format!("{tag} = load i32, ptr {tag_ptr}"));
|
||||
let done = self.label("owned_choice_done");
|
||||
let arms = variants
|
||||
.iter()
|
||||
.map(|_| self.label("owned_variant"))
|
||||
.collect::<Vec<_>>();
|
||||
let cases = arms
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, arm)| format!("i32 {index}, label %{arm}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
self.line(format!("switch i32 {tag}, label %{done} [{cases}]"));
|
||||
for (variant, fields) in variants.iter().enumerate() {
|
||||
self.start(&arms[variant]);
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = getelementptr %T{id}, ptr {address}, i32 0, i32 1"
|
||||
));
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
if !contains_owned(self.module.program, field) {
|
||||
continue;
|
||||
}
|
||||
let pointer = self.temp();
|
||||
self.line(format!(
|
||||
"{pointer} = getelementptr %T{id}V{variant}, ptr {payload}, i32 0, i32 {index}"
|
||||
));
|
||||
self.adjust(field, &pointer, retain);
|
||||
}
|
||||
self.line(format!("br label %{done}"));
|
||||
}
|
||||
self.start(&done);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
//! Parallel dispatch preserves the runtime's bounded producer and task owners.
|
||||
use super::*;
|
||||
|
||||
pub(super) const DECLARATIONS: &str = "\
|
||||
%dever.parallel_function = type { ptr, ptr, ptr, ptr, ptr }
|
||||
declare ptr @dever_rt_v1_async_parallel_each(ptr, i32, i64, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_parallel_each(ptr, i32, i64, ptr, ptr, ptr, ptr)
|
||||
";
|
||||
|
||||
impl Module<'_> {
|
||||
fn parallel_descriptor(&mut self, instance: &Specialization) -> String {
|
||||
let name = self.names[instance].clone();
|
||||
let descriptor = format!("@dever_parallel_{name}");
|
||||
if !self.parallel_functions.insert(instance.clone()) {
|
||||
return descriptor;
|
||||
}
|
||||
let function = &self.program.functions[instance.function];
|
||||
let input = asynchronous::input_type(function);
|
||||
let Type::Outputs(fields) = &input else {
|
||||
unreachable!()
|
||||
};
|
||||
let context = fields.get(1).map_or("null".into(), |field| {
|
||||
format!("@dever_type_{}", self.type_index(&field.ty))
|
||||
});
|
||||
let mut callback = format!(
|
||||
"define internal void @dever_parallel_pack_{name}(ptr %element, ptr %context, ptr %out) {{\nentry:\n"
|
||||
);
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
let source = if index == 0 { "%element" } else { "%context" };
|
||||
writeln!(callback, " %p{index} = getelementptr {}, ptr %out, i32 0, i32 {index}\n call void @dever_clone_{}(ptr {source}, ptr %p{index})", self.ty(&input), self.type_index(&field.ty)).unwrap();
|
||||
}
|
||||
callback.push_str(" ret void\n}\n");
|
||||
self.declarations.push_str(&callback);
|
||||
let (asynchronous, synchronous) = if specialize::suspends(self.program, instance) {
|
||||
(format!("@dever_async_{name}"), "null".into())
|
||||
} else {
|
||||
("null".into(), format!("@dever_sync_{name}"))
|
||||
};
|
||||
writeln!(self.declarations, "{descriptor} = private constant %dever.parallel_function {{ ptr {asynchronous}, ptr {synchronous}, ptr @dever_owned_{}, ptr {context}, ptr @dever_parallel_pack_{name} }}", self.type_index(&input)).unwrap();
|
||||
descriptor
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn parallel_each(
|
||||
&mut self,
|
||||
handler: crate::hir::HandlerTarget,
|
||||
sequence: SequenceKind,
|
||||
arguments: &[Expression],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
let source = self
|
||||
.expression(&arguments[0])
|
||||
.expect("checked parallel source");
|
||||
let limit = self
|
||||
.expression(&arguments[1])
|
||||
.expect("checked parallel limit");
|
||||
let context = arguments.get(2).map_or_else(
|
||||
|| "null".into(),
|
||||
|argument| {
|
||||
let value = self.expression(argument).expect("checked parallel context");
|
||||
self.row_pointer(&argument.ty, &value)
|
||||
},
|
||||
);
|
||||
let instance = Specialization {
|
||||
function: specialize::resolve_handler(handler, &self.bindings),
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let descriptor = self.module.parallel_descriptor(&instance);
|
||||
let kind = match sequence {
|
||||
SequenceKind::List => 0,
|
||||
SequenceKind::Bytes => 1,
|
||||
SequenceKind::Stream => 2,
|
||||
SequenceKind::AsyncStream => 3,
|
||||
_ => unreachable!("checked parallel sequence"),
|
||||
};
|
||||
let arguments = vec![
|
||||
format!("ptr {source}"),
|
||||
format!("i32 {kind}"),
|
||||
format!("i64 {limit}"),
|
||||
format!("ptr {descriptor}"),
|
||||
format!("ptr {context}"),
|
||||
];
|
||||
if self.asynchronous && sequence != SequenceKind::Stream {
|
||||
let operation = self.async_operation("async_parallel_each", arguments, result.span);
|
||||
self.await_operation(&operation, &Type::Unit, result.span, true)
|
||||
} else {
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let status = self.temp();
|
||||
self.line(format!(
|
||||
"{status} = call i32 @dever_rt_v1_parallel_each({}, ptr %fault, ptr {error})",
|
||||
arguments.join(", ")
|
||||
));
|
||||
let legacy = self.poll_status(&status, &error, result.span);
|
||||
self.propagate_status(&legacy, result.span, true);
|
||||
"zeroinitializer".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
//! Closed Port dispatch and entry-owned, immutable selected Adapter settings.
|
||||
use super::*;
|
||||
|
||||
pub(super) const DECLARATIONS: &str = "\
|
||||
declare i32 @dever_rt_v1_adapter_settings_load(ptr, ptr)
|
||||
declare void @dever_rt_v1_adapter_settings_release(ptr)
|
||||
declare i32 @dever_rt_v1_adapter_settings_validate_names(ptr, ptr, i64, ptr)
|
||||
declare i32 @dever_rt_v1_adapter_settings_select(ptr, ptr, ptr, i64, ptr, ptr, ptr, ptr)
|
||||
";
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn emit_port_slots(&mut self) {
|
||||
let app = self.application.as_ref().unwrap();
|
||||
for port in &app.ports {
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@dever_port_{port} = private global i64 0"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
for adapter in &app.adapters {
|
||||
let adapter = &self.program.adapters[*adapter];
|
||||
if let Some(ty) = adapter.setting {
|
||||
writeln!(self.declarations, "@dever_setting_{} = private global %T{ty} zeroinitializer\n@dever_setting_live_{} = private global i1 false", adapter.owner, adapter.owner).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn emit_port_body(
|
||||
&mut self,
|
||||
function: &crate::hir::Function,
|
||||
) -> Result<bool, String> {
|
||||
if function.setting.is_some() {
|
||||
let ty = output_type(&function.outputs);
|
||||
self.line(format!(
|
||||
"call void @dever_clone_{}(ptr @dever_setting_{}, ptr %out)",
|
||||
self.module.type_index(&ty),
|
||||
function.owner
|
||||
));
|
||||
self.exit("0");
|
||||
return Ok(true);
|
||||
}
|
||||
let Some(port) = &function.port else {
|
||||
return Ok(false);
|
||||
};
|
||||
let selected = self.temp();
|
||||
self.line(format!(
|
||||
"{selected} = load i64, ptr @dever_port_{}",
|
||||
self.instance.function
|
||||
));
|
||||
let cases = port
|
||||
.implementations
|
||||
.iter()
|
||||
.map(|id| format!("i64 {id}, label %port_{id}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
self.line(format!(
|
||||
"switch i64 {selected}, label %port_invalid [{cases}]"
|
||||
));
|
||||
self.start("port_invalid");
|
||||
self.line("unreachable");
|
||||
let arguments = function
|
||||
.parameters
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, parameter)| {
|
||||
parameter
|
||||
.value_type()
|
||||
.map(|ty| (ty.clone(), self.parameter(index)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let output = output_type(&function.outputs);
|
||||
for implementation in &port.implementations {
|
||||
self.start(&format!("port_{implementation}"));
|
||||
let target = Specialization {
|
||||
function: *implementation,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let (status, row) = self.invoke_values(&target, &arguments, &output, function.span);
|
||||
self.propagate_status(&status, function.span, false);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {row}",
|
||||
self.module.ty(&output)
|
||||
));
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr %out",
|
||||
self.module.ty(&output)
|
||||
));
|
||||
self.exit("0");
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(super) fn initialize_ports(&mut self, span: Span) {
|
||||
let app = self.module.application.as_ref().unwrap().clone();
|
||||
if let Some(index) = app.test {
|
||||
for port in app.ports {
|
||||
let implementation = self.module.program.tests[index].port_bindings[&port];
|
||||
self.line(format!(
|
||||
"store i64 {implementation}, ptr @dever_port_{port}"
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if app.ports.is_empty() {
|
||||
return;
|
||||
}
|
||||
let settings = self.protocol_call("dever_rt_v1_adapter_settings_load", vec![], "ptr", span);
|
||||
let settings_guard = self.protocol_owner("adapter_settings", &settings);
|
||||
let identities = self
|
||||
.module
|
||||
.program
|
||||
.functions
|
||||
.iter()
|
||||
.filter_map(|function| function.port.as_ref().map(|port| port.identity.clone()))
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
let names = self.module.wire_names(&identities);
|
||||
self.wire_write(
|
||||
"dever_rt_v1_adapter_settings_validate_names",
|
||||
vec![
|
||||
format!("ptr {settings}"),
|
||||
format!("ptr {names}"),
|
||||
format!("i64 {}", identities.len()),
|
||||
],
|
||||
);
|
||||
let owners = app
|
||||
.ports
|
||||
.iter()
|
||||
.map(|port| self.module.program.functions[*port].owner)
|
||||
.collect::<BTreeSet<_>>();
|
||||
for owner in owners {
|
||||
let adapters = app
|
||||
.adapters
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
let adapter = &self.module.program.adapters[*id];
|
||||
(adapter.port_owner == owner).then_some(adapter.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let names = self.module.wire_names(
|
||||
&adapters
|
||||
.iter()
|
||||
.map(|adapter| adapter.name.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
let identity = self.module.wire_names(&[adapters[0].identity.clone()]);
|
||||
let index = self.entry_slot_ir("i64");
|
||||
let setting = self.entry_slot_ir("ptr");
|
||||
let present = self.entry_slot_ir("i8");
|
||||
self.wire_write(
|
||||
"dever_rt_v1_adapter_settings_select",
|
||||
vec![
|
||||
format!("ptr {settings}"),
|
||||
format!("ptr {identity}"),
|
||||
format!("ptr {names}"),
|
||||
format!("i64 {}", adapters.len()),
|
||||
format!("ptr {index}"),
|
||||
format!("ptr {setting}"),
|
||||
format!("ptr {present}"),
|
||||
],
|
||||
);
|
||||
let optional = self.optional_row(
|
||||
&Type::Nullable(Box::new(Type::Text)),
|
||||
&Type::Text,
|
||||
&setting,
|
||||
&present,
|
||||
);
|
||||
let setting_guard =
|
||||
self.own_value(&Type::Nullable(Box::new(Type::Text)), &optional, false);
|
||||
let has_setting = self.temp();
|
||||
self.line(format!(
|
||||
"{has_setting} = extractvalue {{ i1, ptr }} {optional}, 0"
|
||||
));
|
||||
let selected = self.temp();
|
||||
self.line(format!("{selected} = load i64, ptr {index}"));
|
||||
let done = self.label("adapter_ready");
|
||||
let invalid = self.label("adapter_invalid");
|
||||
let labels = adapters
|
||||
.iter()
|
||||
.map(|_| self.label("adapter_selected"))
|
||||
.collect::<Vec<_>>();
|
||||
let arms = labels
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, label)| format!("i64 {index}, label %{label}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
self.line(format!("switch i64 {selected}, label %{invalid} [{arms}]"));
|
||||
self.start(&invalid);
|
||||
self.line("unreachable");
|
||||
for (adapter, label) in adapters.iter().zip(labels) {
|
||||
self.start(&label);
|
||||
let valid = self.label("setting_valid");
|
||||
let failed = self.label("setting_invalid");
|
||||
self.line(if adapter.setting.is_some() {
|
||||
format!("br i1 {has_setting}, label %{valid}, label %{failed}")
|
||||
} else {
|
||||
format!("br i1 {has_setting}, label %{failed}, label %{valid}")
|
||||
});
|
||||
self.start(&failed);
|
||||
self.application_fault(
|
||||
if adapter.setting.is_some() {
|
||||
"selected Adapter requires setting"
|
||||
} else {
|
||||
"selected Adapter does not declare setting"
|
||||
},
|
||||
span,
|
||||
);
|
||||
self.start(&valid);
|
||||
if let Some(id) = adapter.setting {
|
||||
let ty = Type::Named(id);
|
||||
let callback =
|
||||
self.module
|
||||
.wire_callback(&ty, crate::wire::Policy::SettingInput, false);
|
||||
let text = self.temp();
|
||||
self.line(format!("{text} = extractvalue {{ i1, ptr }} {optional}, 1"));
|
||||
let value = self.runtime_call(
|
||||
"dever_rt_v1_wire_decode",
|
||||
vec![
|
||||
format!("ptr {text}"),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(&ty)),
|
||||
format!("ptr @{callback}"),
|
||||
],
|
||||
&ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr @dever_setting_{}",
|
||||
self.module.ty(&ty),
|
||||
adapter.owner
|
||||
));
|
||||
self.line(format!(
|
||||
"store i1 true, ptr @dever_setting_live_{}",
|
||||
adapter.owner
|
||||
));
|
||||
}
|
||||
let external_setting = if adapter.setting.is_some() {
|
||||
let text = self.temp();
|
||||
self.line(format!("{text} = extractvalue {{ i1, ptr }} {optional}, 1"));
|
||||
text
|
||||
} else {
|
||||
"null".into()
|
||||
};
|
||||
self.start_external_adapter(adapter, &external_setting, span);
|
||||
for (port, implementation) in &adapter.operations {
|
||||
if app.ports.contains(port) {
|
||||
self.line(format!(
|
||||
"store i64 {implementation}, ptr @dever_port_{port}"
|
||||
));
|
||||
}
|
||||
}
|
||||
self.line(format!("br label %{done}"));
|
||||
}
|
||||
self.start(&done);
|
||||
self.release_guard(setting_guard);
|
||||
}
|
||||
self.release_guard(settings_guard);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
//! Process glue reuses callable roots, typed fault rendering and their owners.
|
||||
use super::*;
|
||||
|
||||
const DECLARATIONS: &str = "\
|
||||
declare i32 @dever_rt_v1_process_init()
|
||||
declare i32 @dever_rt_v1_process_stderr(ptr, i64)
|
||||
declare i32 @dever_rt_v1_process_error(ptr)
|
||||
";
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn emit_process_entry(&mut self, root: &Specialization) {
|
||||
let render = self.http_fault_message(root);
|
||||
self.declarations.push_str(DECLARATIONS);
|
||||
let calls = Calls {
|
||||
run: "@dever_application_entry(ptr %out, ptr %fault)".into(),
|
||||
render: format!("{render}(ptr %fault, ptr %message, ptr %error)"),
|
||||
release_outputs: "@dever_outputs_release(ptr %out)".into(),
|
||||
release_fault: "@dever_fault_release(ptr %fault)".into(),
|
||||
};
|
||||
emit_start(&mut self.functions);
|
||||
self.functions.push_str(
|
||||
" %fault = alloca %dever.fault, align 8\n store %dever.fault zeroinitializer, ptr %fault\n",
|
||||
);
|
||||
emit_body(&mut self.functions, &calls);
|
||||
emit_diagnostic_literals(&mut self.declarations);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn emit_test_main(suite: &mut String) {
|
||||
suite.push_str(DECLARATIONS);
|
||||
suite.push_str("declare i32 @dever_rt_v1_process_test_index(i32, ptr, i64, ptr)\n");
|
||||
emit_diagnostic_literals(suite);
|
||||
let invalid = literal(
|
||||
suite,
|
||||
"dever_process_invalid_index",
|
||||
"test program requires a valid case index\n",
|
||||
);
|
||||
emit_start(suite);
|
||||
writeln!(
|
||||
suite,
|
||||
" %index_slot = alloca i64, align 8
|
||||
%count = call i64 @dever_test_count()
|
||||
%parsed = call i32 @dever_rt_v1_process_test_index(i32 %argc, ptr %argv, i64 %count, ptr %index_slot)
|
||||
%valid = icmp eq i32 %parsed, 0
|
||||
br i1 %valid, label %selected, label %invalid
|
||||
invalid:
|
||||
%written = call i32 @dever_rt_v1_process_stderr({invalid})
|
||||
call void @dever_rt_v1_log_flush()
|
||||
ret i32 1
|
||||
selected:
|
||||
%index = load i64, ptr %index_slot
|
||||
%size = call i64 @dever_test_fault_size(i64 %index)
|
||||
%fault = alloca i8, i64 %size, align 8"
|
||||
)
|
||||
.unwrap();
|
||||
// The checked case entry initializes its complete concrete fault layout.
|
||||
// Size, render and release must all use this same validated stable index.
|
||||
let calls = Calls {
|
||||
run: "@dever_test_entry(i64 %index, ptr %out, ptr %fault)".into(),
|
||||
render: "@dever_test_fault_render(i64 %index, ptr %fault, ptr %message, ptr %error)".into(),
|
||||
release_outputs: "@dever_test_outputs_release(i64 %index, ptr %out)".into(),
|
||||
release_fault: "@dever_test_fault_release(i64 %index, ptr %fault)".into(),
|
||||
};
|
||||
emit_body(suite, &calls);
|
||||
}
|
||||
|
||||
struct Calls {
|
||||
run: String,
|
||||
render: String,
|
||||
release_outputs: String,
|
||||
release_fault: String,
|
||||
}
|
||||
|
||||
fn emit_start(output: &mut String) {
|
||||
writeln!(
|
||||
output,
|
||||
"define i32 @main(i32 %argc, ptr %argv) {{
|
||||
entry:
|
||||
%initialized = call i32 @dever_rt_v1_process_init()
|
||||
%init_ok = icmp eq i32 %initialized, 0
|
||||
br i1 %init_ok, label %ready, label %init_failed
|
||||
init_failed:
|
||||
%init_written = call i32 @dever_rt_v1_process_stderr(ptr @dever_process_init_failed, i64 {})
|
||||
ret i32 1
|
||||
ready:",
|
||||
INIT_FAILED.len()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Both roots return Unit; diagnostics are the only additional process owners.
|
||||
/// Every exit after invocation releases the original fault even if rendering
|
||||
/// fails, so a secondary diagnostic error cannot leak its business payload.
|
||||
fn emit_body(output: &mut String, calls: &Calls) {
|
||||
writeln!(
|
||||
output,
|
||||
" %out = alloca i8
|
||||
store i8 0, ptr %out
|
||||
%message = alloca ptr
|
||||
store ptr null, ptr %message
|
||||
%error = alloca {{ ptr, i64 }}
|
||||
store {{ ptr, i64 }} zeroinitializer, ptr %error
|
||||
%status = call i32 {}
|
||||
%failed = icmp ne i32 %status, 0
|
||||
br i1 %failed, label %report, label %cleanup
|
||||
report:
|
||||
%rendered = call i32 {}
|
||||
%render_ok = icmp eq i32 %rendered, 0
|
||||
br i1 %render_ok, label %log, label %render_failed
|
||||
log:
|
||||
%text = load ptr, ptr %message
|
||||
%logged = call i32 @dever_rt_v1_process_error(ptr %text)
|
||||
%log_ok = icmp eq i32 %logged, 0
|
||||
br i1 %log_ok, label %cleanup, label %log_failed
|
||||
log_failed:
|
||||
%log_fallback = call i32 @dever_rt_v1_process_stderr(ptr @dever_process_log_failed, i64 {})
|
||||
br label %cleanup
|
||||
render_failed:
|
||||
%render_fallback = call i32 @dever_rt_v1_process_stderr(ptr @dever_process_render_failed, i64 {})
|
||||
%error_value = load {{ ptr, i64 }}, ptr %error
|
||||
%error_bytes = extractvalue {{ ptr, i64 }} %error_value, 0
|
||||
%error_length = extractvalue {{ ptr, i64 }} %error_value, 1
|
||||
%error_written = call i32 @dever_rt_v1_process_stderr(ptr %error_bytes, i64 %error_length)
|
||||
%newline_written = call i32 @dever_rt_v1_process_stderr(ptr @dever_process_newline, i64 1)
|
||||
br label %cleanup
|
||||
cleanup:
|
||||
%owned_message = load ptr, ptr %message
|
||||
call void @dever_rt_v1_text_release(ptr %owned_message)
|
||||
%owned_error = load {{ ptr, i64 }}, ptr %error
|
||||
%owned_bytes = extractvalue {{ ptr, i64 }} %owned_error, 0
|
||||
%owned_length = extractvalue {{ ptr, i64 }} %owned_error, 1
|
||||
call void @dever_rt_v1_buffer_free(ptr %owned_bytes, i64 %owned_length)
|
||||
call void {}
|
||||
call void {}
|
||||
call void @dever_rt_v1_log_flush()
|
||||
%exit = zext i1 %failed to i32
|
||||
ret i32 %exit
|
||||
}}",
|
||||
calls.run,
|
||||
calls.render,
|
||||
LOG_FAILED.len(),
|
||||
RENDER_FAILED.len(),
|
||||
calls.release_outputs,
|
||||
calls.release_fault,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
const LOG_FAILED: &str = "cannot log application failure\n";
|
||||
const INIT_FAILED: &str = "cannot initialize application process\n";
|
||||
const RENDER_FAILED: &str = "cannot render application failure: ";
|
||||
|
||||
fn emit_diagnostic_literals(output: &mut String) {
|
||||
for (name, value) in [
|
||||
("dever_process_init_failed", INIT_FAILED),
|
||||
("dever_process_log_failed", LOG_FAILED),
|
||||
("dever_process_render_failed", RENDER_FAILED),
|
||||
("dever_process_newline", "\n"),
|
||||
] {
|
||||
literal(output, name, value);
|
||||
}
|
||||
}
|
||||
|
||||
fn literal(output: &mut String, name: &str, value: &str) -> String {
|
||||
let bytes = value
|
||||
.bytes()
|
||||
.map(|byte| format!("\\{byte:02X}"))
|
||||
.collect::<String>();
|
||||
writeln!(
|
||||
output,
|
||||
"@{name} = private constant [{} x i8] c\"{bytes}\"",
|
||||
value.len()
|
||||
)
|
||||
.unwrap();
|
||||
format!("ptr @{name}, i64 {}", value.len())
|
||||
}
|
||||
@@ -0,0 +1,644 @@
|
||||
//! Typed display only at terminal request diagnostics; faults stay owned and typed.
|
||||
use super::*;
|
||||
|
||||
impl Module<'_> {
|
||||
fn related_renderer(&mut self, inner: &Type, instance: &Specialization, span: Span) -> String {
|
||||
let index = self.type_index(inner);
|
||||
let name = format!("dever_render_related_{index}");
|
||||
if !self.related_renderers.insert(index) {
|
||||
return name;
|
||||
}
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.start("entry");
|
||||
emitter.display_literal("Related.Loaded(", span);
|
||||
let owner = emitter.guards.len() - 1;
|
||||
let output = emitter.guards[owner].pointer.clone();
|
||||
let value = emitter.runtime_call(
|
||||
"dever_rt_v1_db_related_get",
|
||||
vec!["ptr %input".into()],
|
||||
inner,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
emitter.own_value(inner, &value, false);
|
||||
emitter.display_value(&output, inner, &value, span);
|
||||
emitter.display_append_literal(&output, ")", span);
|
||||
let result = emitter.temp();
|
||||
emitter.line(format!("{result} = load ptr, ptr {output}"));
|
||||
emitter.line(format!("store ptr {result}, ptr %out"));
|
||||
emitter.disarm(owner);
|
||||
let body = emitter.finish_protocol_callback(&name, "ptr %input, ptr %out, ptr %error");
|
||||
self.declarations.push_str(&body);
|
||||
name
|
||||
}
|
||||
pub(super) fn emit_location_renderer(&mut self) {
|
||||
let mut entries = Vec::new();
|
||||
for span in self.locations.clone() {
|
||||
let source = self.sources.get(span.source);
|
||||
let (line, column) = source.position(span.start);
|
||||
let (literal, length) =
|
||||
self.text_literal(&format!("{}:{line}:{column}: ", source.path().display()));
|
||||
entries.push(format!("{{ ptr, i64 }} {{ ptr {literal}, i64 {length} }}"));
|
||||
}
|
||||
let length = entries.len();
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"@dever_location_strings = private constant [{length} x {{ ptr, i64 }}] [{}]",
|
||||
entries.join(", ")
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(self.declarations, "define internal i32 @dever_location_text(i32 %index, ptr %out, ptr %error) {{\nentry:\n %slot = getelementptr [{length} x {{ ptr, i64 }}], ptr @dever_location_strings, i32 0, i32 %index\n %value = load {{ ptr, i64 }}, ptr %slot\n %bytes = extractvalue {{ ptr, i64 }} %value, 0\n %length = extractvalue {{ ptr, i64 }} %value, 1\n %status = call i32 @dever_rt_v1_text_new(ptr %bytes, i64 %length, ptr %out, ptr %error)\n ret i32 %status\n}}").unwrap();
|
||||
}
|
||||
|
||||
pub(super) fn http_fault_message(&mut self, instance: &Specialization) -> String {
|
||||
let name = format!("dever_http_fault_{}", self.names[instance]);
|
||||
let span = self.program.functions[instance.function].span;
|
||||
let failures = specialize::failures(self.program, instance);
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.start("entry");
|
||||
emitter.display_literal("", span);
|
||||
let guard = emitter.guards.len() - 1;
|
||||
let output = emitter.guards[guard].pointer.clone();
|
||||
let depth_ptr = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{depth_ptr} = getelementptr %dever.fault, ptr %input, i32 0, i32 2"
|
||||
));
|
||||
let depth = emitter.temp();
|
||||
emitter.line(format!("{depth} = load i32, ptr {depth_ptr}"));
|
||||
let index = emitter.entry_slot_ir("i32");
|
||||
emitter.line(format!("store i32 {depth}, ptr {index}"));
|
||||
let next = emitter.label("trace_next");
|
||||
let frame = emitter.label("trace_frame");
|
||||
let origin = emitter.label("trace_origin");
|
||||
emitter.line(format!("br label %{next}"));
|
||||
emitter.start(&next);
|
||||
let position = emitter.temp();
|
||||
emitter.line(format!("{position} = load i32, ptr {index}"));
|
||||
let has_frame = emitter.temp();
|
||||
emitter.line(format!("{has_frame} = icmp ne i32 {position}, 0"));
|
||||
emitter.line(format!(
|
||||
"br i1 {has_frame}, label %{frame}, label %{origin}"
|
||||
));
|
||||
emitter.start(&frame);
|
||||
let previous = emitter.temp();
|
||||
emitter.line(format!("{previous} = sub i32 {position}, 1"));
|
||||
emitter.line(format!("store i32 {previous}, ptr {index}"));
|
||||
let pointer = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{pointer} = getelementptr %dever.fault, ptr %input, i32 0, i32 3, i32 {previous}"
|
||||
));
|
||||
emitter.display_location(&output, &pointer, span);
|
||||
emitter.line(format!("br label %{next}"));
|
||||
emitter.start(&origin);
|
||||
let pointer = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{pointer} = getelementptr %dever.fault, ptr %input, i32 0, i32 1"
|
||||
));
|
||||
emitter.display_location(&output, &pointer, span);
|
||||
let kind_ptr = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{kind_ptr} = getelementptr %dever.fault, ptr %input, i32 0, i32 0"
|
||||
));
|
||||
let kind = emitter.temp();
|
||||
emitter.line(format!("{kind} = load i32, ptr {kind_ptr}"));
|
||||
let done = emitter.label("message_ready");
|
||||
emitter.line("switch i32 ".to_owned() + &kind + ", label %invalid [i32 1, label %overflow i32 2, label %division i32 3, label %runtime i32 4, label %business]");
|
||||
emitter.start("invalid");
|
||||
emitter.line("unreachable");
|
||||
for (label, text) in [
|
||||
("overflow", "Int overflow"),
|
||||
("division", "Int division by zero"),
|
||||
] {
|
||||
emitter.start(label);
|
||||
emitter.display_append_literal(&output, text, span);
|
||||
emitter.line(format!("br label %{done}"));
|
||||
}
|
||||
emitter.start("runtime");
|
||||
let message_ptr = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{message_ptr} = getelementptr %dever.fault, ptr %input, i32 0, i32 4"
|
||||
));
|
||||
let message = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{message} = load {{ ptr, i64 }}, ptr {message_ptr}"
|
||||
));
|
||||
let bytes = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{bytes} = extractvalue {{ ptr, i64 }} {message}, 0"
|
||||
));
|
||||
let length = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{length} = extractvalue {{ ptr, i64 }} {message}, 1"
|
||||
));
|
||||
let first = emitter.guards.len();
|
||||
let text = emitter.runtime_call(
|
||||
"dever_rt_v1_text_new",
|
||||
vec![format!("ptr {bytes}"), format!("i64 {length}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
emitter.own_value(&Type::Text, &text, false);
|
||||
emitter.display_append(&output, &text, span);
|
||||
emitter.release_from(first);
|
||||
emitter.line(format!("br label %{done}"));
|
||||
emitter.start("business");
|
||||
let type_ptr = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{type_ptr} = getelementptr %dever.fault, ptr %input, i32 0, i32 5"
|
||||
));
|
||||
let type_id = emitter.temp();
|
||||
emitter.line(format!("{type_id} = load i32, ptr {type_ptr}"));
|
||||
let types = failures
|
||||
.iter()
|
||||
.map(|failure| failure.ty)
|
||||
.collect::<BTreeSet<_>>();
|
||||
emitter.line(format!(
|
||||
"switch i32 {type_id}, label %invalid [{}]",
|
||||
types
|
||||
.iter()
|
||||
.map(|id| format!("i32 {}, label %business{id}", id + 1))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
));
|
||||
for id in types {
|
||||
emitter.start(&format!("business{id}"));
|
||||
let payload_ptr = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{payload_ptr} = getelementptr %dever.fault, ptr %input, i32 0, i32 7"
|
||||
));
|
||||
let payload = emitter.temp();
|
||||
emitter.line(format!("{payload} = load %T{id}, ptr {payload_ptr}"));
|
||||
let variants = failures
|
||||
.iter()
|
||||
.filter(|failure| failure.ty == id)
|
||||
.map(|failure| failure.variant)
|
||||
.collect::<BTreeSet<_>>();
|
||||
emitter.display_named(&output, id, &payload, span, Some(&variants));
|
||||
emitter.line(format!("br label %{done}"));
|
||||
}
|
||||
emitter.start(&done);
|
||||
let cause_ptr = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{cause_ptr} = getelementptr %dever.fault, ptr %input, i32 0, i32 8"
|
||||
));
|
||||
let cause = emitter.temp();
|
||||
emitter.line(format!("{cause} = load ptr, ptr {cause_ptr}"));
|
||||
let present = emitter.temp();
|
||||
emitter.line(format!("{present} = icmp ne ptr {cause}, null"));
|
||||
let append = emitter.label("message_cause");
|
||||
let complete = emitter.label("message_complete");
|
||||
emitter.line(format!(
|
||||
"br i1 {present}, label %{append}, label %{complete}"
|
||||
));
|
||||
emitter.start(&append);
|
||||
emitter.display_append_literal(&output, "; ", span);
|
||||
emitter.display_append(&output, &cause, span);
|
||||
emitter.line(format!("br label %{complete}"));
|
||||
emitter.start(&complete);
|
||||
let text = emitter.temp();
|
||||
emitter.line(format!("{text} = load ptr, ptr {output}"));
|
||||
emitter.line(format!("store ptr {text}, ptr %out"));
|
||||
emitter.disarm(guard);
|
||||
let callback = emitter.finish_protocol_callback(&name, "ptr %input, ptr %out, ptr %error");
|
||||
self.declarations.push_str(&callback);
|
||||
format!("@{name}")
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn display_literal(&mut self, text: &str, span: Span) -> String {
|
||||
let (bytes, length) = self.module.text_literal(text);
|
||||
let value = self.runtime_call(
|
||||
"dever_rt_v1_text_new",
|
||||
vec![format!("ptr {bytes}"), format!("i64 {length}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &value, false);
|
||||
value
|
||||
}
|
||||
|
||||
fn display_append(&mut self, output: &str, text: &str, span: Span) {
|
||||
let previous = self.temp();
|
||||
self.line(format!("{previous} = load ptr, ptr {output}"));
|
||||
let joined = self.runtime_call(
|
||||
"dever_rt_v1_text_concat",
|
||||
vec![format!("ptr {previous}"), format!("ptr {text}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.line(format!(
|
||||
"call void @dever_rt_v1_text_release(ptr {previous})"
|
||||
));
|
||||
self.line(format!("store ptr {joined}, ptr {output}"));
|
||||
}
|
||||
|
||||
pub(super) fn display_append_literal(&mut self, output: &str, text: &str, span: Span) {
|
||||
let first = self.guards.len();
|
||||
let text = self.display_literal(text, span);
|
||||
self.display_append(output, &text, span);
|
||||
self.release_from(first);
|
||||
}
|
||||
|
||||
fn display_location(&mut self, output: &str, pointer: &str, span: Span) {
|
||||
let first = self.guards.len();
|
||||
let location = self.temp();
|
||||
self.line(format!("{location} = load i32, ptr {pointer}"));
|
||||
let text = self.runtime_call(
|
||||
"dever_location_text",
|
||||
vec![format!("i32 {location}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &text, false);
|
||||
self.display_append(output, &text, span);
|
||||
self.release_from(first);
|
||||
}
|
||||
|
||||
pub(super) fn display_value(&mut self, output: &str, ty: &Type, value: &str, span: Span) {
|
||||
assert!(
|
||||
ty.observable(&self.module.program.types),
|
||||
"checked diagnostic payload must be observable"
|
||||
);
|
||||
let first = self.guards.len();
|
||||
match ty {
|
||||
Type::Text | Type::Json | Type::Id => self.display_append(output, value, span),
|
||||
Type::Int
|
||||
| Type::DateTime
|
||||
| Type::Date
|
||||
| Type::Time
|
||||
| Type::Duration
|
||||
| Type::Float
|
||||
| Type::Decimal
|
||||
| Type::Uuid => {
|
||||
let text = match ty {
|
||||
Type::Decimal => {
|
||||
let parts = self.decimal_parts(value);
|
||||
self.render_text("dever_rt_v1_decimal_to_text", parts, span)
|
||||
}
|
||||
Type::Float => self.render_text(
|
||||
"dever_rt_v1_float_to_text",
|
||||
vec![format!("double {value}")],
|
||||
span,
|
||||
),
|
||||
Type::Uuid => self.runtime_call(
|
||||
"dever_rt_v1_uuid_to_text",
|
||||
vec![format!("ptr {value}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
),
|
||||
_ => self.render_text(
|
||||
"dever_rt_v1_int_to_text",
|
||||
vec![format!("i64 {value}")],
|
||||
span,
|
||||
),
|
||||
};
|
||||
self.own_value(&Type::Text, &text, false);
|
||||
self.display_append(output, &text, span);
|
||||
}
|
||||
Type::Bool => {
|
||||
let yes = self.label("display_true");
|
||||
let no = self.label("display_false");
|
||||
let done = self.label("display_bool");
|
||||
self.line(format!("br i1 {value}, label %{yes}, label %{no}"));
|
||||
self.start(&yes);
|
||||
self.display_append_literal(output, "true", span);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&no);
|
||||
self.display_append_literal(output, "false", span);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
}
|
||||
Type::Nullable(inner) => {
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let some = self.label("display_some");
|
||||
let null = self.label("display_null");
|
||||
let done = self.label("display_optional");
|
||||
self.line(format!("br i1 {present}, label %{some}, label %{null}"));
|
||||
self.start(&null);
|
||||
self.display_append_literal(output, "null", span);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&some);
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = extractvalue {} {value}, 1",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
self.display_value(output, inner, &payload, span);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
}
|
||||
Type::Named(id) if self.module.program.types[*id].kind == DefinitionKind::ModelId => {
|
||||
self.display_value(output, &Type::Int, value, span);
|
||||
}
|
||||
Type::Named(id) => self.display_named(output, *id, value, span, None),
|
||||
Type::List(_) | Type::Bytes | Type::Map(_, _) => {
|
||||
self.display_collection(output, ty, value, span)
|
||||
}
|
||||
Type::MapEntry(key, mapped) => {
|
||||
self.display_append_literal(output, "{key = ", span);
|
||||
for (index, field) in [key.as_ref(), mapped.as_ref()].iter().enumerate() {
|
||||
if index == 1 {
|
||||
self.display_append_literal(output, ", value = ", span);
|
||||
}
|
||||
let part = self.temp();
|
||||
self.line(format!(
|
||||
"{part} = extractvalue {} {value}, {index}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
self.display_value(output, field, &part, span);
|
||||
}
|
||||
self.display_append_literal(output, "}", span);
|
||||
}
|
||||
Type::File | Type::Socket | Type::Listener => {
|
||||
self.display_append_literal(output, "<resource>", span)
|
||||
}
|
||||
Type::Stream(_) => self.display_append_literal(output, "<stream>", span),
|
||||
Type::AsyncStream(_) | Type::RowStream(_) => {
|
||||
self.display_append_literal(output, "<async-stream>", span)
|
||||
}
|
||||
Type::Related(inner) => {
|
||||
let loaded = self.temp();
|
||||
self.line(format!("{loaded} = icmp ne ptr {value}, null"));
|
||||
let some = self.label("display_related_loaded");
|
||||
let none = self.label("display_related_unloaded");
|
||||
let done = self.label("display_related_done");
|
||||
self.line(format!("br i1 {loaded}, label %{some}, label %{none}"));
|
||||
self.start(&none);
|
||||
self.display_append_literal(output, "Related.Unloaded", span);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&some);
|
||||
let renderer = self.module.related_renderer(inner, self.instance, span);
|
||||
let text = self.runtime_call(
|
||||
&renderer,
|
||||
vec![format!("ptr {value}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &text, false);
|
||||
self.display_append(output, &text, span);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
}
|
||||
Type::HttpReply => self.display_append_literal(output, "HttpReply(<resource>)", span),
|
||||
Type::WebSocket => self.display_append_literal(output, "WebSocket(<resource>)", span),
|
||||
Type::ClientTls => self.display_append_literal(output, "ClientTls(<resource>)", span),
|
||||
Type::ServerTls => self.display_append_literal(output, "ServerTls(<resource>)", span),
|
||||
Type::HttpClient => self.display_append_literal(output, "HttpClient(<resource>)", span),
|
||||
Type::Channel(_) => self.display_append_literal(output, "Channel(<resource>)", span),
|
||||
_ => unreachable!("checked observable error payload"),
|
||||
}
|
||||
self.release_from(first);
|
||||
}
|
||||
|
||||
fn display_named(
|
||||
&mut self,
|
||||
output: &str,
|
||||
id: usize,
|
||||
value: &str,
|
||||
span: Span,
|
||||
selected: Option<&BTreeSet<usize>>,
|
||||
) {
|
||||
let definition = self.module.program.types[id].clone();
|
||||
match definition.shape {
|
||||
Shape::Record(fields) => {
|
||||
self.display_append_literal(output, &format!("{} {{ ", definition.name), span);
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
self.display_append_literal(
|
||||
output,
|
||||
&format!("{}{} = ", if index == 0 { "" } else { ", " }, field.name),
|
||||
span,
|
||||
);
|
||||
let part = self.temp();
|
||||
self.line(format!("{part} = extractvalue %T{id} {value}, {index}"));
|
||||
self.display_value(output, &field.ty, &part, span);
|
||||
}
|
||||
self.display_append_literal(output, " }", span);
|
||||
}
|
||||
Shape::Choice(variants) => {
|
||||
let row = self.row_pointer(&Type::Named(id), value);
|
||||
let tag = self.temp();
|
||||
self.line(format!("{tag} = extractvalue %T{id} {value}, 0"));
|
||||
let labels = variants
|
||||
.iter()
|
||||
.map(|_| self.label("display_variant"))
|
||||
.collect::<Vec<_>>();
|
||||
let invalid = self.label("display_invalid");
|
||||
let done = self.label("display_choice");
|
||||
self.line(format!(
|
||||
"switch i32 {tag}, label %{invalid} [{}]",
|
||||
labels
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(
|
||||
|(index, _)| selected.is_none_or(|selected| selected.contains(index))
|
||||
)
|
||||
.map(|(index, label)| format!("i32 {index}, label %{label}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
));
|
||||
self.start(&invalid);
|
||||
self.line("unreachable");
|
||||
for (index, variant) in variants.iter().enumerate() {
|
||||
if selected.is_some_and(|selected| !selected.contains(&index)) {
|
||||
continue;
|
||||
}
|
||||
self.start(&labels[index]);
|
||||
self.display_append_literal(
|
||||
output,
|
||||
&format!("{}.{}", definition.name, variant.name),
|
||||
span,
|
||||
);
|
||||
if !variant.fields.is_empty() {
|
||||
self.display_append_literal(output, "(", span);
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = getelementptr %T{id}, ptr {row}, i32 0, i32 1"
|
||||
));
|
||||
for (field_index, field) in variant.fields.iter().enumerate() {
|
||||
if field_index != 0 {
|
||||
self.display_append_literal(output, ", ", span);
|
||||
}
|
||||
let pointer = self.temp();
|
||||
self.line(format!("{pointer} = getelementptr %T{id}V{index}, ptr {payload}, i32 0, i32 {field_index}"));
|
||||
let part = self.temp();
|
||||
self.line(format!(
|
||||
"{part} = load {}, ptr {pointer}",
|
||||
self.module.ty(&field.ty)
|
||||
));
|
||||
self.display_value(output, &field.ty, &part, span);
|
||||
}
|
||||
self.display_append_literal(output, ")", span);
|
||||
}
|
||||
self.line(format!("br label %{done}"));
|
||||
}
|
||||
self.start(&done);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn display_collection(&mut self, output: &str, ty: &Type, value: &str, span: Span) {
|
||||
let map = matches!(ty, Type::Map(_, _));
|
||||
let bytes = *ty == Type::Bytes;
|
||||
self.display_append_literal(output, if map { "{" } else { "[" }, span);
|
||||
let types = match ty {
|
||||
Type::Bytes => vec![Type::Int],
|
||||
Type::List(element) => vec![element.as_ref().clone()],
|
||||
Type::Map(key, value) => vec![key.as_ref().clone(), value.as_ref().clone()],
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let kind = if map { "map" } else { "list" };
|
||||
let cursor = if bytes {
|
||||
None
|
||||
} else {
|
||||
let retained = self.temp();
|
||||
self.line(format!(
|
||||
"{retained} = call ptr @dever_rt_v1_{kind}_retain(ptr {value})"
|
||||
));
|
||||
let cursor = self.runtime_call(
|
||||
&format!("dever_rt_v1_{kind}_cursor_take"),
|
||||
vec![format!("ptr {retained}")],
|
||||
&Type::Unit,
|
||||
false,
|
||||
Some("ptr"),
|
||||
span,
|
||||
);
|
||||
let pointer = self.entry_slot_ir("ptr");
|
||||
self.line(format!("store ptr {cursor}, ptr {pointer}"));
|
||||
let guard = self.register_owned(
|
||||
if map {
|
||||
OwnedKind::MapCursor
|
||||
} else {
|
||||
OwnedKind::ListCursor
|
||||
},
|
||||
pointer,
|
||||
);
|
||||
self.mark_live(guard);
|
||||
Some((cursor, guard))
|
||||
};
|
||||
let length = bytes.then(|| {
|
||||
self.runtime_call(
|
||||
"dever_rt_v1_bytes_length",
|
||||
vec![format!("ptr {value}")],
|
||||
&Type::Int,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
)
|
||||
});
|
||||
let index = self.entry_slot_ir("i64");
|
||||
self.line(format!("store i64 0, ptr {index}"));
|
||||
let rows = types
|
||||
.iter()
|
||||
.map(|ty| {
|
||||
let row = self.entry_slot(ty);
|
||||
let guard = contains_owned(self.module.program, ty)
|
||||
.then(|| self.register_guard(ty, row.clone()));
|
||||
(row, guard)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let present = self.entry_slot_ir("i8");
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let next = self.label("display_next");
|
||||
let body = self.label("display_element");
|
||||
let separator = self.label("display_separator");
|
||||
let content = self.label("display_content");
|
||||
let done = self.label("display_collection_done");
|
||||
self.line(format!("br label %{next}"));
|
||||
self.start(&next);
|
||||
let position = self.temp();
|
||||
self.line(format!("{position} = load i64, ptr {index}"));
|
||||
let exists = self.temp();
|
||||
if let Some(length) = length {
|
||||
self.line(format!("{exists} = icmp slt i64 {position}, {length}"));
|
||||
} else {
|
||||
let mut args = vec![format!("ptr {}", cursor.as_ref().unwrap().0)];
|
||||
args.extend(rows.iter().map(|(row, _)| format!("ptr {row}")));
|
||||
args.extend([format!("ptr {present}"), format!("ptr {error}")]);
|
||||
let status = self.temp();
|
||||
self.line(format!(
|
||||
"{status} = call i32 @dever_rt_v1_{kind}_cursor_next({})",
|
||||
args.join(", ")
|
||||
));
|
||||
self.check_runtime_status(&status, &error, span);
|
||||
let flag = self.temp();
|
||||
self.line(format!("{flag} = load i8, ptr {present}"));
|
||||
self.line(format!("{exists} = icmp ne i8 {flag}, 0"));
|
||||
}
|
||||
self.line(format!("br i1 {exists}, label %{body}, label %{done}"));
|
||||
self.start(&body);
|
||||
for (_, guard) in &rows {
|
||||
if let Some(guard) = guard {
|
||||
self.mark_live(*guard);
|
||||
}
|
||||
}
|
||||
let first = self.temp();
|
||||
self.line(format!("{first} = icmp eq i64 {position}, 0"));
|
||||
self.line(format!(
|
||||
"br i1 {first}, label %{content}, label %{separator}"
|
||||
));
|
||||
self.start(&separator);
|
||||
self.display_append_literal(output, ", ", span);
|
||||
self.line(format!("br label %{content}"));
|
||||
self.start(&content);
|
||||
for (column, (ty, (row, _))) in types.iter().zip(&rows).enumerate() {
|
||||
if column != 0 {
|
||||
self.display_append_literal(output, " = ", span);
|
||||
}
|
||||
let element = if bytes {
|
||||
let nullable = self.runtime_call(
|
||||
"dever_rt_v1_bytes_at",
|
||||
vec![format!("ptr {value}"), format!("i64 {position}")],
|
||||
&Type::Nullable(Box::new(Type::Int)),
|
||||
true,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let byte = self.temp();
|
||||
self.line(format!("{byte} = extractvalue {{ i1, i64 }} {nullable}, 1"));
|
||||
byte
|
||||
} else {
|
||||
let element = self.temp();
|
||||
self.line(format!(
|
||||
"{element} = load {}, ptr {row}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
element
|
||||
};
|
||||
self.display_value(output, ty, &element, span);
|
||||
}
|
||||
for (_, guard) in &rows {
|
||||
if let Some(guard) = guard {
|
||||
self.release_guard(*guard);
|
||||
}
|
||||
}
|
||||
let following = self.temp();
|
||||
self.line(format!("{following} = add i64 {position}, 1"));
|
||||
self.line(format!("store i64 {following}, ptr {index}"));
|
||||
self.line(format!("br label %{next}"));
|
||||
self.start(&done);
|
||||
if let Some((_, guard)) = cursor {
|
||||
self.release_guard(guard);
|
||||
}
|
||||
self.display_append_literal(output, if map { "}" } else { "]" }, span);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
//! Resource choices share the existing recoverable Bytes failure channel.
|
||||
//! File I/O and shared Stream cursor ownership remain in the runtime.
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(super) struct ResourceRow {
|
||||
pub status: String,
|
||||
pub output: String,
|
||||
pub present: Option<String>,
|
||||
pub error: String,
|
||||
pub protocol_output: bool,
|
||||
}
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn read_event(&mut self, id: usize) -> String {
|
||||
let name = format!("@dever_read_event_{id}");
|
||||
if !self.read_events.insert(id) {
|
||||
return name;
|
||||
}
|
||||
let Shape::Choice(variants) = &self.program.types[id].shape else {
|
||||
unreachable!("checked read event choice")
|
||||
};
|
||||
let connected = variants.iter().any(|variant| variant.name == "Connected");
|
||||
let definitions = [
|
||||
if connected {
|
||||
("Connected", Type::Socket, "socket")
|
||||
} else {
|
||||
("Chunk", Type::Bytes, "bytes")
|
||||
},
|
||||
("Failed", Type::Text, "text"),
|
||||
]
|
||||
.map(|(label, ty, kind)| {
|
||||
let variant = variants
|
||||
.iter()
|
||||
.position(|value| value.name == label)
|
||||
.expect("checked read event variant");
|
||||
(label, ty, kind, variant)
|
||||
});
|
||||
for (label, ty, kind, variant) in definitions {
|
||||
let instance = self
|
||||
.instances
|
||||
.iter()
|
||||
.next()
|
||||
.expect("reachable entry")
|
||||
.clone();
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.start("entry");
|
||||
let retained = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{retained} = call ptr @dever_rt_v1_{kind}_retain(ptr %handle)"
|
||||
));
|
||||
emitter.write_variant(id, variant, &[(ty, retained)], "%out");
|
||||
emitter.line("ret void");
|
||||
let callback = format!(
|
||||
"define internal void @dever_read_{label}_{id}(ptr %handle, ptr %out) {{\nentry:\n{} }}\n",
|
||||
emitter.body
|
||||
);
|
||||
self.declarations.push_str(&callback);
|
||||
}
|
||||
let success = if connected { "Connected" } else { "Chunk" };
|
||||
writeln!(self.declarations,
|
||||
"{name} = private constant {{ ptr, ptr, ptr }} {{ ptr @dever_type_{}, ptr @dever_read_{success}_{id}, ptr @dever_read_Failed_{id} }}",
|
||||
self.type_index(&Type::Named(id))).unwrap();
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn file_result(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
mut arguments: Vec<String>,
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
use Intrinsic::*;
|
||||
let (symbol, success, end) = match operation {
|
||||
FileOpen => ("file_open", "Opened", None),
|
||||
FileCreate => ("file_create", "Opened", None),
|
||||
FileRead => ("file_read", "Read", Some("End")),
|
||||
FileWrite => ("file_write", "Done", None),
|
||||
FileClose => ("file_close", "Closed", None),
|
||||
FileChunks => {
|
||||
let Type::Named(id) = result.ty else {
|
||||
unreachable!("checked chunks choice")
|
||||
};
|
||||
let Shape::Choice(variants) = &self.module.program.types[id].shape else {
|
||||
unreachable!("checked chunks choice")
|
||||
};
|
||||
let variant = variants
|
||||
.iter()
|
||||
.find(|value| value.name == "Streaming")
|
||||
.expect("checked Streaming variant");
|
||||
let Type::Stream(element) = &variant.fields[0].ty else {
|
||||
unreachable!("checked chunks stream")
|
||||
};
|
||||
let Type::Named(event) = element.as_ref() else {
|
||||
unreachable!("checked read event identity")
|
||||
};
|
||||
let descriptor = self.module.read_event(*event);
|
||||
arguments.push(format!("ptr {descriptor}"));
|
||||
("file_chunks", "Streaming", None)
|
||||
}
|
||||
_ => unreachable!("checked File intrinsic"),
|
||||
};
|
||||
self.recoverable_call(
|
||||
&format!("dever_rt_v1_{symbol}"),
|
||||
arguments,
|
||||
result,
|
||||
success,
|
||||
end,
|
||||
)
|
||||
}
|
||||
|
||||
/// Only runtime status 1 is recoverable. Invalid ABI input never becomes a
|
||||
/// user business error; an absent row never reads the uninitialized out slot.
|
||||
pub(super) fn recoverable_call(
|
||||
&mut self,
|
||||
symbol: &str,
|
||||
mut arguments: Vec<String>,
|
||||
result: &Expression,
|
||||
success_name: &str,
|
||||
end_name: Option<&str>,
|
||||
) -> String {
|
||||
let Type::Named(id) = result.ty else {
|
||||
unreachable!("checked resource choice")
|
||||
};
|
||||
let Shape::Choice(variants) = &self.module.program.types[id].shape else {
|
||||
unreachable!("checked resource choice")
|
||||
};
|
||||
let success = variants
|
||||
.iter()
|
||||
.position(|value| value.name == success_name)
|
||||
.expect("checked resource success");
|
||||
let value_type = variants[success]
|
||||
.fields
|
||||
.first()
|
||||
.map(|field| field.ty.clone());
|
||||
let output = match &value_type {
|
||||
Some(ty) => self.entry_slot(ty),
|
||||
None => self.entry_slot_ir("i8"),
|
||||
};
|
||||
arguments.push(format!("ptr {output}"));
|
||||
let present = end_name.map(|_| {
|
||||
let slot = self.entry_slot_ir("i8");
|
||||
arguments.push(format!("ptr {slot}"));
|
||||
slot
|
||||
});
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
arguments.push(format!("ptr {error}"));
|
||||
let status = self.temp();
|
||||
self.line(format!(
|
||||
"{status} = call i32 @{symbol}({})",
|
||||
arguments.join(", ")
|
||||
));
|
||||
self.resource_row(
|
||||
result,
|
||||
success_name,
|
||||
end_name,
|
||||
ResourceRow {
|
||||
status,
|
||||
output,
|
||||
present,
|
||||
error,
|
||||
protocol_output: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn resource_row(
|
||||
&mut self,
|
||||
result: &Expression,
|
||||
success_name: &str,
|
||||
end_name: Option<&str>,
|
||||
row: ResourceRow,
|
||||
) -> String {
|
||||
let ResourceRow {
|
||||
status,
|
||||
output,
|
||||
present,
|
||||
error,
|
||||
protocol_output,
|
||||
} = row;
|
||||
let Type::Named(id) = result.ty else {
|
||||
unreachable!("checked resource choice")
|
||||
};
|
||||
let Shape::Choice(variants) = &self.module.program.types[id].shape else {
|
||||
unreachable!()
|
||||
};
|
||||
let success = variants
|
||||
.iter()
|
||||
.position(|variant| variant.name == success_name)
|
||||
.expect("checked resource success");
|
||||
let failed = variants
|
||||
.iter()
|
||||
.position(|variant| variant.name == "Failed")
|
||||
.expect("checked resource failure");
|
||||
let end = end_name.map(|name| {
|
||||
variants
|
||||
.iter()
|
||||
.position(|variant| variant.name == name)
|
||||
.expect("checked resource end")
|
||||
});
|
||||
let value_type = variants[success]
|
||||
.fields
|
||||
.first()
|
||||
.map(|field| field.ty.clone());
|
||||
let choice = self.entry_slot(&result.ty);
|
||||
let success_block = self.label("resource_success");
|
||||
let failure_block = self.label("resource_failed");
|
||||
let done = self.label("resource_done");
|
||||
let ok = self.temp();
|
||||
self.line(format!("{ok} = icmp eq i32 {status}, 0"));
|
||||
let not_ok = self.label("resource_status");
|
||||
self.line(format!(
|
||||
"br i1 {ok}, label %{success_block}, label %{not_ok}"
|
||||
));
|
||||
self.start(¬_ok);
|
||||
let business = self.temp();
|
||||
self.line(format!("{business} = icmp eq i32 {status}, 1"));
|
||||
let abi_fault = self.label("resource_abi_fault");
|
||||
self.line(format!(
|
||||
"br i1 {business}, label %{failure_block}, label %{abi_fault}"
|
||||
));
|
||||
self.start(&abi_fault);
|
||||
self.check_runtime_status(&status, &error, result.span);
|
||||
self.line("unreachable");
|
||||
self.start(&success_block);
|
||||
if let (Some(end), Some(present)) = (end, present) {
|
||||
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 available = self.label("resource_present");
|
||||
let absent = self.label("resource_end");
|
||||
self.line(format!(
|
||||
"br i1 {has_value}, label %{available}, label %{absent}"
|
||||
));
|
||||
self.start(&absent);
|
||||
self.write_variant(id, end, &[], &choice);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&available);
|
||||
}
|
||||
let fields = value_type
|
||||
.map(|ty| {
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {output}",
|
||||
if protocol_output {
|
||||
"ptr".into()
|
||||
} else {
|
||||
self.module.ty(&ty)
|
||||
}
|
||||
));
|
||||
let value = if protocol_output {
|
||||
self.protocol_to_language(&ty, &value, result.span)
|
||||
} else {
|
||||
value
|
||||
};
|
||||
(ty, value)
|
||||
})
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
self.write_variant(id, success, &fields, &choice);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&failure_block);
|
||||
let text = self.error_text(&error, result.span);
|
||||
self.write_variant(id, failed, &[(Type::Text, text)], &choice);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load %T{id}, ptr {choice}"));
|
||||
value
|
||||
}
|
||||
|
||||
pub(super) fn error_text(&mut self, error: &str, span: Span) -> String {
|
||||
let buffer = self.temp();
|
||||
self.line(format!("{buffer} = load {{ ptr, i64 }}, ptr {error}"));
|
||||
let bytes = self.temp();
|
||||
self.line(format!("{bytes} = extractvalue {{ ptr, i64 }} {buffer}, 0"));
|
||||
let length = self.temp();
|
||||
self.line(format!(
|
||||
"{length} = extractvalue {{ ptr, i64 }} {buffer}, 1"
|
||||
));
|
||||
let message = self.entry_slot(&Type::Text);
|
||||
let conversion_error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let converted = self.temp();
|
||||
self.line(format!("{converted} = call i32 @dever_rt_v1_text_new(ptr {bytes}, i64 {length}, ptr {message}, ptr {conversion_error})"));
|
||||
self.line(format!(
|
||||
"call void @dever_rt_v1_buffer_free(ptr {bytes}, i64 {length})"
|
||||
));
|
||||
self.check_runtime_status(&converted, &conversion_error, span);
|
||||
let text = self.temp();
|
||||
self.line(format!("{text} = load ptr, ptr {message}"));
|
||||
text
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,931 @@
|
||||
//! REST adapters reuse typed Model query, bind, row, and transaction lowering.
|
||||
use super::*;
|
||||
use crate::hir::{ApiRest, ModelCondition, ModelOperation, ModelOrder, QueryPlan};
|
||||
use crate::model::{DatabaseOwner, ModelFieldType, RestOwnerSource, RestValueSource};
|
||||
use crate::native::orm as sql;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Handler {
|
||||
pub function: usize,
|
||||
pub route: usize,
|
||||
pub method: &'static str,
|
||||
pub detail: bool,
|
||||
pub inputs: Vec<Input>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Input {
|
||||
pub name: String,
|
||||
pub ty: Type,
|
||||
pub field: Option<usize>,
|
||||
}
|
||||
|
||||
pub(super) fn prepare(program: &mut Program) -> (Vec<Handler>, Option<usize>) {
|
||||
if program.api_rest.is_empty() {
|
||||
return (Vec::new(), None);
|
||||
}
|
||||
let span = program.api_rest[0].span;
|
||||
let properties = program
|
||||
.types
|
||||
.iter()
|
||||
.find(|definition| definition.kind == DefinitionKind::ModelId)
|
||||
.expect("REST Model id properties")
|
||||
.properties
|
||||
.clone();
|
||||
let error = program.types.len();
|
||||
program.types.push(crate::types::Definition {
|
||||
name: "<generated REST Error>".into(),
|
||||
owner: 0,
|
||||
public: false,
|
||||
span,
|
||||
shape: Shape::Choice(
|
||||
["Invalid", "Forbidden", "NotFound"]
|
||||
.iter()
|
||||
.map(|name| crate::types::Variant {
|
||||
name: (*name).into(),
|
||||
fields: if *name == "Invalid" {
|
||||
vec![crate::types::Field {
|
||||
name: "message".into(),
|
||||
ty: Type::Text,
|
||||
private: false,
|
||||
bounds: Vec::new(),
|
||||
}]
|
||||
} else {
|
||||
Vec::new()
|
||||
},
|
||||
error: true,
|
||||
label: None,
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
properties,
|
||||
kind: DefinitionKind::Regular,
|
||||
});
|
||||
let mut handlers = Vec::new();
|
||||
for (route_index, route) in program.api_rest.clone().iter().enumerate() {
|
||||
let model = &program.models[route.model];
|
||||
let Shape::Record(fields) = program.types[model.record].shape.clone() else {
|
||||
unreachable!();
|
||||
};
|
||||
for (method, detail) in [
|
||||
("GET", false),
|
||||
("POST", false),
|
||||
("GET", true),
|
||||
("PUT", true),
|
||||
("DELETE", true),
|
||||
] {
|
||||
let mut inputs = Vec::new();
|
||||
if detail {
|
||||
inputs.push(Input {
|
||||
name: "id".into(),
|
||||
ty: Type::Int,
|
||||
field: None,
|
||||
});
|
||||
}
|
||||
if method == "GET" && !detail {
|
||||
for name in ["page", "size"] {
|
||||
inputs.push(Input {
|
||||
name: name.into(),
|
||||
ty: Type::Nullable(Box::new(Type::Int)),
|
||||
field: None,
|
||||
});
|
||||
}
|
||||
if let Some(search) = &route.contract.search {
|
||||
inputs.push(Input {
|
||||
name: fields[search.field].name.clone(),
|
||||
ty: fields[search.field].ty.clone().nullable(),
|
||||
field: Some(search.field),
|
||||
});
|
||||
}
|
||||
}
|
||||
let writes = match method {
|
||||
"POST" => &route.contract.create[..],
|
||||
"PUT" => &route.contract.replace[..],
|
||||
_ => &[],
|
||||
};
|
||||
for write in writes {
|
||||
if matches!(
|
||||
write.source,
|
||||
RestValueSource::Input
|
||||
| RestValueSource::Binding {
|
||||
uses_input: true,
|
||||
..
|
||||
}
|
||||
) {
|
||||
inputs.push(Input {
|
||||
name: fields[write.field].name.clone(),
|
||||
ty: fields[write.field].ty.clone(),
|
||||
field: Some(write.field),
|
||||
});
|
||||
}
|
||||
}
|
||||
let mut function = application::generated_function(
|
||||
format!("<generated REST {route_index} {method} {detail}>"),
|
||||
route.span,
|
||||
);
|
||||
function.parameters = inputs
|
||||
.iter()
|
||||
.map(|input| Parameter::Value(input.ty.clone()))
|
||||
.collect();
|
||||
function.outputs = vec![crate::types::Field {
|
||||
name: "encoded".into(),
|
||||
ty: Type::Text,
|
||||
private: false,
|
||||
bounds: Vec::new(),
|
||||
}];
|
||||
let bindings = writes
|
||||
.iter()
|
||||
.filter_map(|write| match &write.source {
|
||||
RestValueSource::Binding { expression, .. } => Some(expression.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.chain(
|
||||
route
|
||||
.contract
|
||||
.search
|
||||
.iter()
|
||||
.map(|search| search.expression.clone()),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
// Metadata-only body preserves every actual expression dependency;
|
||||
// emit_rest_body owns the compiler-generated orchestration.
|
||||
function.clauses = vec![crate::hir::Clause {
|
||||
patterns: Vec::new(),
|
||||
bindings: Vec::new(),
|
||||
locals: fields.iter().map(|field| field.ty.clone()).collect(),
|
||||
outputs: Vec::new(),
|
||||
body: bindings.into_iter().map(Statement::Call).collect(),
|
||||
terminates: false,
|
||||
}];
|
||||
let id = application::append_generated(program, function);
|
||||
program.database_effects[id].insert(DatabaseOwner::Model(route.model));
|
||||
program.failures[id].extend(crate::capture::database_errors(&program.types));
|
||||
program.failures[id]
|
||||
.extend((0..3).map(|variant| crate::hir::Failure { ty: error, variant }));
|
||||
let instance = Specialization {
|
||||
function: id,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let reachable = specialize::execution_reachable(&program.functions, [instance])
|
||||
.expect("checked REST binding call graph");
|
||||
for root in reachable {
|
||||
let failures = specialize::failures(program, &root);
|
||||
program.failures[id].extend(failures);
|
||||
}
|
||||
let mut direct = BTreeSet::new();
|
||||
crate::check::visit(&program.functions[id], |expression| {
|
||||
if let ExpressionKind::Fail(error) = &expression.kind
|
||||
&& let Type::Named(ty) = error.ty
|
||||
&& let ExpressionKind::Variant { variant, .. } = error.kind
|
||||
{
|
||||
direct.insert(crate::hir::Failure { ty, variant });
|
||||
}
|
||||
});
|
||||
program.failures[id].extend(direct);
|
||||
handlers.push(Handler {
|
||||
function: id,
|
||||
route: route_index,
|
||||
method,
|
||||
detail,
|
||||
inputs,
|
||||
});
|
||||
}
|
||||
}
|
||||
(handlers, Some(error))
|
||||
}
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn rest_fault_message(&mut self) {
|
||||
let Some(id) = self
|
||||
.application
|
||||
.as_ref()
|
||||
.and_then(|application| application.rest_error)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
writeln!(self.declarations, "define internal ptr @dever_rest_fault_message(ptr %fault) {{\nentry:\n %code_ptr = getelementptr %dever.fault, ptr %fault, i32 0, i32 0\n %code = load i32, ptr %code_ptr\n %business = icmp eq i32 %code, 4\n %type_ptr = getelementptr %dever.fault, ptr %fault, i32 0, i32 5\n %type = load i32, ptr %type_ptr\n %matches = icmp eq i32 %type, {}\n %variant_ptr = getelementptr %dever.fault, ptr %fault, i32 0, i32 6\n %variant = load i32, ptr %variant_ptr\n %invalid = icmp eq i32 %variant, 0\n %nominal = and i1 %business, %matches\n %selected = and i1 %nominal, %invalid\n br i1 %selected, label %message, label %absent\nmessage:\n %payload = getelementptr %dever.fault, ptr %fault, i32 0, i32 7\n %field = getelementptr %T{id}, ptr %payload, i32 0, i32 1\n %text = load ptr, ptr %field\n ret ptr %text\nabsent:\n ret ptr null\n}}", id + 1).unwrap();
|
||||
}
|
||||
pub(super) fn emit_rest_body(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
handler: &Handler,
|
||||
) -> Result<(), String> {
|
||||
let route = self.program.api_rest[handler.route].clone();
|
||||
let function = self.program.functions[instance.function].clone();
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
let parameters = emitter.begin_generated_function(&function);
|
||||
let encoded = emitter.rest_response(handler, &route)?;
|
||||
let owner = emitter.own_value(&Type::Text, &encoded, false);
|
||||
if handler.method != "GET" {
|
||||
emitter.commit_command_transaction();
|
||||
}
|
||||
emitter.line(format!("store ptr {encoded}, ptr %out"));
|
||||
emitter.disarm(owner);
|
||||
emitter.exit("0");
|
||||
let name = emitter.module.names[instance].clone();
|
||||
let body = emitter.finish_function(&name, ¶meters);
|
||||
self.functions.push_str(&body);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn rest_handler(&mut self, index: usize, handler: &Handler) -> String {
|
||||
let instance = Specialization {
|
||||
function: handler.function,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let input = asynchronous::input_type(&self.program.functions[handler.function]);
|
||||
let decoded_inputs = &handler.inputs[usize::from(handler.detail)..];
|
||||
let decoded_type = Type::Outputs(
|
||||
decoded_inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, input)| crate::types::Field {
|
||||
name: format!("p{index}"),
|
||||
ty: input.ty.clone(),
|
||||
private: false,
|
||||
bounds: Vec::new(),
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
let list = handler.method == "GET" && !handler.detail;
|
||||
let fields = decoded_inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, input)| {
|
||||
let decoded = if list && index == 2 {
|
||||
let Type::Nullable(inner) = &input.ty else {
|
||||
unreachable!()
|
||||
};
|
||||
inner.as_ref()
|
||||
} else {
|
||||
&input.ty
|
||||
};
|
||||
(
|
||||
input.name.clone(),
|
||||
input.ty.clone(),
|
||||
self.wire_callback(decoded, crate::wire::Policy::ApiInput, false),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.protocol_callback = true;
|
||||
emitter.start("entry");
|
||||
let value = emitter.decode_fields(
|
||||
&decoded_type,
|
||||
&fields,
|
||||
"%inputs",
|
||||
if list {
|
||||
wire::FieldInput::RestList
|
||||
} else {
|
||||
wire::FieldInput::Rest
|
||||
},
|
||||
);
|
||||
let owner = emitter.own_value(&decoded_type, &value, false);
|
||||
let mut row = "zeroinitializer".to_owned();
|
||||
if handler.detail {
|
||||
let next = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{next} = insertvalue {} {row}, i64 %detail_id, 0",
|
||||
emitter.module.ty(&input)
|
||||
));
|
||||
row = next;
|
||||
}
|
||||
for (index, field) in decoded_inputs.iter().enumerate() {
|
||||
let child = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{child} = extractvalue {} {value}, {index}",
|
||||
emitter.module.ty(&decoded_type)
|
||||
));
|
||||
if let Some(field_index) = field.field {
|
||||
let model = &emitter.module.program.models
|
||||
[emitter.module.program.api_rest[handler.route].model];
|
||||
let storage = model.fields[field_index].ty.clone();
|
||||
emitter.rest_text_bounds(&field.name, &field.ty, &child, &storage, false);
|
||||
}
|
||||
let next = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{next} = insertvalue {} {row}, {} {child}, {}",
|
||||
emitter.module.ty(&input),
|
||||
emitter.module.ty(&field.ty),
|
||||
index + usize::from(handler.detail)
|
||||
));
|
||||
row = next;
|
||||
}
|
||||
emitter.line(format!(
|
||||
"store {} {row}, ptr %out",
|
||||
emitter.module.ty(&input)
|
||||
));
|
||||
emitter.disarm(owner);
|
||||
let callback = format!("dever_rest_decode_{index}");
|
||||
let body = emitter.finish_protocol_callback(
|
||||
&callback,
|
||||
"ptr %inputs, i64 %detail_id, ptr %out, ptr %error",
|
||||
);
|
||||
self.declarations.push_str(&body);
|
||||
let name = &self.names[&instance];
|
||||
let descriptor = format!("@dever_rest_handler_{index}");
|
||||
writeln!(self.declarations, "{descriptor} = private constant %dever.api_handler {{ ptr @dever_async_{name}, ptr null, ptr @dever_owned_{}, ptr @{callback}, ptr @dever_rest_fault_status, ptr @dever_rest_fault_message }}", self.type_index(&input)).unwrap();
|
||||
descriptor
|
||||
}
|
||||
|
||||
pub(super) fn rest_encoder(&mut self, route_index: usize, page: bool) -> String {
|
||||
let name = format!("dever_rest_encode_{route_index}_{}", u8::from(page));
|
||||
if !self.rest_encoders.insert((route_index, page)) {
|
||||
return name;
|
||||
}
|
||||
let route = self.program.api_rest[route_index].clone();
|
||||
let model = self.program.models[route.model].clone();
|
||||
let id = if page { model.page } else { model.record };
|
||||
let Shape::Record(fields) = self.program.types[id].shape.clone() else {
|
||||
unreachable!();
|
||||
};
|
||||
let selected = if page {
|
||||
(0..4).collect::<Vec<_>>()
|
||||
} else {
|
||||
route.contract.read.clone()
|
||||
};
|
||||
let mut codecs = Vec::new();
|
||||
for field in &selected {
|
||||
codecs.push(if page && *field == 0 {
|
||||
self.rest_encoder(route_index, false)
|
||||
} else {
|
||||
self.wire_callback(&fields[*field].ty, crate::wire::Policy::Output, true)
|
||||
});
|
||||
}
|
||||
let instance = self.instances.first().unwrap().clone();
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.protocol_callback = true;
|
||||
emitter.start("entry");
|
||||
let value = emitter.temp();
|
||||
emitter.line(format!("{value} = load %T{id}, ptr %input"));
|
||||
emitter.wire_write(
|
||||
"dever_rt_v1_wire_encoder_begin_object",
|
||||
vec!["ptr %encoder".into()],
|
||||
);
|
||||
for (index, codec) in selected.iter().zip(codecs) {
|
||||
let key = emitter.module.wire_names(&[fields[*index].name.clone()]);
|
||||
emitter.wire_write(
|
||||
"dever_rt_v1_wire_encoder_key",
|
||||
vec!["ptr %encoder".into(), format!("ptr {key}")],
|
||||
);
|
||||
let child = emitter.temp();
|
||||
emitter.line(format!("{child} = extractvalue %T{id} {value}, {index}"));
|
||||
if page && *index == 0 {
|
||||
emitter.encode_list_callback(&Type::Named(model.record), &codec, &child);
|
||||
} else {
|
||||
let row = emitter.row_pointer(&fields[*index].ty, &child);
|
||||
emitter.wire_write(&codec, vec![format!("ptr {row}"), "ptr %encoder".into()]);
|
||||
}
|
||||
}
|
||||
emitter.wire_write("dever_rt_v1_wire_encoder_end", vec!["ptr %encoder".into()]);
|
||||
let body = emitter.finish_protocol_callback(&name, "ptr %input, ptr %encoder, ptr %error");
|
||||
self.declarations.push_str(&body);
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
fn rest_response(&mut self, handler: &Handler, route: &ApiRest) -> Result<String, String> {
|
||||
let span = route.span;
|
||||
let model = self.module.program.models[route.model].clone();
|
||||
let Shape::Record(fields) = self.module.program.types[model.record].shape.clone() else {
|
||||
unreachable!();
|
||||
};
|
||||
// Bindings own one compiler-assigned input local. Other decoded values
|
||||
// live in separate slots so evaluating a binding cannot alias them.
|
||||
let empty = self.entry_slot(&Type::Unit);
|
||||
self.locals.push((Type::Unit, empty));
|
||||
let mut inputs = Vec::new();
|
||||
for (index, input) in handler.inputs.iter().enumerate() {
|
||||
inputs.push(self.rest_local(&input.ty, &self.parameter(index), span));
|
||||
}
|
||||
let mut writes = self.rest_write_values(handler, route, &inputs)?;
|
||||
if handler.method == "GET" && !handler.detail && route.contract.search.is_some() {
|
||||
inputs[2] = self.rest_search_value(route, &inputs[2])?;
|
||||
}
|
||||
let owner = if let Some((field, source)) = route.contract.owner {
|
||||
let symbol = match source {
|
||||
RestOwnerSource::User => "auth_user_id",
|
||||
RestOwnerSource::Tenant => "auth_tenant_id",
|
||||
};
|
||||
let optional = self.runtime_call(
|
||||
&format!("dever_rt_v1_{symbol}"),
|
||||
Vec::new(),
|
||||
&Type::Nullable(Box::new(Type::Int)),
|
||||
true,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
let present = self.temp();
|
||||
let id = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {{ i1, i64 }} {optional}, 0"
|
||||
));
|
||||
let found = self.label("rest_owner");
|
||||
let missing = self.label("rest_owner_missing");
|
||||
self.line(format!("br i1 {present}, label %{found}, label %{missing}"));
|
||||
self.start(&missing);
|
||||
self.rest_fail(1, span);
|
||||
self.start(&found);
|
||||
self.line(format!("{id} = extractvalue {{ i1, i64 }} {optional}, 1"));
|
||||
Some((field, self.rest_local(&fields[field].ty, &id, span)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut condition = owner
|
||||
.as_ref()
|
||||
.map(|(field, value)| ModelCondition::Compare {
|
||||
field: *field,
|
||||
operator: BinaryOperator::Equal,
|
||||
value: value.clone(),
|
||||
});
|
||||
if handler.detail {
|
||||
let id = ModelCondition::Compare {
|
||||
field: 0,
|
||||
operator: BinaryOperator::Equal,
|
||||
value: inputs[0].clone(),
|
||||
};
|
||||
condition = Some(match condition {
|
||||
Some(owner) => ModelCondition::And(Box::new(id), Box::new(owner)),
|
||||
None => id,
|
||||
});
|
||||
}
|
||||
let constant = |value| Expression {
|
||||
kind: ExpressionKind::Constant(Constant::Int(value)),
|
||||
ty: Type::Int,
|
||||
span,
|
||||
};
|
||||
let query = QueryPlan {
|
||||
condition,
|
||||
order: vec![ModelOrder {
|
||||
field: 0,
|
||||
descending: true,
|
||||
}],
|
||||
relations: Vec::new(),
|
||||
after: None,
|
||||
page: constant(1),
|
||||
size: constant(20),
|
||||
};
|
||||
if handler.method == "POST"
|
||||
&& let Some(owner) = owner
|
||||
{
|
||||
writes.push(owner);
|
||||
}
|
||||
if handler.method != "GET" {
|
||||
self.begin_transaction_scope(self.instance, &Type::Unit, span);
|
||||
}
|
||||
let model_value = if handler.method == "GET" && !handler.detail {
|
||||
(
|
||||
Type::Named(model.page),
|
||||
self.rest_list(route, &inputs, query)?,
|
||||
)
|
||||
} else if handler.method == "DELETE" {
|
||||
let result = Expression {
|
||||
ty: Type::Int,
|
||||
span,
|
||||
kind: ExpressionKind::Constant(Constant::Null),
|
||||
};
|
||||
let affected =
|
||||
self.model_operation(route.model, &ModelOperation::Delete { query }, &result)?;
|
||||
self.rest_require_affected(&affected, span);
|
||||
// The wrapper owns the encoded response across commit and transfers
|
||||
// it to its caller. Do not register a second owner for this Text.
|
||||
let (pointer, length) = self.module.text_literal("null");
|
||||
return Ok(self.runtime_call(
|
||||
"dever_rt_v1_text_new",
|
||||
vec![format!("ptr {pointer}"), format!("i64 {length}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
));
|
||||
} else if handler.method == "POST" {
|
||||
let result = Expression {
|
||||
ty: Type::Named(model.record),
|
||||
span,
|
||||
kind: ExpressionKind::Constant(Constant::Null),
|
||||
};
|
||||
(
|
||||
result.ty.clone(),
|
||||
self.model_operation(
|
||||
route.model,
|
||||
&ModelOperation::Create { values: writes },
|
||||
&result,
|
||||
)?,
|
||||
)
|
||||
} else {
|
||||
let (statement, values) = if handler.method == "PUT" {
|
||||
let mut expressions = writes
|
||||
.iter()
|
||||
.map(|(field, expression)| (expression, &model.fields[*field].ty))
|
||||
.collect::<Vec<_>>();
|
||||
let assignments =
|
||||
sql::update_assignments(&model, writes.iter().map(|(field, _)| *field), 1);
|
||||
let condition =
|
||||
sql::condition_sql(&model, query.condition.as_ref(), &mut expressions);
|
||||
let values = self.model_bind(&expressions)?;
|
||||
(
|
||||
self.module.planned_sql(&format!(
|
||||
"UPDATE {} SET {assignments}{condition} RETURNING {}",
|
||||
sql::quoted(&model.table),
|
||||
sql::select_columns(&model)
|
||||
)),
|
||||
values,
|
||||
)
|
||||
} else {
|
||||
self.model_select(&model, &query, " LIMIT 1")?
|
||||
};
|
||||
let rows = self.model_query(route.model, &statement, &values, "0", span);
|
||||
let value = self.model_first(
|
||||
&rows,
|
||||
&model,
|
||||
None,
|
||||
Some((7, "REST record was not found".into())),
|
||||
span,
|
||||
);
|
||||
(Type::Named(model.record), value)
|
||||
};
|
||||
self.own_value(&model_value.0, &model_value.1, false);
|
||||
let row = self.row_pointer(&model_value.0, &model_value.1);
|
||||
let encoder = self
|
||||
.module
|
||||
.rest_encoder(handler.route, handler.method == "GET" && !handler.detail);
|
||||
Ok(self.runtime_call(
|
||||
"dever_rt_v1_wire_encode",
|
||||
vec![
|
||||
format!("ptr {row}"),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(&model_value.0)),
|
||||
format!("ptr @{encoder}"),
|
||||
],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
fn rest_write_values(
|
||||
&mut self,
|
||||
handler: &Handler,
|
||||
route: &ApiRest,
|
||||
inputs: &[Expression],
|
||||
) -> Result<Vec<(usize, Expression)>, String> {
|
||||
let model = self.module.program.models[route.model].clone();
|
||||
let Shape::Record(fields) = self.module.program.types[model.record].shape.clone() else {
|
||||
unreachable!();
|
||||
};
|
||||
let write_fields = match handler.method {
|
||||
"POST" => &route.contract.create[..],
|
||||
"PUT" => &route.contract.replace[..],
|
||||
_ => &[],
|
||||
};
|
||||
let mut writes = Vec::new();
|
||||
for write in write_fields {
|
||||
let input = handler
|
||||
.inputs
|
||||
.iter()
|
||||
.position(|input| input.field == Some(write.field))
|
||||
.map(|index| &inputs[index]);
|
||||
let value = match &write.source {
|
||||
RestValueSource::Input => input.unwrap().clone(),
|
||||
RestValueSource::Binding { expression, .. } => {
|
||||
self.rest_binding(expression, input)?
|
||||
}
|
||||
};
|
||||
if let RestValueSource::Binding { uses_input, .. } = write.source {
|
||||
let rendered = self.expression(&value)?;
|
||||
self.rest_text_bounds(
|
||||
&fields[write.field].name,
|
||||
&value.ty,
|
||||
&rendered,
|
||||
&model.fields[write.field].ty,
|
||||
uses_input,
|
||||
);
|
||||
}
|
||||
writes.push((write.field, value));
|
||||
}
|
||||
Ok(writes)
|
||||
}
|
||||
|
||||
fn rest_search_value(
|
||||
&mut self,
|
||||
route: &ApiRest,
|
||||
input: &Expression,
|
||||
) -> Result<Expression, String> {
|
||||
let search = route.contract.search.as_ref().unwrap();
|
||||
let model = self.module.program.models[route.model].clone();
|
||||
let Shape::Record(fields) = self.module.program.types[model.record].shape.clone() else {
|
||||
unreachable!();
|
||||
};
|
||||
let field = &fields[search.field];
|
||||
let value = self.expression(input)?;
|
||||
let slot = self.entry_slot(&input.ty);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {slot}",
|
||||
self.module.ty(&input.ty)
|
||||
));
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(&input.ty)
|
||||
));
|
||||
let bind = self.label("rest_search_binding");
|
||||
let done = self.label("rest_search_bound");
|
||||
self.line(format!("br i1 {present}, label %{bind}, label %{done}"));
|
||||
self.start(&bind);
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = extractvalue {} {value}, 1",
|
||||
self.module.ty(&input.ty)
|
||||
));
|
||||
let local = self.rest_local(&field.ty, &payload, route.span);
|
||||
let value = self.rest_binding(&search.expression, Some(&local))?;
|
||||
let rendered = self.expression(&value)?;
|
||||
self.rest_text_bounds(
|
||||
&field.name,
|
||||
&field.ty,
|
||||
&rendered,
|
||||
&model.fields[search.field].ty,
|
||||
true,
|
||||
);
|
||||
let flag = self.temp();
|
||||
let row = self.temp();
|
||||
self.line(format!(
|
||||
"{flag} = insertvalue {} zeroinitializer, i1 true, 0",
|
||||
self.module.ty(&input.ty)
|
||||
));
|
||||
self.line(format!(
|
||||
"{row} = insertvalue {} {flag}, {} {rendered}, 1",
|
||||
self.module.ty(&input.ty),
|
||||
self.module.ty(&field.ty)
|
||||
));
|
||||
self.line(format!(
|
||||
"store {} {row}, ptr {slot}",
|
||||
self.module.ty(&input.ty)
|
||||
));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {slot}",
|
||||
self.module.ty(&input.ty)
|
||||
));
|
||||
let owner = self.own_value(&input.ty, &value, true);
|
||||
let value = self.load_owned_value(&input.ty, owner);
|
||||
Ok(self.rest_local(&input.ty, &value, route.span))
|
||||
}
|
||||
|
||||
fn rest_list(
|
||||
&mut self,
|
||||
route: &ApiRest,
|
||||
inputs: &[Expression],
|
||||
mut query: QueryPlan,
|
||||
) -> Result<String, String> {
|
||||
let span = route.span;
|
||||
let model = self.module.program.models[route.model].clone();
|
||||
let Shape::Record(fields) = self.module.program.types[model.record].shape.clone() else {
|
||||
unreachable!();
|
||||
};
|
||||
query.page = self.rest_default_int(&inputs[0], 1)?;
|
||||
query.size = self.rest_default_int(&inputs[1], 20)?;
|
||||
let maximum = self.model_maximum(route.model, span);
|
||||
let page = self.expression(&query.page)?;
|
||||
let size = self.expression(&query.size)?;
|
||||
let p_slot = self.entry_slot(&Type::Int);
|
||||
let s_slot = self.entry_slot(&Type::Int);
|
||||
let offset = self.entry_slot(&Type::Int);
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_api_pagination(i64 {page}, i64 {size}, i64 {maximum}, ptr {p_slot}, ptr {s_slot}, ptr {offset}, ptr {error})"));
|
||||
self.rest_input_status(&status, &error);
|
||||
let result = Expression {
|
||||
ty: Type::Named(model.page),
|
||||
span,
|
||||
kind: ExpressionKind::Constant(Constant::Null),
|
||||
};
|
||||
let Some(search) = &route.contract.search else {
|
||||
return self.model_page(route.model, &model, &query, &result);
|
||||
};
|
||||
let input = &inputs[2];
|
||||
let value = self.expression(input)?;
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(&input.ty)
|
||||
));
|
||||
let filtered = self.label("rest_search");
|
||||
let all = self.label("rest_unfiltered");
|
||||
let done = self.label("rest_list_done");
|
||||
let slot = self.entry_slot(&result.ty);
|
||||
self.line(format!("br i1 {present}, label %{filtered}, label %{all}"));
|
||||
self.start(&filtered);
|
||||
let input_value = self.temp();
|
||||
self.line(format!(
|
||||
"{input_value} = extractvalue {} {value}, 1",
|
||||
self.module.ty(&input.ty)
|
||||
));
|
||||
let value = self.rest_local(&fields[search.field].ty, &input_value, span);
|
||||
let filter = ModelCondition::Compare {
|
||||
field: search.field,
|
||||
operator: BinaryOperator::Equal,
|
||||
value,
|
||||
};
|
||||
let mut filtered_query = query.clone();
|
||||
filtered_query.condition = Some(match query.condition.clone() {
|
||||
Some(owner) => ModelCondition::And(Box::new(owner), Box::new(filter)),
|
||||
None => filter,
|
||||
});
|
||||
let value = self.model_page(route.model, &model, &filtered_query, &result)?;
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {slot}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&all);
|
||||
let value = self.model_page(route.model, &model, &query, &result)?;
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {slot}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {slot}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn rest_default_int(
|
||||
&mut self,
|
||||
expression: &Expression,
|
||||
default: i64,
|
||||
) -> Result<Expression, String> {
|
||||
let value = self.expression(expression)?;
|
||||
let present = self.temp();
|
||||
let number = self.temp();
|
||||
let selected = self.temp();
|
||||
self.line(format!("{present} = extractvalue {{ i1, i64 }} {value}, 0"));
|
||||
self.line(format!("{number} = extractvalue {{ i1, i64 }} {value}, 1"));
|
||||
self.line(format!(
|
||||
"{selected} = select i1 {present}, i64 {number}, i64 {default}"
|
||||
));
|
||||
Ok(self.rest_local(&Type::Int, &selected, expression.span))
|
||||
}
|
||||
|
||||
fn rest_require_affected(&mut self, affected: &str, span: Span) {
|
||||
let found = self.temp();
|
||||
let success = self.label("rest_deleted");
|
||||
let missing = self.label("rest_not_found");
|
||||
self.line(format!("{found} = icmp sgt i64 {affected}, 0"));
|
||||
self.line(format!("br i1 {found}, label %{success}, label %{missing}"));
|
||||
self.start(&missing);
|
||||
self.rest_fail(2, span);
|
||||
self.start(&success);
|
||||
}
|
||||
|
||||
fn rest_fail(&mut self, variant: usize, span: Span) {
|
||||
let id = self
|
||||
.module
|
||||
.application
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.rest_error
|
||||
.unwrap();
|
||||
self.fail(
|
||||
&Expression {
|
||||
ty: Type::Named(id),
|
||||
span,
|
||||
kind: ExpressionKind::Variant {
|
||||
variant,
|
||||
arguments: Vec::new(),
|
||||
},
|
||||
},
|
||||
span,
|
||||
)
|
||||
.unwrap();
|
||||
self.line("unreachable");
|
||||
}
|
||||
|
||||
fn rest_local(&mut self, ty: &Type, value: &str, span: Span) -> Expression {
|
||||
let slot = self.row_pointer(ty, value);
|
||||
let index = self.locals.len();
|
||||
self.locals.push((ty.clone(), slot));
|
||||
Expression {
|
||||
ty: ty.clone(),
|
||||
kind: ExpressionKind::Local(index),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
fn rest_binding(
|
||||
&mut self,
|
||||
expression: &Expression,
|
||||
input: Option<&Expression>,
|
||||
) -> Result<Expression, String> {
|
||||
let previous = self.locals.first().cloned();
|
||||
if let Some(input) = input {
|
||||
let value = self.expression(input)?;
|
||||
let row = self.row_pointer(&input.ty, &value);
|
||||
self.locals[0] = (input.ty.clone(), row);
|
||||
}
|
||||
let value = self.expression(expression)?;
|
||||
if let Some(previous) = previous {
|
||||
self.locals[0] = previous;
|
||||
}
|
||||
Ok(self.rest_local(&expression.ty, &value, expression.span))
|
||||
}
|
||||
|
||||
fn rest_text_bounds(
|
||||
&mut self,
|
||||
name: &str,
|
||||
ty: &Type,
|
||||
value: &str,
|
||||
storage: &ModelFieldType,
|
||||
source_fault: bool,
|
||||
) {
|
||||
let ModelFieldType::Text { minimum, maximum } = storage else {
|
||||
return;
|
||||
};
|
||||
let name = self.module.wire_names(&[name.into()]);
|
||||
let text = if matches!(ty, Type::Nullable(_)) {
|
||||
self.nullable_handle(ty, value)
|
||||
} else {
|
||||
value.into()
|
||||
};
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_api_text_bounds(ptr {name}, ptr {text}, i64 {minimum}, i64 {}, i8 {}, ptr {error})", maximum.unwrap_or(0), u8::from(maximum.is_some())));
|
||||
if source_fault {
|
||||
self.rest_input_status(&status, &error);
|
||||
} else {
|
||||
self.check_runtime_status(
|
||||
&status,
|
||||
&error,
|
||||
self.module.program.functions[self.instance.function].span,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn rest_input_status(&mut self, status: &str, error: &str) {
|
||||
let failed = self.temp();
|
||||
let invalid = self.label("rest_invalid");
|
||||
let done = self.label("rest_valid");
|
||||
self.line(format!("{failed} = icmp eq i32 {status}, 1"));
|
||||
self.line(format!("br i1 {failed}, label %{invalid}, label %{done}"));
|
||||
self.start(&invalid);
|
||||
let pointer = self.temp();
|
||||
let length = self.temp();
|
||||
let buffer = self.temp();
|
||||
self.line(format!("{buffer} = load {{ ptr, i64 }}, ptr {error}"));
|
||||
self.line(format!(
|
||||
"{pointer} = extractvalue {{ ptr, i64 }} {buffer}, 0"
|
||||
));
|
||||
self.line(format!(
|
||||
"{length} = extractvalue {{ ptr, i64 }} {buffer}, 1"
|
||||
));
|
||||
let span = self.module.program.functions[self.instance.function].span;
|
||||
let text = self.runtime_call(
|
||||
"dever_rt_v1_text_new",
|
||||
vec![format!("ptr {pointer}"), format!("i64 {length}")],
|
||||
&Type::Text,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.own_value(&Type::Text, &text, false);
|
||||
self.line(format!(
|
||||
"call void @dever_rt_v1_buffer_free(ptr {pointer}, i64 {length})"
|
||||
));
|
||||
let message = self.rest_local(&Type::Text, &text, span);
|
||||
let id = self
|
||||
.module
|
||||
.application
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.rest_error
|
||||
.unwrap();
|
||||
self.fail(
|
||||
&Expression {
|
||||
ty: Type::Named(id),
|
||||
span,
|
||||
kind: ExpressionKind::Variant {
|
||||
variant: 0,
|
||||
arguments: vec![message],
|
||||
},
|
||||
},
|
||||
span,
|
||||
)
|
||||
.unwrap();
|
||||
self.line("unreachable");
|
||||
self.start(&done);
|
||||
self.check_runtime_status(
|
||||
status,
|
||||
error,
|
||||
self.module.program.functions[self.instance.function].span,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
//! Non-application system capabilities keep their native runtime contracts.
|
||||
use super::*;
|
||||
|
||||
pub(super) const DECLARATIONS: &str = "\
|
||||
declare i32 @dever_rt_v1_process_arguments(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_unix_millis(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_now(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_monotonic_nanos(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_parse_datetime(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_parse_date(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_parse_time(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_format_datetime(i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_format_date(i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_format_time(i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_add(i64, i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_subtract(i64, i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_time_difference(i64, i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_stdout_write(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_log_write(i32, ptr, ptr, ptr, ptr)
|
||||
declare void @dever_rt_v1_log_flush()
|
||||
declare i32 @dever_rt_v1_uuid_parse(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_uuid_to_text(ptr, ptr, ptr)
|
||||
declare i8 @dever_rt_v1_uuid_equal(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_secret_from_text(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_crypto_token(i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_crypto_password_hash(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_crypto_password_verify(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_crypto_sha256(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_crypto_hmac_sha256(ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_crypto_constant_time_eq(ptr, ptr, ptr, ptr)
|
||||
";
|
||||
|
||||
pub(super) fn supported(operation: Intrinsic) -> bool {
|
||||
use Intrinsic::*;
|
||||
matches!(
|
||||
operation,
|
||||
ProcessArguments
|
||||
| TimeUnixMillis
|
||||
| TimeNow
|
||||
| TimeMonotonicNanos
|
||||
| TimeParseDateTime
|
||||
| TimeParseDate
|
||||
| TimeParseTime
|
||||
| TimeFormatDateTime
|
||||
| TimeFormatDate
|
||||
| TimeFormatTime
|
||||
| TimeDuration
|
||||
| TimeAdd
|
||||
| TimeSubtract
|
||||
| TimeDifference
|
||||
| StdoutWrite
|
||||
| LogDebug
|
||||
| LogInfo
|
||||
| LogWarn
|
||||
| LogError
|
||||
| UuidParse
|
||||
| UuidToText
|
||||
| TestSecret
|
||||
| CryptoToken
|
||||
| CryptoPasswordHash
|
||||
| CryptoPasswordVerify
|
||||
| CryptoSha256
|
||||
| CryptoHmacSha256
|
||||
| CryptoConstantTimeEq
|
||||
)
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn uuid_from_literal_text(&mut self, text: &str, span: Span) -> String {
|
||||
let parsed = self.runtime_call(
|
||||
"dever_rt_v1_uuid_parse",
|
||||
vec![format!("ptr {text}")],
|
||||
&Type::Nullable(Box::new(Type::Uuid)),
|
||||
true,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
// Literal validation already proved this exact spelling parses.
|
||||
let uuid = self.temp();
|
||||
self.line(format!("{uuid} = extractvalue {{ i1, ptr }} {parsed}, 1"));
|
||||
uuid
|
||||
}
|
||||
pub(super) fn system_intrinsic(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
mut args: Vec<String>,
|
||||
values: &[String],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
use Intrinsic::*;
|
||||
let (symbol, success) = match operation {
|
||||
TimeDuration => return values[0].clone(),
|
||||
ProcessArguments => {
|
||||
args.push(format!(
|
||||
"ptr @dever_type_{}",
|
||||
self.module.type_index(&Type::Text)
|
||||
));
|
||||
("process_arguments", Some("Read"))
|
||||
}
|
||||
TimeUnixMillis => ("time_unix_millis", Some("Read")),
|
||||
TimeNow => ("time_now", Some("Read")),
|
||||
TimeMonotonicNanos => ("time_monotonic_nanos", Some("Read")),
|
||||
TimeParseDateTime => ("time_parse_datetime", Some("Read")),
|
||||
TimeParseDate => ("time_parse_date", Some("Read")),
|
||||
TimeParseTime => ("time_parse_time", Some("Read")),
|
||||
TimeFormatDateTime => ("time_format_datetime", Some("Read")),
|
||||
TimeFormatDate => ("time_format_date", Some("Read")),
|
||||
TimeFormatTime => ("time_format_time", Some("Read")),
|
||||
TimeAdd => ("time_add", Some("Read")),
|
||||
TimeSubtract => ("time_subtract", Some("Read")),
|
||||
TimeDifference => ("time_difference", Some("Read")),
|
||||
CryptoToken => ("crypto_token", Some("Ready")),
|
||||
CryptoPasswordHash => ("crypto_password_hash", Some("Ready")),
|
||||
CryptoPasswordVerify => ("crypto_password_verify", Some("Ready")),
|
||||
CryptoSha256 => ("crypto_sha256", None),
|
||||
CryptoHmacSha256 => ("crypto_hmac_sha256", None),
|
||||
CryptoConstantTimeEq => ("crypto_constant_time_eq", None),
|
||||
TestSecret => ("secret_from_text", None),
|
||||
UuidParse => ("uuid_parse", None),
|
||||
UuidToText => ("uuid_to_text", None),
|
||||
StdoutWrite => ("stdout_write", None),
|
||||
LogDebug | LogInfo | LogWarn | LogError => {
|
||||
let level = match operation {
|
||||
LogDebug => 0,
|
||||
LogInfo => 1,
|
||||
LogWarn => 2,
|
||||
LogError => 3,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
args.insert(0, format!("i32 {level}"));
|
||||
("log_write", None)
|
||||
}
|
||||
_ => unreachable!("checked system intrinsic"),
|
||||
};
|
||||
let symbol = format!("dever_rt_v1_{symbol}");
|
||||
if let Some(success) = success {
|
||||
return self.recoverable_call(&symbol, args, result, success, None);
|
||||
}
|
||||
let physical = matches!(result.ty, Type::Bool | Type::Unit).then_some("i8");
|
||||
let value = self.runtime_call(
|
||||
&symbol,
|
||||
args,
|
||||
&result.ty,
|
||||
matches!(result.ty, Type::Nullable(_)),
|
||||
physical,
|
||||
result.span,
|
||||
);
|
||||
if result.ty == Type::Unit {
|
||||
"zeroinitializer".into()
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
//! One compiled suite with independently checked, namespaced case modules.
|
||||
use super::*;
|
||||
|
||||
pub(super) fn supported(operation: Intrinsic) -> bool {
|
||||
matches!(
|
||||
operation,
|
||||
Intrinsic::TestAssert
|
||||
| Intrinsic::TestAssertEq
|
||||
| Intrinsic::TestJobDrain
|
||||
| Intrinsic::TestClockAdvance
|
||||
)
|
||||
}
|
||||
|
||||
/// Emit the same checked suite with one C ABI main selecting exactly one case.
|
||||
/// The CLI handles zero discovered cases without compiling a suite.
|
||||
pub fn emit_test_executable(program: &Program, sources: &SourceMap) -> Result<String, String> {
|
||||
let mut suite = emit_test_suite(program, sources)?;
|
||||
process_entry::emit_test_main(&mut suite);
|
||||
Ok(suite)
|
||||
}
|
||||
|
||||
/// Emit one suite; the runner starts its executable once per stable case index.
|
||||
/// Fault storage is opaque, eight-byte aligned, and sized/released with the same
|
||||
/// index. The rendered diagnostic is an owned runtime Text (text_release).
|
||||
pub fn emit_test_suite(program: &Program, sources: &SourceMap) -> Result<String, String> {
|
||||
if program.tests.is_empty() {
|
||||
return Err("cannot compile an empty test suite".into());
|
||||
}
|
||||
let mut declarations = BTreeSet::new();
|
||||
let mut suite = String::new();
|
||||
for index in 0..program.tests.len() {
|
||||
let mut case = crate::check::test_program(program, index).map_err(|errors| {
|
||||
errors
|
||||
.iter()
|
||||
.map(|error| error.render(sources))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
})?;
|
||||
case.api_routes.clear();
|
||||
case.api_rest.clear();
|
||||
case.api_commands.clear();
|
||||
case.auth.clear();
|
||||
case.permissions.clear();
|
||||
let ir = application::emit_program(&case, sources, Some(index))?;
|
||||
for line in ir.lines() {
|
||||
if line.starts_with("declare ") && !declarations.insert(line.to_owned()) {
|
||||
continue;
|
||||
}
|
||||
writeln!(suite, "{line}").unwrap();
|
||||
}
|
||||
}
|
||||
writeln!(
|
||||
suite,
|
||||
"define i64 @dever_test_count() {{\nentry:\n ret i64 {}\n}}",
|
||||
program.tests.len()
|
||||
)
|
||||
.unwrap();
|
||||
for (name, result, parameters, arguments, invalid) in [
|
||||
(
|
||||
"entry",
|
||||
"i32",
|
||||
"ptr %out, ptr %fault",
|
||||
"ptr %out, ptr %fault",
|
||||
"ret i32 2",
|
||||
),
|
||||
("fault_size", "i64", "", "", "ret i64 0"),
|
||||
(
|
||||
"outputs_release",
|
||||
"void",
|
||||
"ptr %out",
|
||||
"ptr %out",
|
||||
"ret void",
|
||||
),
|
||||
(
|
||||
"fault_release",
|
||||
"void",
|
||||
"ptr %fault",
|
||||
"ptr %fault",
|
||||
"ret void",
|
||||
),
|
||||
(
|
||||
"fault_render",
|
||||
"i32",
|
||||
"ptr %fault, ptr %out, ptr %error",
|
||||
"ptr %fault, ptr %out, ptr %error",
|
||||
"ret i32 1",
|
||||
),
|
||||
] {
|
||||
let extra = if parameters.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(", {parameters}")
|
||||
};
|
||||
writeln!(suite, "define {result} @dever_test_{name}(i64 %index{extra}) {{\nentry:\n switch i64 %index, label %invalid [{}]\ninvalid:\n {invalid}", (0..program.tests.len()).map(|index| format!("i64 {index}, label %case{index}")).collect::<Vec<_>>().join(" ")).unwrap();
|
||||
for index in 0..program.tests.len() {
|
||||
let target = match name {
|
||||
"entry" => "dever_application_entry".to_owned(),
|
||||
"outputs_release" => "dever_outputs_release".to_owned(),
|
||||
_ => format!("dever_test_{name}"),
|
||||
};
|
||||
writeln!(suite, "case{index}:").unwrap();
|
||||
if result == "void" {
|
||||
writeln!(
|
||||
suite,
|
||||
" call void @case{index}_{target}({arguments})\n ret void"
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(suite, " %result{index} = call {result} @case{index}_{target}({arguments})\n ret {result} %result{index}").unwrap();
|
||||
}
|
||||
}
|
||||
suite.push_str("}\n");
|
||||
}
|
||||
suite.push_str("define void @dever_test_text_release(ptr %text) {\nentry:\n call void @dever_rt_v1_text_release(ptr %text)\n ret void\n}\n");
|
||||
Ok(suite)
|
||||
}
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn emit_test_body(&mut self, instance: &Specialization) -> Result<(), String> {
|
||||
let index = self.application.as_ref().unwrap().test.unwrap();
|
||||
let function = self.program.functions[instance.function].clone();
|
||||
let target = Specialization {
|
||||
function: self.program.tests[index].function,
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let mut emitter = FunctionEmitter::new(self, instance);
|
||||
let parameters = emitter.begin_generated_function(&function);
|
||||
emitter.initialize_clock(function.span);
|
||||
emitter.initialize_database(function.span, "false");
|
||||
emitter.initialize_ports(function.span);
|
||||
if emitter.module.jobs_enabled() {
|
||||
let input = emitter.entry_slot(&Type::Unit);
|
||||
emitter.invoke_system(&target, &input, "0", function.span);
|
||||
} else {
|
||||
let (status, _) = emitter.invoke_values(&target, &[], &Type::Unit, function.span);
|
||||
emitter.propagate_status(&status, function.span, false);
|
||||
}
|
||||
emitter.exit("0");
|
||||
let name = emitter.module.names[instance].clone();
|
||||
let body = emitter.finish_function(&name, ¶meters);
|
||||
self.functions.push_str(&body);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn emit_test_exports(&mut self, root: &Specialization) {
|
||||
let render = self.http_fault_message(root);
|
||||
writeln!(self.functions, "define i64 @dever_test_fault_size() {{\nentry:\n ret i64 ptrtoint (ptr getelementptr (%dever.fault, ptr null, i32 1) to i64)\n}}\ndefine void @dever_test_fault_release(ptr %fault) {{\nentry:\n call void @dever_fault_release(ptr %fault)\n ret void\n}}\ndefine i32 @dever_test_fault_render(ptr %fault, ptr %out, ptr %error) {{\nentry:\n %status = call i32 {render}(ptr %fault, ptr %out, ptr %error)\n ret i32 %status\n}}").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn test_intrinsic(
|
||||
&mut self,
|
||||
operation: Intrinsic,
|
||||
arguments: &[Expression],
|
||||
values: &[String],
|
||||
span: Span,
|
||||
) -> String {
|
||||
if operation == Intrinsic::TestClockAdvance {
|
||||
let clock = self.temp();
|
||||
self.line(format!("{clock} = load ptr, ptr @dever_job_clock"));
|
||||
self.wire_write_at(
|
||||
"dever_rt_v1_job_clock_advance",
|
||||
vec![format!("ptr {clock}"), format!("i64 {}", values[0])],
|
||||
span,
|
||||
);
|
||||
return "zeroinitializer".into();
|
||||
}
|
||||
if operation == Intrinsic::TestJobDrain {
|
||||
if self.module.jobs_enabled() {
|
||||
let session = self.temp();
|
||||
self.line(format!("{session} = load ptr, ptr @dever_job_session"));
|
||||
let operation = self.async_operation(
|
||||
"job_drain",
|
||||
vec![format!("ptr {session}"), format!("i64 {}", values[0])],
|
||||
span,
|
||||
);
|
||||
return self.await_operation(&operation, &Type::Int, span, true);
|
||||
}
|
||||
let low = self.compare("icmp sge", &Type::Int, &values[0], "1");
|
||||
let high = self.compare("icmp sle", &Type::Int, &values[0], "10000");
|
||||
let valid = self.reduce_bool("and", vec![low, high], "1");
|
||||
self.application_require(
|
||||
&valid,
|
||||
"test Job drain limit must be between 1 and 10000",
|
||||
span,
|
||||
);
|
||||
return "0".into();
|
||||
}
|
||||
let valid = if operation == Intrinsic::TestAssert {
|
||||
values[0].clone()
|
||||
} else {
|
||||
self.equals(&arguments[0].ty, &values[0], &values[1])
|
||||
.expect("checked comparable assertion")
|
||||
};
|
||||
let success = self.label("assertion_passed");
|
||||
let failure = self.label("assertion_failed");
|
||||
self.line(format!("br i1 {valid}, label %{success}, label %{failure}"));
|
||||
self.start(&failure);
|
||||
if operation == Intrinsic::TestAssert {
|
||||
self.application_fault("assertion failed", span);
|
||||
} else {
|
||||
self.display_literal("assert_eq failed: actual = ", span);
|
||||
let owner = self.guards.len() - 1;
|
||||
let output = self.guards[owner].pointer.clone();
|
||||
self.display_value(&output, &arguments[0].ty, &values[0], span);
|
||||
self.display_append_literal(&output, ", expected = ", span);
|
||||
self.display_value(&output, &arguments[1].ty, &values[1], span);
|
||||
let text = self.temp();
|
||||
self.line(format!("{text} = load ptr, ptr {output}"));
|
||||
self.application_fault_text(&text, span);
|
||||
}
|
||||
self.start(&success);
|
||||
"zeroinitializer".into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Qualify only module-defined symbols. Runtime/LLVM declarations and string
|
||||
/// constants are not names: treating them as text replacement corrupts payloads.
|
||||
pub(super) fn namespace_module(ir: &str, index: usize) -> String {
|
||||
let mut globals = BTreeSet::new();
|
||||
let mut types = BTreeSet::new();
|
||||
for line in ir.lines() {
|
||||
let line = line.trim_start();
|
||||
let (prefix, table) = if line.starts_with('@') || line.starts_with("define ") {
|
||||
('@', &mut globals)
|
||||
} else if line.starts_with('%') && line.contains(" = type ") {
|
||||
('%', &mut types)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
if let Some(start) = line.find(prefix) {
|
||||
let name = line[start + 1..]
|
||||
.chars()
|
||||
.take_while(|character| identifier(*character))
|
||||
.collect::<String>();
|
||||
table.insert(name);
|
||||
}
|
||||
}
|
||||
let mut output = String::with_capacity(ir.len());
|
||||
let mut characters = ir.chars().peekable();
|
||||
let mut quoted = false;
|
||||
let mut comment = false;
|
||||
while let Some(character) = characters.next() {
|
||||
if character == '\n' {
|
||||
comment = false;
|
||||
}
|
||||
if !quoted && character == ';' {
|
||||
comment = true;
|
||||
}
|
||||
if !comment && character == '"' {
|
||||
quoted = !quoted;
|
||||
}
|
||||
output.push(character);
|
||||
if quoted || comment || !matches!(character, '@' | '%') {
|
||||
continue;
|
||||
}
|
||||
let mut name = String::new();
|
||||
while characters
|
||||
.peek()
|
||||
.is_some_and(|character| identifier(*character))
|
||||
{
|
||||
name.push(characters.next().unwrap());
|
||||
}
|
||||
let names = if character == '@' { &globals } else { &types };
|
||||
if names.contains(&name) {
|
||||
write!(output, "case{index}_").unwrap();
|
||||
}
|
||||
output.push_str(&name);
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn identifier(character: char) -> bool {
|
||||
character.is_ascii_alphanumeric() || matches!(character, '_' | '.' | '$' | '-')
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
//! Sequential calls borrow the current transaction; detached descriptors pass NULL.
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct TransactionScope {
|
||||
outer: String,
|
||||
finishing: String,
|
||||
transaction: String,
|
||||
output_guard: Option<usize>,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn begin_transaction(&mut self, function: &crate::hir::Function) {
|
||||
if function.kind != FunctionKind::Transaction
|
||||
|| !self.module.database_function(self.instance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.begin_transaction_scope(
|
||||
self.instance,
|
||||
&output_type(&function.outputs),
|
||||
function.span,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn begin_command_transaction(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
span: Span,
|
||||
) -> bool {
|
||||
if !specialize::writes_database(self.module.program, instance) {
|
||||
return false;
|
||||
}
|
||||
self.begin_transaction_scope(instance, &Type::Unit, span);
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn begin_transaction_scope(
|
||||
&mut self,
|
||||
instance: &Specialization,
|
||||
output: &Type,
|
||||
span: Span,
|
||||
) {
|
||||
let owners = specialize::database_effects(self.module.program, instance);
|
||||
let owner_database = *owners.first().expect("checked database transaction");
|
||||
let outer = self.entry_slot_ir("i1");
|
||||
let finishing = self.entry_slot_ir("i1");
|
||||
writeln!(
|
||||
self.prologue,
|
||||
" store i1 0, ptr {outer}\n store i1 0, ptr {finishing}"
|
||||
)
|
||||
.unwrap();
|
||||
let transaction = self.entry_slot_ir("ptr");
|
||||
let owner = self.register_owned(OwnedKind::Protocol("db_transaction"), transaction.clone());
|
||||
let context = self.entry_slot_ir("ptr");
|
||||
self.line(format!(
|
||||
"store ptr {}, ptr {context}",
|
||||
self.database_context
|
||||
));
|
||||
let output_guard = (*output != Type::Unit)
|
||||
.then(|| self.register_owned(OwnedKind::Output(output.clone()), "%out".into()));
|
||||
self.transactions.push(TransactionScope {
|
||||
outer: outer.clone(),
|
||||
finishing,
|
||||
transaction: transaction.clone(),
|
||||
output_guard,
|
||||
span,
|
||||
});
|
||||
let fresh = self.temp();
|
||||
self.line(format!(
|
||||
"{fresh} = icmp eq ptr {}, null",
|
||||
self.database_context
|
||||
));
|
||||
let begin = self.label("transaction_begin");
|
||||
let ready = self.label("transaction_ready");
|
||||
self.line(format!("br i1 {fresh}, label %{begin}, label %{ready}"));
|
||||
self.start(&begin);
|
||||
let database = self.owner_database(owner_database, span);
|
||||
let operation = self.database_operation("db_begin", vec![format!("ptr {database}")], span);
|
||||
let handle = self.await_database_handle(&operation, span);
|
||||
self.line(format!("store ptr {handle}, ptr {transaction}"));
|
||||
self.mark_live(owner);
|
||||
self.line(format!("store i1 1, ptr {outer}"));
|
||||
self.line(format!("store ptr {handle}, ptr {context}"));
|
||||
self.line(format!("br label %{ready}"));
|
||||
self.start(&ready);
|
||||
let handle = self.temp();
|
||||
self.line(format!("{handle} = load ptr, ptr {context}"));
|
||||
self.database_context = handle;
|
||||
}
|
||||
|
||||
pub(super) fn finish_transaction(&mut self) {
|
||||
for scope in self.transactions.clone().into_iter().rev() {
|
||||
self.finish_transaction_scope(&scope);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn commit_command_transaction(&mut self) {
|
||||
let scope = self
|
||||
.transactions
|
||||
.last()
|
||||
.expect("writing CMD transaction")
|
||||
.clone();
|
||||
self.line(format!("store i1 1, ptr {}", scope.finishing));
|
||||
let transaction = self.temp();
|
||||
self.line(format!(
|
||||
"{transaction} = load ptr, ptr {}",
|
||||
scope.transaction
|
||||
));
|
||||
let operation = self.database_operation(
|
||||
"db_finish",
|
||||
vec![format!("ptr {transaction}"), "i8 1".into()],
|
||||
scope.span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Unit, scope.span, false);
|
||||
self.database_context = "null".into();
|
||||
}
|
||||
|
||||
fn finish_transaction_scope(&mut self, scope: &TransactionScope) {
|
||||
let outer = self.temp();
|
||||
let finishing = self.temp();
|
||||
let cancelling = self.temp();
|
||||
self.line(format!("{outer} = load i1, ptr {}", scope.outer));
|
||||
self.line(format!("{finishing} = load i1, ptr {}", scope.finishing));
|
||||
self.line(format!("{cancelling} = load i1, ptr %cancelling"));
|
||||
let skip = self.temp();
|
||||
let fresh = self.temp();
|
||||
let needed = self.temp();
|
||||
self.line(format!("{skip} = or i1 {finishing}, {cancelling}"));
|
||||
self.line(format!("{fresh} = xor i1 {skip}, 1"));
|
||||
self.line(format!("{needed} = and i1 {outer}, {fresh}"));
|
||||
let finish = self.label("transaction_finish");
|
||||
let done = self.label("transaction_cleaned");
|
||||
self.line(format!("br i1 {needed}, label %{finish}, label %{done}"));
|
||||
self.start(&finish);
|
||||
self.line(format!("store i1 1, ptr {}", scope.finishing));
|
||||
let transaction = self.temp();
|
||||
self.line(format!(
|
||||
"{transaction} = load ptr, ptr {}",
|
||||
scope.transaction
|
||||
));
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = load i32, ptr %exit_status"));
|
||||
let success = self.temp();
|
||||
self.line(format!("{success} = icmp eq i32 {status}, 0"));
|
||||
let commit = self.label("transaction_commit");
|
||||
let rollback = self.label("transaction_rollback");
|
||||
self.line(format!(
|
||||
"br i1 {success}, label %{commit}, label %{rollback}"
|
||||
));
|
||||
self.start(&commit);
|
||||
if let Some(guard) = scope.output_guard {
|
||||
self.mark_live(guard);
|
||||
}
|
||||
let operation = self.database_operation(
|
||||
"db_finish",
|
||||
vec![format!("ptr {transaction}"), "i8 1".into()],
|
||||
scope.span,
|
||||
);
|
||||
self.await_operation(&operation, &Type::Unit, scope.span, false);
|
||||
if let Some(guard) = scope.output_guard {
|
||||
self.disarm(guard);
|
||||
}
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&rollback);
|
||||
let descriptor = format!("@dever_fault_owned_{}", self.module.names[self.instance]);
|
||||
let primary = self.entry_slot_ir("%dever.fault");
|
||||
self.line(format!(
|
||||
"call void @dever_fault_move(ptr %fault, ptr {primary})"
|
||||
));
|
||||
let primary_owner = self.register_owned(OwnedKind::Fault, primary.clone());
|
||||
self.mark_live(primary_owner);
|
||||
let operation = self.database_operation(
|
||||
"db_rollback_take",
|
||||
vec![
|
||||
format!("ptr {transaction}"),
|
||||
format!("ptr {primary}"),
|
||||
format!("ptr {descriptor}"),
|
||||
],
|
||||
scope.span,
|
||||
);
|
||||
self.disarm(primary_owner);
|
||||
let (status, _, _, error) = self.poll_raw(&operation, &Type::Unit);
|
||||
let status = self.poll_status(&status, &error, scope.span);
|
||||
self.line(format!("store i32 {status}, ptr %exit_status"));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
use super::*;
|
||||
use crate::hir::HandlerTarget;
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
pub(super) fn invoke_handler(
|
||||
&mut self,
|
||||
handler: HandlerTarget,
|
||||
arguments: &[(Type, String)],
|
||||
span: Span,
|
||||
) -> (String, Type) {
|
||||
let target = Specialization {
|
||||
function: specialize::resolve_handler(handler, &self.bindings),
|
||||
handlers: Vec::new(),
|
||||
};
|
||||
let output_type = output_type(&self.module.program.functions[target.function].outputs);
|
||||
let (status, output) = self.invoke_values(&target, arguments, &output_type, span);
|
||||
let failed = self.temp();
|
||||
self.line(format!("{failed} = icmp ne i32 {status}, 0"));
|
||||
let fault = self.label("handler_fault");
|
||||
let okay = self.label("handler_ok");
|
||||
self.line(format!("br i1 {failed}, label %{fault}, label %{okay}"));
|
||||
self.start(&fault);
|
||||
self.append_frame(span);
|
||||
self.exit(&status);
|
||||
self.start(&okay);
|
||||
if output_type == Type::Unit {
|
||||
("zeroinitializer".into(), output_type)
|
||||
} else {
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {output}",
|
||||
self.module.ty(&output_type)
|
||||
));
|
||||
(value, output_type)
|
||||
}
|
||||
}
|
||||
|
||||
fn append_output(
|
||||
&mut self,
|
||||
list_pointer: &str,
|
||||
list_guard: usize,
|
||||
list_ty: &Type,
|
||||
element_ty: &Type,
|
||||
element: &str,
|
||||
span: Span,
|
||||
) {
|
||||
let row = self.row_pointer(element_ty, element);
|
||||
let current = self.temp();
|
||||
self.line(format!("{current} = load ptr, ptr {list_pointer}"));
|
||||
self.disarm(list_guard);
|
||||
let appended = self.runtime_call(
|
||||
"dever_rt_v1_list_append_take",
|
||||
vec![format!("ptr {current}"), format!("ptr {row}")],
|
||||
list_ty,
|
||||
false,
|
||||
None,
|
||||
span,
|
||||
);
|
||||
self.line(format!("store ptr {appended}, ptr {list_pointer}"));
|
||||
self.mark_live(list_guard);
|
||||
}
|
||||
|
||||
fn add_numeric(&mut self, ty: &Type, left: &str, right: &str, span: Span) -> String {
|
||||
match ty {
|
||||
Type::Int => self.checked_integer("sadd", left, right, span),
|
||||
Type::Float => {
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = fadd double {left}, {right}"));
|
||||
value
|
||||
}
|
||||
Type::Decimal => {
|
||||
let mut args = self.decimal_parts(left);
|
||||
args.extend(self.decimal_parts(right));
|
||||
self.runtime_call("dever_rt_v1_decimal_add", args, ty, false, None, span)
|
||||
}
|
||||
_ => unreachable!("checked numeric Sum"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn traverse(
|
||||
&mut self,
|
||||
operation: CollectionOp,
|
||||
handler: Option<HandlerTarget>,
|
||||
sequence: SequenceKind,
|
||||
arguments: &[Expression],
|
||||
result: &Expression,
|
||||
) -> String {
|
||||
let source = self.expression(&arguments[0]).expect("validated sequence");
|
||||
let source_guard = self.guards.len() - 1;
|
||||
let element_ty = match &arguments[0].ty {
|
||||
Type::List(element)
|
||||
| Type::Stream(element)
|
||||
| Type::AsyncStream(element)
|
||||
| Type::RowStream(element) => element.as_ref().clone(),
|
||||
Type::Bytes => Type::Int,
|
||||
_ => unreachable!("checked sequence"),
|
||||
};
|
||||
let additional = arguments.get(1).map(|value| {
|
||||
self.expression(value)
|
||||
.expect("validated traversal argument")
|
||||
});
|
||||
let state_ty = matches!(operation, CollectionOp::Reduce | CollectionOp::ReduceUntil)
|
||||
.then(|| arguments[1].ty.clone());
|
||||
let (state_pointer, state_guard) =
|
||||
if let (Some(ty), Some(initial)) = (&state_ty, &additional) {
|
||||
let guard = contains_owned(self.module.program, ty).then(|| self.guards.len() - 1);
|
||||
let pointer = if let Some(guard) = guard {
|
||||
self.guards[guard].pointer.clone()
|
||||
} else {
|
||||
let pointer = self.entry_slot(ty);
|
||||
self.line(format!(
|
||||
"store {} {initial}, ptr {pointer}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
pointer
|
||||
};
|
||||
(Some(pointer), guard)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
// Argument evaluation precedes traversal ownership, as for native loops.
|
||||
let stream_scope = if matches!(
|
||||
sequence,
|
||||
SequenceKind::AsyncStream | SequenceKind::RowStream
|
||||
) {
|
||||
let pointer = self.row_pointer(&arguments[0].ty, &source);
|
||||
let kind = if sequence == SequenceKind::RowStream {
|
||||
OwnedKind::DatabaseStreamScope
|
||||
} else {
|
||||
OwnedKind::AsyncStreamScope
|
||||
};
|
||||
let guard = self.register_owned(kind, pointer);
|
||||
self.mark_live(guard);
|
||||
Some(guard)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (cursor, cursor_guard, length, index_pointer) = match sequence {
|
||||
SequenceKind::List => {
|
||||
self.disarm(source_guard);
|
||||
let cursor = self.runtime_call(
|
||||
"dever_rt_v1_list_cursor_take",
|
||||
vec![format!("ptr {source}")],
|
||||
&Type::Unit,
|
||||
false,
|
||||
Some("ptr"),
|
||||
result.span,
|
||||
);
|
||||
let pointer = self.entry_slot_ir("ptr");
|
||||
self.line(format!("store ptr {cursor}, ptr {pointer}"));
|
||||
let guard = self.register_owned(OwnedKind::ListCursor, pointer);
|
||||
self.mark_live(guard);
|
||||
(Some(cursor), Some(guard), None, None)
|
||||
}
|
||||
SequenceKind::Bytes => {
|
||||
let length = self.runtime_call(
|
||||
"dever_rt_v1_bytes_length",
|
||||
vec![format!("ptr {source}")],
|
||||
&Type::Int,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
);
|
||||
let index = self.entry_slot(&Type::Int);
|
||||
self.line(format!("store i64 0, ptr {index}"));
|
||||
(None, None, Some(length), Some(index))
|
||||
}
|
||||
SequenceKind::Stream | SequenceKind::AsyncStream | SequenceKind::RowStream => {
|
||||
(Some(source.clone()), None, None, None)
|
||||
}
|
||||
};
|
||||
|
||||
let builder = matches!(operation, CollectionOp::Filter)
|
||||
|| operation == CollectionOp::Each && result.ty != Type::Unit;
|
||||
let (list_pointer, list_guard) = if builder {
|
||||
let Type::List(element) = &result.ty else {
|
||||
unreachable!("checked List result")
|
||||
};
|
||||
let list = self.runtime_call(
|
||||
"dever_rt_v1_list_new",
|
||||
vec![
|
||||
format!("ptr @dever_type_{}", self.module.type_index(element)),
|
||||
"ptr null".into(),
|
||||
"i64 0".into(),
|
||||
],
|
||||
&result.ty,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
);
|
||||
let pointer = self.entry_slot(&result.ty);
|
||||
self.line(format!("store ptr {list}, ptr {pointer}"));
|
||||
let guard = self.register_guard(&result.ty, pointer.clone());
|
||||
self.mark_live(guard);
|
||||
(Some(pointer), Some(guard))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let result_pointer = match operation {
|
||||
CollectionOp::Find => {
|
||||
let pointer = self.entry_slot(&result.ty);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {pointer}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
Some(pointer)
|
||||
}
|
||||
CollectionOp::Sum => {
|
||||
let pointer = self.entry_slot(&result.ty);
|
||||
let zero = match result.ty {
|
||||
Type::Int => "0".into(),
|
||||
Type::Float => "0.0".into(),
|
||||
Type::Decimal => self.runtime_call(
|
||||
"dever_rt_v1_decimal_from_int",
|
||||
vec!["i64 0".into()],
|
||||
&Type::Decimal,
|
||||
false,
|
||||
None,
|
||||
result.span,
|
||||
),
|
||||
_ => unreachable!("checked Sum type"),
|
||||
};
|
||||
self.line(format!(
|
||||
"store {} {zero}, ptr {pointer}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
Some(pointer)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let element_pointer = matches!(
|
||||
sequence,
|
||||
SequenceKind::List
|
||||
| SequenceKind::Stream
|
||||
| SequenceKind::AsyncStream
|
||||
| SequenceKind::RowStream
|
||||
)
|
||||
.then(|| self.entry_slot(&element_ty));
|
||||
let element_guard = element_pointer.as_ref().and_then(|pointer| {
|
||||
contains_owned(self.module.program, &element_ty)
|
||||
.then(|| self.register_guard(&element_ty, pointer.clone()))
|
||||
});
|
||||
let loop_label = self.label("sequence_next");
|
||||
let body = self.label("sequence_body");
|
||||
let done = self.label("sequence_done");
|
||||
self.line(format!("br label %{loop_label}"));
|
||||
self.start(&loop_label);
|
||||
let element = if let Some(cursor) = &cursor {
|
||||
let output = element_pointer.as_ref().unwrap();
|
||||
let present = if matches!(
|
||||
sequence,
|
||||
SequenceKind::AsyncStream | SequenceKind::RowStream
|
||||
) {
|
||||
let operation = if sequence == SequenceKind::RowStream {
|
||||
self.database_operation(
|
||||
"db_stream_pull",
|
||||
vec![format!("ptr {cursor}")],
|
||||
result.span,
|
||||
)
|
||||
} else {
|
||||
let operation = self.temp();
|
||||
self.line(format!(
|
||||
"{operation} = call ptr @dever_rt_v1_async_stream_pull(ptr {cursor})"
|
||||
));
|
||||
operation
|
||||
};
|
||||
let (status, row, present, error) = self.poll_raw(&operation, &element_ty);
|
||||
let legacy = self.poll_status(&status, &error, result.span);
|
||||
self.propagate_status(&legacy, result.span, false);
|
||||
// Only the presence branch may load the physical element row.
|
||||
let flag = self.temp();
|
||||
self.line(format!("{flag} = load i8, ptr {present}"));
|
||||
let available = self.temp();
|
||||
self.line(format!("{available} = icmp ne i8 {flag}, 0"));
|
||||
let copy = self.label("stream_copy_row");
|
||||
let copied = self.label("stream_row_ready");
|
||||
self.line(format!("br i1 {available}, label %{copy}, label %{copied}"));
|
||||
self.start(©);
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {row}",
|
||||
self.module.ty(&element_ty)
|
||||
));
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {output}",
|
||||
self.module.ty(&element_ty)
|
||||
));
|
||||
self.line(format!("br label %{copied}"));
|
||||
self.start(&copied);
|
||||
present
|
||||
} else {
|
||||
let present = self.entry_slot_ir("i8");
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let status = self.temp();
|
||||
let symbol = if sequence == SequenceKind::Stream {
|
||||
"stream_pull"
|
||||
} else {
|
||||
"list_cursor_next"
|
||||
};
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_{symbol}(ptr {cursor}, ptr {output}, ptr {present}, ptr {error})"));
|
||||
self.check_runtime_status(&status, &error, result.span);
|
||||
present
|
||||
};
|
||||
let flag = self.temp();
|
||||
self.line(format!("{flag} = load i8, ptr {present}"));
|
||||
let available = self.temp();
|
||||
self.line(format!("{available} = icmp ne i8 {flag}, 0"));
|
||||
self.line(format!("br i1 {available}, label %{body}, label %{done}"));
|
||||
self.start(&body);
|
||||
if let Some(guard) = element_guard {
|
||||
self.mark_live(guard);
|
||||
}
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {output}",
|
||||
self.module.ty(&element_ty)
|
||||
));
|
||||
value
|
||||
} else {
|
||||
let index_pointer = index_pointer.as_ref().unwrap();
|
||||
let index = self.temp();
|
||||
self.line(format!("{index} = load i64, ptr {index_pointer}"));
|
||||
let available = self.temp();
|
||||
self.line(format!(
|
||||
"{available} = icmp slt i64 {index}, {}",
|
||||
length.as_ref().unwrap()
|
||||
));
|
||||
self.line(format!("br i1 {available}, label %{body}, label %{done}"));
|
||||
self.start(&body);
|
||||
let optional = self.runtime_call(
|
||||
"dever_rt_v1_bytes_at",
|
||||
vec![format!("ptr {source}"), format!("i64 {index}")],
|
||||
&Type::Nullable(Box::new(Type::Int)),
|
||||
true,
|
||||
None,
|
||||
result.span,
|
||||
);
|
||||
let next = self.temp();
|
||||
self.line(format!("{next} = add i64 {index}, 1"));
|
||||
self.line(format!("store i64 {next}, ptr {index_pointer}"));
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = extractvalue {{ i1, i64 }} {optional}, 1"
|
||||
));
|
||||
value
|
||||
};
|
||||
|
||||
let projected = if let Some(handler) = handler {
|
||||
let mut parameters = vec![(element_ty.clone(), element.clone())];
|
||||
if let Some(state_ty) = &state_ty {
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {}",
|
||||
self.module.ty(state_ty),
|
||||
state_pointer.as_ref().unwrap()
|
||||
));
|
||||
parameters.push((state_ty.clone(), value));
|
||||
} else if let Some(context) = &additional {
|
||||
parameters.push((arguments[1].ty.clone(), context.clone()));
|
||||
}
|
||||
Some(self.invoke_handler(handler, ¶meters, result.span))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match operation {
|
||||
CollectionOp::Each => {
|
||||
if result.ty != Type::Unit {
|
||||
let (value, ty) = projected.as_ref().unwrap();
|
||||
let guard = contains_owned(self.module.program, ty)
|
||||
.then(|| self.own_value(ty, value, false));
|
||||
self.append_output(
|
||||
list_pointer.as_ref().unwrap(),
|
||||
list_guard.unwrap(),
|
||||
&result.ty,
|
||||
ty,
|
||||
value,
|
||||
result.span,
|
||||
);
|
||||
if let Some(guard) = guard {
|
||||
self.release_guard(guard);
|
||||
}
|
||||
}
|
||||
}
|
||||
CollectionOp::Filter | CollectionOp::Find => {
|
||||
let condition = &projected.as_ref().unwrap().0;
|
||||
let selected = self.label("sequence_selected");
|
||||
let next = self.label("sequence_continue");
|
||||
self.line(format!(
|
||||
"br i1 {condition}, label %{selected}, label %{next}"
|
||||
));
|
||||
self.start(&selected);
|
||||
if operation == CollectionOp::Filter {
|
||||
self.append_output(
|
||||
list_pointer.as_ref().unwrap(),
|
||||
list_guard.unwrap(),
|
||||
&result.ty,
|
||||
&element_ty,
|
||||
&element,
|
||||
result.span,
|
||||
);
|
||||
self.line(format!("br label %{next}"));
|
||||
} else {
|
||||
let found = if element_ty == result.ty {
|
||||
element.clone()
|
||||
} else {
|
||||
let first = self.temp();
|
||||
self.line(format!(
|
||||
"{first} = insertvalue {} undef, i1 1, 0",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
let wrapped = self.temp();
|
||||
self.line(format!(
|
||||
"{wrapped} = insertvalue {} {first}, {} {element}, 1",
|
||||
self.module.ty(&result.ty),
|
||||
self.module.ty(&element_ty)
|
||||
));
|
||||
wrapped
|
||||
};
|
||||
self.line(format!(
|
||||
"store {} {found}, ptr {}",
|
||||
self.module.ty(&result.ty),
|
||||
result_pointer.as_ref().unwrap()
|
||||
));
|
||||
if let Some(guard) = element_guard {
|
||||
self.disarm(guard);
|
||||
}
|
||||
self.line(format!("br label %{done}"));
|
||||
}
|
||||
self.start(&next);
|
||||
}
|
||||
CollectionOp::Reduce | CollectionOp::ReduceUntil => {
|
||||
let (step, step_ty) = projected.as_ref().unwrap();
|
||||
let next_state = if operation == CollectionOp::ReduceUntil {
|
||||
let state = self.temp();
|
||||
self.line(format!(
|
||||
"{state} = extractvalue {} {step}, 0",
|
||||
self.module.ty(step_ty)
|
||||
));
|
||||
state
|
||||
} else {
|
||||
step.clone()
|
||||
};
|
||||
if let Some(guard) = state_guard {
|
||||
self.release_guard(guard);
|
||||
}
|
||||
self.line(format!(
|
||||
"store {} {next_state}, ptr {}",
|
||||
self.module.ty(state_ty.as_ref().unwrap()),
|
||||
state_pointer.as_ref().unwrap()
|
||||
));
|
||||
if let Some(guard) = state_guard {
|
||||
self.mark_live(guard);
|
||||
}
|
||||
if operation == CollectionOp::ReduceUntil {
|
||||
let stop = self.temp();
|
||||
self.line(format!(
|
||||
"{stop} = extractvalue {} {step}, 1",
|
||||
self.module.ty(step_ty)
|
||||
));
|
||||
if let Some(guard) = element_guard {
|
||||
self.release_guard(guard);
|
||||
}
|
||||
self.line(format!("br i1 {stop}, label %{done}, label %{loop_label}"));
|
||||
}
|
||||
}
|
||||
CollectionOp::Sum => {
|
||||
let value = projected
|
||||
.as_ref()
|
||||
.map_or(&element, |projected| &projected.0);
|
||||
let pointer = result_pointer.as_ref().unwrap();
|
||||
let current = self.temp();
|
||||
self.line(format!(
|
||||
"{current} = load {}, ptr {pointer}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
let sum = self.add_numeric(&result.ty, ¤t, value, result.span);
|
||||
self.line(format!(
|
||||
"store {} {sum}, ptr {pointer}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
}
|
||||
_ => unreachable!("preflighted traversal"),
|
||||
}
|
||||
if operation != CollectionOp::ReduceUntil {
|
||||
if let Some(guard) = element_guard {
|
||||
self.release_guard(guard);
|
||||
}
|
||||
self.line(format!("br label %{loop_label}"));
|
||||
}
|
||||
self.start(&done);
|
||||
if let Some(guard) = stream_scope {
|
||||
self.release_guard(guard);
|
||||
}
|
||||
if let Some(guard) = cursor_guard {
|
||||
self.release_guard(guard);
|
||||
}
|
||||
if let (Some(pointer), Some(guard)) = (&list_pointer, list_guard) {
|
||||
let output = self.temp();
|
||||
self.line(format!("{output} = load ptr, ptr {pointer}"));
|
||||
self.disarm(guard);
|
||||
return output;
|
||||
}
|
||||
if let Some(pointer) = &state_pointer {
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {pointer}",
|
||||
self.module.ty(state_ty.as_ref().unwrap())
|
||||
));
|
||||
if let Some(guard) = state_guard {
|
||||
self.disarm(guard);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
if let Some(pointer) = &result_pointer {
|
||||
let value = self.temp();
|
||||
self.line(format!(
|
||||
"{value} = load {}, ptr {pointer}",
|
||||
self.module.ty(&result.ty)
|
||||
));
|
||||
return value;
|
||||
}
|
||||
"zeroinitializer".into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,703 @@
|
||||
//! Concrete codecs consume the checked wire DAG; runtime nodes never enter HIR values.
|
||||
use super::*;
|
||||
use crate::wire::{Node, Policy, Schema};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) enum FieldInput {
|
||||
Record,
|
||||
External,
|
||||
Command,
|
||||
Api,
|
||||
Rest,
|
||||
RestList,
|
||||
}
|
||||
|
||||
pub(super) const DECLARATIONS: &str = "\
|
||||
declare i32 @dever_rt_v1_wire_decode(ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_cmd_decode(ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_cmd_input_field(ptr, ptr, i8, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encode(ptr, ptr, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_is_null(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_bool(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_int(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_float(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_text(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_bytes(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_raw(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_decimal(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_uuid(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_secret(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_datetime(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_date(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_time(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_fields(ptr, ptr, i64, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_field(ptr, ptr, i8, ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_list_len(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_node_list_at(ptr, i64, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_null(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_bool(ptr, i8, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_int(ptr, i64, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_float(ptr, double, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_text(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_bytes(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_json(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_decimal(ptr, i64, i64, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_uuid(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_datetime(ptr, i64, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_date(ptr, i64, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_time(ptr, i64, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_begin_array(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_begin_object(ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_key(ptr, ptr, ptr)
|
||||
declare i32 @dever_rt_v1_wire_encoder_end(ptr, ptr)
|
||||
";
|
||||
|
||||
impl Module<'_> {
|
||||
pub(super) fn wire_names(&mut self, names: &[String]) -> String {
|
||||
let entries = names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let (pointer, length) = self.text_literal(name);
|
||||
format!("{{ ptr, i64 }} {{ ptr {pointer}, i64 {length} }}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let name = format!("@dever_wire_names_{}", self.next_literal);
|
||||
self.next_literal += 1;
|
||||
writeln!(
|
||||
self.declarations,
|
||||
"{name} = private constant [{} x {{ ptr, i64 }}] [{entries}]",
|
||||
names.len()
|
||||
)
|
||||
.unwrap();
|
||||
name
|
||||
}
|
||||
|
||||
pub(super) fn wire_codec(&mut self, ty: &Type, policy: Policy) -> String {
|
||||
let schema = Schema::build(ty, &self.program.types, policy).expect("checked wire schema");
|
||||
if let Some(name) = self.wire_codecs.get(schema.fingerprint()) {
|
||||
return name.clone();
|
||||
}
|
||||
let prefix = format!("dever_wire_{}", self.wire_codecs.len());
|
||||
self.wire_codecs
|
||||
.insert(schema.fingerprint().to_owned(), prefix.clone());
|
||||
let instance = self.instances.first().unwrap().clone();
|
||||
for (id, node) in schema.nodes.iter().enumerate() {
|
||||
let ty = node_type(&schema, id);
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.protocol_callback = true;
|
||||
emitter.start("entry");
|
||||
let value = emitter.decode_wire_node(&schema, node, &prefix, "%node");
|
||||
emitter.line(format!(
|
||||
"store {} {value}, ptr %out",
|
||||
emitter.module.ty(&ty)
|
||||
));
|
||||
let function = emitter.finish_protocol_callback(
|
||||
&format!("{prefix}_d{id}"),
|
||||
"ptr %node, ptr %out, ptr %error",
|
||||
);
|
||||
self.declarations.push_str(&function);
|
||||
if !policy.permits_secret() {
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.protocol_callback = true;
|
||||
emitter.start("entry");
|
||||
let value = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{value} = load {}, ptr %input",
|
||||
emitter.module.ty(&ty)
|
||||
));
|
||||
emitter.encode_wire_node(&schema, node, &prefix, &value);
|
||||
let function = emitter.finish_protocol_callback(
|
||||
&format!("{prefix}_e{id}"),
|
||||
"ptr %input, ptr %encoder, ptr %error",
|
||||
);
|
||||
self.declarations.push_str(&function);
|
||||
}
|
||||
}
|
||||
prefix
|
||||
}
|
||||
|
||||
pub(super) fn wire_callback(&mut self, ty: &Type, policy: Policy, encode: bool) -> String {
|
||||
let schema = Schema::build(ty, &self.program.types, policy).expect("checked schema");
|
||||
let prefix = self.wire_codec(ty, policy);
|
||||
format!("{prefix}_{}{}", if encode { "e" } else { "d" }, schema.root)
|
||||
}
|
||||
|
||||
/// External operation envelopes always retain field names, including a
|
||||
/// single output or an explicitly null field. Their values use the same DAG.
|
||||
pub(super) fn wire_fields_callback(
|
||||
&mut self,
|
||||
fields: &[crate::types::Field],
|
||||
name: &str,
|
||||
policy: Policy,
|
||||
encode: bool,
|
||||
) -> String {
|
||||
let ty = Type::Outputs(fields.to_vec());
|
||||
let callbacks = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let callback = self.wire_callback(&field.ty, policy, encode);
|
||||
(field.name.clone(), field.ty.clone(), callback)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let instance = self.instances.first().unwrap().clone();
|
||||
let mut emitter = FunctionEmitter::new(self, &instance);
|
||||
emitter.asynchronous = false;
|
||||
emitter.protocol_callback = true;
|
||||
emitter.start("entry");
|
||||
let parameters = if encode {
|
||||
emitter.wire_write(
|
||||
"dever_rt_v1_wire_encoder_begin_object",
|
||||
vec!["ptr %encoder".into()],
|
||||
);
|
||||
for (index, (field, _, callback)) in callbacks.iter().enumerate() {
|
||||
let key = emitter.module.wire_names(std::slice::from_ref(field));
|
||||
emitter.wire_write(
|
||||
"dever_rt_v1_wire_encoder_key",
|
||||
vec!["ptr %encoder".into(), format!("ptr {key}")],
|
||||
);
|
||||
let pointer = emitter.temp();
|
||||
emitter.line(format!(
|
||||
"{pointer} = getelementptr {}, ptr %input, i32 0, i32 {index}",
|
||||
emitter.module.ty(&ty)
|
||||
));
|
||||
emitter.wire_write(
|
||||
callback,
|
||||
vec![format!("ptr {pointer}"), "ptr %encoder".into()],
|
||||
);
|
||||
}
|
||||
emitter.wire_write("dever_rt_v1_wire_encoder_end", vec!["ptr %encoder".into()]);
|
||||
"ptr %input, ptr %encoder, ptr %error"
|
||||
} else {
|
||||
let value = emitter.decode_fields(&ty, &callbacks, "%node", FieldInput::External);
|
||||
emitter.line(format!(
|
||||
"store {} {value}, ptr %out",
|
||||
emitter.module.ty(&ty)
|
||||
));
|
||||
"ptr %node, ptr %out, ptr %error"
|
||||
};
|
||||
let body = emitter.finish_protocol_callback(name, parameters);
|
||||
self.declarations.push_str(&body);
|
||||
name.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
fn node_type(schema: &Schema, id: usize) -> Type {
|
||||
match &schema.nodes[id] {
|
||||
Node::Bytes => Type::Bytes,
|
||||
Node::Bool => Type::Bool,
|
||||
Node::Int => Type::Int,
|
||||
Node::Float => Type::Float,
|
||||
Node::Decimal => Type::Decimal,
|
||||
Node::Text => Type::Text,
|
||||
Node::Id => Type::Id,
|
||||
Node::Uuid => Type::Uuid,
|
||||
Node::DateTime => Type::DateTime,
|
||||
Node::Date => Type::Date,
|
||||
Node::Time => Type::Time,
|
||||
Node::Duration => Type::Duration,
|
||||
Node::Json => Type::Json,
|
||||
Node::Secret => Type::Secret,
|
||||
Node::Nullable(child) => Type::Nullable(Box::new(node_type(schema, *child))),
|
||||
Node::List(child) => Type::List(Box::new(node_type(schema, *child))),
|
||||
Node::Record { id, .. } | Node::ModelId { id, .. } => Type::Named(*id),
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEmitter<'_, '_> {
|
||||
fn wire_span(&self) -> Span {
|
||||
self.module.program.functions[self.instance.function].span
|
||||
}
|
||||
|
||||
pub(super) fn wire_write(&mut self, symbol: &str, arguments: Vec<String>) {
|
||||
self.wire_write_at(symbol, arguments, self.wire_span());
|
||||
}
|
||||
|
||||
pub(super) fn wire_write_at(&mut self, symbol: &str, mut arguments: Vec<String>, span: Span) {
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
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);
|
||||
}
|
||||
|
||||
fn decode_wire_child(&mut self, prefix: &str, child: usize, ty: &Type, node: &str) -> String {
|
||||
self.runtime_call(
|
||||
&format!("{prefix}_d{child}"),
|
||||
vec![format!("ptr {node}")],
|
||||
ty,
|
||||
false,
|
||||
None,
|
||||
self.wire_span(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn decode_fields(
|
||||
&mut self,
|
||||
ty: &Type,
|
||||
fields: &[(String, Type, String)],
|
||||
node: &str,
|
||||
input: FieldInput,
|
||||
) -> String {
|
||||
let names = self.module.wire_names(
|
||||
&fields
|
||||
.iter()
|
||||
.map(|(name, _, _)| name.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
if matches!(input, FieldInput::Record | FieldInput::External) {
|
||||
self.wire_write(
|
||||
"dever_rt_v1_wire_node_fields",
|
||||
vec![
|
||||
format!("ptr {node}"),
|
||||
format!("ptr {names}"),
|
||||
format!("i64 {}", fields.len()),
|
||||
],
|
||||
);
|
||||
}
|
||||
let row = self.entry_slot(ty);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {row}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
let guard = self.register_guard(ty, row.clone());
|
||||
self.mark_live(guard);
|
||||
for (index, (_, field, callback)) in fields.iter().enumerate() {
|
||||
let name = self.temp();
|
||||
self.line(format!(
|
||||
"{name} = getelementptr {{ ptr, i64 }}, ptr {names}, i64 {index}"
|
||||
));
|
||||
if *field == Type::Upload {
|
||||
let upload = self.runtime_call(
|
||||
"dever_rt_v1_api_input_upload",
|
||||
vec![format!("ptr {node}"), format!("ptr {name}")],
|
||||
field,
|
||||
false,
|
||||
None,
|
||||
self.wire_span(),
|
||||
);
|
||||
let target = self.temp();
|
||||
self.line(format!(
|
||||
"{target} = getelementptr {}, ptr {row}, i32 0, i32 {index}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
self.line(format!("store ptr {upload}, ptr {target}"));
|
||||
continue;
|
||||
}
|
||||
let optional =
|
||||
matches!(field, Type::Nullable(_)) && !matches!(input, FieldInput::External);
|
||||
let mut arguments = vec![
|
||||
format!("ptr {node}"),
|
||||
format!("ptr {name}"),
|
||||
format!("i8 {}", u8::from(!optional)),
|
||||
];
|
||||
let converted_query = matches!(input, FieldInput::Api)
|
||||
|| matches!(input, FieldInput::RestList) && index < 2;
|
||||
if converted_query {
|
||||
arguments.push(format!(
|
||||
"i32 {}",
|
||||
api_query_kind(self.module.program, field)
|
||||
));
|
||||
}
|
||||
let found = self.runtime_call(
|
||||
match input {
|
||||
FieldInput::Record | FieldInput::External => "dever_rt_v1_wire_node_field",
|
||||
FieldInput::Command => "dever_rt_v1_cmd_input_field",
|
||||
FieldInput::Api => "dever_rt_v1_api_input_field",
|
||||
FieldInput::Rest => "dever_rt_v1_api_input_raw_field",
|
||||
FieldInput::RestList if index < 2 => "dever_rt_v1_api_input_field",
|
||||
FieldInput::RestList => "dever_rt_v1_api_input_raw_field",
|
||||
},
|
||||
arguments,
|
||||
&Type::Nullable(Box::new(Type::Text)),
|
||||
true,
|
||||
None,
|
||||
self.wire_span(),
|
||||
);
|
||||
// CMD Inputs transfers an owned raw Text. Record Nodes remain
|
||||
// borrowed from their enclosing synchronous wire decode callback.
|
||||
let raw_guard = (!matches!(input, FieldInput::Record | FieldInput::External))
|
||||
.then(|| self.own_value(&Type::Nullable(Box::new(Type::Text)), &found, false));
|
||||
let present = self.temp();
|
||||
self.line(format!("{present} = extractvalue {{ i1, ptr }} {found}, 0"));
|
||||
let read = self.label("wire_field");
|
||||
let done = self.label("wire_field_done");
|
||||
self.line(format!("br i1 {present}, label %{read}, label %{done}"));
|
||||
self.start(&read);
|
||||
let child = self.temp();
|
||||
self.line(format!("{child} = extractvalue {{ i1, ptr }} {found}, 1"));
|
||||
let search = matches!(input, FieldInput::RestList) && index == 2;
|
||||
let decoded = if search {
|
||||
let Type::Nullable(inner) = field else {
|
||||
unreachable!()
|
||||
};
|
||||
inner.as_ref()
|
||||
} else {
|
||||
field
|
||||
};
|
||||
let (decode, arguments) = match input {
|
||||
FieldInput::Record | FieldInput::External => {
|
||||
(callback.as_str(), vec![format!("ptr {child}")])
|
||||
}
|
||||
FieldInput::Command | FieldInput::Api | FieldInput::Rest | FieldInput::RestList => {
|
||||
(
|
||||
"dever_rt_v1_wire_decode",
|
||||
vec![
|
||||
format!("ptr {child}"),
|
||||
format!("ptr @dever_type_{}", self.module.type_index(decoded)),
|
||||
format!("ptr @{callback}"),
|
||||
],
|
||||
)
|
||||
}
|
||||
};
|
||||
let mut value =
|
||||
self.runtime_call(decode, arguments, decoded, false, None, self.wire_span());
|
||||
if search {
|
||||
let flag = self.temp();
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{flag} = insertvalue {} zeroinitializer, i1 true, 0",
|
||||
self.module.ty(field)
|
||||
));
|
||||
self.line(format!(
|
||||
"{payload} = insertvalue {} {flag}, {} {value}, 1",
|
||||
self.module.ty(field),
|
||||
self.module.ty(decoded)
|
||||
));
|
||||
value = payload;
|
||||
}
|
||||
let target = self.temp();
|
||||
self.line(format!(
|
||||
"{target} = getelementptr {}, ptr {row}, i32 0, i32 {index}",
|
||||
self.module.ty(ty)
|
||||
));
|
||||
self.line(format!(
|
||||
"store {} {value}, ptr {target}",
|
||||
self.module.ty(field)
|
||||
));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
if let Some(guard) = raw_guard {
|
||||
self.release_guard(guard);
|
||||
}
|
||||
}
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load {}, ptr {row}", self.module.ty(ty)));
|
||||
self.disarm(guard);
|
||||
value
|
||||
}
|
||||
|
||||
fn decode_wire_node(
|
||||
&mut self,
|
||||
schema: &Schema,
|
||||
node: &Node,
|
||||
prefix: &str,
|
||||
input: &str,
|
||||
) -> String {
|
||||
if let Node::Record { id, fields, .. } = node {
|
||||
let fields = fields
|
||||
.iter()
|
||||
.map(|(name, child)| {
|
||||
(
|
||||
name.clone(),
|
||||
node_type(schema, *child),
|
||||
format!("{prefix}_d{child}"),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
return self.decode_fields(&Type::Named(*id), &fields, input, FieldInput::Record);
|
||||
}
|
||||
if let Node::Nullable(child) = node {
|
||||
let ty = Type::Nullable(Box::new(node_type(schema, *child)));
|
||||
let row = self.entry_slot(&ty);
|
||||
self.line(format!(
|
||||
"store {} zeroinitializer, ptr {row}",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
let null = self.runtime_call(
|
||||
"dever_rt_v1_wire_node_is_null",
|
||||
vec![format!("ptr {input}")],
|
||||
&Type::Bool,
|
||||
false,
|
||||
Some("i8"),
|
||||
self.wire_span(),
|
||||
);
|
||||
let some = self.label("wire_some");
|
||||
let done = self.label("wire_optional_done");
|
||||
self.line(format!("br i1 {null}, label %{done}, label %{some}"));
|
||||
self.start(&some);
|
||||
let child_ty = node_type(schema, *child);
|
||||
let value = self.decode_wire_child(prefix, *child, &child_ty, input);
|
||||
let result = self.temp();
|
||||
self.line(format!(
|
||||
"{result} = insertvalue {} {{ i1 true, {} zeroinitializer }}, {} {value}, 1",
|
||||
self.module.ty(&ty),
|
||||
self.module.ty(&child_ty),
|
||||
self.module.ty(&child_ty)
|
||||
));
|
||||
self.line(format!("store {} {result}, ptr {row}", self.module.ty(&ty)));
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load {}, ptr {row}", self.module.ty(&ty)));
|
||||
return value;
|
||||
}
|
||||
if let Node::List(child) = node {
|
||||
return self.decode_wire_list(schema, *child, prefix, input);
|
||||
}
|
||||
let (kind, ty, physical) = match node {
|
||||
Node::Bytes => ("bytes", Type::Bytes, None),
|
||||
Node::Bool => ("bool", Type::Bool, Some("i8")),
|
||||
Node::Int | Node::ModelId { .. } | Node::Duration => ("int", Type::Int, None),
|
||||
Node::Float => ("float", Type::Float, None),
|
||||
Node::Decimal => ("decimal", Type::Decimal, None),
|
||||
Node::Text | Node::Id => ("text", Type::Text, None),
|
||||
Node::Json => ("raw", Type::Json, None),
|
||||
Node::Uuid => ("uuid", Type::Uuid, None),
|
||||
Node::Secret => ("secret", Type::Secret, None),
|
||||
Node::DateTime => ("datetime", Type::DateTime, None),
|
||||
Node::Date => ("date", Type::Date, None),
|
||||
Node::Time => ("time", Type::Time, None),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.runtime_call(
|
||||
&format!("dever_rt_v1_wire_node_{kind}"),
|
||||
vec![format!("ptr {input}")],
|
||||
&ty,
|
||||
false,
|
||||
physical,
|
||||
self.wire_span(),
|
||||
)
|
||||
}
|
||||
|
||||
fn decode_wire_list(
|
||||
&mut self,
|
||||
schema: &Schema,
|
||||
child: usize,
|
||||
prefix: &str,
|
||||
input: &str,
|
||||
) -> String {
|
||||
let element = node_type(schema, child);
|
||||
let ty = Type::List(Box::new(element.clone()));
|
||||
let list = self.runtime_call(
|
||||
"dever_rt_v1_list_new",
|
||||
vec![
|
||||
format!("ptr @dever_type_{}", self.module.type_index(&element)),
|
||||
"ptr null".into(),
|
||||
"i64 0".into(),
|
||||
],
|
||||
&ty,
|
||||
false,
|
||||
None,
|
||||
self.wire_span(),
|
||||
);
|
||||
let guard = self.own_value(&ty, &list, false);
|
||||
let slot = self.guards[guard].pointer.clone();
|
||||
let length = self.runtime_call(
|
||||
"dever_rt_v1_wire_node_list_len",
|
||||
vec![format!("ptr {input}")],
|
||||
&Type::Int,
|
||||
false,
|
||||
None,
|
||||
self.wire_span(),
|
||||
);
|
||||
let index = self.entry_slot_ir("i64");
|
||||
self.line(format!("store i64 0, ptr {index}"));
|
||||
let next = self.label("wire_list_next");
|
||||
let body = self.label("wire_list_item");
|
||||
let done = self.label("wire_list_done");
|
||||
self.line(format!("br label %{next}"));
|
||||
self.start(&next);
|
||||
let position = self.temp();
|
||||
self.line(format!("{position} = load i64, ptr {index}"));
|
||||
let more = self.temp();
|
||||
self.line(format!("{more} = icmp ult i64 {position}, {length}"));
|
||||
self.line(format!("br i1 {more}, label %{body}, label %{done}"));
|
||||
self.start(&body);
|
||||
let first = self.guards.len();
|
||||
let node = self.protocol_call(
|
||||
"dever_rt_v1_wire_node_list_at",
|
||||
vec![format!("ptr {input}"), format!("i64 {position}")],
|
||||
"ptr",
|
||||
self.wire_span(),
|
||||
);
|
||||
let value = self.decode_wire_child(prefix, child, &element, &node);
|
||||
self.own_value(&element, &value, false);
|
||||
let item = self.row_pointer(&element, &value);
|
||||
let old = self.temp();
|
||||
self.line(format!("{old} = load ptr, ptr {slot}"));
|
||||
self.disarm(guard);
|
||||
let appended = self.runtime_call(
|
||||
"dever_rt_v1_list_append_take",
|
||||
vec![format!("ptr {old}"), format!("ptr {item}")],
|
||||
&ty,
|
||||
false,
|
||||
None,
|
||||
self.wire_span(),
|
||||
);
|
||||
self.line(format!("store ptr {appended}, ptr {slot}"));
|
||||
self.mark_live(guard);
|
||||
self.release_from(first);
|
||||
let increment = self.temp();
|
||||
self.line(format!("{increment} = add i64 {position}, 1"));
|
||||
self.line(format!("store i64 {increment}, ptr {index}"));
|
||||
self.line(format!("br label %{next}"));
|
||||
self.start(&done);
|
||||
let value = self.temp();
|
||||
self.line(format!("{value} = load ptr, ptr {slot}"));
|
||||
self.disarm(guard);
|
||||
value
|
||||
}
|
||||
|
||||
fn encode_wire_node(&mut self, schema: &Schema, node: &Node, prefix: &str, value: &str) {
|
||||
if let Node::Nullable(child) = node {
|
||||
let ty = Type::Nullable(Box::new(node_type(schema, *child)));
|
||||
let present = self.temp();
|
||||
self.line(format!(
|
||||
"{present} = extractvalue {} {value}, 0",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
let some = self.label("wire_encode_some");
|
||||
let none = self.label("wire_encode_none");
|
||||
let done = self.label("wire_encode_done");
|
||||
self.line(format!("br i1 {present}, label %{some}, label %{none}"));
|
||||
self.start(&none);
|
||||
self.wire_write("dever_rt_v1_wire_encoder_null", vec!["ptr %encoder".into()]);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&some);
|
||||
let payload = self.temp();
|
||||
self.line(format!(
|
||||
"{payload} = extractvalue {} {value}, 1",
|
||||
self.module.ty(&ty)
|
||||
));
|
||||
self.encode_wire_child(schema, *child, prefix, &payload);
|
||||
self.line(format!("br label %{done}"));
|
||||
self.start(&done);
|
||||
return;
|
||||
}
|
||||
if let Node::Record { id, fields, .. } = node {
|
||||
self.wire_write(
|
||||
"dever_rt_v1_wire_encoder_begin_object",
|
||||
vec!["ptr %encoder".into()],
|
||||
);
|
||||
for (index, (name, child)) in fields.iter().enumerate() {
|
||||
let key = self.module.wire_names(std::slice::from_ref(name));
|
||||
self.wire_write(
|
||||
"dever_rt_v1_wire_encoder_key",
|
||||
vec!["ptr %encoder".into(), format!("ptr {key}")],
|
||||
);
|
||||
let field = self.temp();
|
||||
self.line(format!("{field} = extractvalue %T{id} {value}, {index}"));
|
||||
self.encode_wire_child(schema, *child, prefix, &field);
|
||||
}
|
||||
self.wire_write("dever_rt_v1_wire_encoder_end", vec!["ptr %encoder".into()]);
|
||||
return;
|
||||
}
|
||||
if let Node::List(child) = node {
|
||||
self.encode_wire_list(schema, *child, prefix, value);
|
||||
return;
|
||||
}
|
||||
let mut args = vec!["ptr %encoder".into()];
|
||||
let (kind, llvm) = match node {
|
||||
Node::Bytes => ("bytes", "ptr"),
|
||||
Node::Bool => ("bool", "i8"),
|
||||
Node::Int | Node::Duration | Node::ModelId { .. } => ("int", "i64"),
|
||||
Node::Float => ("float", "double"),
|
||||
Node::Text | Node::Id => ("text", "ptr"),
|
||||
Node::Json => ("json", "ptr"),
|
||||
Node::Decimal => ("decimal", ""),
|
||||
Node::Uuid => ("uuid", "ptr"),
|
||||
Node::DateTime => ("datetime", "i64"),
|
||||
Node::Date => ("date", "i64"),
|
||||
Node::Time => ("time", "i64"),
|
||||
_ => unreachable!("checked output wire node"),
|
||||
};
|
||||
if matches!(node, Node::Decimal) {
|
||||
args.extend(self.decimal_parts(value));
|
||||
} else if matches!(node, Node::Bool) {
|
||||
let byte = self.temp();
|
||||
self.line(format!("{byte} = zext i1 {value} to i8"));
|
||||
args.push(format!("i8 {byte}"));
|
||||
} else {
|
||||
args.push(format!("{llvm} {value}"));
|
||||
}
|
||||
self.wire_write(&format!("dever_rt_v1_wire_encoder_{kind}"), args);
|
||||
}
|
||||
|
||||
fn encode_wire_child(&mut self, schema: &Schema, child: usize, prefix: &str, value: &str) {
|
||||
let row = self.row_pointer(&node_type(schema, child), value);
|
||||
self.wire_write(
|
||||
&format!("{prefix}_e{child}"),
|
||||
vec![format!("ptr {row}"), "ptr %encoder".into()],
|
||||
);
|
||||
}
|
||||
|
||||
fn encode_wire_list(&mut self, schema: &Schema, child: usize, prefix: &str, value: &str) {
|
||||
self.encode_list_callback(
|
||||
&node_type(schema, child),
|
||||
&format!("{prefix}_e{child}"),
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn encode_list_callback(&mut self, element: &Type, callback: &str, value: &str) {
|
||||
self.wire_write(
|
||||
"dever_rt_v1_wire_encoder_begin_array",
|
||||
vec!["ptr %encoder".into()],
|
||||
);
|
||||
let retained = self.temp();
|
||||
self.line(format!(
|
||||
"{retained} = call ptr @dever_rt_v1_list_retain(ptr {value})"
|
||||
));
|
||||
let cursor = self.protocol_call(
|
||||
"dever_rt_v1_list_cursor_take",
|
||||
vec![format!("ptr {retained}")],
|
||||
"ptr",
|
||||
self.wire_span(),
|
||||
);
|
||||
let slot = self.entry_slot_ir("ptr");
|
||||
self.line(format!("store ptr {cursor}, ptr {slot}"));
|
||||
let cursor_guard = self.register_owned(OwnedKind::ListCursor, slot);
|
||||
self.mark_live(cursor_guard);
|
||||
let row = self.entry_slot(element);
|
||||
let guard = self.register_guard(element, row.clone());
|
||||
let present = self.entry_slot_ir("i8");
|
||||
let error = self.entry_slot_ir("{ ptr, i64 }");
|
||||
let next = self.label("wire_encode_next");
|
||||
let body = self.label("wire_encode_item");
|
||||
let done = self.label("wire_encode_list_done");
|
||||
self.line(format!("br label %{next}"));
|
||||
self.start(&next);
|
||||
let status = self.temp();
|
||||
self.line(format!("{status} = call i32 @dever_rt_v1_list_cursor_next(ptr {cursor}, ptr {row}, ptr {present}, ptr {error})"));
|
||||
self.check_runtime_status(&status, &error, self.wire_span());
|
||||
let flag = self.temp();
|
||||
self.line(format!("{flag} = load i8, ptr {present}"));
|
||||
let more = self.temp();
|
||||
self.line(format!("{more} = icmp ne i8 {flag}, 0"));
|
||||
self.line(format!("br i1 {more}, label %{body}, label %{done}"));
|
||||
self.start(&body);
|
||||
self.mark_live(guard);
|
||||
self.wire_write(callback, vec![format!("ptr {row}"), "ptr %encoder".into()]);
|
||||
self.release_guard(guard);
|
||||
self.line(format!("br label %{next}"));
|
||||
self.start(&done);
|
||||
self.release_guard(cursor_guard);
|
||||
self.wire_write("dever_rt_v1_wire_encoder_end", vec!["ptr %encoder".into()]);
|
||||
}
|
||||
}
|
||||
|
||||
fn api_query_kind(program: &Program, ty: &Type) -> u32 {
|
||||
let schema = Schema::build(ty, &program.types, Policy::ApiInput).expect("checked API schema");
|
||||
match schema.query_kind() {
|
||||
"Int" => 1,
|
||||
"Bool" => 2,
|
||||
"Float" => 3,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user