refactor(codegen): parameterise the copy-pasted IR-emission families

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.
This commit is contained in:
2026-06-02 02:02:17 +02:00
parent 7ae92d3e60
commit 7d9ab4d29d
3 changed files with 196 additions and 220 deletions
+62 -56
View File
@@ -78,25 +78,53 @@ impl<'a> Emitter<'a> {
pub(crate) fn emit_drop_fn_for_type(&mut self, td: &TypeDef) { pub(crate) fn emit_drop_fn_for_type(&mut self, td: &TypeDef) {
// Monomorphic / intrinsic ADTs: one drop fn, empty subst, no // Monomorphic / intrinsic ADTs: one drop fn, empty subst, no
// suffix — byte-identical to the pre-leg-C emission. // 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 // Polymorphic ADTs: one drop fn per concrete instantiation
// collected workspace-wide (leg C). Each fn substitutes the // collected workspace-wide (leg C). Each fn substitutes the
// declared type-vars to the instantiation's concrete args so // declared type-vars to the instantiation's concrete args so
// the dec-vs-skip decision is made on the *monomorph* field // the dec-vs-skip decision is made on the *monomorph* field
// type (a value-type field is an inline scalar → skipped; a // type (a value-type field is an inline scalar → skipped; a
// heap field is dec'd via its own per-monomorph drop symbol). // heap field is dec'd via its own per-monomorph drop symbol).
for args in self.drop_monos.instantiations(&key) { self.emit_drop_fn_wrapper(td, Self::emit_drop_fn_for_instantiation);
let subst: BTreeMap<String, Type> = }
td.vars.iter().cloned().zip(args.iter().cloned()).collect();
let suffix = self /// Shared wrapper for [`Self::emit_drop_fn_for_type`],
.drop_monos /// [`Self::emit_iterative_drop_fn_for_type`], and
.suffix_for(&key, &args) /// [`Self::emit_partial_drop_fn_for_type`]: the three differ only in
.unwrap_or_default(); /// which `*_for_instantiation` emitter they dispatch to. For a
self.emit_drop_fn_for_instantiation(td, &subst, &suffix); /// 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<String, Type>, &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, Type>, String)> = self
.drop_monos
.instantiations(&key)
.into_iter()
.map(|args| {
let subst: BTreeMap<String, Type> =
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 /// worklist entry shape. The mono-typed version captures the
/// stack-overflow-on-long-self-chains problem fully. /// stack-overflow-on-long-self-chains problem fully.
pub(crate) fn emit_iterative_drop_fn_for_type(&mut self, td: &TypeDef) { pub(crate) fn emit_iterative_drop_fn_for_type(&mut self, td: &TypeDef) {
let key = (self.module_name.to_string(), td.name.clone()); self.emit_drop_fn_wrapper(td, Self::emit_iterative_drop_fn_for_instantiation);
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<String, Type> =
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);
}
} }
/// emit one iterative `drop_<m>_<T><suffix>` body (worklist variant). /// emit one iterative `drop_<m>_<T><suffix>` body (worklist variant).
@@ -742,20 +757,7 @@ impl<'a> Emitter<'a> {
/// cascade points — the unmoved fields go through their own /// cascade points — the unmoved fields go through their own
/// `drop_<m>_<F>` which itself decides recursive vs iterative). /// `drop_<m>_<F>` which itself decides recursive vs iterative).
pub(crate) fn emit_partial_drop_fn_for_type(&mut self, td: &TypeDef) { pub(crate) fn emit_partial_drop_fn_for_type(&mut self, td: &TypeDef) {
let key = (self.module_name.to_string(), td.name.clone()); self.emit_drop_fn_wrapper(td, Self::emit_partial_drop_fn_for_instantiation);
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<String, Type> =
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);
}
} }
/// emit one `partial_drop_<m>_<T><suffix>` body. `subst` / /// emit one `partial_drop_<m>_<T><suffix>` body. `subst` /
@@ -856,20 +858,7 @@ impl<'a> Emitter<'a> {
/// and `@ailang_rc_dec` call form as the generic drop's `join` /// and `@ailang_rc_dec` call form as the generic drop's `join`
/// tail, minus the tag-switch. /// tail, minus the tag-switch.
pub(crate) fn emit_flat_intrinsic_drop_fn(&mut self, td: &TypeDef) { pub(crate) fn emit_flat_intrinsic_drop_fn(&mut self, td: &TypeDef) {
let m = self.module_name; self.emit_flat_intrinsic_drop_fn_inner(td, "drop", "ptr %p");
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);
} }
/// raw-buf.4: emit the flat `partial_drop_<m>_<T>` for an /// raw-buf.4: emit the flat `partial_drop_<m>_<T>` for an
@@ -880,11 +869,28 @@ impl<'a> Emitter<'a> {
/// field — so the `%mask` arg is ignored and the body is the same /// field — so the `%mask` arg is ignored and the body is the same
/// flat outer rc-dec as the drop fn. /// flat outer rc-dec as the drop fn.
pub(crate) fn emit_flat_intrinsic_partial_drop_fn(&mut self, td: &TypeDef) { 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 m = self.module_name;
let tname = &td.name; let tname = &td.name;
let mut out = String::new(); let mut out = String::new();
out.push_str(&format!( 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("entry:\n");
out.push_str(" %is_null = icmp eq ptr %p, null\n"); out.push_str(" %is_null = icmp eq ptr %p, null\n");
+119 -164
View File
@@ -293,26 +293,36 @@ pub(crate) fn check_sig(
// replaced by `Ok(())` because the shim wraps the bool. // replaced by `Ok(())` because the shim wraps the bool.
// --------------------------------------------------------------- // ---------------------------------------------------------------
/// Emit the `getelementptr inbounds i8, ptr <src>, 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<()> { pub(crate) fn emit_eq_str(emitter: &mut Emitter<'_>) -> Result<()> {
// The two params are the two most recently pushed locals; // The two params are the two most recently pushed locals;
// `emit_fn` populated `self.locals` from `f.params` before // `emit_fn` populated `self.locals` from `f.params` before
// dispatching here. Pull their SSA names without assuming // dispatching here. Pull their SSA names without assuming
// a specific surface-level binder name. // a specific surface-level binder name.
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
// IR-Str pointers now land on the // IR-Str pointers now land on the
// `len`-field of the packed-struct slab; @ail_str_eq's // `len`-field of the packed-struct slab; @ail_str_eq's
// strcmp-based body needs the bytes pointer 8 bytes // strcmp-based body needs the bytes pointer 8 bytes
// further on. // further on.
let a_bytes = emitter.fresh_ssa(); let a_bytes = emit_str_bytes_gep(emitter, &a_ssa);
let b_bytes = emitter.fresh_ssa(); let b_bytes = emit_str_bytes_gep(emitter, &b_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 dst = emitter.fresh_ssa(); let dst = emitter.fresh_ssa();
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {dst} = call zeroext i1 @ail_str_eq(ptr {a_bytes}, ptr {b_bytes})\n" " {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<()> { pub(crate) fn emit_compare_int(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
emitter.emit_compare_ladder( emitter.emit_compare_ladder(
&format!("icmp slt i64 {a_ssa}, {b_ssa}"), &format!("icmp slt i64 {a_ssa}, {b_ssa}"),
&format!("icmp eq 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<()> { pub(crate) fn emit_compare_bool(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
emitter.emit_compare_ladder( emitter.emit_compare_ladder(
&format!("icmp ult i1 {a_ssa}, {b_ssa}"), &format!("icmp ult i1 {a_ssa}, {b_ssa}"),
&format!("icmp eq 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<()> { pub(crate) fn emit_compare_str(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
// IR-Str pointers now land on the // IR-Str pointers now land on the
// `len`-field of the packed-struct slab; @ail_str_compare's // `len`-field of the packed-struct slab; @ail_str_compare's
// strcmp-based body needs the bytes pointer 8 bytes // strcmp-based body needs the bytes pointer 8 bytes
// further on. // further on.
let a_bytes = emitter.fresh_ssa(); let a_bytes = emit_str_bytes_gep(emitter, &a_ssa);
let b_bytes = emitter.fresh_ssa(); let b_bytes = emit_str_bytes_gep(emitter, &b_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 cmp_res = emitter.fresh_ssa(); let cmp_res = emitter.fresh_ssa();
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {cmp_res} = call i32 @ail_str_compare(ptr {a_bytes}, ptr {b_bytes})\n" " {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<()> { pub(crate) fn emit_eq_int(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
let r = emitter.fresh_ssa(); let r = emitter.fresh_ssa();
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {r} = icmp eq i64 {a_ssa}, {b_ssa}\n" " {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<()> { pub(crate) fn emit_eq_bool(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
let r = emitter.fresh_ssa(); let r = emitter.fresh_ssa();
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {r} = icmp eq i1 {a_ssa}, {b_ssa}\n" " {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<()> { pub(crate) fn emit_float_eq(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
let r = emitter.fresh_ssa(); let r = emitter.fresh_ssa();
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {r} = fcmp oeq double {a_ssa}, {b_ssa}\n" " {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<()> { pub(crate) fn emit_float_ne(emitter: &mut Emitter<'_>) -> Result<()> {
// `fcmp une` ("unordered or not-equal"), not `one`, // `fcmp une` ("unordered or not-equal"), not `one`,
// mirroring float-semantics.md: NaN != NaN must be true. // mirroring float-semantics.md: NaN != NaN must be true.
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
let r = emitter.fresh_ssa(); let r = emitter.fresh_ssa();
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {r} = fcmp une double {a_ssa}, {b_ssa}\n" " {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<()> { pub(crate) fn emit_float_lt(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
let r = emitter.fresh_ssa(); let r = emitter.fresh_ssa();
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {r} = fcmp olt double {a_ssa}, {b_ssa}\n" " {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<()> { pub(crate) fn emit_float_le(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
let r = emitter.fresh_ssa(); let r = emitter.fresh_ssa();
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {r} = fcmp ole double {a_ssa}, {b_ssa}\n" " {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<()> { pub(crate) fn emit_float_gt(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
let r = emitter.fresh_ssa(); let r = emitter.fresh_ssa();
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {r} = fcmp ogt double {a_ssa}, {b_ssa}\n" " {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<()> { pub(crate) fn emit_float_ge(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let params = emitter.last_param_ssas(2);
let a_ssa = emitter.locals[n - 2].1.clone(); let a_ssa = params[0].clone();
let b_ssa = emitter.locals[n - 1].1.clone(); let b_ssa = params[1].clone();
let r = emitter.fresh_ssa(); let r = emitter.fresh_ssa();
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {r} = fcmp oge double {a_ssa}, {b_ssa}\n" " {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()`). // `@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<()> { fn emit_rawbuf_new(emitter: &mut Emitter<'_>, elem_width: u64) -> Result<()> {
let n = emitter.locals.len(); let cap = emitter.last_param_ssas(1)[0].clone(); // i64 capacity
let cap = emitter.locals[n - 1].1.clone(); // i64 capacity
let elems_bytes = emitter.fresh_ssa(); let elems_bytes = emitter.fresh_ssa();
let total = emitter.fresh_ssa(); let total = emitter.fresh_ssa();
let slab = 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.push_str(&format!(" {total} = add i64 {elems_bytes}, 8\n"));
emitter emitter
.body .body
@@ -563,49 +575,56 @@ pub(crate) fn emit_rawbuf_new_int(emitter: &mut Emitter<'_>) -> Result<()> {
Ok(()) Ok(())
} }
pub(crate) fn emit_rawbuf_get_int(emitter: &mut Emitter<'_>) -> Result<()> { fn emit_rawbuf_get(
let n = emitter.locals.len(); emitter: &mut Emitter<'_>,
let b = emitter.locals[n - 2].1.clone(); elem_width: u64,
let i = emitter.locals[n - 1].1.clone(); 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 off = emitter.fresh_ssa();
let byteoff = emitter.fresh_ssa(); let byteoff = emitter.fresh_ssa();
let ptr = emitter.fresh_ssa(); let ptr = emitter.fresh_ssa();
let v = 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!(" {byteoff} = add i64 {off}, 8\n"));
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n" " {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!(" {v} = load {elem_ty}, ptr {ptr}\n"));
emitter.body.push_str(&format!(" ret i64 {v}\n")); emitter.body.push_str(&format!(" ret {elem_ty} {v}\n"));
emitter.body.push_str("}\n\n"); emitter.body.push_str("}\n\n");
emitter.block_terminated = true; emitter.block_terminated = true;
Ok(()) Ok(())
} }
pub(crate) fn emit_rawbuf_set_int(emitter: &mut Emitter<'_>) -> Result<()> { fn emit_rawbuf_set(
let n = emitter.locals.len(); emitter: &mut Emitter<'_>,
let b = emitter.locals[n - 3].1.clone(); elem_width: u64,
let i = emitter.locals[n - 2].1.clone(); elem_ty: &str,
let v = emitter.locals[n - 1].1.clone(); ) -> 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 off = emitter.fresh_ssa();
let byteoff = emitter.fresh_ssa(); let byteoff = emitter.fresh_ssa();
let ptr = 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!(" {byteoff} = add i64 {off}, 8\n"));
emitter.body.push_str(&format!( emitter.body.push_str(&format!(
" {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n" " {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(&format!(" ret ptr {b}\n"));
emitter.body.push_str("}\n\n"); emitter.body.push_str("}\n\n");
emitter.block_terminated = true; emitter.block_terminated = true;
Ok(()) Ok(())
} }
pub(crate) fn emit_rawbuf_size_int(emitter: &mut Emitter<'_>) -> Result<()> { fn emit_rawbuf_size(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); let b = emitter.last_param_ssas(1)[0].clone();
let b = emitter.locals[n - 1].1.clone();
let sz = emitter.fresh_ssa(); let sz = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {sz} = load i64, ptr {b}\n")); emitter.body.push_str(&format!(" {sz} = load i64, ptr {b}\n"));
emitter.body.push_str(&format!(" ret i64 {sz}\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(()) 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) --- // --- Float variants (element type double, width 8) ---
pub(crate) fn emit_rawbuf_new_float(emitter: &mut Emitter<'_>) -> Result<()> { 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(emitter, 8)
emit_rawbuf_new_int(emitter)
} }
pub(crate) fn emit_rawbuf_get_float(emitter: &mut Emitter<'_>) -> Result<()> { pub(crate) fn emit_rawbuf_get_float(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); emit_rawbuf_get(emitter, 8, "double")
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(())
} }
pub(crate) fn emit_rawbuf_set_float(emitter: &mut Emitter<'_>) -> Result<()> { pub(crate) fn emit_rawbuf_set_float(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); emit_rawbuf_set(emitter, 8, "double")
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(())
} }
pub(crate) fn emit_rawbuf_size_float(emitter: &mut Emitter<'_>) -> Result<()> { pub(crate) fn emit_rawbuf_size_float(emitter: &mut Emitter<'_>) -> Result<()> {
// Header always i64 — byte-identical to Int's size. emit_rawbuf_size(emitter)
emit_rawbuf_size_int(emitter)
} }
// --- Bool variants (element type i1, width 1) --- // --- Bool variants (element type i1, width 1) ---
pub(crate) fn emit_rawbuf_new_bool(emitter: &mut Emitter<'_>) -> Result<()> { pub(crate) fn emit_rawbuf_new_bool(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); emit_rawbuf_new(emitter, 1)
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(())
} }
pub(crate) fn emit_rawbuf_get_bool(emitter: &mut Emitter<'_>) -> Result<()> { pub(crate) fn emit_rawbuf_get_bool(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); emit_rawbuf_get(emitter, 1, "i1")
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(())
} }
pub(crate) fn emit_rawbuf_set_bool(emitter: &mut Emitter<'_>) -> Result<()> { pub(crate) fn emit_rawbuf_set_bool(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len(); emit_rawbuf_set(emitter, 1, "i1")
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(())
} }
pub(crate) fn emit_rawbuf_size_bool(emitter: &mut Emitter<'_>) -> Result<()> { pub(crate) fn emit_rawbuf_size_bool(emitter: &mut Emitter<'_>) -> Result<()> {
// Header always i64 — byte-identical to Int's size. emit_rawbuf_size(emitter)
emit_rawbuf_size_int(emitter)
} }
#[cfg(test)] #[cfg(test)]
+15
View File
@@ -3097,6 +3097,21 @@ impl<'a> Emitter<'a> {
self.counter += 1; self.counter += 1;
format!("%v{}", self.counter) 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<String> {
let n = self.locals.len();
self.locals[n - count..]
.iter()
.map(|l| l.1.clone())
.collect()
}
pub(crate) fn fresh_id(&mut self) -> u64 { pub(crate) fn fresh_id(&mut self) -> u64 {
self.counter += 1; self.counter += 1;
self.counter self.counter