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"
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user