iter 23.2.2: codegen — declare @ail_str_eq + eq__Str body intercept
This commit is contained in:
@@ -8,6 +8,7 @@ declare i32 @printf(ptr, ...)
|
||||
declare i32 @puts(ptr)
|
||||
declare ptr @GC_malloc(i64)
|
||||
declare i32 @strcmp(ptr, ptr)
|
||||
declare zeroext i1 @ail_str_eq(ptr, ptr)
|
||||
declare i64 @llvm.fptosi.sat.i64.f64(double)
|
||||
|
||||
@ail_hello_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_hello_main_adapter, ptr null }
|
||||
|
||||
@@ -8,6 +8,7 @@ declare i32 @printf(ptr, ...)
|
||||
declare i32 @puts(ptr)
|
||||
declare ptr @GC_malloc(i64)
|
||||
declare i32 @strcmp(ptr, ptr)
|
||||
declare zeroext i1 @ail_str_eq(ptr, ptr)
|
||||
declare i64 @llvm.fptosi.sat.i64.f64(double)
|
||||
|
||||
@ail_list_sum_list_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_list_sum_list_adapter, ptr null }
|
||||
|
||||
@@ -8,6 +8,7 @@ declare i32 @printf(ptr, ...)
|
||||
declare i32 @puts(ptr)
|
||||
declare ptr @GC_malloc(i64)
|
||||
declare i32 @strcmp(ptr, ptr)
|
||||
declare zeroext i1 @ail_str_eq(ptr, ptr)
|
||||
declare i64 @llvm.fptosi.sat.i64.f64(double)
|
||||
|
||||
@ail_max3_max_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_max3_max_adapter, ptr null }
|
||||
|
||||
@@ -8,6 +8,7 @@ declare i32 @printf(ptr, ...)
|
||||
declare i32 @puts(ptr)
|
||||
declare ptr @GC_malloc(i64)
|
||||
declare i32 @strcmp(ptr, ptr)
|
||||
declare zeroext i1 @ail_str_eq(ptr, ptr)
|
||||
declare i64 @llvm.fptosi.sat.i64.f64(double)
|
||||
|
||||
@ail_sum_sum_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_sum_sum_adapter, ptr null }
|
||||
|
||||
@@ -8,6 +8,7 @@ declare i32 @printf(ptr, ...)
|
||||
declare i32 @puts(ptr)
|
||||
declare ptr @GC_malloc(i64)
|
||||
declare i32 @strcmp(ptr, ptr)
|
||||
declare zeroext i1 @ail_str_eq(ptr, ptr)
|
||||
declare i64 @llvm.fptosi.sat.i64.f64(double)
|
||||
|
||||
@ail_ws_lib_add_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_ws_lib_add_adapter, ptr null }
|
||||
|
||||
@@ -473,6 +473,14 @@ fn lower_workspace_inner(ws: &Workspace, alloc: AllocStrategy) -> Result<String>
|
||||
// `icmp eq i32 0`. NUL-terminated strings make this a one-liner;
|
||||
// libc supplies `strcmp` so no extra link flag is needed.
|
||||
out.push_str("declare i32 @strcmp(ptr, ptr)\n");
|
||||
// Iter 23.2: `runtime/str.c::ail_str_eq` backs the prelude's
|
||||
// `eq__Str` mono symbol (see `try_emit_primitive_instance_body`).
|
||||
// Declared unconditionally — the codegen intercept emits a call
|
||||
// to it whenever the monomorphiser synthesises `eq__Str` in any
|
||||
// module; the .o supplying the symbol is linked unconditionally
|
||||
// by `ail build` (see `crates/ail/src/main.rs::build_to`). The
|
||||
// `zeroext i1` return matches clang's lowering of C `_Bool`.
|
||||
out.push_str("declare zeroext i1 @ail_str_eq(ptr, ptr)\n");
|
||||
// Floats iter 4.4: saturating fp-to-int intrinsic for
|
||||
// float_to_int_truncate. NaN → 0, +Inf → i64::MAX, -Inf →
|
||||
// i64::MIN, finite-out-of-range saturates, finite-in-range
|
||||
@@ -1051,6 +1059,13 @@ impl<'a> Emitter<'a> {
|
||||
self.body.push_str(&sig);
|
||||
self.start_block("entry");
|
||||
|
||||
// Iter 23.2: primitive-instance body intercept. Returns true if
|
||||
// the body was emitted in full; we then skip the normal body
|
||||
// lowering for this fn.
|
||||
if self.try_emit_primitive_instance_body(&f.name, &llvm_param_tys, &llvm_ret)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (val, val_ty) = self.lower_term(&f.body)?;
|
||||
if !self.block_terminated {
|
||||
if val_ty != llvm_ret {
|
||||
@@ -2410,6 +2425,56 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 23.2: hand-rolled body for monomorphiser-synthesised
|
||||
/// primitive instance methods whose natural lambda-lowering would
|
||||
/// not produce the spec-mandated IR shape. Currently the only
|
||||
/// inhabitant is `eq__Str`, which must call `@ail_str_eq` (the
|
||||
/// stable AILang-namespaced Str-equality ABI in `runtime/str.c`)
|
||||
/// rather than the `@strcmp + icmp eq i32 0` shape that
|
||||
/// `lower_eq`'s Str arm would emit if we let the lambda body
|
||||
/// `(== x y)` go through the normal path. Returns `Ok(true)` if
|
||||
/// the body was emitted (caller must return immediately from
|
||||
/// `emit_fn`); `Ok(false)` lets `emit_fn` continue with the
|
||||
/// normal body lowering.
|
||||
///
|
||||
/// Future iters add `compare__Str` here (23.3); int/bool primitive
|
||||
/// methods (`eq__Int`, `eq__Bool`, `compare__Int`, `compare__Bool`)
|
||||
/// ride the natural `lower_eq` / `lower_compare` dispatch and do
|
||||
/// not need a hand-rolled body.
|
||||
fn try_emit_primitive_instance_body(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
param_tys: &[String],
|
||||
ret_ty: &str,
|
||||
) -> Result<bool> {
|
||||
match fn_name {
|
||||
"eq__Str" => {
|
||||
if param_tys != ["ptr", "ptr"] || ret_ty != "i1" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"eq__Str body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (ptr, ptr) -> i1)"
|
||||
)));
|
||||
}
|
||||
// The two params are the two most recently pushed locals;
|
||||
// `emit_fn` populated `self.locals` from `f.params` before
|
||||
// dispatching here. Pull their SSA names without assuming
|
||||
// a specific surface-level binder name.
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
let dst = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {dst} = call zeroext i1 @ail_str_eq(ptr {a_ssa}, ptr {b_ssa})\n"
|
||||
));
|
||||
self.body.push_str(&format!(" ret i1 {dst}\n"));
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
Ok(true)
|
||||
}
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 16e: lower a `==` call after the two operands have been
|
||||
/// emitted. Dispatches on the resolved AIL type of the arg side
|
||||
/// (both sides have the same type after typecheck). The `_a_ll`
|
||||
@@ -3359,4 +3424,61 @@ mod tests {
|
||||
"format string `%g\\n` not interned: {ir}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 23.2: codegen intercepts a fn named `eq__Str` (the
|
||||
/// monomorphiser's synthesised symbol for `Eq Str.eq`) and emits
|
||||
/// a two-instruction body that calls @ail_str_eq, regardless of
|
||||
/// what the lambda body in the source would lower to. Pinned with
|
||||
/// a synthetic FnDef so the test does not depend on prelude
|
||||
/// auto-injection.
|
||||
#[test]
|
||||
fn eq_str_mono_symbol_emits_ail_str_eq_call() {
|
||||
let m = Module {
|
||||
schema: SCHEMA.into(),
|
||||
name: "prelude".into(),
|
||||
imports: vec![],
|
||||
defs: vec![
|
||||
Def::Fn(FnDef {
|
||||
name: "eq__Str".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![Type::str_(), Type::str_()],
|
||||
ret: Box::new(Type::bool_()),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
params: vec!["x".into(), "y".into()],
|
||||
// Body is a placeholder — the intercept must
|
||||
// ignore it. We use Lit::Bool false so even if the
|
||||
// intercept misfires the test still compiles.
|
||||
body: Term::Lit { lit: Literal::Bool { value: false } },
|
||||
suppress: vec![],
|
||||
doc: None,
|
||||
}),
|
||||
Def::Fn(FnDef {
|
||||
name: "main".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![],
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
suppress: vec![],
|
||||
doc: None,
|
||||
}),
|
||||
],
|
||||
};
|
||||
let ir = emit_ir(&m).unwrap();
|
||||
assert!(
|
||||
ir.contains("declare zeroext i1 @ail_str_eq(ptr, ptr)"),
|
||||
"header missing @ail_str_eq declaration; ir was:\n{ir}"
|
||||
);
|
||||
assert!(
|
||||
ir.contains("call zeroext i1 @ail_str_eq("),
|
||||
"eq__Str body must call @ail_str_eq; ir was:\n{ir}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user