iter raw-buf.3 typescoped-intrinsic-mechanism (DONE 2/2): scope-qualified mono symbols + bijection expansion (refs #7)
New language mechanism: a type-scoped polymorphic top-level (intrinsic) op now mints a scope-qualified mono symbol (StubT_peek__Int, not the colliding bare peek__Int), and the intrinsic<->intercept bijection expresses one such polymorphic marker as its N per-param-in-element entries. Ratified standalone on the stub via a new StubT.peek op; no RawBuf, no Term::New, no codegen E2E this iteration (RawBuf consumes the mechanism in raw-buf.4). Task 1 — scope threading. Added scope: Option<String> to FreeFnCall + MonoTarget::FreeFn + the synth free_fn_owner tuple. Populated Some(prefix) ONLY on the type-scoped T.f resolution branch (lib.rs:3538, gated on type_home.is_some()); None on every local / same-module / implicit-import branch, so existing bare-resolved symbols stay byte-identical. Consumed via a scoped_base helper at both mono-symbol mint sites (synthesise + rewrite, which read the same MonoTarget → automatically in lockstep) and folded into mono_target_key so a scoped op and a bare op of the same name never dedup-collide. Task 2 — ratifier + bijection. Added StubT.peek to the stub source; extended workspace_intrinsic_markers with a type-scoped-poly arm that finds the unique same-module TypeDef referenced in the fn signature and expands over its param-in set, building the T_f__<elem> strings via the SAME mono_symbol_n on Type::Con elems so collector and mint agree byte-for-byte; registered StubT_peek__Int/Float entries + emit fns; extended kernel_stub_module_round_trips to cover peek; added mono_scoped_symbol integration test (own-param calls, no Term::New, no codegen — asserts both scoped symbols minted and bare peek__Int absent). Latent bug fixed inline (regression caught at the full-suite gate, RED via the existing escape_local_demo E2E). kernel_stub is implicitly imported, so poly_free_fn_names_for_module unconditionally added peek's bare name to every consumer; a consumer with its OWN monomorphic peek then had its local call mistreated as a poly-free-fn site, over-advancing the mono slot cursor and misaligning a print target. Fix: suppress the bare implicit-import name when the current module declares a same-name global, mirroring synth's resolution ladder (same-module global beats implicit-import). Applied in lockstep to both poly_free_fn name + and constraint-count maps; dot-qualified / type-scoped spellings stay unconditional. This was a pre-existing inconsistency between mono's name-set and synth's resolution that peek (first implicitly-imported poly free fn colliding with a fixture-local name) exposed. Plan deviations (all sound): collect_con_heads omits Borrow/Own recursion (no such Type variant — borrow/own are ParamMode on Type::Fn, verified ast.rs:752); scope bound via the existing synthesise_mono_fn_for_free_fn MonoTarget destructure rather than a new param; the mono-pin fixture uses own params (borrow + peek-returns-a trips consume-while-borrowed; peek is never instantiated so the mode is immaterial to the scope-symbol ratification). Verification (orchestrator, this session): cargo build --workspace clean; cargo test --workspace 670 passed / 0 failed / 2 ignored (669 baseline + the one new mono test). Bijection green (1 marker -> 2 entries), round-trip green, escape_local_demo green. .ll snapshots unchanged (peek polymorphic -> never instantiated without a call site). Existing eq__*/ compare__*/float_* symbols byte-identical (mono_hash_stability green).
This commit is contained in:
@@ -171,6 +171,20 @@ pub(crate) static INTERCEPTS: &[Intercept] = &[
|
||||
wants_alwaysinline: false,
|
||||
emit: emit_answer,
|
||||
},
|
||||
Intercept {
|
||||
name: "StubT_peek__Int",
|
||||
expected_params: &["ptr"], // borrow (con StubT Int) lowers to ptr
|
||||
expected_ret: "i64",
|
||||
wants_alwaysinline: false,
|
||||
emit: emit_stubt_peek_int,
|
||||
},
|
||||
Intercept {
|
||||
name: "StubT_peek__Float",
|
||||
expected_params: &["ptr"],
|
||||
expected_ret: "double",
|
||||
wants_alwaysinline: false,
|
||||
emit: emit_stubt_peek_float,
|
||||
},
|
||||
];
|
||||
|
||||
pub(crate) fn lookup(name: &str) -> Option<&'static Intercept> {
|
||||
@@ -458,11 +472,38 @@ pub(crate) fn emit_answer(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// raw-buf.3 ratifier emit. NOTE: never instantiated to codegen this
|
||||
/// iteration (no Term::New to build a StubT; unit-test-ratified only).
|
||||
/// The load offset/correctness is exercised end-to-end only by
|
||||
/// raw-buf.4's RawBuf path; here the entry exists to satisfy the
|
||||
/// bijection. Retires with the stub in raw-buf.5.
|
||||
pub(crate) fn emit_stubt_peek_int(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||
let n = emitter.locals.len();
|
||||
let s = emitter.locals[n - 1].1.clone();
|
||||
let v = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(" {v} = load i64, ptr {s}\n"));
|
||||
emitter.body.push_str(&format!(" ret i64 {v}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn emit_stubt_peek_float(emitter: &mut Emitter<'_>) -> Result<()> {
|
||||
let n = emitter.locals.len();
|
||||
let s = emitter.locals[n - 1].1.clone();
|
||||
let v = emitter.fresh_ssa();
|
||||
emitter.body.push_str(&format!(" {v} = load double, ptr {s}\n"));
|
||||
emitter.body.push_str(&format!(" ret double {v}\n"));
|
||||
emitter.body.push_str("}\n\n");
|
||||
emitter.block_terminated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{lookup, INTERCEPTS};
|
||||
use ailang_check::mono::mono_symbol;
|
||||
use ailang_core::ast::{Def, Term};
|
||||
use ailang_check::mono::{mono_symbol, mono_symbol_n};
|
||||
use ailang_core::ast::{Def, FnDef, Module, Term, Type};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// INTERCEPTS entries that intercept the monomorphised `__Int`
|
||||
@@ -485,6 +526,34 @@ mod tests {
|
||||
] {
|
||||
for def in &module.defs {
|
||||
match def {
|
||||
// raw-buf.3: a polymorphic top-level (intrinsic) op
|
||||
// type-scoped to a same-module TypeDef T (its
|
||||
// signature mentions `(con T …)`) expands to one
|
||||
// marker per element type in T's `param-in` set —
|
||||
// the same `T_f__<elem>` strings the mono pass mints
|
||||
// (mono::scoped_base + mono_symbol_n). One marker → N
|
||||
// entries.
|
||||
Def::Fn(f)
|
||||
if matches!(f.body, Term::Intrinsic)
|
||||
&& matches!(f.ty, Type::Forall { .. }) =>
|
||||
{
|
||||
match scope_typedef_and_elems(f, &module) {
|
||||
Some((tdef, elems)) => {
|
||||
for elem in elems {
|
||||
markers.insert(mono_symbol_n(
|
||||
&format!("{tdef}_{}", f.name),
|
||||
std::slice::from_ref(&elem),
|
||||
));
|
||||
}
|
||||
}
|
||||
// a Forall intrinsic not scoped to a
|
||||
// param-in TypeDef keeps the bare name (no
|
||||
// such case ships today; future-proofing).
|
||||
None => {
|
||||
markers.insert(f.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Top-level intrinsic fn: name is already the symbol
|
||||
// (float_eq, answer, ...).
|
||||
Def::Fn(f) if matches!(f.body, Term::Intrinsic) => {
|
||||
@@ -548,4 +617,71 @@ mod tests {
|
||||
"optimisation-only allowlist names not in INTERCEPTS: {stale_allow:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// raw-buf.3: for a type-scoped polymorphic intrinsic fn `f`, return
|
||||
/// `(TypeDef-name, element-types)` where the TypeDef is the unique
|
||||
/// same-module `Def::Type` referenced via `(con T …)` in `f`'s
|
||||
/// signature and the element types are its `param-in` set expressed as
|
||||
/// `Type::Con` values (so `mono_symbol_n` produces the same suffix the
|
||||
/// mono pass mints). `None` if `f` references zero or several
|
||||
/// same-module TypeDefs.
|
||||
fn scope_typedef_and_elems(f: &FnDef, module: &Module) -> Option<(String, Vec<Type>)> {
|
||||
let typedef_names: BTreeSet<&str> = module
|
||||
.defs
|
||||
.iter()
|
||||
.filter_map(|d| match d {
|
||||
Def::Type(td) => Some(td.name.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
// collect Con-heads referenced anywhere in f's type
|
||||
let mut referenced: BTreeSet<String> = BTreeSet::new();
|
||||
collect_con_heads(&f.ty, &mut referenced);
|
||||
let scoped: Vec<&String> = referenced
|
||||
.iter()
|
||||
.filter(|n| typedef_names.contains(n.as_str()))
|
||||
.collect();
|
||||
let tdef = match scoped.as_slice() {
|
||||
[one] => (*one).clone(),
|
||||
_ => return None,
|
||||
};
|
||||
let td = module.defs.iter().find_map(|d| match d {
|
||||
Def::Type(td) if td.name == tdef => Some(td),
|
||||
_ => None,
|
||||
})?;
|
||||
// param-in is keyed by the type var; take its allowed surface
|
||||
// names and build `Type::Con` element types. The element strings
|
||||
// (`Int`, `Float`) flow through `mono_symbol_n`'s
|
||||
// `primitive_surface_name` gate to the same `__Int` / `__Float`
|
||||
// suffixes the mono pass mints.
|
||||
let allowed = td.param_in.values().next()?; // single-var TypeDefs
|
||||
let elems: Vec<Type> = allowed
|
||||
.iter()
|
||||
.map(|s| Type::Con { name: s.clone(), args: vec![] })
|
||||
.collect();
|
||||
Some((tdef, elems))
|
||||
}
|
||||
|
||||
/// raw-buf.3: push every `Type::Con` head name reachable in `ty`
|
||||
/// into `out`. `borrow` / `own` are `ParamMode` metadata on
|
||||
/// `Type::Fn`, not wrapper `Type` variants, so the only recursive
|
||||
/// shapes are `Forall.body`, `Fn.params`/`ret`, and `Con.args`.
|
||||
fn collect_con_heads(ty: &Type, out: &mut BTreeSet<String>) {
|
||||
match ty {
|
||||
Type::Con { name, args } => {
|
||||
out.insert(name.clone());
|
||||
for a in args {
|
||||
collect_con_heads(a, out);
|
||||
}
|
||||
}
|
||||
Type::Fn { params, ret, .. } => {
|
||||
for p in params {
|
||||
collect_con_heads(p, out);
|
||||
}
|
||||
collect_con_heads(ret, out);
|
||||
}
|
||||
Type::Forall { body, .. } => collect_con_heads(body, out),
|
||||
Type::Var { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user