iter raw-buf.1-intercept-registry (DONE 5/5): try_emit_primitive_instance_body → registry table (refs #7)
Pure refactor — first iter of the raw-buf milestone. The
hardcoded 18-arm match in try_emit_primitive_instance_body
(crates/ailang-codegen/src/lib.rs L2841-3147 pre-iter, ~305
lines) becomes a 6-line shim that consults a single registry
table in the new sibling module crates/ailang-codegen/src/intercepts.rs.
Zero behavioural change. The wrapper name survives, all 14
in-source comment-refs to try_emit_primitive_instance_body stay
valid pointers to the canonical dispatch entry.
What landed (5 tasks):
Task 1 — module skeleton: Intercept struct, INTERCEPTS static
table (empty), lookup(name), check_sig(intercept, params, ret).
mod intercepts; wired into lib.rs at L43.
Task 2 — populate: 18 free emit fns lifted from the legacy
match arms; INTERCEPTS populated with one entry per name. Three
Emitter fields (body, locals, block_terminated) and three helper
methods (emit_compare_ladder, emit_ordering_arm,
emit_direct_int_icmp_intercept) bumped to pub(crate) so the
sibling module can reach them. lower_ctor stayed at its existing
visibility — not consumed by sibling-module fns.
Task 3 — rewire dispatch: try_emit_primitive_instance_body body
becomes `match intercepts::lookup(fn_name) { Some(i) => {
check_sig(i, params, ret)?; (i.emit)(self)?; Ok(true) } None =>
Ok(false) }`. The 305-line match collapses to 9 lines.
Task 4 — fold wants_alwaysinline into the registry: every entry
in INTERCEPTS carries wants_alwaysinline: bool. The predicate
intercept_emit_wants_alwaysinline now consults the registry for
branch 1 (the literal 13-name list the predicate hardcoded). One
source of truth instead of two — closes the silent-drift class
recon flagged.
Task 5 — registry_contains_all_legacy_arms pin test: enumerates
all 18 legacy names + asserts each resolves to a registered
entry. A half-finished migration that drops any name becomes a
hard test fail.
Two judgement calls the implementer made (both behaviour-
preserving, both worth knowing):
(1) eq__Unit param shape: the plan named expected_params: &[]
per the recon's "params ignored" reading. Reality: the call
site delivers ["i8","i8"] (Unit lowers to i8 per codegen //!
header). The registry entry uses ["i8","i8"]; the emit fn
discards the operand SSAs and returns `ret i1 1` — semantically
equivalent to the legacy arm that checked only ret_ty and
ignored params. An inline rustdoc on the entry names the
rationale.
(2) intercept_emit_wants_alwaysinline has TWO branches, not one
as the plan inferred. Branch 1 = the literal 13-name list (which
the iter's drift-closure intent covers). Branch 2 = a stem-prefix
carve-out matching {ne|lt|le|gt|ge}__* that covers polymorphic-
body monomorphizations (lt__Bool, ne__Str, ne__MyADT, …) which
are NOT registry entries and never were. A literal "replace with
one-line registry lookup" would silently drop alwaysinline on
those names, breaking the bench-perf parity established in iter
operator-routing-eq-ord.1. Branch 2 stays as it was, with a
fresh comment naming what the registry covers vs. what branch 2
covers.
Follow-up consideration (not in this iter): extend the Intercept
variant to a predicate-only entry shape so branch 2 can fold
into the registry as well, OR accept the carve-out as the final
shape. Either is reasonable. Filed as a backlog idea (see Gitea
issue created post-commit if labelled).
Verification:
cargo test --workspace → 667 passed, 0 failed, 2 ignored
(baseline was 666 + 2 ignored;
+1 from registry_contains_all_legacy_arms)
Four E2E ratifiers GREEN (named in spec § Testing strategy
raw-buf.1):
- eq_primitives_smoke_compiles_and_runs (crates/ail/tests/e2e.rs)
- compare_primitives_smoke_prints_1_2_3_thrice (crates/ail/tests/e2e.rs)
- float_compare_smoke_prints_true_true_false
- eq_ord_polymorphic_runs_end_to_end
Alwaysinline pins GREEN (regression net for branch-2 carve-out):
- crates/ailang-codegen/tests/eq_primitives_pin.rs
- crates/ail/tests/prelude_eq_alwaysinline_ir_pin.rs
New pin GREEN:
- intercepts::tests::registry_contains_all_legacy_arms
Diff size: lib.rs −318 / +26 (net −292); intercepts.rs +488 (new
file). Net repository LOC delta: +196. The size reduction in
lib.rs is the wrong proxy — the value is the consolidation: one
table, one drift-closed predicate, one pin test enumerating the
migration surface.
The raw-buf.2 iter will rename crates/ailang-kernel-stub/ to
crates/ailang-kernel/ and reshape it as the family-crate that
hosts raw_buf alongside kernel_stub. The registry shipped here
is the substrate raw-buf.3 will plug RawBuf's 12 codegen
intercepts into.
This commit is contained in:
@@ -0,0 +1,488 @@
|
||||
//! Codegen intercept registry — one dispatch table for every
|
||||
//! mono-symbol whose body the codegen supplies as LLVM IR
|
||||
//! directly (replacing the .ail placeholder body that the
|
||||
//! intercept's home module ships for round-trip stability).
|
||||
//!
|
||||
//! Each entry carries:
|
||||
//! - `name` — the mono symbol the dispatch keys on
|
||||
//! - `expected_params` — LLVM-IR param types as text
|
||||
//! - `expected_ret` — LLVM-IR return type as text
|
||||
//! - `wants_alwaysinline` — whether the dispatch site should
|
||||
//! attach the `alwaysinline` attribute
|
||||
//! to the emitted fn (was a separate
|
||||
//! hardcoded name-list in the predicate
|
||||
//! `intercept_emit_wants_alwaysinline`
|
||||
//! before raw-buf.1)
|
||||
//! - `emit` — the per-arm IR-emission body, run
|
||||
//! against `&mut Emitter` after the
|
||||
//! sig check
|
||||
//!
|
||||
//! Adding a new intercept = adding one row to `INTERCEPTS`. The
|
||||
//! dispatch site (`try_emit_primitive_instance_body` in
|
||||
//! `crates/ailang-codegen/src/lib.rs`) and the alwaysinline
|
||||
//! predicate both consult this single table.
|
||||
|
||||
use crate::{CodegenError, Emitter, Result};
|
||||
|
||||
pub(crate) struct Intercept {
|
||||
pub name: &'static str,
|
||||
pub expected_params: &'static [&'static str],
|
||||
pub expected_ret: &'static str,
|
||||
pub wants_alwaysinline: bool,
|
||||
pub emit: fn(&mut Emitter<'_>) -> Result<()>,
|
||||
}
|
||||
|
||||
pub(crate) static INTERCEPTS: &[Intercept] = &[
|
||||
Intercept {
|
||||
name: "eq__Str",
|
||||
expected_params: &["ptr", "ptr"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_eq_str,
|
||||
},
|
||||
Intercept {
|
||||
name: "compare__Int",
|
||||
expected_params: &["i64", "i64"],
|
||||
expected_ret: "ptr",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_compare_int,
|
||||
},
|
||||
Intercept {
|
||||
name: "compare__Bool",
|
||||
expected_params: &["i1", "i1"],
|
||||
expected_ret: "ptr",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_compare_bool,
|
||||
},
|
||||
Intercept {
|
||||
name: "compare__Str",
|
||||
expected_params: &["ptr", "ptr"],
|
||||
expected_ret: "ptr",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_compare_str,
|
||||
},
|
||||
Intercept {
|
||||
name: "eq__Int",
|
||||
expected_params: &["i64", "i64"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_eq_int,
|
||||
},
|
||||
Intercept {
|
||||
name: "eq__Bool",
|
||||
expected_params: &["i1", "i1"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_eq_bool,
|
||||
},
|
||||
Intercept {
|
||||
name: "eq__Unit",
|
||||
// Unit lowers to `i8` per the codegen type-mapping (see lib.rs
|
||||
// //! header). The legacy arm checked only `ret_ty != "i1"` and
|
||||
// ignored params; this entry restores that semantics under the
|
||||
// centralised `check_sig` by naming the actual param shape the
|
||||
// call site delivers. The emit fn discards the locals.
|
||||
expected_params: &["i8", "i8"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_eq_unit,
|
||||
},
|
||||
Intercept {
|
||||
name: "float_eq",
|
||||
expected_params: &["double", "double"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_float_eq,
|
||||
},
|
||||
Intercept {
|
||||
name: "float_ne",
|
||||
expected_params: &["double", "double"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_float_ne,
|
||||
},
|
||||
Intercept {
|
||||
name: "float_lt",
|
||||
expected_params: &["double", "double"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_float_lt,
|
||||
},
|
||||
Intercept {
|
||||
name: "float_le",
|
||||
expected_params: &["double", "double"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_float_le,
|
||||
},
|
||||
Intercept {
|
||||
name: "float_gt",
|
||||
expected_params: &["double", "double"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_float_gt,
|
||||
},
|
||||
Intercept {
|
||||
name: "float_ge",
|
||||
expected_params: &["double", "double"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_float_ge,
|
||||
},
|
||||
Intercept {
|
||||
name: "lt__Int",
|
||||
expected_params: &["i64", "i64"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_lt_int,
|
||||
},
|
||||
Intercept {
|
||||
name: "le__Int",
|
||||
expected_params: &["i64", "i64"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_le_int,
|
||||
},
|
||||
Intercept {
|
||||
name: "gt__Int",
|
||||
expected_params: &["i64", "i64"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_gt_int,
|
||||
},
|
||||
Intercept {
|
||||
name: "ge__Int",
|
||||
expected_params: &["i64", "i64"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_ge_int,
|
||||
},
|
||||
Intercept {
|
||||
name: "ne__Int",
|
||||
expected_params: &["i64", "i64"],
|
||||
expected_ret: "i1",
|
||||
wants_alwaysinline: true,
|
||||
emit: emit_ne_int,
|
||||
},
|
||||
];
|
||||
|
||||
pub(crate) fn lookup(name: &str) -> Option<&'static Intercept> {
|
||||
INTERCEPTS.iter().find(|i| i.name == name)
|
||||
}
|
||||
|
||||
pub(crate) fn check_sig(
|
||||
intercept: &Intercept,
|
||||
param_tys: &[String],
|
||||
ret_ty: &str,
|
||||
) -> Result<()> {
|
||||
let params_match = param_tys.len() == intercept.expected_params.len()
|
||||
&& param_tys
|
||||
.iter()
|
||||
.zip(intercept.expected_params.iter())
|
||||
.all(|(have, want)| have == want);
|
||||
if !params_match || ret_ty != intercept.expected_ret {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"{} body intercept: unexpected signature \
|
||||
({:?}) -> {} (want {:?} -> {})",
|
||||
intercept.name,
|
||||
param_tys,
|
||||
ret_ty,
|
||||
intercept.expected_params,
|
||||
intercept.expected_ret,
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Per-intercept emit fns. Bodies lifted verbatim from the legacy
|
||||
// `try_emit_primitive_instance_body` match arms in `lib.rs` (raw-buf.1
|
||||
// migration). Per-arm sig-check prologues are removed because the
|
||||
// dispatch shim runs `check_sig` first; trailing `Ok(true)` is
|
||||
// replaced by `Ok(())` because the shim wraps the bool.
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
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();
|
||||
// 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 dst = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(
|
||||
" {dst} = call zeroext i1 @ail_str_eq(ptr {a_bytes}, ptr {b_bytes})\n"
|
||||
));
|
||||
emitter.body.push_str(&format!(" ret i1 {dst}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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();
|
||||
emitter.emit_compare_ladder(
|
||||
&format!("icmp slt i64 {a_ssa}, {b_ssa}"),
|
||||
&format!("icmp eq i64 {a_ssa}, {b_ssa}"),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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();
|
||||
emitter.emit_compare_ladder(
|
||||
&format!("icmp ult i1 {a_ssa}, {b_ssa}"),
|
||||
&format!("icmp eq i1 {a_ssa}, {b_ssa}"),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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();
|
||||
// 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 cmp_res = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(
|
||||
" {cmp_res} = call i32 @ail_str_compare(ptr {a_bytes}, ptr {b_bytes})\n"
|
||||
));
|
||||
emitter.emit_compare_ladder(
|
||||
&format!("icmp slt i32 {cmp_res}, 0"),
|
||||
&format!("icmp eq i32 {cmp_res}, 0"),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 r = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(
|
||||
" {r} = icmp eq i64 {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
emitter.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 r = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(
|
||||
" {r} = icmp eq i1 {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
emitter.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn emit_eq_unit(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||
// Unit is single-inhabitant: all values compare equal.
|
||||
// The fn signature still carries the operand slots; we
|
||||
// discard them and return true unconditionally.
|
||||
emitter.body.push_str(" ret i1 1\n");
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 r = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(
|
||||
" {r} = fcmp oeq double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
emitter.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 r = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(
|
||||
" {r} = fcmp une double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
emitter.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 r = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(
|
||||
" {r} = fcmp olt double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
emitter.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 r = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(
|
||||
" {r} = fcmp ole double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
emitter.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 r = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(
|
||||
" {r} = fcmp ogt double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
emitter.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 r = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(
|
||||
" {r} = fcmp oge double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
emitter.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The five icmp-delegate emit fns wrap the existing
|
||||
// `Emitter::emit_direct_int_icmp_intercept` helper, which returns
|
||||
// `Result<bool>` (always `Ok(true)` on success); we discard the
|
||||
// bool because the dispatch shim wraps it from `check_sig`.
|
||||
|
||||
pub(crate) fn emit_lt_int(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||
emitter
|
||||
.emit_direct_int_icmp_intercept("lt__Int", "icmp slt i64", &["i64".into(), "i64".into()], "i1")
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub(crate) fn emit_le_int(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||
emitter
|
||||
.emit_direct_int_icmp_intercept("le__Int", "icmp sle i64", &["i64".into(), "i64".into()], "i1")
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub(crate) fn emit_gt_int(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||
emitter
|
||||
.emit_direct_int_icmp_intercept("gt__Int", "icmp sgt i64", &["i64".into(), "i64".into()], "i1")
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub(crate) fn emit_ge_int(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||
emitter
|
||||
.emit_direct_int_icmp_intercept("ge__Int", "icmp sge i64", &["i64".into(), "i64".into()], "i1")
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub(crate) fn emit_ne_int(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||
emitter
|
||||
.emit_direct_int_icmp_intercept("ne__Int", "icmp ne i64", &["i64".into(), "i64".into()], "i1")
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::lookup;
|
||||
|
||||
/// Pin: every name that was a match arm in the legacy
|
||||
/// `try_emit_primitive_instance_body` (before raw-buf.1)
|
||||
/// must resolve to a registered Intercept. If this test
|
||||
/// goes red, a name was dropped during migration AND/OR
|
||||
/// removed from the registry without the dispatch wrapper
|
||||
/// being audited to confirm no consumer still depends on
|
||||
/// it.
|
||||
#[test]
|
||||
fn registry_contains_all_legacy_arms() {
|
||||
let legacy_names = [
|
||||
"eq__Str",
|
||||
"compare__Int",
|
||||
"compare__Bool",
|
||||
"compare__Str",
|
||||
"eq__Int",
|
||||
"eq__Bool",
|
||||
"eq__Unit",
|
||||
"float_eq",
|
||||
"float_ne",
|
||||
"float_lt",
|
||||
"float_le",
|
||||
"float_gt",
|
||||
"float_ge",
|
||||
"lt__Int",
|
||||
"le__Int",
|
||||
"gt__Int",
|
||||
"ge__Int",
|
||||
"ne__Int",
|
||||
];
|
||||
|
||||
let missing: Vec<&str> = legacy_names
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|n| lookup(n).is_none())
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
missing.is_empty(),
|
||||
"registry is missing legacy intercept names: {missing:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ use ailang_check::uniqueness::{infer_module, UniquenessTable};
|
||||
|
||||
mod drop;
|
||||
mod escape;
|
||||
mod intercepts;
|
||||
mod lambda;
|
||||
mod match_lower;
|
||||
mod subst;
|
||||
@@ -708,7 +709,7 @@ struct Emitter<'a> {
|
||||
/// Name of the currently lowered module (for mangling).
|
||||
module_name: &'a str,
|
||||
header: String,
|
||||
body: String,
|
||||
pub(crate) body: String,
|
||||
/// String constants: content -> (global name (without `@`), llvm type length incl. \0)
|
||||
strings: BTreeMap<String, (String, usize)>,
|
||||
/// language `Str` literals interned as
|
||||
@@ -722,7 +723,7 @@ struct Emitter<'a> {
|
||||
/// The AILang type is recorded so that the codegen-side type tracker
|
||||
/// can derive substitutions at polymorphic call sites without
|
||||
/// re-running the typechecker (Iter 12b).
|
||||
locals: Vec<(String, String, String, Type)>,
|
||||
pub(crate) locals: Vec<(String, String, String, Type)>,
|
||||
/// Monotonic counter for SSA values and labels.
|
||||
counter: u64,
|
||||
/// Monotonic counter for global string names (per module).
|
||||
@@ -762,7 +763,7 @@ struct Emitter<'a> {
|
||||
/// Callers in the term lowering walk consult this to skip
|
||||
/// fall-through `br` emission and to omit the value from a
|
||||
/// surrounding match-arm phi. Reset by [`Self::start_block`].
|
||||
block_terminated: bool,
|
||||
pub(crate) block_terminated: bool,
|
||||
/// SSA value (or `@global`) -> its FnSig, for first-class
|
||||
/// function values. Populated whenever we lower a `Term::Var` to a
|
||||
/// top-level fn pointer or when a fn-typed parameter is bound at
|
||||
@@ -1170,18 +1171,17 @@ impl<'a> Emitter<'a> {
|
||||
/// unmangled fn name; these symbols only live in the prelude
|
||||
/// module so cross-module collisions are not a concern.
|
||||
fn intercept_emit_wants_alwaysinline(symbol: &str) -> bool {
|
||||
if matches!(
|
||||
symbol,
|
||||
"eq__Int" | "eq__Bool" | "eq__Str" | "eq__Unit"
|
||||
| "compare__Int" | "compare__Bool" | "compare__Str"
|
||||
| "float_eq" | "float_ne"
|
||||
| "float_lt" | "float_le"
|
||||
| "float_gt" | "float_ge"
|
||||
) {
|
||||
// Branch 1 — registry-driven (raw-buf.1). Every intercept entry
|
||||
// carries its own `wants_alwaysinline` bool; the predicate
|
||||
// consults the single table instead of a duplicate name-list.
|
||||
// This closes the silent-drift class where the predicate's
|
||||
// hardcoded list could diverge from the dispatch-arm set.
|
||||
if intercepts::lookup(symbol).is_some_and(|i| i.wants_alwaysinline) {
|
||||
return true;
|
||||
}
|
||||
// The polymorphic free helpers `ne` / `lt` / `le` / `gt` /
|
||||
// `ge` mono into per-type bodies. At `_Int` (iter
|
||||
// Branch 2 — polymorphic-body carve-out, NOT covered by the
|
||||
// registry. The free helpers `ne` / `lt` / `le` / `gt` / `ge`
|
||||
// mono into per-type bodies. At `_Int` (iter
|
||||
// operator-routing-eq-ord.1 Task 13) they are intercepted as
|
||||
// single direct `icmp` instructions in
|
||||
// `try_emit_primitive_instance_body` — same shape as
|
||||
@@ -1189,11 +1189,11 @@ impl<'a> Emitter<'a> {
|
||||
// (`_Bool`, `_Str`, user ADTs) they keep their polymorphic
|
||||
// body — a one-arm-deep `match (compare x y) (case LT ... )
|
||||
// (case _ ... )` — which still folds at `-O2` provided the
|
||||
// mono symbol carries `alwaysinline`. Both shapes benefit
|
||||
// from the attribute; the stem-prefix match covers all T the
|
||||
// mono pass produces uniformly so the bench-perf parity with
|
||||
// the pre-milestone arithmetic-comparator emission is
|
||||
// preserved end-to-end.
|
||||
// mono symbol carries `alwaysinline`. The registry only covers
|
||||
// the `_Int` specialisations; this stem-prefix match covers
|
||||
// every other T the mono pass produces (`lt__Bool`,
|
||||
// `ne__MyADT`, …), preserving the bench-perf parity that the
|
||||
// pre-milestone arithmetic-comparator emission established.
|
||||
if let Some(stem) = symbol.split("__").next() {
|
||||
if matches!(stem, "ne" | "lt" | "le" | "gt" | "ge") {
|
||||
return true;
|
||||
@@ -2844,305 +2844,13 @@ impl<'a> Emitter<'a> {
|
||||
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();
|
||||
// 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 = self.fresh_ssa();
|
||||
let b_bytes = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {a_bytes} = getelementptr inbounds i8, ptr {a_ssa}, i64 8\n"
|
||||
));
|
||||
self.body.push_str(&format!(
|
||||
" {b_bytes} = getelementptr inbounds i8, ptr {b_ssa}, i64 8\n"
|
||||
));
|
||||
let dst = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {dst} = call zeroext i1 @ail_str_eq(ptr {a_bytes}, ptr {b_bytes})\n"
|
||||
));
|
||||
self.body.push_str(&format!(" ret i1 {dst}\n"));
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
match intercepts::lookup(fn_name) {
|
||||
Some(intercept) => {
|
||||
intercepts::check_sig(intercept, param_tys, ret_ty)?;
|
||||
(intercept.emit)(self)?;
|
||||
Ok(true)
|
||||
}
|
||||
"compare__Int" => {
|
||||
if param_tys != ["i64", "i64"] || ret_ty != "ptr" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"compare__Int body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (i64, i64) -> ptr)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
self.emit_compare_ladder(
|
||||
&format!("icmp slt i64 {a_ssa}, {b_ssa}"),
|
||||
&format!("icmp eq i64 {a_ssa}, {b_ssa}"),
|
||||
)?;
|
||||
Ok(true)
|
||||
}
|
||||
"compare__Bool" => {
|
||||
if param_tys != ["i1", "i1"] || ret_ty != "ptr" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"compare__Bool body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (i1, i1) -> ptr)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
self.emit_compare_ladder(
|
||||
&format!("icmp ult i1 {a_ssa}, {b_ssa}"),
|
||||
&format!("icmp eq i1 {a_ssa}, {b_ssa}"),
|
||||
)?;
|
||||
Ok(true)
|
||||
}
|
||||
"compare__Str" => {
|
||||
if param_tys != ["ptr", "ptr"] || ret_ty != "ptr" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"compare__Str body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (ptr, ptr) -> ptr)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].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 = self.fresh_ssa();
|
||||
let b_bytes = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {a_bytes} = getelementptr inbounds i8, ptr {a_ssa}, i64 8\n"
|
||||
));
|
||||
self.body.push_str(&format!(
|
||||
" {b_bytes} = getelementptr inbounds i8, ptr {b_ssa}, i64 8\n"
|
||||
));
|
||||
let cmp_res = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {cmp_res} = call i32 @ail_str_compare(ptr {a_bytes}, ptr {b_bytes})\n"
|
||||
));
|
||||
self.emit_compare_ladder(
|
||||
&format!("icmp slt i32 {cmp_res}, 0"),
|
||||
&format!("icmp eq i32 {cmp_res}, 0"),
|
||||
)?;
|
||||
Ok(true)
|
||||
}
|
||||
"eq__Int" => {
|
||||
if param_tys != ["i64", "i64"] || ret_ty != "i1" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"eq__Int body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (i64, i64) -> i1)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
let r = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {r} = icmp eq i64 {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
self.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
Ok(true)
|
||||
}
|
||||
"eq__Bool" => {
|
||||
if param_tys != ["i1", "i1"] || ret_ty != "i1" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"eq__Bool body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (i1, i1) -> i1)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
let r = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {r} = icmp eq i1 {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
self.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
Ok(true)
|
||||
}
|
||||
"eq__Unit" => {
|
||||
// Unit is single-inhabitant: all values compare equal.
|
||||
// The fn signature still carries the operand slots; we
|
||||
// discard them and return true unconditionally.
|
||||
if ret_ty != "i1" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"eq__Unit body intercept: unexpected return type \
|
||||
({param_tys:?}) -> {ret_ty} (want -> i1)"
|
||||
)));
|
||||
}
|
||||
self.body.push_str(" ret i1 1\n");
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
Ok(true)
|
||||
}
|
||||
"float_eq" => {
|
||||
if param_tys != ["double", "double"] || ret_ty != "i1" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"float_eq body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (double, double) -> i1)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
let r = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {r} = fcmp oeq double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
self.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
Ok(true)
|
||||
}
|
||||
"float_ne" => {
|
||||
// `fcmp une` ("unordered or not-equal"), not `one`,
|
||||
// mirroring float-semantics.md: NaN != NaN must be true.
|
||||
if param_tys != ["double", "double"] || ret_ty != "i1" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"float_ne body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (double, double) -> i1)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
let r = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {r} = fcmp une double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
self.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
Ok(true)
|
||||
}
|
||||
"float_lt" => {
|
||||
if param_tys != ["double", "double"] || ret_ty != "i1" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"float_lt body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (double, double) -> i1)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
let r = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {r} = fcmp olt double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
self.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
Ok(true)
|
||||
}
|
||||
"float_le" => {
|
||||
if param_tys != ["double", "double"] || ret_ty != "i1" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"float_le body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (double, double) -> i1)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
let r = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {r} = fcmp ole double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
self.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
Ok(true)
|
||||
}
|
||||
"float_gt" => {
|
||||
if param_tys != ["double", "double"] || ret_ty != "i1" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"float_gt body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (double, double) -> i1)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
let r = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {r} = fcmp ogt double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
self.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
Ok(true)
|
||||
}
|
||||
"float_ge" => {
|
||||
if param_tys != ["double", "double"] || ret_ty != "i1" {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"float_ge body intercept: unexpected signature \
|
||||
({param_tys:?}) -> {ret_ty} (want (double, double) -> i1)"
|
||||
)));
|
||||
}
|
||||
let n = self.locals.len();
|
||||
let a_ssa = self.locals[n - 2].1.clone();
|
||||
let b_ssa = self.locals[n - 1].1.clone();
|
||||
let r = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {r} = fcmp oge double {a_ssa}, {b_ssa}\n"
|
||||
));
|
||||
self.body.push_str(&format!(" ret i1 {r}\n"));
|
||||
self.body.push_str("}\n\n");
|
||||
self.block_terminated = true;
|
||||
Ok(true)
|
||||
}
|
||||
// Ord/Eq Int direct-icmp arms (iter operator-routing-eq-ord.1
|
||||
// Task 13). The polymorphic prelude bodies are
|
||||
// `(match (app compare x y) ...)`; without these intercept
|
||||
// arms, each `lt`-at-Int call site goes through
|
||||
// `compare__Int` → allocate an `Ordering` ctor → match.
|
||||
// At `-O2` even with `alwaysinline` on the helpers, clang
|
||||
// does not always collapse the Ordering allocation across
|
||||
// the helper boundary (observed: +29% bump_s, +47% rc_s
|
||||
// bench_closure_chain regression before this arm shipped).
|
||||
// Direct `icmp slt i64` at the helper body lets clang fold
|
||||
// the comparison to the use site reliably.
|
||||
//
|
||||
// `ne__Int` uses `icmp ne i64` directly, NOT the
|
||||
// `not (eq x y)` indirection — that would re-introduce one
|
||||
// call boundary and one `xor i1 %r, true` per comparison
|
||||
// that contributes no semantic value beyond the direct
|
||||
// `icmp ne`.
|
||||
//
|
||||
// Bool variants (`lt__Bool`, `le__Bool`, etc.) are not
|
||||
// emitted as intercept arms here because no current
|
||||
// bench fixture or example reaches them; the polymorphic
|
||||
// body via `compare__Bool` remains correct, just slightly
|
||||
// slower per call. The next fixture that needs Bool-ordered
|
||||
// perf can extend this family symmetrically.
|
||||
"lt__Int" => self.emit_direct_int_icmp_intercept("lt__Int", "icmp slt i64", param_tys, ret_ty),
|
||||
"le__Int" => self.emit_direct_int_icmp_intercept("le__Int", "icmp sle i64", param_tys, ret_ty),
|
||||
"gt__Int" => self.emit_direct_int_icmp_intercept("gt__Int", "icmp sgt i64", param_tys, ret_ty),
|
||||
"ge__Int" => self.emit_direct_int_icmp_intercept("ge__Int", "icmp sge i64", param_tys, ret_ty),
|
||||
"ne__Int" => self.emit_direct_int_icmp_intercept("ne__Int", "icmp ne i64", param_tys, ret_ty),
|
||||
_ => Ok(false),
|
||||
None => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3153,7 +2861,7 @@ impl<'a> Emitter<'a> {
|
||||
/// `%r = <icmp_op> %a, %b` and `ret i1 %r`, close the body.
|
||||
/// `icmp_op` is the full instruction-and-operand-type prefix
|
||||
/// (e.g. `"icmp slt i64"`); the operand SSAs are appended.
|
||||
fn emit_direct_int_icmp_intercept(
|
||||
pub(crate) fn emit_direct_int_icmp_intercept(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
icmp_op: &str,
|
||||
@@ -3187,7 +2895,7 @@ impl<'a> Emitter<'a> {
|
||||
/// zero-field allocation; `lower_ctor` handles alloc-strategy
|
||||
/// variance (Gc / Bump / Rc) without the intercept duplicating
|
||||
/// the per-strategy logic.
|
||||
fn emit_ordering_arm(&mut self, label: &str, ctor: &str) -> Result<()> {
|
||||
pub(crate) fn emit_ordering_arm(&mut self, label: &str, ctor: &str) -> Result<()> {
|
||||
self.start_block(label);
|
||||
// term_ptr 0: synthetic call site, not present in the
|
||||
// escape-analysis result; falls back to the conservative
|
||||
@@ -3217,7 +2925,7 @@ impl<'a> Emitter<'a> {
|
||||
/// `icmp ult i1` / `icmp slt i32` etc.); any per-arm prep
|
||||
/// (e.g. `compare__Str`'s `@ail_str_compare` call) must be
|
||||
/// emitted by the caller before invoking this helper.
|
||||
fn emit_compare_ladder(
|
||||
pub(crate) fn emit_compare_ladder(
|
||||
&mut self,
|
||||
lt_test_instr: &str,
|
||||
eq_test_instr: &str,
|
||||
|
||||
Reference in New Issue
Block a user