feat: publish Dever core with project scaffolding and versioned AI skills

This commit is contained in:
Antigravity committed 2026-10-04 13:37:30 +00:00
1 parent 70fa73cdc2
commit ac458ffa7f
960 files changed
+221319 -1163

No files matched your search

+319
View File
@@ -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
}
}
+570
View File
@@ -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, &parameters);
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,
)
}
}
+326
View File
@@ -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
}
}
+798
View File
@@ -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, &parameters);
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);
}
}
+677
View File
@@ -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);
}
}
+367
View File
@@ -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
}
}
+292
View File
@@ -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)
}
}
+238
View File
@@ -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
}
}
+356
View File
@@ -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}"));
}
}
+984
View File
@@ -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)
}
}
+436
View File
@@ -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,
)
}
}
+310
View File
@@ -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)));
}
}
}
+227
View File
@@ -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)
}
}
+272
View File
@@ -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)
}
}
+549
View File
@@ -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
}
}
+291
View File
@@ -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)
}
}
+390
View File
@@ -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, &parameters);
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);
}
}
+207
View File
@@ -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,
},
)
}
}
+512
View File
@@ -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, &parameter.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);
}
}
+534
View File
@@ -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
}
}
+470
View File
@@ -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
}
}
+89
View File
@@ -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))
}
}
+388
View File
@@ -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);
}
}
+99
View File
@@ -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()
}
}
}
+266
View File
@@ -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);
}
}
+176
View File
@@ -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())
}
+644
View File
@@ -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);
}
}
+296
View File
@@ -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(&not_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
}
}
+931
View File
@@ -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, &parameters);
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,
);
}
}
+154
View File
@@ -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
}
}
}
+276
View File
@@ -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, &parameters);
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, '_' | '.' | '$' | '-')
}
+190
View File
@@ -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);
}
}
+526
View File
@@ -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(&copy);
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, &parameters, 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, &current, 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()
}
}
+703
View File
@@ -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,
}
}