From 7d9ab4d29de81cc621d9e268f4cbb8efd6c79390 Mon Sep 17 00:00:00 2001 From: Brummel Date: Tue, 2 Jun 2026 02:02:17 +0200 Subject: [PATCH] refactor(codegen): parameterise the copy-pasted IR-emission families MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emitted IR is byte-identical for every case — proven by the IR-pin suites (eq_primitives_pin, ord_int_intercept_ir_pin, the raw_buf_* and drop/leak pins) and the full e2e suite, all green. intercepts.rs: - The twelve emit_rawbuf_{new,get,set,size}_{int,float,bool} functions were copy-paste across three element types, differing only in the element width (8/8/1) and the LLVM load/store type (i64/double/i1). Collapsed to four parameterised cores; the element variants are thin call sites passing the (width, type) pair. - Replaced the repeated, off-by-one-prone parameter-SSA extraction idiom (`locals[n-2]`, `locals[n-1]`) with an Emitter::last_param_ssas helper. - Factored the shared IR-Str bytes GEP out of emit_eq_str / emit_compare_str into emit_str_bytes_gep. drop.rs: - The three emit_*_drop_fn_for_type methods shared one wrapping shape (monomorphic short-circuit, else loop over instantiations dispatching to a *_for_instantiation helper); hoisted into emit_drop_fn_wrapper taking the per-instantiation emitter as a fn pointer. - emit_flat_intrinsic_drop_fn / _partial_drop_fn differed only in the symbol prefix and an ignored mask param; merged into one inner emitter. No INTERCEPTS-registry or (intrinsic)-marker changes. --- crates/ailang-codegen/src/drop.rs | 118 +++++----- crates/ailang-codegen/src/intercepts.rs | 283 ++++++++++-------------- crates/ailang-codegen/src/lib.rs | 15 ++ 3 files changed, 196 insertions(+), 220 deletions(-) diff --git a/crates/ailang-codegen/src/drop.rs b/crates/ailang-codegen/src/drop.rs index 9d33cd5..4c3b2f8 100644 --- a/crates/ailang-codegen/src/drop.rs +++ b/crates/ailang-codegen/src/drop.rs @@ -78,25 +78,53 @@ impl<'a> Emitter<'a> { pub(crate) fn emit_drop_fn_for_type(&mut self, td: &TypeDef) { // Monomorphic / intrinsic ADTs: one drop fn, empty subst, no // suffix — byte-identical to the pre-leg-C emission. - let key = (self.module_name.to_string(), td.name.clone()); - if !self.drop_monos.is_suffixed(&key) { - self.emit_drop_fn_for_instantiation(td, &BTreeMap::new(), ""); - return; - } // Polymorphic ADTs: one drop fn per concrete instantiation // collected workspace-wide (leg C). Each fn substitutes the // declared type-vars to the instantiation's concrete args so // the dec-vs-skip decision is made on the *monomorph* field // type (a value-type field is an inline scalar → skipped; a // heap field is dec'd via its own per-monomorph drop symbol). - for args in self.drop_monos.instantiations(&key) { - let subst: BTreeMap = - td.vars.iter().cloned().zip(args.iter().cloned()).collect(); - let suffix = self - .drop_monos - .suffix_for(&key, &args) - .unwrap_or_default(); - self.emit_drop_fn_for_instantiation(td, &subst, &suffix); + self.emit_drop_fn_wrapper(td, Self::emit_drop_fn_for_instantiation); + } + + /// Shared wrapper for [`Self::emit_drop_fn_for_type`], + /// [`Self::emit_iterative_drop_fn_for_type`], and + /// [`Self::emit_partial_drop_fn_for_type`]: the three differ only in + /// which `*_for_instantiation` emitter they dispatch to. For a + /// monomorphic / intrinsic ADT (`!is_suffixed`) emit exactly one fn + /// with an empty subst and empty suffix; for a leg-C polymorphic + /// ADT emit one fn per concrete instantiation, in + /// `drop_monos.instantiations` order. The set and order of emitted + /// functions — hence the emitted IR — is identical to the + /// pre-dedup form. The instantiation plan is materialised up front + /// so all `self.drop_monos` reads finish before `emit` borrows + /// `&mut self`. + fn emit_drop_fn_wrapper( + &mut self, + td: &TypeDef, + emit: fn(&mut Self, &TypeDef, &BTreeMap, &str), + ) { + let key = (self.module_name.to_string(), td.name.clone()); + if !self.drop_monos.is_suffixed(&key) { + emit(self, td, &BTreeMap::new(), ""); + return; + } + let plan: Vec<(BTreeMap, String)> = self + .drop_monos + .instantiations(&key) + .into_iter() + .map(|args| { + let subst: BTreeMap = + td.vars.iter().cloned().zip(args.iter().cloned()).collect(); + let suffix = self + .drop_monos + .suffix_for(&key, &args) + .unwrap_or_default(); + (subst, suffix) + }) + .collect(); + for (subst, suffix) in &plan { + emit(self, td, subst, suffix); } } @@ -263,20 +291,7 @@ impl<'a> Emitter<'a> { /// worklist entry shape. The mono-typed version captures the /// stack-overflow-on-long-self-chains problem fully. pub(crate) fn emit_iterative_drop_fn_for_type(&mut self, td: &TypeDef) { - let key = (self.module_name.to_string(), td.name.clone()); - if !self.drop_monos.is_suffixed(&key) { - self.emit_iterative_drop_fn_for_instantiation(td, &BTreeMap::new(), ""); - return; - } - for args in self.drop_monos.instantiations(&key) { - let subst: BTreeMap = - td.vars.iter().cloned().zip(args.iter().cloned()).collect(); - let suffix = self - .drop_monos - .suffix_for(&key, &args) - .unwrap_or_default(); - self.emit_iterative_drop_fn_for_instantiation(td, &subst, &suffix); - } + self.emit_drop_fn_wrapper(td, Self::emit_iterative_drop_fn_for_instantiation); } /// emit one iterative `drop__` body (worklist variant). @@ -742,20 +757,7 @@ impl<'a> Emitter<'a> { /// cascade points — the unmoved fields go through their own /// `drop__` which itself decides recursive vs iterative). pub(crate) fn emit_partial_drop_fn_for_type(&mut self, td: &TypeDef) { - let key = (self.module_name.to_string(), td.name.clone()); - if !self.drop_monos.is_suffixed(&key) { - self.emit_partial_drop_fn_for_instantiation(td, &BTreeMap::new(), ""); - return; - } - for args in self.drop_monos.instantiations(&key) { - let subst: BTreeMap = - td.vars.iter().cloned().zip(args.iter().cloned()).collect(); - let suffix = self - .drop_monos - .suffix_for(&key, &args) - .unwrap_or_default(); - self.emit_partial_drop_fn_for_instantiation(td, &subst, &suffix); - } + self.emit_drop_fn_wrapper(td, Self::emit_partial_drop_fn_for_instantiation); } /// emit one `partial_drop__` body. `subst` / @@ -856,20 +858,7 @@ impl<'a> Emitter<'a> { /// and `@ailang_rc_dec` call form as the generic drop's `join` /// tail, minus the tag-switch. pub(crate) fn emit_flat_intrinsic_drop_fn(&mut self, td: &TypeDef) { - let m = self.module_name; - let tname = &td.name; - let mut out = String::new(); - out.push_str(&format!("define void @drop_{m}_{tname}(ptr %p) {{\n")); - out.push_str("entry:\n"); - out.push_str(" %is_null = icmp eq ptr %p, null\n"); - out.push_str(" br i1 %is_null, label %ret, label %live\n"); - out.push_str("live:\n"); - out.push_str(" call void @ailang_rc_dec(ptr %p)\n"); - out.push_str(" br label %ret\n"); - out.push_str("ret:\n"); - out.push_str(" ret void\n"); - out.push_str("}\n\n"); - self.body.push_str(&out); + self.emit_flat_intrinsic_drop_fn_inner(td, "drop", "ptr %p"); } /// raw-buf.4: emit the flat `partial_drop__` for an @@ -880,11 +869,28 @@ impl<'a> Emitter<'a> { /// field — so the `%mask` arg is ignored and the body is the same /// flat outer rc-dec as the drop fn. pub(crate) fn emit_flat_intrinsic_partial_drop_fn(&mut self, td: &TypeDef) { + self.emit_flat_intrinsic_drop_fn_inner(td, "partial_drop", "ptr %p, i64 %mask"); + } + + /// Shared body of [`Self::emit_flat_intrinsic_drop_fn`] and + /// [`Self::emit_flat_intrinsic_partial_drop_fn`]. The two differ + /// only in the symbol `prefix` (`drop` vs `partial_drop`) and the + /// `params` list (`ptr %p` vs `ptr %p, i64 %mask`); everything from + /// `entry:` down — the null guard, the single `@ailang_rc_dec`, and + /// the `ret` tail — is identical, since the `%mask` is ignored. + /// Interpolating `prefix`/`params` reproduces each function's old + /// IR byte-for-byte. + fn emit_flat_intrinsic_drop_fn_inner( + &mut self, + td: &TypeDef, + prefix: &str, + params: &str, + ) { let m = self.module_name; let tname = &td.name; let mut out = String::new(); out.push_str(&format!( - "define void @partial_drop_{m}_{tname}(ptr %p, i64 %mask) {{\n" + "define void @{prefix}_{m}_{tname}({params}) {{\n" )); out.push_str("entry:\n"); out.push_str(" %is_null = icmp eq ptr %p, null\n"); diff --git a/crates/ailang-codegen/src/intercepts.rs b/crates/ailang-codegen/src/intercepts.rs index e276cd2..191303f 100644 --- a/crates/ailang-codegen/src/intercepts.rs +++ b/crates/ailang-codegen/src/intercepts.rs @@ -293,26 +293,36 @@ pub(crate) fn check_sig( // replaced by `Ok(())` because the shim wraps the bool. // --------------------------------------------------------------- +/// Emit the `getelementptr inbounds i8, ptr , i64 8` that walks +/// an IR-Str pointer (which lands on the `len` field of the packed +/// slab) forward to its bytes pointer, returning the fresh SSA holding +/// the result. Shared by `emit_eq_str` / `emit_compare_str`. Because +/// `fresh_ssa` allocates sequentially, calling this twice in a row +/// yields the same `%vK`, `%vK+1` pair — and the same two GEP lines in +/// the same order — as the inlined form it replaces, so the emitted IR +/// is byte-identical. +fn emit_str_bytes_gep(emitter: &mut Emitter<'_>, src: &str) -> String { + let bytes = emitter.fresh_ssa(); + emitter.body.push_str(&format!( + " {bytes} = getelementptr inbounds i8, ptr {src}, i64 8\n" + )); + bytes +} + pub(crate) fn emit_eq_str(emitter: &mut Emitter<'_>) -> Result<()> { // 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 = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); // IR-Str pointers now land on the // `len`-field of the packed-struct slab; @ail_str_eq's // strcmp-based body needs the bytes pointer 8 bytes // further on. - let a_bytes = emitter.fresh_ssa(); - let b_bytes = emitter.fresh_ssa(); - emitter.body.push_str(&format!( - " {a_bytes} = getelementptr inbounds i8, ptr {a_ssa}, i64 8\n" - )); - emitter.body.push_str(&format!( - " {b_bytes} = getelementptr inbounds i8, ptr {b_ssa}, i64 8\n" - )); + let a_bytes = emit_str_bytes_gep(emitter, &a_ssa); + let b_bytes = emit_str_bytes_gep(emitter, &b_ssa); let dst = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {dst} = call zeroext i1 @ail_str_eq(ptr {a_bytes}, ptr {b_bytes})\n" @@ -324,9 +334,9 @@ pub(crate) fn emit_eq_str(emitter: &mut Emitter<'_>) -> Result<()> { } pub(crate) fn emit_compare_int(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); emitter.emit_compare_ladder( &format!("icmp slt i64 {a_ssa}, {b_ssa}"), &format!("icmp eq i64 {a_ssa}, {b_ssa}"), @@ -335,9 +345,9 @@ pub(crate) fn emit_compare_int(emitter: &mut Emitter<'_>) -> Result<()> { } pub(crate) fn emit_compare_bool(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); emitter.emit_compare_ladder( &format!("icmp ult i1 {a_ssa}, {b_ssa}"), &format!("icmp eq i1 {a_ssa}, {b_ssa}"), @@ -346,21 +356,15 @@ pub(crate) fn emit_compare_bool(emitter: &mut Emitter<'_>) -> Result<()> { } pub(crate) fn emit_compare_str(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); // IR-Str pointers now land on the // `len`-field of the packed-struct slab; @ail_str_compare's // strcmp-based body needs the bytes pointer 8 bytes // further on. - let a_bytes = emitter.fresh_ssa(); - let b_bytes = emitter.fresh_ssa(); - emitter.body.push_str(&format!( - " {a_bytes} = getelementptr inbounds i8, ptr {a_ssa}, i64 8\n" - )); - emitter.body.push_str(&format!( - " {b_bytes} = getelementptr inbounds i8, ptr {b_ssa}, i64 8\n" - )); + let a_bytes = emit_str_bytes_gep(emitter, &a_ssa); + let b_bytes = emit_str_bytes_gep(emitter, &b_ssa); let cmp_res = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {cmp_res} = call i32 @ail_str_compare(ptr {a_bytes}, ptr {b_bytes})\n" @@ -373,9 +377,9 @@ pub(crate) fn emit_compare_str(emitter: &mut Emitter<'_>) -> Result<()> { } pub(crate) fn emit_eq_int(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); let r = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {r} = icmp eq i64 {a_ssa}, {b_ssa}\n" @@ -387,9 +391,9 @@ pub(crate) fn emit_eq_int(emitter: &mut Emitter<'_>) -> Result<()> { } pub(crate) fn emit_eq_bool(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); let r = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {r} = icmp eq i1 {a_ssa}, {b_ssa}\n" @@ -411,9 +415,9 @@ pub(crate) fn emit_eq_unit(emitter: &mut Emitter<'_>) -> Result<()> { } pub(crate) fn emit_float_eq(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); let r = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {r} = fcmp oeq double {a_ssa}, {b_ssa}\n" @@ -427,9 +431,9 @@ pub(crate) fn emit_float_eq(emitter: &mut Emitter<'_>) -> Result<()> { pub(crate) fn emit_float_ne(emitter: &mut Emitter<'_>) -> Result<()> { // `fcmp une` ("unordered or not-equal"), not `one`, // mirroring float-semantics.md: NaN != NaN must be true. - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); let r = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {r} = fcmp une double {a_ssa}, {b_ssa}\n" @@ -441,9 +445,9 @@ pub(crate) fn emit_float_ne(emitter: &mut Emitter<'_>) -> Result<()> { } pub(crate) fn emit_float_lt(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); let r = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {r} = fcmp olt double {a_ssa}, {b_ssa}\n" @@ -455,9 +459,9 @@ pub(crate) fn emit_float_lt(emitter: &mut Emitter<'_>) -> Result<()> { } pub(crate) fn emit_float_le(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); let r = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {r} = fcmp ole double {a_ssa}, {b_ssa}\n" @@ -469,9 +473,9 @@ pub(crate) fn emit_float_le(emitter: &mut Emitter<'_>) -> Result<()> { } pub(crate) fn emit_float_gt(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); let r = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {r} = fcmp ogt double {a_ssa}, {b_ssa}\n" @@ -483,9 +487,9 @@ pub(crate) fn emit_float_gt(emitter: &mut Emitter<'_>) -> Result<()> { } pub(crate) fn emit_float_ge(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let a_ssa = emitter.locals[n - 2].1.clone(); - let b_ssa = emitter.locals[n - 1].1.clone(); + let params = emitter.last_param_ssas(2); + let a_ssa = params[0].clone(); + let b_ssa = params[1].clone(); let r = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {r} = fcmp oge double {a_ssa}, {b_ssa}\n" @@ -543,15 +547,23 @@ pub(crate) fn emit_ne_int(emitter: &mut Emitter<'_>) -> Result<()> { // `@ailang_rc_alloc` is hardcoded (not `alloc.fn_name()`). // --------------------------------------------------------------- -// --- Int variants (element type i64, width 8) --- +// Parameterised cores. The three element variants (Int, Float, Bool) +// differ in exactly two axes: the per-element byte width used in the +// index `mul` / capacity `mul` (Int=8, Float=8, Bool=1) and the LLVM +// load/store type used by `get`/`set` (Int=i64, Float=double, Bool=i1). +// `new`/`size` carry only the width axis (their slab header is always +// i64). Each core emits the IR for one operation; the public per-type +// wrappers below pass the right `(width, elem_ty)` pair, so the emitted +// IR for every variant is byte-identical to the pre-dedup form. -pub(crate) fn emit_rawbuf_new_int(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let cap = emitter.locals[n - 1].1.clone(); // i64 capacity +fn emit_rawbuf_new(emitter: &mut Emitter<'_>, elem_width: u64) -> Result<()> { + let cap = emitter.last_param_ssas(1)[0].clone(); // i64 capacity let elems_bytes = emitter.fresh_ssa(); let total = emitter.fresh_ssa(); let slab = emitter.fresh_ssa(); - emitter.body.push_str(&format!(" {elems_bytes} = mul i64 {cap}, 8\n")); + emitter + .body + .push_str(&format!(" {elems_bytes} = mul i64 {cap}, {elem_width}\n")); emitter.body.push_str(&format!(" {total} = add i64 {elems_bytes}, 8\n")); emitter .body @@ -563,49 +575,56 @@ pub(crate) fn emit_rawbuf_new_int(emitter: &mut Emitter<'_>) -> Result<()> { Ok(()) } -pub(crate) fn emit_rawbuf_get_int(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let b = emitter.locals[n - 2].1.clone(); - let i = emitter.locals[n - 1].1.clone(); +fn emit_rawbuf_get( + emitter: &mut Emitter<'_>, + elem_width: u64, + elem_ty: &str, +) -> Result<()> { + let params = emitter.last_param_ssas(2); + let b = params[0].clone(); + let i = params[1].clone(); let off = emitter.fresh_ssa(); let byteoff = emitter.fresh_ssa(); let ptr = emitter.fresh_ssa(); let v = emitter.fresh_ssa(); - emitter.body.push_str(&format!(" {off} = mul i64 {i}, 8\n")); + emitter.body.push_str(&format!(" {off} = mul i64 {i}, {elem_width}\n")); emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n")); emitter.body.push_str(&format!( " {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n" )); - emitter.body.push_str(&format!(" {v} = load i64, ptr {ptr}\n")); - emitter.body.push_str(&format!(" ret i64 {v}\n")); + emitter.body.push_str(&format!(" {v} = load {elem_ty}, ptr {ptr}\n")); + emitter.body.push_str(&format!(" ret {elem_ty} {v}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } -pub(crate) fn emit_rawbuf_set_int(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let b = emitter.locals[n - 3].1.clone(); - let i = emitter.locals[n - 2].1.clone(); - let v = emitter.locals[n - 1].1.clone(); +fn emit_rawbuf_set( + emitter: &mut Emitter<'_>, + elem_width: u64, + elem_ty: &str, +) -> Result<()> { + let params = emitter.last_param_ssas(3); + let b = params[0].clone(); + let i = params[1].clone(); + let v = params[2].clone(); let off = emitter.fresh_ssa(); let byteoff = emitter.fresh_ssa(); let ptr = emitter.fresh_ssa(); - emitter.body.push_str(&format!(" {off} = mul i64 {i}, 8\n")); + emitter.body.push_str(&format!(" {off} = mul i64 {i}, {elem_width}\n")); emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n")); emitter.body.push_str(&format!( " {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n" )); - emitter.body.push_str(&format!(" store i64 {v}, ptr {ptr}\n")); + emitter.body.push_str(&format!(" store {elem_ty} {v}, ptr {ptr}\n")); emitter.body.push_str(&format!(" ret ptr {b}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } -pub(crate) fn emit_rawbuf_size_int(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let b = emitter.locals[n - 1].1.clone(); +fn emit_rawbuf_size(emitter: &mut Emitter<'_>) -> Result<()> { + let b = emitter.last_param_ssas(1)[0].clone(); let sz = emitter.fresh_ssa(); emitter.body.push_str(&format!(" {sz} = load i64, ptr {b}\n")); emitter.body.push_str(&format!(" ret i64 {sz}\n")); @@ -614,122 +633,58 @@ pub(crate) fn emit_rawbuf_size_int(emitter: &mut Emitter<'_>) -> Result<()> { Ok(()) } +// --- Int variants (element type i64, width 8) --- + +pub(crate) fn emit_rawbuf_new_int(emitter: &mut Emitter<'_>) -> Result<()> { + emit_rawbuf_new(emitter, 8) +} + +pub(crate) fn emit_rawbuf_get_int(emitter: &mut Emitter<'_>) -> Result<()> { + emit_rawbuf_get(emitter, 8, "i64") +} + +pub(crate) fn emit_rawbuf_set_int(emitter: &mut Emitter<'_>) -> Result<()> { + emit_rawbuf_set(emitter, 8, "i64") +} + +pub(crate) fn emit_rawbuf_size_int(emitter: &mut Emitter<'_>) -> Result<()> { + emit_rawbuf_size(emitter) +} + // --- Float variants (element type double, width 8) --- pub(crate) fn emit_rawbuf_new_float(emitter: &mut Emitter<'_>) -> Result<()> { - // Header always i64; element width 8 — byte-identical to Int's new. - emit_rawbuf_new_int(emitter) + emit_rawbuf_new(emitter, 8) } pub(crate) fn emit_rawbuf_get_float(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let b = emitter.locals[n - 2].1.clone(); - let i = emitter.locals[n - 1].1.clone(); - let off = emitter.fresh_ssa(); - let byteoff = emitter.fresh_ssa(); - let ptr = emitter.fresh_ssa(); - let v = emitter.fresh_ssa(); - emitter.body.push_str(&format!(" {off} = mul i64 {i}, 8\n")); - emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n")); - emitter.body.push_str(&format!( - " {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n" - )); - emitter.body.push_str(&format!(" {v} = load double, ptr {ptr}\n")); - emitter.body.push_str(&format!(" ret double {v}\n")); - emitter.body.push_str("}\n\n"); - emitter.block_terminated = true; - Ok(()) + emit_rawbuf_get(emitter, 8, "double") } pub(crate) fn emit_rawbuf_set_float(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let b = emitter.locals[n - 3].1.clone(); - let i = emitter.locals[n - 2].1.clone(); - let v = emitter.locals[n - 1].1.clone(); - let off = emitter.fresh_ssa(); - let byteoff = emitter.fresh_ssa(); - let ptr = emitter.fresh_ssa(); - emitter.body.push_str(&format!(" {off} = mul i64 {i}, 8\n")); - emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n")); - emitter.body.push_str(&format!( - " {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n" - )); - emitter.body.push_str(&format!(" store double {v}, ptr {ptr}\n")); - emitter.body.push_str(&format!(" ret ptr {b}\n")); - emitter.body.push_str("}\n\n"); - emitter.block_terminated = true; - Ok(()) + emit_rawbuf_set(emitter, 8, "double") } pub(crate) fn emit_rawbuf_size_float(emitter: &mut Emitter<'_>) -> Result<()> { - // Header always i64 — byte-identical to Int's size. - emit_rawbuf_size_int(emitter) + emit_rawbuf_size(emitter) } // --- Bool variants (element type i1, width 1) --- pub(crate) fn emit_rawbuf_new_bool(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let cap = emitter.locals[n - 1].1.clone(); // i64 capacity - let elems_bytes = emitter.fresh_ssa(); - let total = emitter.fresh_ssa(); - let slab = emitter.fresh_ssa(); - // element width 1 byte for Bool. - emitter.body.push_str(&format!(" {elems_bytes} = mul i64 {cap}, 1\n")); - emitter.body.push_str(&format!(" {total} = add i64 {elems_bytes}, 8\n")); - emitter - .body - .push_str(&format!(" {slab} = call ptr @ailang_rc_alloc(i64 {total})\n")); - emitter.body.push_str(&format!(" store i64 {cap}, ptr {slab}\n")); - emitter.body.push_str(&format!(" ret ptr {slab}\n")); - emitter.body.push_str("}\n\n"); - emitter.block_terminated = true; - Ok(()) + emit_rawbuf_new(emitter, 1) } pub(crate) fn emit_rawbuf_get_bool(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let b = emitter.locals[n - 2].1.clone(); - let i = emitter.locals[n - 1].1.clone(); - let off = emitter.fresh_ssa(); - let byteoff = emitter.fresh_ssa(); - let ptr = emitter.fresh_ssa(); - let v = emitter.fresh_ssa(); - emitter.body.push_str(&format!(" {off} = mul i64 {i}, 1\n")); - emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n")); - emitter.body.push_str(&format!( - " {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n" - )); - emitter.body.push_str(&format!(" {v} = load i1, ptr {ptr}\n")); - emitter.body.push_str(&format!(" ret i1 {v}\n")); - emitter.body.push_str("}\n\n"); - emitter.block_terminated = true; - Ok(()) + emit_rawbuf_get(emitter, 1, "i1") } pub(crate) fn emit_rawbuf_set_bool(emitter: &mut Emitter<'_>) -> Result<()> { - let n = emitter.locals.len(); - let b = emitter.locals[n - 3].1.clone(); - let i = emitter.locals[n - 2].1.clone(); - let v = emitter.locals[n - 1].1.clone(); - let off = emitter.fresh_ssa(); - let byteoff = emitter.fresh_ssa(); - let ptr = emitter.fresh_ssa(); - emitter.body.push_str(&format!(" {off} = mul i64 {i}, 1\n")); - emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n")); - emitter.body.push_str(&format!( - " {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n" - )); - emitter.body.push_str(&format!(" store i1 {v}, ptr {ptr}\n")); - emitter.body.push_str(&format!(" ret ptr {b}\n")); - emitter.body.push_str("}\n\n"); - emitter.block_terminated = true; - Ok(()) + emit_rawbuf_set(emitter, 1, "i1") } pub(crate) fn emit_rawbuf_size_bool(emitter: &mut Emitter<'_>) -> Result<()> { - // Header always i64 — byte-identical to Int's size. - emit_rawbuf_size_int(emitter) + emit_rawbuf_size(emitter) } #[cfg(test)] diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index 65458d0..e193daf 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -3097,6 +3097,21 @@ impl<'a> Emitter<'a> { self.counter += 1; format!("%v{}", self.counter) } + + /// Return the SSA names of the last `count` locals, in their + /// original (ascending-index) order. Intercept emit fns use this + /// to pull their parameter SSAs off `self.locals` without the + /// off-by-one-prone `locals[len - k]` idiom. For `count == 2` the + /// result is `[locals[n-2].1, locals[n-1].1]`, i.e. first param + /// then second — identical to the hand-rolled extraction it + /// replaces. + pub(crate) fn last_param_ssas(&self, count: usize) -> Vec { + let n = self.locals.len(); + self.locals[n - count..] + .iter() + .map(|l| l.1.clone()) + .collect() + } pub(crate) fn fresh_id(&mut self) -> u64 { self.counter += 1; self.counter