use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use crate::ast::Spanned;
use crate::ir::FnId;
use super::super::expr::{MirCallee, MirExpr};
use super::super::program::MirProgram;
const MAX_DEPTH: u32 = 64;
fn is_target_consuming_builtin(name: &str) -> bool {
matches!(
name,
"Vector.set"
| "Vector.get"
| "Vector.len"
| "Map.set"
| "Map.get"
| "Map.has"
| "Map.remove"
| "Map.keys"
| "Map.values"
| "Map.len"
| "Map.entries"
)
}
struct CallSite {
target: FnId,
caller: FnId,
args: Vec<Spanned<MirExpr>>,
}
pub fn own_param_refine(mut program: MirProgram) -> MirProgram {
if std::env::var("AVER_NO_OWN_PARAM").is_ok() {
return program;
}
if program.external_callers_possible || program.modules.len() > 1 {
return program;
}
let mut prone: HashMap<FnId, Vec<usize>> = HashMap::new();
for (id, f) in program.iter() {
let nparams = f.params.len();
let v: Vec<usize> = (0..nparams)
.filter(|&i| f.aliased_slots.get(i).copied().unwrap_or(false))
.collect();
if !v.is_empty() {
prone.insert(*id, v);
}
}
if prone.is_empty() {
return program;
}
let mut provenance: HashMap<FnId, HashMap<u32, Spanned<MirExpr>>> = HashMap::new();
for (id, f) in program.iter() {
let mut m = HashMap::new();
collect_let_bindings(&f.body.node, &mut m);
provenance.insert(*id, m);
}
let builtins = program.builtins.clone();
let captures_param = compute_capture_summary(&program, &provenance, &builtins);
let mut leaking: HashMap<FnId, HashSet<u32>> = HashMap::new();
for (id, f) in program.iter() {
let prov = provenance.get(id).cloned().unwrap_or_default();
let mut leak: HashSet<u32> = HashSet::new();
scan_leaks(&f.body.node, &prov, &captures_param, &builtins, &mut leak);
let nparams = f.params.len();
collect_live_aliased_params(&f.body.node, &prov, &builtins, nparams, &mut leak);
if !leak.is_empty() {
leaking.insert(*id, leak);
}
}
let mut leaked_param_slots: HashMap<FnId, HashSet<usize>> = HashMap::new();
for (id, f) in program.fns.iter_mut() {
let Some(leak) = leaking.get(id) else {
continue;
};
if leak.is_empty() {
continue;
}
let nparams = f.params.len();
let mut slots = f.aliased_slots.as_ref().clone();
if let Some(&max) = leak.iter().max()
&& (max as usize) >= slots.len()
{
slots.resize(max as usize + 1, false);
}
let mut leaked_params: HashSet<usize> = HashSet::new();
for &s in leak {
slots[s as usize] = true;
if (s as usize) < nparams {
leaked_params.insert(s as usize);
}
}
if !leaked_params.is_empty() {
leaked_param_slots.insert(*id, leaked_params);
}
f.aliased_slots = Arc::new(slots);
}
let mut address_taken: HashSet<String> = HashSet::new();
for (_, f) in program.iter() {
collect_fn_values(&f.body.node, &mut address_taken);
}
let pinned: HashSet<FnId> = program
.iter()
.filter(|(_, f)| f.name == "main" || address_taken.contains(&f.name))
.map(|(id, _)| *id)
.collect();
let mut call_sites: Vec<CallSite> = Vec::new();
for (caller, f) in program.iter() {
collect_call_sites(*caller, &f.body.node, &mut call_sites);
}
let mut live_aliased: HashMap<FnId, HashSet<u32>> = HashMap::new();
for (id, f) in program.iter() {
let prov = provenance.get(id).cloned().unwrap_or_default();
let mut set: HashSet<u32> = HashSet::new();
collect_live_aliased_slots(&f.body.node, &prov, &builtins, &mut set);
if !set.is_empty() {
live_aliased.insert(*id, set);
}
}
let mut owned: HashMap<(FnId, usize), bool> = HashMap::new();
for (id, idxs) in &prone {
let pin = pinned.contains(id);
let leaked = leaked_param_slots.get(id);
for &i in idxs {
let body_leak = leaked.is_some_and(|c| c.contains(&i));
owned.insert((*id, i), !pin && !body_leak);
}
}
loop {
let mut changed = false;
for cs in &call_sites {
let Some(idxs) = prone.get(&cs.target) else {
continue;
};
if pinned.contains(&cs.target) {
continue;
}
let mut slot_counts: HashMap<u32, u32> = HashMap::new();
for &i in idxs {
if let Some(a) = cs.args.get(i)
&& let MirExpr::Local(l) = &a.node
{
*slot_counts.entry(l.node.slot.0).or_insert(0) += 1;
}
}
let caller_live_aliased = live_aliased.get(&cs.caller);
for &i in idxs {
let key = (cs.target, i);
if !owned.get(&key).copied().unwrap_or(false) {
continue; }
let arg = match cs.args.get(i) {
Some(a) => a,
None => {
if owned.insert(key, false).unwrap_or(false) {
changed = true;
}
continue;
}
};
let dup = matches!(&arg.node, MirExpr::Local(l) if slot_counts.get(&l.node.slot.0).copied().unwrap_or(0) > 1);
let caller_aliased = matches!(&arg.node, MirExpr::Local(l)
if caller_live_aliased.is_some_and(|s| s.contains(&l.node.slot.0)));
let ok = !dup
&& !caller_aliased
&& uniquely_owned(
&arg.node,
cs.caller,
&program,
&owned,
&provenance,
&builtins,
0,
);
if !ok && owned.insert(key, false) != Some(false) {
changed = true;
}
}
}
if !changed {
break;
}
}
let to_clear: Vec<(FnId, usize)> = owned
.iter()
.filter_map(|(k, v)| if *v { Some(*k) } else { None })
.collect();
for (id, idx) in to_clear {
if let Some(f) = program.fns.get_mut(&id) {
let mut slots = f.aliased_slots.as_ref().clone();
if let Some(bit) = slots.get_mut(idx) {
*bit = false;
}
f.aliased_slots = Arc::new(slots);
}
}
program
}
#[allow(clippy::too_many_arguments)]
fn uniquely_owned(
e: &MirExpr,
caller: FnId,
program: &MirProgram,
owned: &HashMap<(FnId, usize), bool>,
provenance: &HashMap<FnId, HashMap<u32, Spanned<MirExpr>>>,
builtins: &[String],
depth: u32,
) -> bool {
if depth > MAX_DEPTH {
return false;
}
match e {
MirExpr::MapLiteral(_) => true,
MirExpr::Call(c) => match &c.node.callee {
MirCallee::Builtin(id) => {
let name = builtins
.get(id.0 as usize)
.map(String::as_str)
.unwrap_or("");
match name {
"Vector.new" => c
.node
.args
.get(1)
.is_some_and(|d| matches!(&d.node, MirExpr::Literal(_))),
"Map.new" => true,
"Vector.set" | "Map.set" => c.node.args.first().is_some_and(|v| {
uniquely_owned(
&v.node,
caller,
program,
owned,
provenance,
builtins,
depth + 1,
)
}),
"Option.withDefault" if c.node.args.len() == 2 => {
if let MirExpr::Call(inner) = &c.node.args[0].node
&& let MirCallee::Builtin(iid) = inner.node.callee
&& matches!(
builtins.get(iid.0 as usize).map(String::as_str),
Some("Vector.set") | Some("Map.set")
)
&& let Some(set_vec) = inner.node.args.first()
&& let MirExpr::Local(v) = &set_vec.node
&& let MirExpr::Local(d) = &c.node.args[1].node
&& v.node.slot == d.node.slot
{
let live = v.node.last_use || d.node.last_use;
return live
&& slot_owned(
v.node.slot.0,
caller,
program,
owned,
provenance,
builtins,
depth + 1,
);
}
uniquely_owned(
&c.node.args[0].node,
caller,
program,
owned,
provenance,
builtins,
depth + 1,
) && uniquely_owned(
&c.node.args[1].node,
caller,
program,
owned,
provenance,
builtins,
depth + 1,
)
}
_ => false,
}
}
MirCallee::Fn(_) | MirCallee::LocalSlot { .. } | MirCallee::Intrinsic(_) => false,
},
MirExpr::Local(l) => {
l.node.last_use
&& slot_owned(
l.node.slot.0,
caller,
program,
owned,
provenance,
builtins,
depth + 1,
)
}
_ => false,
}
}
#[allow(clippy::too_many_arguments)]
fn slot_owned(
slot: u32,
caller: FnId,
program: &MirProgram,
owned: &HashMap<(FnId, usize), bool>,
provenance: &HashMap<FnId, HashMap<u32, Spanned<MirExpr>>>,
builtins: &[String],
depth: u32,
) -> bool {
if depth > MAX_DEPTH {
return false;
}
let caller_fn = match program.fn_by_id(caller) {
Some(f) => f,
None => return false,
};
let is_param = (slot as usize) < caller_fn.params.len();
if caller_fn
.aliased_slots
.get(slot as usize)
.copied()
.unwrap_or(false)
{
if is_param {
return owned
.get(&(caller, slot as usize))
.copied()
.unwrap_or(false);
}
return false;
}
if is_param {
return owned.get(&(caller, slot as usize)).copied().unwrap_or(true);
}
match provenance.get(&caller).and_then(|m| m.get(&slot)) {
Some(rhs) => uniquely_owned(
&rhs.node,
caller,
program,
owned,
provenance,
builtins,
depth + 1,
),
None => false,
}
}
fn collect_fn_values(e: &MirExpr, out: &mut HashSet<String>) {
visit_children(e, &mut |c| collect_fn_values(c, out));
if let MirExpr::FnValue(name) = e {
out.insert(name.clone());
}
}
fn collect_let_bindings(e: &MirExpr, out: &mut HashMap<u32, Spanned<MirExpr>>) {
if let MirExpr::Let(l) = e {
out.entry(l.node.binding.0)
.or_insert_with(|| (*l.node.value).clone());
}
visit_children(e, &mut |c| collect_let_bindings(c, out));
}
fn alias_roots(
e: &MirExpr,
prov: &HashMap<u32, Spanned<MirExpr>>,
builtins: &[String],
depth: u32,
out: &mut HashSet<u32>,
) {
if depth > MAX_DEPTH {
return;
}
match e {
MirExpr::Local(l) => {
let slot = l.node.slot.0;
out.insert(slot);
if let Some(rhs) = prov.get(&slot) {
alias_roots(&rhs.node, prov, builtins, depth + 1, out);
}
}
MirExpr::Match(m) => {
for arm in &m.node.arms {
alias_roots(&arm.body.node, prov, builtins, depth + 1, out);
}
}
MirExpr::IfThenElse(ite) => {
alias_roots(&ite.node.then_branch.node, prov, builtins, depth + 1, out);
alias_roots(&ite.node.else_branch.node, prov, builtins, depth + 1, out);
}
MirExpr::Call(c) => match &c.node.callee {
MirCallee::Fn(_) | MirCallee::LocalSlot { .. } | MirCallee::Intrinsic(_) => {
for a in &c.node.args {
alias_roots(&a.node, prov, builtins, depth + 1, out);
}
}
MirCallee::Builtin(id) => {
let name = builtins
.get(id.0 as usize)
.map(String::as_str)
.unwrap_or("");
match name {
"Vector.new" | "Map.new" => {}
"Vector.set" | "Map.set" | "Vector.get" | "Map.get" => {
if let Some(t) = c.node.args.first() {
alias_roots(&t.node, prov, builtins, depth + 1, out);
}
}
"Option.withDefault" => {
for a in &c.node.args {
alias_roots(&a.node, prov, builtins, depth + 1, out);
}
}
_ => {
for a in &c.node.args {
alias_roots(&a.node, prov, builtins, depth + 1, out);
}
}
}
}
},
MirExpr::Return(inner) | MirExpr::Try(inner) => {
alias_roots(&inner.node, prov, builtins, depth + 1, out)
}
_ => {}
}
}
fn scan_leaks(
e: &MirExpr,
prov: &HashMap<u32, Spanned<MirExpr>>,
captures_param: &HashMap<FnId, HashSet<usize>>,
builtins: &[String],
out: &mut HashSet<u32>,
) {
let mut retain = |child: &Spanned<MirExpr>, out: &mut HashSet<u32>| {
alias_roots(&child.node, prov, builtins, 0, out);
scan_leaks(&child.node, prov, captures_param, builtins, out);
};
match e {
MirExpr::Let(l) => {
scan_leaks(&l.node.value.node, prov, captures_param, builtins, out);
scan_leaks(&l.node.body.node, prov, captures_param, builtins, out);
}
MirExpr::Match(m) => {
scan_leaks(&m.node.subject.node, prov, captures_param, builtins, out);
for arm in &m.node.arms {
scan_leaks(&arm.body.node, prov, captures_param, builtins, out);
}
}
MirExpr::IfThenElse(ite) => {
scan_leaks(&ite.node.cond.node, prov, captures_param, builtins, out);
scan_leaks(
&ite.node.then_branch.node,
prov,
captures_param,
builtins,
out,
);
scan_leaks(
&ite.node.else_branch.node,
prov,
captures_param,
builtins,
out,
);
}
MirExpr::Return(inner)
| MirExpr::Try(inner)
| MirExpr::Box(inner)
| MirExpr::Unbox(inner) => {
scan_leaks(&inner.node, prov, captures_param, builtins, out);
}
MirExpr::Call(c) => match &c.node.callee {
MirCallee::Builtin(bid) => {
let name = builtins
.get(bid.0 as usize)
.map(String::as_str)
.unwrap_or("");
if name == "Option.withDefault" {
for a in &c.node.args {
scan_leaks(&a.node, prov, captures_param, builtins, out);
}
return;
}
let target_safe = is_target_consuming_builtin(name);
scan_builtin_args(
c,
target_safe,
prov,
captures_param,
builtins,
&mut retain,
out,
);
}
MirCallee::Fn(target) => {
let captured = captures_param.get(target);
for (i, a) in c.node.args.iter().enumerate() {
if captured.is_some_and(|c| c.contains(&i)) {
retain(a, out);
} else {
scan_leaks(&a.node, prov, captures_param, builtins, out);
}
}
}
MirCallee::LocalSlot { .. } | MirCallee::Intrinsic(_) => {
for a in &c.node.args {
retain(a, out);
}
}
},
MirExpr::TailCall(tc) => {
let captured = captures_param.get(&tc.node.target);
for (i, a) in tc.node.args.iter().enumerate() {
if captured.is_some_and(|c| c.contains(&i)) {
retain(a, out);
} else {
scan_leaks(&a.node, prov, captures_param, builtins, out);
}
}
}
MirExpr::RecordCreate(r) => {
for f in &r.node.fields {
retain(&f.value, out);
}
}
MirExpr::RecordUpdate(u) => {
retain(&u.node.base, out);
for f in &u.node.updates {
retain(&f.value, out);
}
}
MirExpr::Construct(c) => {
for a in &c.node.args {
retain(a, out);
}
}
MirExpr::Tuple(items) | MirExpr::List(items) => {
for i in items {
retain(i, out);
}
}
MirExpr::MapLiteral(pairs) => {
for (k, v) in pairs {
retain(k, out);
retain(v, out);
}
}
MirExpr::IndependentProduct(ip) => {
for i in &ip.node.items {
retain(i, out);
}
}
MirExpr::BinOp(b) => {
scan_leaks(&b.node.lhs.node, prov, captures_param, builtins, out);
scan_leaks(&b.node.rhs.node, prov, captures_param, builtins, out);
}
MirExpr::Neg(inner) => scan_leaks(&inner.node, prov, captures_param, builtins, out),
MirExpr::Project(p) => {
scan_leaks(&p.node.base.node, prov, captures_param, builtins, out)
}
MirExpr::InterpolatedStr(parts) => {
for p in parts {
if let super::super::expr::MirStrPart::Expr(e) = p {
scan_leaks(&e.node, prov, captures_param, builtins, out);
}
}
}
MirExpr::Local(_) | MirExpr::Literal(_) | MirExpr::FnValue(_) => {}
}
}
#[allow(clippy::too_many_arguments)]
fn scan_builtin_args(
c: &Spanned<super::super::expr::MirCall>,
target_safe: bool,
prov: &HashMap<u32, Spanned<MirExpr>>,
captures_param: &HashMap<FnId, HashSet<usize>>,
builtins: &[String],
retain: &mut dyn FnMut(&Spanned<MirExpr>, &mut HashSet<u32>),
out: &mut HashSet<u32>,
) {
for (i, a) in c.node.args.iter().enumerate() {
if i == 0 && target_safe {
scan_leaks(&a.node, prov, captures_param, builtins, out);
} else {
retain(a, out);
}
}
}
fn collect_live_aliased_params(
body: &MirExpr,
prov: &HashMap<u32, Spanned<MirExpr>>,
builtins: &[String],
nparams: usize,
out: &mut HashSet<u32>,
) {
let mut read: HashSet<u32> = HashSet::new();
collect_local_reads(body, &mut read);
for (&binding, rhs) in prov {
if !read.contains(&binding) {
continue;
}
let mut roots: HashSet<u32> = HashSet::new();
rename_roots(&rhs.node, prov, builtins, 0, &mut roots);
for &r in &roots {
if r != binding && (r as usize) < nparams {
out.insert(r);
}
}
}
}
fn collect_live_aliased_slots(
body: &MirExpr,
prov: &HashMap<u32, Spanned<MirExpr>>,
builtins: &[String],
out: &mut HashSet<u32>,
) {
let mut read: HashSet<u32> = HashSet::new();
collect_local_reads(body, &mut read);
for (&binding, rhs) in prov {
if !read.contains(&binding) {
continue;
}
let mut roots: HashSet<u32> = HashSet::new();
rename_roots(&rhs.node, prov, builtins, 0, &mut roots);
for &r in &roots {
if r != binding {
out.insert(r);
}
}
}
}
fn rename_roots(
e: &MirExpr,
prov: &HashMap<u32, Spanned<MirExpr>>,
builtins: &[String],
depth: u32,
out: &mut HashSet<u32>,
) {
if depth > MAX_DEPTH {
return;
}
match e {
MirExpr::Local(l) => {
let slot = l.node.slot.0;
out.insert(slot);
if let Some(rhs) = prov.get(&slot) {
rename_roots(&rhs.node, prov, builtins, depth + 1, out);
}
}
MirExpr::Match(m) => {
for arm in &m.node.arms {
rename_roots(&arm.body.node, prov, builtins, depth + 1, out);
}
}
MirExpr::IfThenElse(ite) => {
rename_roots(&ite.node.then_branch.node, prov, builtins, depth + 1, out);
rename_roots(&ite.node.else_branch.node, prov, builtins, depth + 1, out);
}
MirExpr::Call(c) => match &c.node.callee {
MirCallee::Fn(_) | MirCallee::LocalSlot { .. } | MirCallee::Intrinsic(_) => {
for a in &c.node.args {
rename_roots(&a.node, prov, builtins, depth + 1, out);
}
}
MirCallee::Builtin(id) => {
let name = builtins
.get(id.0 as usize)
.map(String::as_str)
.unwrap_or("");
match name {
"Vector.set" | "Map.set" | "Vector.new" | "Map.new" => {}
"Option.withDefault"
if c.node.args.len() == 2
&& is_self_keep_set(
&c.node.args[0].node,
&c.node.args[1].node,
builtins,
) => {}
"Option.withDefault" => {
for a in &c.node.args {
rename_roots(&a.node, prov, builtins, depth + 1, out);
}
}
_ => {
for a in &c.node.args {
rename_roots(&a.node, prov, builtins, depth + 1, out);
}
}
}
}
},
MirExpr::Return(inner) | MirExpr::Try(inner) => {
rename_roots(&inner.node, prov, builtins, depth + 1, out)
}
_ => {}
}
}
fn is_self_keep_set(set_arg: &MirExpr, default_arg: &MirExpr, builtins: &[String]) -> bool {
if let MirExpr::Call(inner) = set_arg
&& let MirCallee::Builtin(iid) = inner.node.callee
&& matches!(
builtins.get(iid.0 as usize).map(String::as_str),
Some("Vector.set") | Some("Map.set")
)
&& let Some(set_vec) = inner.node.args.first()
&& let MirExpr::Local(v) = &set_vec.node
&& let MirExpr::Local(d) = default_arg
{
return v.node.slot == d.node.slot;
}
false
}
fn collect_local_reads(e: &MirExpr, out: &mut HashSet<u32>) {
if let MirExpr::Local(l) = e {
out.insert(l.node.slot.0);
}
visit_children(e, &mut |c| collect_local_reads(c, out));
}
fn compute_capture_summary(
program: &MirProgram,
provenance: &HashMap<FnId, HashMap<u32, Spanned<MirExpr>>>,
builtins: &[String],
) -> HashMap<FnId, HashSet<usize>> {
let mut param_slot_to_idx: HashMap<FnId, HashMap<u32, usize>> = HashMap::new();
for (id, f) in program.iter() {
let m: HashMap<u32, usize> = f
.params
.iter()
.enumerate()
.map(|(i, p)| (p.local.0, i))
.collect();
param_slot_to_idx.insert(*id, m);
}
let mut captures: HashMap<FnId, HashSet<usize>> = HashMap::new();
loop {
let mut changed = false;
for (id, f) in program.iter() {
let prov = provenance.get(id).cloned().unwrap_or_default();
let nparams = f.params.len();
let mut leak: HashSet<u32> = HashSet::new();
scan_leaks(&f.body.node, &prov, &captures, builtins, &mut leak);
collect_live_aliased_params(&f.body.node, &prov, builtins, nparams, &mut leak);
let idx_map = ¶m_slot_to_idx[id];
let entry = captures.entry(*id).or_default();
for &s in &leak {
if (s as usize) < nparams
&& let Some(&i) = idx_map.get(&s)
&& entry.insert(i)
{
changed = true;
}
}
}
if !changed {
break;
}
}
captures
}
fn collect_call_sites(caller: FnId, e: &MirExpr, out: &mut Vec<CallSite>) {
match e {
MirExpr::Call(c) => {
if let MirCallee::Fn(target) = c.node.callee {
out.push(CallSite {
target,
caller,
args: c.node.args.clone(),
});
}
}
MirExpr::TailCall(tc) => {
out.push(CallSite {
target: tc.node.target,
caller,
args: tc.node.args.clone(),
});
}
_ => {}
}
visit_children(e, &mut |c| collect_call_sites(caller, c, out));
}
fn visit_children(e: &MirExpr, f: &mut dyn FnMut(&MirExpr)) {
match e {
MirExpr::Literal(_) | MirExpr::Local(_) | MirExpr::FnValue(_) => {}
MirExpr::Let(l) => {
f(&l.node.value.node);
f(&l.node.body.node);
}
MirExpr::Call(c) => {
for a in &c.node.args {
f(&a.node);
}
}
MirExpr::TailCall(tc) => {
for a in &tc.node.args {
f(&a.node);
}
}
MirExpr::BinOp(b) => {
f(&b.node.lhs.node);
f(&b.node.rhs.node);
}
MirExpr::Neg(inner)
| MirExpr::Try(inner)
| MirExpr::Return(inner)
| MirExpr::Box(inner)
| MirExpr::Unbox(inner) => f(&inner.node),
MirExpr::Match(m) => {
f(&m.node.subject.node);
for arm in &m.node.arms {
f(&arm.body.node);
}
}
MirExpr::Construct(c) => {
for a in &c.node.args {
f(&a.node);
}
}
MirExpr::RecordCreate(r) => {
for field in &r.node.fields {
f(&field.value.node);
}
}
MirExpr::RecordUpdate(u) => {
f(&u.node.base.node);
for field in &u.node.updates {
f(&field.value.node);
}
}
MirExpr::Project(p) => f(&p.node.base.node),
MirExpr::IfThenElse(ite) => {
f(&ite.node.cond.node);
f(&ite.node.then_branch.node);
f(&ite.node.else_branch.node);
}
MirExpr::List(items) | MirExpr::Tuple(items) => {
for i in items {
f(&i.node);
}
}
MirExpr::MapLiteral(pairs) => {
for (k, v) in pairs {
f(&k.node);
f(&v.node);
}
}
MirExpr::InterpolatedStr(parts) => {
for p in parts {
if let super::super::expr::MirStrPart::Expr(e) = p {
f(&e.node);
}
}
}
MirExpr::IndependentProduct(ip) => {
for i in &ip.node.items {
f(&i.node);
}
}
}
}
#[cfg(test)]
mod tests {
use super::own_param_refine;
use crate::ast::{Literal, Spanned};
use crate::ir::FnId;
use crate::ir::mir::expr::MirExpr;
use crate::ir::mir::program::{LocalId, MirFn, MirParam, MirProgram};
use std::sync::Arc;
fn span<T>(node: T) -> Spanned<T> {
Spanned {
node,
line: 0,
ty: std::sync::OnceLock::new(),
}
}
fn one_flagged_param_program(external_callers_possible: bool) -> (MirProgram, FnId) {
let id = FnId(0);
let mut p = MirProgram {
external_callers_possible,
..MirProgram::default()
};
p.fns.insert(
id,
MirFn {
fn_id: id,
name: "f".to_string(),
params: vec![MirParam {
local: LocalId(0),
name: "v".to_string(),
ty: "Vector<Int>".to_string(),
}],
return_type: "Int".to_string(),
effects: vec![],
body: span(MirExpr::Literal(span(Literal::Int(0)))),
local_count: 1,
aliased_slots: Arc::new(vec![true]),
repr: crate::ir::mir::program::MirFnRepr::default(),
},
);
(p, id)
}
#[test]
fn whole_program_graduates_unaliased_param() {
let (prog, id) = one_flagged_param_program(false);
let out = own_param_refine(prog);
assert!(
!out.fns[&id].aliased_slots[0],
"whole-program: a non-retaining, uncalled collection param must graduate to owned"
);
}
#[test]
fn dependency_fragment_keeps_param_flagged() {
let (prog, id) = one_flagged_param_program(true);
let out = own_param_refine(prog);
assert!(
out.fns[&id].aliased_slots[0],
"dependency fragment: own_param must bail — an unseen cross-module caller may alias the param"
);
}
}