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) {
// 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<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_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<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
/// 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<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);
}
self.emit_drop_fn_wrapper(td, Self::emit_iterative_drop_fn_for_instantiation);
}
/// 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
/// `drop_<m>_<F>` 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<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);
}
self.emit_drop_fn_wrapper(td, Self::emit_partial_drop_fn_for_instantiation);
}
/// 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`
/// 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_<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
/// 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");