feat(lang): eliminate the Implicit ownership default — totality + the drop-soundness it demasks (#55)
Deletes `ParamMode::Implicit`. `ParamMode` is now `{Own, Borrow}`:
every fn-type slot on every signature carries an explicit `own` or
`borrow`, no defaulted position survives anywhere (model 0008 §2,
spec 0062). The parser rejects a bare fn-type slot; `borrow-return`
and `borrow-over-value` reject at the signature; the corpus is
migrated to minimal-ownership modes (consumed ⇒ own, read-only-heap
⇒ borrow, value ⇒ trivial-own). The documented `Implicit`-ret-mode
leak is fixed: an owned heap return now drops exactly once (live=0,
acceptance criterion 5).
This was the easy half. Removing the default ACTIVATED a family of
drop paths that `Implicit` had silently skipped — the pre-cutover
language was leaking (and in places mis-dropping) here rather than
crashing, because an Implicit scrutinee turned the drop off. Making
the modes explicit (Own) turned those paths on and exposed two
latent-bug clusters, all fixed RED-first as part of this cutover:
Drop-soundness family (four legs):
A. lit-sub-pattern double-free — the desugar re-matched the same
owned scrutinee in the lit fall-through; fixed by grouping
consecutive same-ctor arms into one match (bind fields once),
in ailang-core desugar.
B. Cons-husk leak on non-tail arm bodies — the lit-sub-pattern
desugar rebound the owned scrutinee via `Let $mp = xs`, which
bumped consume_count and suppressed the existing fn-return
partial_drop. Fixed by not rebinding a bare-Var scrutinee
(one husk-freeing mechanism, not two).
C. polymorphic `drop_<T>` rc_dec'd monomorphised value fields —
the per-ADT drop fn was emitted once from the polymorphic
TypeDef, defaulting type-var fields to ptr and rc_dec'ing
inline Ints (segfault). Fixed with per-monomorph drop
functions (new ailang-codegen::dropmono): the drop set is
collected from the lowered MIR, value-type fields are skipped,
heap fields still freed once; monomorphic-concrete ADTs keep
their byte-identical un-suffixed drop symbol.
D. static Str literal passed to an `(own Str)` param — the
literal lowers to a header-less rodata constant; the callee's
now-active rc_dec read its length field as a refcount and
freed a static address (segfault). Fixed with the missing
fourth StrRep::Static→Heap promotion in lower_to_mir's App arm,
gated on Own mode (borrow args stay static, no regression).
over-strict-mode lint over-fired: it suggested `(borrow V)` for
value-typed params (which `borrow-over-value` rejects — own is the
only legal mode there) and fired on `(intrinsic)` bodies (whose
consumption the linearity walk cannot observe). Tightened to skip
both; contract 0008 updated to the narrowed firing scope.
Irreversible step — canonical-form hash reset (model 0008 §6,
acceptance criterion 6). Every signature now carries explicit modes,
so the hashable canonical JSON changed for every module. RATIFY:
the corpus-wide hash-pin reset (hash_pin, prelude_module_hash_pin,
mono_hash_stability, eq_ord_e2e, embed_export_hash_stable, the
ct4/iter*/loop_recur schema-extension pins) and the list ir_snapshot
golden were regenerated once, deliberately, as the intended one-time
consequence of removing the mode elision from the canonical form —
not a regression. Each regenerated hash verified deterministic across
two runs.
Also fixes a pre-existing latent failure surfaced by the verification
gate, unrelated to this cutover: the `every_contract_names_a_resolvable_
ratifying_test` resolver (design_index_pin) could not resolve the
" + " dual-link ratifying-test form (`uniqueness.rs + linearity.rs`)
that the #57 audit-close (dfdc65f) introduced — it shipped red on that
commit. Resolver taught the dual-link form, mirroring its sibling.
Verification: cargo test --workspace = 731 passed, 0 failed (twice,
stable); e2e 102 passed, no binary exits non-zero (corpus crash-free);
grep-clean for Implicit/fn_implicit/mode_eq across crates; every drop
fix confirmed via emitted IR + AILANG_RC_STATS balance on the head==K,
head!=K, and Nil paths. Three BLOCKEDs en route (the unsound first
husk-dec attempt, the over-strict derivation premise, the leg-B fix
direction) were each treated as a real design/spec gap and rediagnosed,
not patched over.
Supersedes #54 (return-position-only leak patch). Precondition #57
(linearity hardening) was already met. Spec docs/specs/0062, plan
docs/plans/0121.
closes #55
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
|
||||
use ailang_core::ast::{ParamMode, Type, TypeDef};
|
||||
use ailang_mir::{Callee, MTerm};
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use super::synth::llvm_type;
|
||||
use super::{AllocStrategy, Emitter, FnSig, Result};
|
||||
@@ -76,10 +76,44 @@ impl<'a> Emitter<'a> {
|
||||
/// is known to grow, recursive cascade everywhere else (cheaper
|
||||
/// IR, no worklist allocation).
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// emit one recursive `drop_<m>_<T><suffix>` body. `subst` maps the
|
||||
/// declared type-vars to the concrete instantiation args (empty for
|
||||
/// monomorphic ADTs); `sym_suffix` is the `__<...>` mono suffix
|
||||
/// (empty for monomorphic ADTs).
|
||||
fn emit_drop_fn_for_instantiation(
|
||||
&mut self,
|
||||
td: &TypeDef,
|
||||
subst: &BTreeMap<String, Type>,
|
||||
sym_suffix: &str,
|
||||
) {
|
||||
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(&format!("define void @drop_{m}_{tname}{sym_suffix}(ptr %p) {{\n"));
|
||||
out.push_str("entry:\n");
|
||||
// Null guard: a null payload is a no-op (matches
|
||||
// `runtime/rc.c::ailang_rc_dec`'s null guard).
|
||||
@@ -101,14 +135,16 @@ impl<'a> Emitter<'a> {
|
||||
let mut local = 0u64;
|
||||
for (i, ctor) in td.ctors.iter().enumerate() {
|
||||
out.push_str(&format!("arm_{i}:\n"));
|
||||
for (j, fty) in ctor.fields.iter().enumerate() {
|
||||
// Decide what to call for this field. If the field
|
||||
// lowers to `ptr` (boxed), we issue a `dec` call.
|
||||
// For known ADT field types we route through that
|
||||
// ADT's own drop fn so the recursion cascades; for
|
||||
// anything else that lowers to `ptr` (Str, fn-typed,
|
||||
// unresolved Var), fall back to plain `ailang_rc_dec`.
|
||||
let lty = llvm_type(fty).unwrap_or_else(|_| "ptr".into());
|
||||
for (j, fty_decl) in ctor.fields.iter().enumerate() {
|
||||
// Substitute the declared field type to its monomorph.
|
||||
// A value-type field (`Int`/`Bool`/`Float`/`Unit`)
|
||||
// lowers to a non-`ptr` scalar → skip the dec (it is
|
||||
// stored inline, NOT a heap pointer — `rc_dec` on it
|
||||
// would SIGSEGV). A heap field lowers to `ptr` → dec via
|
||||
// `field_drop_call` on the *concrete* type, so the
|
||||
// cascade targets the field's own per-monomorph symbol.
|
||||
let fty = super::subst::apply_subst_to_type(fty_decl, subst);
|
||||
let lty = llvm_type(&fty).unwrap_or_else(|_| "ptr".into());
|
||||
if lty != "ptr" {
|
||||
continue;
|
||||
}
|
||||
@@ -124,7 +160,7 @@ impl<'a> Emitter<'a> {
|
||||
out.push_str(&format!(
|
||||
" %v{val_id} = load ptr, ptr %a{addr_id}, align 8\n"
|
||||
));
|
||||
let drop_call = self.field_drop_call(fty);
|
||||
let drop_call = self.field_drop_call(&fty);
|
||||
// Recursive call into the field's drop fn. If the
|
||||
// field's type is itself `(drop-iterative)`, that drop
|
||||
// fn is the worklist variant — recursion stops at one
|
||||
@@ -227,10 +263,36 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// emit one iterative `drop_<m>_<T><suffix>` body (worklist variant).
|
||||
/// `subst` / `sym_suffix` carry the leg-C per-monomorph
|
||||
/// instantiation, identical in role to
|
||||
/// [`Self::emit_drop_fn_for_instantiation`].
|
||||
fn emit_iterative_drop_fn_for_instantiation(
|
||||
&mut self,
|
||||
td: &TypeDef,
|
||||
subst: &BTreeMap<String, Type>,
|
||||
sym_suffix: &str,
|
||||
) {
|
||||
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(&format!("define void @drop_{m}_{tname}{sym_suffix}(ptr %p) {{\n"));
|
||||
out.push_str("entry:\n");
|
||||
// Null guard — symmetric with the recursive variant. A null
|
||||
// payload skips worklist allocation entirely.
|
||||
@@ -265,8 +327,12 @@ impl<'a> Emitter<'a> {
|
||||
let mut local = 0u64;
|
||||
for (i, ctor) in td.ctors.iter().enumerate() {
|
||||
out.push_str(&format!("arm_{i}:\n"));
|
||||
for (j, fty) in ctor.fields.iter().enumerate() {
|
||||
let lty = llvm_type(fty).unwrap_or_else(|_| "ptr".into());
|
||||
for (j, fty_decl) in ctor.fields.iter().enumerate() {
|
||||
// Substitute to the monomorph (same reasoning as the
|
||||
// recursive variant): a value-type field is inline and
|
||||
// must not be dec'd/pushed.
|
||||
let fty = super::subst::apply_subst_to_type(fty_decl, subst);
|
||||
let lty = llvm_type(&fty).unwrap_or_else(|_| "ptr".into());
|
||||
if lty != "ptr" {
|
||||
continue;
|
||||
}
|
||||
@@ -281,7 +347,7 @@ impl<'a> Emitter<'a> {
|
||||
out.push_str(&format!(
|
||||
" %v{val_id} = load ptr, ptr %a{addr_id}, align 8\n"
|
||||
));
|
||||
if self.field_is_same_type(fty, &td.name) {
|
||||
if self.field_is_same_type(&fty, &td.name) {
|
||||
// Same-type field: push onto the worklist —
|
||||
// continues the iterative cascade. Null-guarding
|
||||
// is handled inside `ailang_drop_worklist_push`
|
||||
@@ -295,7 +361,7 @@ impl<'a> Emitter<'a> {
|
||||
// iterative). `field_drop_call` resolves the
|
||||
// symbol; its null-guard semantics are the same
|
||||
// as the recursive variant.
|
||||
let drop_call = self.field_drop_call(fty);
|
||||
let drop_call = self.field_drop_call(&fty);
|
||||
out.push_str(&format!(
|
||||
" call void @{drop_call}(ptr %v{val_id})\n"
|
||||
));
|
||||
@@ -374,7 +440,7 @@ impl<'a> Emitter<'a> {
|
||||
/// those are not user-defined ADTs and have no per-type drop fn.
|
||||
pub(crate) fn field_drop_call(&self, fty: &Type) -> String {
|
||||
match fty {
|
||||
Type::Con { name, .. } => {
|
||||
Type::Con { name, args } => {
|
||||
// Built-in pointer-typed cons: Str. No drop fn —
|
||||
// shallow `ailang_rc_dec` is the right answer.
|
||||
// Str has two realisations sharing the consumer
|
||||
@@ -395,19 +461,13 @@ impl<'a> Emitter<'a> {
|
||||
if matches!(name.as_str(), "Str") {
|
||||
return "ailang_rc_dec".to_string();
|
||||
}
|
||||
// Qualified `module.T` → drop fn lives in `module`.
|
||||
if name.matches('.').count() == 1 {
|
||||
let (prefix, suffix) = name.split_once('.').expect("checked");
|
||||
if let Some(target) = self.import_map.get(prefix) {
|
||||
return format!("drop_{target}_{suffix}");
|
||||
}
|
||||
// Fallback: treat the prefix itself as the owner
|
||||
// module (typechecker would have rejected an
|
||||
// unimported prefix earlier).
|
||||
return format!("drop_{prefix}_{suffix}");
|
||||
}
|
||||
// Bare name: declared in the current module.
|
||||
format!("drop_{m}_{name}", m = self.module_name)
|
||||
// Resolve owner + per-monomorph suffix in one place
|
||||
// (leg C). A polymorphic non-intrinsic ADT instantiation
|
||||
// (`Box Int`, `Pair Int Int`) gets the same `__<suffix>`
|
||||
// the emission loop minted for that instantiation;
|
||||
// monomorphic / intrinsic ADTs keep the un-suffixed
|
||||
// symbol byte-for-byte.
|
||||
self.adt_drop_symbol(name, args)
|
||||
}
|
||||
// Type::Fn (closure-typed field) → no per-type drop fn
|
||||
// exists for closures (each closure has its own per-pair
|
||||
@@ -431,6 +491,46 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// resolve the `drop_<owner>_<T>` symbol for an ADT `Type::Con`,
|
||||
/// applying the leg-C per-monomorph suffix when the resolved ADT is
|
||||
/// polymorphic + non-intrinsic. The caller has already excluded
|
||||
/// `Str` and non-`Con` shapes. `name` is the (possibly qualified)
|
||||
/// type name; `args` are its concrete instantiation arguments.
|
||||
///
|
||||
/// The suffix decision lives in [`dropmono::DropAdtMeta`], shared
|
||||
/// with the emission loop, so a call symbol matches its definition
|
||||
/// byte-for-byte (a mismatch is an IR link error).
|
||||
pub(crate) fn adt_drop_symbol(&self, name: &str, args: &[Type]) -> String {
|
||||
let key = super::dropmono::resolve_adt_key(
|
||||
name,
|
||||
self.module_name,
|
||||
&self.import_map,
|
||||
);
|
||||
let base = format!("drop_{owner}_{bare}", owner = key.0, bare = key.1);
|
||||
match self.drop_monos.suffix_for(&key, args) {
|
||||
Some(suffix) => format!("{base}{suffix}"),
|
||||
None => base,
|
||||
}
|
||||
}
|
||||
|
||||
/// resolve the `partial_drop_<owner>_<T>` symbol for an ADT
|
||||
/// `Type::Con`, applying the leg-C per-monomorph suffix. Parallel
|
||||
/// to [`Self::adt_drop_symbol`]. The caller has already excluded
|
||||
/// `Str` / non-`Con` shapes.
|
||||
pub(crate) fn adt_partial_drop_symbol(&self, name: &str, args: &[Type]) -> String {
|
||||
let key = super::dropmono::resolve_adt_key(
|
||||
name,
|
||||
self.module_name,
|
||||
&self.import_map,
|
||||
);
|
||||
let base =
|
||||
format!("partial_drop_{owner}_{bare}", owner = key.0, bare = key.1);
|
||||
match self.drop_monos.suffix_for(&key, args) {
|
||||
Some(suffix) => format!("{base}{suffix}"),
|
||||
None => base,
|
||||
}
|
||||
}
|
||||
|
||||
/// predicate the `Term::Let` lowering uses to decide
|
||||
/// whether a let-binder owns a fresh RC-heap allocation that
|
||||
/// codegen should `dec` at scope close.
|
||||
@@ -448,8 +548,8 @@ impl<'a> Emitter<'a> {
|
||||
/// fn-type carries `ret_mode == Own`. The mode contract states that
|
||||
/// the callee hands the returned cell's ownership to the caller's
|
||||
/// frame; the let-scope close is the right place for the caller's
|
||||
/// dec. Calls whose callee is `Borrow`/`Implicit`-returning are
|
||||
/// still not trackable — they don't carry that signal.
|
||||
/// dec. Calls whose callee is `Borrow`-returning are still not
|
||||
/// trackable — a borrow-return is a view, not an owned ref.
|
||||
///
|
||||
/// Other value shapes (vars, literals, matches, …) return `false`
|
||||
/// here. A `Term::Var` returning an RC-allocated box would already
|
||||
@@ -467,11 +567,10 @@ impl<'a> Emitter<'a> {
|
||||
MTerm::App { callee, .. } => {
|
||||
// a call whose callee carries
|
||||
// `ret_mode == Own` hands a fresh heap allocation to
|
||||
// the caller's frame. Trackable. `Borrow` and
|
||||
// `Implicit` ret-modes do not carry that signal —
|
||||
// returning by Borrow is a view into the callee's
|
||||
// owned data (caller does not own it), and Implicit
|
||||
// is the back-compat lane that 18c.3's debt covers.
|
||||
// the caller's frame. Trackable. A `Borrow` ret-mode
|
||||
// does not carry that signal — returning by Borrow is a
|
||||
// view into the callee's owned data (caller does not
|
||||
// own it).
|
||||
self.synth_callee_ret_mode(callee)
|
||||
.map(|m| matches!(m, ParamMode::Own))
|
||||
.unwrap_or(false)
|
||||
@@ -538,15 +637,16 @@ impl<'a> Emitter<'a> {
|
||||
pub(crate) fn drop_symbol_for_binder(&self, value: &MTerm, val_ssa: &str) -> String {
|
||||
match value {
|
||||
MTerm::Ctor { type_name, .. } => {
|
||||
if type_name.matches('.').count() == 1 {
|
||||
let (prefix, suffix) =
|
||||
type_name.split_once('.').expect("checked");
|
||||
if let Some(target) = self.import_map.get(prefix) {
|
||||
return format!("drop_{target}_{suffix}");
|
||||
}
|
||||
return format!("drop_{prefix}_{suffix}");
|
||||
}
|
||||
format!("drop_{m}_{type_name}", m = self.module_name)
|
||||
// The binder's instantiated result type carries the
|
||||
// concrete args (`Box Int` → args `[Int]`); read them off
|
||||
// `value.ty()` so the leg-C per-monomorph suffix matches
|
||||
// the emitted `drop_<m>_Box__Int`. `type_name` alone
|
||||
// would lose the instantiation.
|
||||
let args = match value.ty() {
|
||||
Type::Con { args, .. } => args,
|
||||
_ => Vec::new(),
|
||||
};
|
||||
self.adt_drop_symbol(type_name, &args)
|
||||
}
|
||||
MTerm::Lam { .. } => self
|
||||
.closure_drops
|
||||
@@ -563,7 +663,7 @@ impl<'a> Emitter<'a> {
|
||||
// var on an as-yet-unmonomorphised polymorphic call —
|
||||
// the monomorphised copies will resolve correctly).
|
||||
MTerm::App { .. } | MTerm::Loop { .. } => {
|
||||
if let Type::Con { name, .. } = value.ty() {
|
||||
if let Type::Con { name, args } = value.ty() {
|
||||
// Symmetric to `field_drop_call`'s Str arm: Str is a
|
||||
// built-in pointer type with no per-type drop fn. Both
|
||||
// heap-Str (rc_header at payload-8) and static-Str
|
||||
@@ -572,15 +672,7 @@ impl<'a> Emitter<'a> {
|
||||
if name == "Str" {
|
||||
return "ailang_rc_dec".to_string();
|
||||
}
|
||||
if name.matches('.').count() == 1 {
|
||||
let (prefix, suffix) =
|
||||
name.split_once('.').expect("checked");
|
||||
if let Some(target) = self.import_map.get(prefix) {
|
||||
return format!("drop_{target}_{suffix}");
|
||||
}
|
||||
return format!("drop_{prefix}_{suffix}");
|
||||
}
|
||||
return format!("drop_{m}_{name}", m = self.module_name);
|
||||
return self.adt_drop_symbol(&name, &args);
|
||||
}
|
||||
"ailang_rc_dec".to_string()
|
||||
}
|
||||
@@ -649,11 +741,36 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// emit one `partial_drop_<m>_<T><suffix>` body. `subst` /
|
||||
/// `sym_suffix` carry the leg-C per-monomorph instantiation,
|
||||
/// identical in role to [`Self::emit_drop_fn_for_instantiation`].
|
||||
fn emit_partial_drop_fn_for_instantiation(
|
||||
&mut self,
|
||||
td: &TypeDef,
|
||||
subst: &BTreeMap<String, Type>,
|
||||
sym_suffix: &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 @partial_drop_{m}_{tname}{sym_suffix}(ptr %p, i64 %mask) {{\n"
|
||||
));
|
||||
out.push_str("entry:\n");
|
||||
out.push_str(" %is_null = icmp eq ptr %p, null\n");
|
||||
@@ -671,8 +788,9 @@ impl<'a> Emitter<'a> {
|
||||
let mut local = 0u64;
|
||||
for (i, ctor) in td.ctors.iter().enumerate() {
|
||||
out.push_str(&format!("arm_{i}:\n"));
|
||||
for (j, fty) in ctor.fields.iter().enumerate() {
|
||||
let lty = llvm_type(fty).unwrap_or_else(|_| "ptr".into());
|
||||
for (j, fty_decl) in ctor.fields.iter().enumerate() {
|
||||
let fty = super::subst::apply_subst_to_type(fty_decl, subst);
|
||||
let lty = llvm_type(&fty).unwrap_or_else(|_| "ptr".into());
|
||||
if lty != "ptr" {
|
||||
continue;
|
||||
}
|
||||
@@ -702,7 +820,7 @@ impl<'a> Emitter<'a> {
|
||||
out.push_str(&format!(
|
||||
" %v{val_id} = load ptr, ptr %a{addr_id}, align 8\n"
|
||||
));
|
||||
let drop_call = self.field_drop_call(fty);
|
||||
let drop_call = self.field_drop_call(&fty);
|
||||
out.push_str(&format!(
|
||||
" call void @{drop_call}(ptr %v{val_id})\n"
|
||||
));
|
||||
@@ -788,21 +906,11 @@ impl<'a> Emitter<'a> {
|
||||
/// populated for those shapes anyway).
|
||||
pub(crate) fn partial_drop_symbol_for_type(&self, ty: &Type) -> Option<String> {
|
||||
match ty {
|
||||
Type::Con { name, .. } => {
|
||||
Type::Con { name, args } => {
|
||||
if name == "Str" {
|
||||
return None;
|
||||
}
|
||||
if name.matches('.').count() == 1 {
|
||||
let (prefix, suffix) = name.split_once('.').expect("checked");
|
||||
if let Some(target) = self.import_map.get(prefix) {
|
||||
return Some(format!("partial_drop_{target}_{suffix}"));
|
||||
}
|
||||
return Some(format!("partial_drop_{prefix}_{suffix}"));
|
||||
}
|
||||
Some(format!(
|
||||
"partial_drop_{m}_{name}",
|
||||
m = self.module_name
|
||||
))
|
||||
Some(self.adt_partial_drop_symbol(name, args))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
@@ -879,12 +987,28 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
};
|
||||
let cref = self.lookup_ctor_by_type(type_name, ctor_name)?;
|
||||
// leg C: substitute the declared field types to the binder's
|
||||
// concrete instantiation (read off `value.ty()`'s args against
|
||||
// the ADT's declared type-vars), so a value-type field is
|
||||
// recognised as inline (non-`ptr`, skipped) and a heap field
|
||||
// routes through its own per-monomorph drop symbol. For a
|
||||
// monomorphic ADT the subst is empty and this is a no-op.
|
||||
let inst_subst: BTreeMap<String, Type> =
|
||||
match (&cref.type_vars, value.ty()) {
|
||||
(vars, Type::Con { args, .. })
|
||||
if !vars.is_empty() && vars.len() == args.len() =>
|
||||
{
|
||||
vars.iter().cloned().zip(args.into_iter()).collect()
|
||||
}
|
||||
_ => BTreeMap::new(),
|
||||
};
|
||||
// Per-field dec for non-moved pointer-typed slots. ail_fields
|
||||
// are the AILang-level field types; field_drop_call resolves
|
||||
// them to either `drop_<owner>_<T>` (ADTs cascade) or
|
||||
// `ailang_rc_dec` (Str / closures / vars).
|
||||
for (idx, fty_ail) in cref.ail_fields.iter().enumerate() {
|
||||
let lty = llvm_type(fty_ail).unwrap_or_else(|_| "ptr".into());
|
||||
for (idx, fty_decl) in cref.ail_fields.iter().enumerate() {
|
||||
let fty_ail = super::subst::apply_subst_to_type(fty_decl, &inst_subst);
|
||||
let lty = llvm_type(&fty_ail).unwrap_or_else(|_| "ptr".into());
|
||||
if lty != "ptr" {
|
||||
continue;
|
||||
}
|
||||
@@ -900,7 +1024,7 @@ impl<'a> Emitter<'a> {
|
||||
self.body.push_str(&format!(
|
||||
" {v} = load ptr, ptr {addr}, align 8\n"
|
||||
));
|
||||
let drop_call = self.field_drop_call(fty_ail);
|
||||
let drop_call = self.field_drop_call(&fty_ail);
|
||||
self.body.push_str(&format!(
|
||||
" call void @{drop_call}(ptr {v})\n"
|
||||
));
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
//! Per-monomorph drop-fn collection for polymorphic ADTs (leg C of
|
||||
//! the #55-cutover drop-soundness family).
|
||||
//!
|
||||
//! Background. Pre-cutover, the per-ADT drop fn was emitted exactly
|
||||
//! once per `Def::Type`, keyed by the *declared* field types. For a
|
||||
//! polymorphic ADT (`Box a`, `Pair a b`) a ctor field typed at a
|
||||
//! type-var `a` failed `synth::llvm_type` and fell back to `ptr`,
|
||||
//! emitting an `ailang_rc_dec` on it. At a value-type monomorph
|
||||
//! (`Box Int`) that field is an inline `i64`, so `rc_dec(7)`
|
||||
//! dereferences the scalar and SIGSEGVs.
|
||||
//!
|
||||
//! Fix. Emit one drop fn per (polymorphic-ADT, concrete instantiation)
|
||||
//! and decide dec-vs-skip on the *substituted* field type. A
|
||||
//! value-type field (`Int`/`Bool`/`Float`/`Unit`) is skipped (inline
|
||||
//! scalar, no RC); a heap field is dec'd through its own per-monomorph
|
||||
//! drop symbol so the cascade composes.
|
||||
//!
|
||||
//! Symbol naming. A concrete-monomorphic ADT (`vars` empty: IntList,
|
||||
//! Ordering) keeps its un-suffixed `drop_<m>_<T>` symbol byte-for-byte
|
||||
//! (the `ir_snapshot` goldens pin these). A polymorphic ADT
|
||||
//! instantiation gets a mono suffix mangled the same way
|
||||
//! `ailang_check::mono::mono_symbol_n` mangles fn symbols
|
||||
//! (`drop_<m>_Pair__Int_Int`; compound args hash-route to stay
|
||||
//! bounded). Intrinsic-storage types (RawBuf) are polymorphic but use
|
||||
//! the flat intrinsic drop path — they are NOT suffixed (their drop is
|
||||
//! element-type independent and the golden pins `drop_<m>_RawBuf`).
|
||||
//!
|
||||
//! This module owns the workspace-global collection
|
||||
//! ([`collect_drop_monos`]) and the shared suffix decision
|
||||
//! ([`DropAdtMeta`]); the emission and call-site manglers in `drop.rs`
|
||||
//! / `match_lower.rs` consult the same [`DropAdtMeta`] so a definition
|
||||
//! and its callers always agree on the symbol (a mismatch is a link
|
||||
//! error).
|
||||
|
||||
use ailang_core::ast::{Def, Module, Term, Type, TypeDef};
|
||||
use ailang_mir::{Callee, MArg, MNewArg, MTerm, MirWorkspace};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use super::subst::{apply_subst_to_type, qualify_local_types_codegen};
|
||||
|
||||
/// A canonical key for an ADT: `(owner_module, bare_type_name)`.
|
||||
pub(crate) type AdtKey = (String, String);
|
||||
|
||||
/// Workspace-global drop-monomorphisation metadata, built once before
|
||||
/// the per-module codegen loop and shared (by reference) with every
|
||||
/// `Emitter`.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct DropAdtMeta {
|
||||
/// ADTs that take a per-monomorph drop-symbol suffix: declared
|
||||
/// `vars` non-empty AND not intrinsic-storage. A `Type::Con` whose
|
||||
/// resolved key is in this set, and whose `args` are non-empty,
|
||||
/// gets a `__<suffix>` appended to its `drop_`/`partial_drop_`
|
||||
/// symbol.
|
||||
suffixed: BTreeSet<AdtKey>,
|
||||
/// For each suffixed ADT, the concrete arg-tuples it is
|
||||
/// instantiated at across the whole workspace, keyed by a canonical
|
||||
/// hash of the arg-tuple (`Type` is not `Ord`, so the tuple itself
|
||||
/// cannot key a `BTreeSet`; the hash gives a deterministic,
|
||||
/// dedup-stable key). The emission loop reads the values to emit one
|
||||
/// drop fn per instantiation in the owning module.
|
||||
monos: BTreeMap<AdtKey, BTreeMap<String, Vec<Type>>>,
|
||||
}
|
||||
|
||||
/// Canonical, deterministic key for an arg-tuple (used to dedup
|
||||
/// instantiations that `Type`'s non-`Ord`-ness blocks from a set key).
|
||||
fn args_key(args: &[Type]) -> String {
|
||||
args.iter()
|
||||
.map(ailang_core::canonical::type_hash)
|
||||
.collect::<Vec<_>>()
|
||||
.join("|")
|
||||
}
|
||||
|
||||
impl DropAdtMeta {
|
||||
/// Is this ADT (resolved to `key`) suffixed (poly + non-intrinsic)?
|
||||
pub(crate) fn is_suffixed(&self, key: &AdtKey) -> bool {
|
||||
self.suffixed.contains(key)
|
||||
}
|
||||
|
||||
/// The concrete instantiations of `key` to emit in its owner
|
||||
/// module. Empty for monomorphic / intrinsic / never-instantiated
|
||||
/// ADTs.
|
||||
pub(crate) fn instantiations(&self, key: &AdtKey) -> Vec<Vec<Type>> {
|
||||
self.monos
|
||||
.get(key)
|
||||
.map(|s| s.values().cloned().collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The mono symbol suffix for an instantiation `args`, mangled the
|
||||
/// same way `mono_symbol_n` mangles fn type-args. `None` when the
|
||||
/// type is not suffixed (monomorphic / intrinsic) or has no args —
|
||||
/// caller keeps the un-suffixed base symbol. The leading `__`
|
||||
/// joiner is included so callers concatenate directly.
|
||||
pub(crate) fn suffix_for(&self, key: &AdtKey, args: &[Type]) -> Option<String> {
|
||||
if args.is_empty() || !self.is_suffixed(key) {
|
||||
return None;
|
||||
}
|
||||
// Reuse the fn-symbol mangler: `mono_symbol_n("", &args)` would
|
||||
// prepend an empty base + joiner. We want only the per-arg
|
||||
// suffix joined by `__`, prefixed with the `__` that separates
|
||||
// it from the drop base. Mirror `mono_symbol_n`'s join exactly.
|
||||
let parts: Vec<String> = args
|
||||
.iter()
|
||||
.map(ailang_check::mono::type_mono_suffix)
|
||||
.collect();
|
||||
Some(format!("__{}", parts.join("__")))
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a (possibly qualified) `Type::Con` name to its canonical
|
||||
/// `(owner_module, bare_name)` key, using `import_map` for the
|
||||
/// qualified case and `current_module` for the bare case.
|
||||
pub(crate) fn resolve_adt_key(
|
||||
name: &str,
|
||||
current_module: &str,
|
||||
import_map: &BTreeMap<String, String>,
|
||||
) -> AdtKey {
|
||||
if name.matches('.').count() == 1 {
|
||||
let (prefix, suffix) = name.split_once('.').expect("checked");
|
||||
let owner = import_map
|
||||
.get(prefix)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or(prefix);
|
||||
(owner.to_string(), suffix.to_string())
|
||||
} else {
|
||||
(current_module.to_string(), name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this `Def::Type` use the flat intrinsic-storage drop path
|
||||
/// (its `new` op is an `(intrinsic)`, not a real term-ctor body)?
|
||||
/// Mirror of the gate in `lib.rs`'s drop-fn emission loop.
|
||||
fn is_intrinsic_storage(module: &Module, td: &TypeDef) -> bool {
|
||||
module.defs.iter().any(|d| {
|
||||
matches!(d, Def::Fn(f)
|
||||
if f.name == "new"
|
||||
&& matches!(f.body, Term::Intrinsic)
|
||||
&& fn_returns_type_name(f, &td.name))
|
||||
})
|
||||
}
|
||||
|
||||
/// Does fn `f` return a `Type::Con` named `tname` (outermost)? Mirror
|
||||
/// of `lib.rs::fn_returns_type` (kept local to avoid widening that
|
||||
/// fn's visibility).
|
||||
fn fn_returns_type_name(f: &ailang_core::ast::FnDef, tname: &str) -> bool {
|
||||
fn outer(t: &Type, tname: &str) -> bool {
|
||||
match t {
|
||||
Type::Con { name, .. } => name == tname,
|
||||
Type::Forall { body, .. } => outer(body, tname),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
if let Type::Fn { ret, .. } = &f.ty {
|
||||
outer(ret, tname)
|
||||
} else if let Type::Forall { body, .. } = &f.ty {
|
||||
if let Type::Fn { ret, .. } = body.as_ref() {
|
||||
outer(ret, tname)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the workspace-global drop-monomorphisation table: which ADTs
|
||||
/// are suffixed, and which concrete instantiations each is used at.
|
||||
pub(crate) fn collect_drop_monos(mir: &MirWorkspace) -> DropAdtMeta {
|
||||
let mut meta = DropAdtMeta::default();
|
||||
|
||||
// Per-module set of locally-declared ADT names. Used to qualify a
|
||||
// bare type-con reference (`List` in `std_list`'s body) to its
|
||||
// canonical `module.Type` form *at the module where it appears*, so
|
||||
// a stored arg / field resolves to the same owner regardless of
|
||||
// which module later emits a drop fn referencing it. Without this,
|
||||
// a `Maybe (List Int)` collected in `std_list` would store a bare
|
||||
// `List`, and the drop fn emitted in `std_maybe` (Maybe's owner)
|
||||
// would resolve that bare `List` to `drop_std_maybe_List` (wrong
|
||||
// owner) — an undefined-symbol link error.
|
||||
let mut local_types: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||
|
||||
// 1. Determine the suffixed set + record each ADT's declared
|
||||
// type-vars and ctor field types (under their owner module),
|
||||
// so the fixpoint below can expand nested field instantiations.
|
||||
// `adt_decl[(m, T)] = (vars, ctor_field_types_flattened)`.
|
||||
let mut adt_decl: BTreeMap<AdtKey, (Vec<String>, Vec<Type>)> = BTreeMap::new();
|
||||
for (mname, mir_module) in &mir.modules {
|
||||
let m = &mir_module.ast;
|
||||
for def in &m.defs {
|
||||
if let Def::Type(td) = def {
|
||||
let key = (mname.clone(), td.name.clone());
|
||||
let fields: Vec<Type> =
|
||||
td.ctors.iter().flat_map(|c| c.fields.clone()).collect();
|
||||
adt_decl.insert(key.clone(), (td.vars.clone(), fields));
|
||||
local_types
|
||||
.entry(mname.clone())
|
||||
.or_default()
|
||||
.insert(td.name.clone());
|
||||
if !td.vars.is_empty() && !is_intrinsic_storage(m, td) {
|
||||
meta.suffixed.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Seed the instantiation set by walking every MIR body / const
|
||||
// and collecting every `Type` that appears. Each walked type is
|
||||
// qualified against its module's local types before collection,
|
||||
// so stored args carry canonical `module.Type` names.
|
||||
let mut seeds: Vec<(AdtKey, Vec<Type>)> = Vec::new();
|
||||
for (mname, mir_module) in &mir.modules {
|
||||
let m = &mir_module.ast;
|
||||
let import_map = build_import_map(m);
|
||||
let owner_local = local_types.get(mname).cloned().unwrap_or_default();
|
||||
let mut sink = |t: &Type| {
|
||||
let q = qualify_local_types_codegen(t, mname, &owner_local);
|
||||
collect_instantiations(&q, mname, &import_map, &meta.suffixed, &mut seeds);
|
||||
};
|
||||
for d in &mir_module.defs {
|
||||
walk_term_types(&d.body, &mut sink);
|
||||
}
|
||||
for c in &mir_module.consts {
|
||||
walk_term_types(&c.body, &mut sink);
|
||||
}
|
||||
// Also walk every fn signature type appearing in the AST defs —
|
||||
// an Own-returned polymorphic ADT whose ctor is built in a
|
||||
// callee still needs its drop emitted in the caller's module
|
||||
// when the let-close decs it. The body walk catches the ctor
|
||||
// site; the signature walk is belt-and-braces for ret types
|
||||
// that surface only through a call.
|
||||
for def in &m.defs {
|
||||
if let Def::Fn(f) = def {
|
||||
walk_type(&f.ty, &mut sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fixpoint: an instantiation `T<args>` needs each of its ctor
|
||||
// fields, substituted by {var -> arg}, to also be emitted (a
|
||||
// `Pair (Box Int) Int` field-drops `Box Int`). Expand until no
|
||||
// new (key, args) pairs appear. Dedup on `(AdtKey, args-hash)`
|
||||
// since `Type` is not `Ord`.
|
||||
let mut worklist: Vec<(AdtKey, Vec<Type>)> = seeds;
|
||||
let mut seen: BTreeSet<(AdtKey, String)> = BTreeSet::new();
|
||||
while let Some((key, args)) = worklist.pop() {
|
||||
let ak = args_key(&args);
|
||||
if !seen.insert((key.clone(), ak.clone())) {
|
||||
continue;
|
||||
}
|
||||
meta.monos
|
||||
.entry(key.clone())
|
||||
.or_default()
|
||||
.insert(ak, args.clone());
|
||||
let Some((vars, fields)) = adt_decl.get(&key) else {
|
||||
continue;
|
||||
};
|
||||
if vars.len() != args.len() {
|
||||
// Defensive: arity mismatch means our resolution missed;
|
||||
// skip rather than mis-substitute.
|
||||
continue;
|
||||
}
|
||||
let subst: BTreeMap<String, Type> =
|
||||
vars.iter().cloned().zip(args.iter().cloned()).collect();
|
||||
// Field types are written in the owner module's local
|
||||
// namespace; resolve against the owner's import map and qualify
|
||||
// the owner's own bare type-cons (e.g. a `List a` self-field, or
|
||||
// a sibling ADT) so the nested instantiation keys on the
|
||||
// canonical owner. The substituted-in args are already
|
||||
// qualified from the seed pass.
|
||||
let owner = key.0.clone();
|
||||
let import_map = mir
|
||||
.modules
|
||||
.get(&owner)
|
||||
.map(|mm| build_import_map(&mm.ast))
|
||||
.unwrap_or_default();
|
||||
let owner_local = local_types.get(&owner).cloned().unwrap_or_default();
|
||||
for f in fields {
|
||||
let concrete = apply_subst_to_type(f, &subst);
|
||||
let concrete = qualify_local_types_codegen(&concrete, &owner, &owner_local);
|
||||
collect_instantiations(
|
||||
&concrete,
|
||||
&owner,
|
||||
&import_map,
|
||||
&meta.suffixed,
|
||||
&mut worklist,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
meta
|
||||
}
|
||||
|
||||
/// Build a module's import map (alias|name -> actual module), mirroring
|
||||
/// the per-module loop in `lower_workspace_inner`.
|
||||
fn build_import_map(m: &Module) -> BTreeMap<String, String> {
|
||||
let mut import_map: BTreeMap<String, String> = BTreeMap::new();
|
||||
for imp in &m.imports {
|
||||
let key = imp.alias.clone().unwrap_or_else(|| imp.module.clone());
|
||||
import_map.insert(key, imp.module.clone());
|
||||
}
|
||||
if m.name != "prelude" {
|
||||
import_map
|
||||
.entry("prelude".to_string())
|
||||
.or_insert_with(|| "prelude".to_string());
|
||||
}
|
||||
import_map
|
||||
}
|
||||
|
||||
/// Collect, from a single `Type`, every suffixed-ADT instantiation
|
||||
/// (recursing into args).
|
||||
fn collect_instantiations(
|
||||
t: &Type,
|
||||
current_module: &str,
|
||||
import_map: &BTreeMap<String, String>,
|
||||
suffixed: &BTreeSet<AdtKey>,
|
||||
out: &mut Vec<(AdtKey, Vec<Type>)>,
|
||||
) {
|
||||
match t {
|
||||
Type::Con { name, args } => {
|
||||
if !args.is_empty() {
|
||||
let key = resolve_adt_key(name, current_module, import_map);
|
||||
if suffixed.contains(&key) {
|
||||
out.push((key, args.clone()));
|
||||
}
|
||||
}
|
||||
for a in args {
|
||||
collect_instantiations(a, current_module, import_map, suffixed, out);
|
||||
}
|
||||
}
|
||||
Type::Fn { params, ret, .. } => {
|
||||
for p in params {
|
||||
collect_instantiations(p, current_module, import_map, suffixed, out);
|
||||
}
|
||||
collect_instantiations(ret, current_module, import_map, suffixed, out);
|
||||
}
|
||||
Type::Forall { body, .. } => {
|
||||
collect_instantiations(body, current_module, import_map, suffixed, out)
|
||||
}
|
||||
Type::Var { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply `sink` to every `Type` reachable from a `Type`.
|
||||
fn walk_type<F: FnMut(&Type)>(t: &Type, sink: &mut F) {
|
||||
sink(t);
|
||||
}
|
||||
|
||||
/// Apply `sink` to every `Type` carried by `term` and its sub-terms.
|
||||
/// `sink` itself recurses into the type structure
|
||||
/// ([`collect_instantiations`]), so this walker only has to surface
|
||||
/// every type-bearing node once.
|
||||
fn walk_term_types<F: FnMut(&Type)>(term: &MTerm, sink: &mut F) {
|
||||
// The node's own static type.
|
||||
let ty = term.ty();
|
||||
sink(&ty);
|
||||
match term {
|
||||
MTerm::Lit { .. }
|
||||
| MTerm::Var { .. }
|
||||
| MTerm::Str { .. }
|
||||
| MTerm::Intrinsic { .. }
|
||||
| MTerm::Recur { .. } => {}
|
||||
MTerm::App { callee, args, .. } => {
|
||||
walk_callee_types(callee, sink);
|
||||
walk_args(args, sink);
|
||||
}
|
||||
MTerm::Do { args, .. } => walk_args(args, sink),
|
||||
MTerm::Ctor { args, .. } => walk_args(args, sink),
|
||||
MTerm::New { elem, args, .. } => {
|
||||
if let Some(e) = elem {
|
||||
sink(e);
|
||||
}
|
||||
for a in args {
|
||||
if let MNewArg::Value(v) = a {
|
||||
walk_term_types(v, sink);
|
||||
} else if let MNewArg::Type(t) = a {
|
||||
sink(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
MTerm::Let { init, body, .. } => {
|
||||
walk_term_types(init, sink);
|
||||
walk_term_types(body, sink);
|
||||
}
|
||||
MTerm::LetRec { sig, body, in_term, .. } => {
|
||||
sink(sig);
|
||||
walk_term_types(body, sink);
|
||||
walk_term_types(in_term, sink);
|
||||
}
|
||||
MTerm::If { cond, then, else_, .. } => {
|
||||
walk_term_types(cond, sink);
|
||||
walk_term_types(then, sink);
|
||||
walk_term_types(else_, sink);
|
||||
}
|
||||
MTerm::Match { scrutinee, arms, .. } => {
|
||||
walk_term_types(scrutinee, sink);
|
||||
for arm in arms {
|
||||
walk_term_types(&arm.body, sink);
|
||||
}
|
||||
}
|
||||
MTerm::Lam { param_tys, ret_ty, body, .. } => {
|
||||
for p in param_tys {
|
||||
sink(p);
|
||||
}
|
||||
sink(ret_ty);
|
||||
walk_term_types(body, sink);
|
||||
}
|
||||
MTerm::Seq { lhs, rhs, .. } => {
|
||||
walk_term_types(lhs, sink);
|
||||
walk_term_types(rhs, sink);
|
||||
}
|
||||
MTerm::Clone { value, .. } => walk_term_types(value, sink),
|
||||
MTerm::ReuseAs { source, body, .. } => {
|
||||
walk_term_types(source, sink);
|
||||
walk_term_types(body, sink);
|
||||
}
|
||||
MTerm::Loop { binders, body, .. } => {
|
||||
for b in binders {
|
||||
sink(&b.ty);
|
||||
walk_term_types(&b.init, sink);
|
||||
}
|
||||
walk_term_types(body, sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_args<F: FnMut(&Type)>(args: &[MArg], sink: &mut F) {
|
||||
for a in args {
|
||||
walk_term_types(&a.term, sink);
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_callee_types<F: FnMut(&Type)>(callee: &Callee, sink: &mut F) {
|
||||
match callee {
|
||||
Callee::Static { sig, .. } | Callee::Builtin { sig, .. } => sink(sig),
|
||||
Callee::Indirect(inner) => walk_term_types(inner, sink),
|
||||
}
|
||||
}
|
||||
@@ -153,15 +153,13 @@ impl<'a> Emitter<'a> {
|
||||
let saved_entry_marker = self.entry_block_end_marker.take();
|
||||
// A lambda thunk is its own fn frame for
|
||||
// param-mode lookup. The outer fn's params are not in scope
|
||||
// inside the thunk; the thunk's own params are pushed below
|
||||
// and (currently) carry no mode annotation, so they default
|
||||
// to `Implicit` — `lower_match`'s Iter A gate will skip arm-
|
||||
// close pattern-binder dec for matches on lambda params,
|
||||
// mirroring the fn-level Implicit-param treatment.
|
||||
// inside the thunk; the thunk's own params carry no surface
|
||||
// annotation, so they are synthesised `Own` — the consume-side
|
||||
// default that owns its argument for the thunk body.
|
||||
let saved_param_modes = std::mem::take(&mut self.current_param_modes);
|
||||
for pname in lam_params.iter() {
|
||||
self.current_param_modes
|
||||
.insert(pname.clone(), ParamMode::Implicit);
|
||||
.insert(pname.clone(), ParamMode::Own);
|
||||
}
|
||||
// Lambdas inside lambdas are fine: they get their own counter
|
||||
// namespace within the enclosing thunk. They share the
|
||||
|
||||
@@ -40,6 +40,7 @@ use ailang_mir::{Callee, MArg, MTerm, MirDef, MirWorkspace, Mode, StrRep};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
mod drop;
|
||||
mod dropmono;
|
||||
mod escape;
|
||||
mod intercepts;
|
||||
mod lambda;
|
||||
@@ -396,6 +397,14 @@ fn lower_workspace_inner(mir: &MirWorkspace, alloc: AllocStrategy, target: Targe
|
||||
module_consts.insert(mname.clone(), consts);
|
||||
}
|
||||
|
||||
// Leg C: workspace-global per-monomorph drop metadata. Built once
|
||||
// here (after the symbol-table pass, before lowering) and shared by
|
||||
// reference with every `Emitter`. Computes which polymorphic ADTs
|
||||
// take a per-instantiation drop fn and the concrete arg-tuples each
|
||||
// is used at, so the emission loop and the call-site manglers agree
|
||||
// on every `drop_<m>_<T>__<suffix>` symbol.
|
||||
let drop_monos = dropmono::collect_drop_monos(mir);
|
||||
|
||||
// Pass 2: lower per module. Globals/strings are accumulated per module,
|
||||
// because they are mangled per module.
|
||||
for (mname, mir_module) in &mir.modules {
|
||||
@@ -432,6 +441,7 @@ fn lower_workspace_inner(mir: &MirWorkspace, alloc: AllocStrategy, target: Targe
|
||||
&module_ctor_index,
|
||||
&module_consts,
|
||||
import_map,
|
||||
&drop_monos,
|
||||
alloc,
|
||||
);
|
||||
emitter
|
||||
@@ -770,6 +780,13 @@ struct Emitter<'a> {
|
||||
module_user_fns: &'a BTreeMap<String, BTreeMap<String, FnSig>>,
|
||||
/// Import map of the current module (alias/module name → actual module name).
|
||||
import_map: BTreeMap<String, String>,
|
||||
/// workspace-global per-monomorph drop metadata (leg C). Tells the
|
||||
/// drop-fn emission loop which polymorphic ADTs to emit one drop fn
|
||||
/// per instantiation for, and tells the call-site manglers whether
|
||||
/// a `Type::Con` takes a per-monomorph `__<suffix>` on its
|
||||
/// `drop_`/`partial_drop_` symbol. Shared by reference with every
|
||||
/// `Emitter`.
|
||||
drop_monos: &'a dropmono::DropAdtMeta,
|
||||
/// ADT table: type_name -> list of ctors in definition order.
|
||||
/// Tag of a ctor = index in this list.
|
||||
/// Kept around for future tools (pretty-printer for ADT values,
|
||||
@@ -874,15 +891,15 @@ struct Emitter<'a> {
|
||||
/// entry) from the fn type's `param_modes`. Consulted by
|
||||
/// `lower_match`'s arm-close pattern-binder dec emission (Iter A) to
|
||||
/// decide whether the scrutinee was statically owned: if the
|
||||
/// scrutinee resolves to a fn-param whose mode is `Borrow` or
|
||||
/// `Implicit`, the pattern-binder dec must NOT fire — the caller
|
||||
/// still holds a reference and dec'ing the pattern-binder would
|
||||
/// fragment the caller's structure.
|
||||
/// scrutinee resolves to a fn-param whose mode is `Borrow`, the
|
||||
/// pattern-binder dec must NOT fire — the caller still holds a
|
||||
/// reference and dec'ing the pattern-binder would fragment the
|
||||
/// caller's structure.
|
||||
///
|
||||
/// Symmetric with the Iter B gate at fn return (`emit_fn`'s Own-
|
||||
/// param dec): both sites must check the param-mode signal before
|
||||
/// dec'ing, because Implicit and Borrow do not carry the "caller
|
||||
/// handed off ownership" signal that makes the dec safe.
|
||||
/// param dec): both sites check the param-mode signal before
|
||||
/// dec'ing, because `Borrow` does not carry the "caller handed off
|
||||
/// ownership" signal that makes the dec safe.
|
||||
current_param_modes: BTreeMap<String, ParamMode>,
|
||||
/// Per-fn map of name → (alloca SSA name, AIL element type) for
|
||||
/// alloca-resident loop binders. Populated on entry to a
|
||||
@@ -973,6 +990,7 @@ impl<'a> Emitter<'a> {
|
||||
module_ctor_index: &'a BTreeMap<String, BTreeMap<String, CtorRef>>,
|
||||
module_consts: &'a BTreeMap<String, BTreeMap<String, ConstDef>>,
|
||||
import_map: BTreeMap<String, String>,
|
||||
drop_monos: &'a dropmono::DropAdtMeta,
|
||||
alloc: AllocStrategy,
|
||||
) -> Self {
|
||||
let mut types: BTreeMap<String, Vec<CtorInfo>> = BTreeMap::new();
|
||||
@@ -1030,6 +1048,7 @@ impl<'a> Emitter<'a> {
|
||||
str_counter: 0,
|
||||
module_user_fns,
|
||||
import_map,
|
||||
drop_monos,
|
||||
types,
|
||||
module_ctor_index,
|
||||
module_consts,
|
||||
@@ -1293,10 +1312,9 @@ impl<'a> Emitter<'a> {
|
||||
fn emit_fn(&mut self, f: &FnDef, body: Option<&MTerm>) -> Result<()> {
|
||||
// also lift `param_modes` out of the fn type. The
|
||||
// fn-return Own-param dec emission below consults it to decide
|
||||
// which params get a drop call before `ret`. `Implicit`
|
||||
// entries (legacy / unannotated) and `Borrow` entries are
|
||||
// skipped — only `Own` carries the static "caller handed off
|
||||
// ownership" signal.
|
||||
// which params get a drop call before `ret`. `Borrow` entries
|
||||
// are skipped — only `Own` carries the static "caller handed
|
||||
// off ownership" signal.
|
||||
let (param_tys, ret_ty, param_modes) = match &f.ty {
|
||||
Type::Fn {
|
||||
params,
|
||||
@@ -1339,7 +1357,10 @@ impl<'a> Emitter<'a> {
|
||||
self.pending_entry_allocas.clear();
|
||||
self.entry_block_end_marker = None;
|
||||
for (i, pname) in f.params.iter().enumerate() {
|
||||
let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Implicit);
|
||||
// Codegen-synthesised fn-defs (lambda thunks, local-rec
|
||||
// lifts) may carry an empty `param_modes`; fall back to
|
||||
// `Own` (the synthesis default) rather than index-panic.
|
||||
let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Own);
|
||||
self.current_param_modes.insert(pname.clone(), mode);
|
||||
}
|
||||
// run escape analysis over the fn body. The result
|
||||
@@ -1469,11 +1490,10 @@ impl<'a> Emitter<'a> {
|
||||
// the caller's frame; caller dec's, not us),
|
||||
// - the current block is still open.
|
||||
//
|
||||
// `Implicit`-mode params do NOT get this dec: they have
|
||||
// no static "caller handed off ownership" signal —
|
||||
// emitting a dec here might double-dec a value the caller
|
||||
// also dec's. `Borrow`-mode params definitely don't get
|
||||
// dec'd (the caller still owns them).
|
||||
// `Borrow`-mode params do NOT get dec'd: the caller still
|
||||
// owns them, so there is no caller-handed-off-ownership
|
||||
// signal and a dec here would fragment the caller's
|
||||
// structure.
|
||||
//
|
||||
// Closes the 18c.3/18c.4 carve-out: "fn parameters still
|
||||
// don't get dec'd at fn return — the caller-handed-off-
|
||||
@@ -1490,7 +1510,7 @@ impl<'a> Emitter<'a> {
|
||||
if plty != "ptr" {
|
||||
continue;
|
||||
}
|
||||
let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Implicit);
|
||||
let mode = param_modes.get(i).copied().unwrap_or(ParamMode::Own);
|
||||
if !matches!(mode, ParamMode::Own) {
|
||||
continue;
|
||||
}
|
||||
@@ -1812,8 +1832,8 @@ impl<'a> Emitter<'a> {
|
||||
// If `value` is a `Term::Var` referencing a name in
|
||||
// `current_param_modes`, the let-binder inherits that
|
||||
// mode for the duration of the body. Without this,
|
||||
// `(let a t (match a ...))` where `t` is an Implicit
|
||||
// / Borrow-mode param defeats the
|
||||
// `(let a t (match a ...))` where `t` is a
|
||||
// `Borrow`-mode param defeats the
|
||||
// `scrutinee_is_owned` gate in `lower_match` (the
|
||||
// gate looks up `a` in `current_param_modes`, misses,
|
||||
// and defaults to "owned" — Iter A then dec's
|
||||
@@ -2690,8 +2710,7 @@ impl<'a> Emitter<'a> {
|
||||
// alias whose owner is some other binder and is dropped
|
||||
// there, never here;
|
||||
// - the matching callee param mode is `Borrow` — `Own` slots
|
||||
// consume the arg (the callee dec's it), `Implicit` is the
|
||||
// back-compat lane that carries no transfer signal;
|
||||
// consume the arg (the callee dec's it);
|
||||
// - the dropped SSA is never the call result `dst` (an input
|
||||
// argument SSA is always distinct from the freshly-minted
|
||||
// result SSA), so this can never dec a value that flows out
|
||||
@@ -2702,7 +2721,7 @@ impl<'a> Emitter<'a> {
|
||||
// `param_modes`), so the borrow-slot test reads it directly
|
||||
// — no re-lookup of the callee's signature from a codegen
|
||||
// sig table. `Borrow` slots borrow the arg, so an Own-ret
|
||||
// heap temp landing in one is dropped here; `Own`/`Implicit`
|
||||
// heap temp landing in one is dropped here; `Own`
|
||||
// slots consume the arg (the callee dec's it).
|
||||
for (arg, (arg_ssa, arg_ty)) in args.iter().zip(compiled_args.iter()) {
|
||||
let is_borrow_slot = matches!(arg.mode, Mode::Borrow);
|
||||
@@ -3168,7 +3187,7 @@ mod tests {
|
||||
ret: Box::new(Type::int()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec!["a".into(), "b".into()],
|
||||
body: Term::App {
|
||||
@@ -3192,7 +3211,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3238,7 +3257,7 @@ mod tests {
|
||||
ret: Box::new(Type::int()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit {
|
||||
@@ -3313,7 +3332,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3386,7 +3405,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3418,7 +3437,7 @@ mod tests {
|
||||
ret: Box::new(ret_ty),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body,
|
||||
@@ -3491,7 +3510,7 @@ mod tests {
|
||||
name: name.into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![], ret: Box::new(ret_ty), effects: vec![],
|
||||
param_modes: vec![], ret_mode: ParamMode::Implicit,
|
||||
param_modes: vec![], ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![], body, suppress: vec![], doc: None,
|
||||
export: None,
|
||||
@@ -3543,7 +3562,7 @@ mod tests {
|
||||
name: name.into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![], ret: Box::new(Type::float()), effects: vec![],
|
||||
param_modes: vec![], ret_mode: ParamMode::Implicit,
|
||||
param_modes: vec![], ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![], body, suppress: vec![], doc: None,
|
||||
export: None,
|
||||
@@ -3553,7 +3572,7 @@ mod tests {
|
||||
name: "main".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![], ret: Box::new(Type::unit()), effects: vec![],
|
||||
param_modes: vec![], ret_mode: ParamMode::Implicit,
|
||||
param_modes: vec![], ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![], body: Term::Lit { lit: Literal::Unit },
|
||||
suppress: vec![], doc: None,
|
||||
@@ -3598,7 +3617,7 @@ mod tests {
|
||||
ret: Box::new(Type::bool_()),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec!["x".into(), "y".into()],
|
||||
// Body is a placeholder — the intercept must
|
||||
@@ -3616,7 +3635,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3661,7 +3680,7 @@ mod tests {
|
||||
ret: Box::new(Type::bool_()),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec!["x".into(), "y".into()],
|
||||
body: Term::Lit { lit: Literal::Bool { value: false } },
|
||||
@@ -3676,7 +3695,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3725,7 +3744,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3824,8 +3843,11 @@ mod tests {
|
||||
params: vec![param_ail_ty.clone(), param_ail_ty.clone()],
|
||||
ret: Box::new(Type::Con { name: "Ordering".into(), args: vec![] }),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
// value-typed params (Int/Bool) cannot be `(borrow V)` —
|
||||
// the cutover's `borrow-over-value` reject forbids it; a
|
||||
// value type is copied, so `own` is the only legal mode.
|
||||
param_modes: vec![ParamMode::Own, ParamMode::Own],
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec!["x".into(), "y".into()],
|
||||
// placeholder body; the `compare__<T>` intercept overrides
|
||||
@@ -3849,7 +3871,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3894,7 +3916,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -3930,7 +3952,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -3972,7 +3994,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -4033,7 +4055,7 @@ mod tests {
|
||||
ret: Box::new(Type::bool_()),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow, ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec!["x".into(), "y".into()],
|
||||
body: Term::Lit { lit: Literal::Bool { value: false } },
|
||||
@@ -4048,7 +4070,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Lit { lit: Literal::Unit },
|
||||
@@ -4129,7 +4151,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -4170,7 +4192,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -4212,7 +4234,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -4254,7 +4276,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
@@ -4296,7 +4318,7 @@ mod tests {
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
|
||||
@@ -256,8 +256,16 @@ impl<'a> Emitter<'a> {
|
||||
} else {
|
||||
cref.ail_fields.clone()
|
||||
};
|
||||
let expected_llvm_tys: Vec<String> = if cref.type_vars.is_empty() {
|
||||
cref.fields.clone()
|
||||
// leg C: the body-ctor field substitution (var -> concrete
|
||||
// arg). Empty for monomorphic ADTs; for a polymorphic body ctor
|
||||
// it pins each declared field var to the instantiation arg
|
||||
// derived from the body args' types. Used both for the expected
|
||||
// LLVM field types AND (further down) for the reuse-arm per-field
|
||||
// dec, so a value-type field is recognised as inline (skipped)
|
||||
// and a heap field routes through its own per-monomorph drop
|
||||
// symbol.
|
||||
let field_subst: BTreeMap<String, Type> = if cref.type_vars.is_empty() {
|
||||
BTreeMap::new()
|
||||
} else {
|
||||
let arg_ail_tys: Vec<Type> = body_args
|
||||
.iter()
|
||||
@@ -269,9 +277,14 @@ impl<'a> Emitter<'a> {
|
||||
for (exp, actual) in qualified_ail_fields.iter().zip(arg_ail_tys.iter()) {
|
||||
unify_for_subst(exp, actual, &var_set, &mut subst)?;
|
||||
}
|
||||
subst
|
||||
};
|
||||
let expected_llvm_tys: Vec<String> = if cref.type_vars.is_empty() {
|
||||
cref.fields.clone()
|
||||
} else {
|
||||
qualified_ail_fields
|
||||
.iter()
|
||||
.map(|f| llvm_type(&apply_subst_to_type(f, &subst)))
|
||||
.map(|f| llvm_type(&apply_subst_to_type(f, &field_subst)))
|
||||
.collect::<Result<_>>()?
|
||||
};
|
||||
// (18d.2 currently does not dec old fields in the reuse
|
||||
@@ -353,8 +366,12 @@ impl<'a> Emitter<'a> {
|
||||
.as_ref()
|
||||
.and_then(|sb| self.moved_slots.get(sb).cloned())
|
||||
.unwrap_or_default();
|
||||
for (idx, fty_ail) in qualified_ail_fields.iter().enumerate() {
|
||||
let lty = llvm_type(fty_ail).unwrap_or_else(|_| "ptr".into());
|
||||
for (idx, fty_decl) in qualified_ail_fields.iter().enumerate() {
|
||||
// leg C: substitute to the body-ctor's monomorph so a
|
||||
// value-type slot is skipped (inline scalar) and a heap slot
|
||||
// dec's through its own per-monomorph drop symbol.
|
||||
let fty_ail = apply_subst_to_type(fty_decl, &field_subst);
|
||||
let lty = llvm_type(&fty_ail).unwrap_or_else(|_| "ptr".into());
|
||||
if lty != "ptr" {
|
||||
continue;
|
||||
}
|
||||
@@ -370,7 +387,7 @@ impl<'a> Emitter<'a> {
|
||||
self.body.push_str(&format!(
|
||||
" {v} = load ptr, ptr {addr}, align 8\n"
|
||||
));
|
||||
let drop_call = self.field_drop_call(fty_ail);
|
||||
let drop_call = self.field_drop_call(&fty_ail);
|
||||
self.body.push_str(&format!(
|
||||
" call void @{drop_call}(ptr {v})\n"
|
||||
));
|
||||
|
||||
@@ -170,13 +170,37 @@ pub(crate) fn apply_subst_to_type(t: &Type, subst: &BTreeMap<String, Type>) -> T
|
||||
name: name.clone(),
|
||||
args: args.iter().map(|a| apply_subst_to_type(a, subst)).collect(),
|
||||
},
|
||||
Type::Fn { params, ret, effects, param_modes, ret_mode } => Type::Fn {
|
||||
params: params.iter().map(|p| apply_subst_to_type(p, subst)).collect(),
|
||||
ret: Box::new(apply_subst_to_type(ret, subst)),
|
||||
effects: effects.clone(),
|
||||
param_modes: param_modes.clone(),
|
||||
ret_mode: *ret_mode,
|
||||
},
|
||||
Type::Fn { params, ret, effects, param_modes, ret_mode } => {
|
||||
let new_params: Vec<Type> =
|
||||
params.iter().map(|p| apply_subst_to_type(p, subst)).collect();
|
||||
let new_ret = apply_subst_to_type(ret, subst);
|
||||
// spec 0062: a polymorphic (borrow a) specialised onto a
|
||||
// value type becomes (own value-type) — borrow-over-value
|
||||
// is forbidden and is a no-op for unboxed types (no RC).
|
||||
let coerce = |ty: &Type, m: &ParamMode| -> ParamMode {
|
||||
if matches!(m, ParamMode::Borrow) {
|
||||
if let Type::Con { name, args } = ty {
|
||||
if args.is_empty() && ailang_core::primitives::is_value_type(name) {
|
||||
return ParamMode::Own;
|
||||
}
|
||||
}
|
||||
}
|
||||
*m
|
||||
};
|
||||
let new_param_modes: Vec<ParamMode> = new_params
|
||||
.iter()
|
||||
.zip(param_modes.iter())
|
||||
.map(|(t, m)| coerce(t, m))
|
||||
.collect();
|
||||
let new_ret_mode = coerce(&new_ret, ret_mode);
|
||||
Type::Fn {
|
||||
params: new_params,
|
||||
ret: Box::new(new_ret),
|
||||
effects: effects.clone(),
|
||||
param_modes: new_param_modes,
|
||||
ret_mode: new_ret_mode,
|
||||
}
|
||||
}
|
||||
Type::Forall { vars, constraints, body } => {
|
||||
// Inner forall shadows: don't substitute re-bound names.
|
||||
let inner: BTreeMap<String, Type> = subst
|
||||
|
||||
Reference in New Issue
Block a user