use std::collections::{HashMap, HashSet};
use std::sync::OnceLock;
use crate::mir::analysis::{BorrowAnalysis, FunctionBorrowSummary};
use crate::mir::types::*;
use crate::type_tracking::{
Aliasability, BindingOwnershipClass, BindingSemantics, BindingStorageClass, EscapeStatus,
MutationCapability,
};
fn var_sharedcow_default_enabled() -> bool {
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| match std::env::var("SHAPE_V2_VAR_SHAREDCOW") {
Ok(v) => !matches!(
v.trim(),
"0" | "false" | "FALSE" | "False" | "off" | "OFF" | "Off" | "no" | "NO" | "No" | ""
),
Err(_) => true,
})
}
pub const INLINE_ARRAY_MAX_ELEMENTS: usize = 8;
#[derive(Debug, Clone)]
pub struct StoragePlan {
pub slot_classes: HashMap<SlotId, BindingStorageClass>,
pub slot_semantics: HashMap<SlotId, BindingSemantics>,
pub inline_array_sizes: HashMap<SlotId, usize>,
pub non_escaping_closure_slots: HashSet<SlotId>,
}
pub struct StoragePlannerInput<'a> {
pub mir: &'a MirFunction,
pub analysis: &'a BorrowAnalysis,
pub binding_semantics: &'a HashMap<u16, BindingSemantics>,
pub closure_captures: &'a HashSet<SlotId>,
pub mutable_captures: &'a HashSet<SlotId>,
pub had_fallbacks: bool,
pub callee_summaries: Option<&'a HashMap<String, FunctionBorrowSummary>>,
}
pub fn collect_closure_captures(mir: &MirFunction) -> (HashSet<SlotId>, HashSet<SlotId>) {
let mut all_captures = HashSet::new();
let mut assign_counts: HashMap<SlotId, u32> = HashMap::new();
for block in mir.iter_blocks() {
for stmt in &block.statements {
match &stmt.kind {
StatementKind::ClosureCapture { operands, .. } => {
for op in operands {
if let Some(slot) = operand_root_slot(op) {
all_captures.insert(slot);
}
}
}
StatementKind::Assign(place, _) => {
if let Place::Local(slot) = place {
*assign_counts.entry(*slot).or_insert(0) += 1;
}
}
_ => {}
}
}
}
let mutable_captures: HashSet<SlotId> = all_captures
.iter()
.filter(|slot| assign_counts.get(slot).copied().unwrap_or(0) > 1)
.copied()
.collect();
(all_captures, mutable_captures)
}
fn operand_root_slot(op: &Operand) -> Option<SlotId> {
match op {
Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
Some(place.root_local())
}
Operand::Constant(_) => None,
}
}
fn slot_has_active_loans(slot: SlotId, analysis: &BorrowAnalysis) -> bool {
for loan_info in analysis.loans.values() {
if loan_info.borrowed_place.root_local() == slot {
return true;
}
}
false
}
fn slot_is_aliased(slot: SlotId, mir: &MirFunction, closure_captures: &HashSet<SlotId>) -> bool {
if closure_captures.contains(&slot) {
return true;
}
let mut use_count = 0u32;
for block in mir.iter_blocks() {
for stmt in &block.statements {
if let StatementKind::Assign(_, rvalue) = &stmt.kind {
if rvalue_uses_slot(rvalue, slot) {
use_count += 1;
if use_count > 1 {
return true;
}
}
}
}
if let TerminatorKind::Call { func, args, .. } = &block.terminator.kind {
if operand_uses_slot(func, slot) {
use_count += 1;
}
for arg in args {
if operand_uses_slot(arg, slot) {
use_count += 1;
}
}
if use_count > 1 {
return true;
}
}
}
false
}
fn slot_is_mutated(slot: SlotId, mir: &MirFunction) -> bool {
let mut assign_count = 0u32;
for block in mir.iter_blocks() {
for stmt in &block.statements {
if let StatementKind::Assign(Place::Local(s), _) = &stmt.kind {
if *s == slot {
assign_count += 1;
if assign_count > 1 {
return true;
}
}
}
}
}
false
}
fn rvalue_uses_slot(rvalue: &Rvalue, slot: SlotId) -> bool {
match rvalue {
Rvalue::Use(op) | Rvalue::Clone(op) | Rvalue::UnaryOp(_, op) => {
operand_uses_slot(op, slot)
}
Rvalue::Borrow(_, place) => place.root_local() == slot,
Rvalue::BinaryOp(_, lhs, rhs) => {
operand_uses_slot(lhs, slot) || operand_uses_slot(rhs, slot)
}
Rvalue::Aggregate(ops) => ops.iter().any(|op| operand_uses_slot(op, slot)),
Rvalue::EnumTest { operand, .. }
| Rvalue::EnumPayload { operand, .. }
| Rvalue::TypePatternTest { operand, .. }
| Rvalue::EnumDiscriminantTest { operand, .. } => operand_uses_slot(operand, slot),
}
}
fn operand_uses_slot(op: &Operand, slot: SlotId) -> bool {
match op {
Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
place.root_local() == slot
}
Operand::Constant(_) => false,
}
}
pub fn plan_storage(input: &StoragePlannerInput<'_>) -> StoragePlan {
let mut slot_classes = HashMap::new();
let mut slot_semantics = HashMap::new();
if input.had_fallbacks {
for slot_idx in 0..input.mir.num_locals {
let slot = SlotId(slot_idx);
slot_classes.insert(slot, BindingStorageClass::Deferred);
slot_semantics.insert(
slot,
BindingSemantics {
ownership_class: BindingOwnershipClass::OwnedImmutable,
storage_class: BindingStorageClass::Deferred,
aliasability: Aliasability::Unique,
mutation_capability: MutationCapability::Immutable,
escape_status: EscapeStatus::Local,
return_ownership_hint: None,
},
);
}
return StoragePlan {
slot_classes,
slot_semantics,
inline_array_sizes: HashMap::new(),
non_escaping_closure_slots: HashSet::new(),
};
}
let var_sharedcow_enabled = var_sharedcow_default_enabled();
for slot_idx in 0..input.mir.num_locals {
let slot = SlotId(slot_idx);
let (storage_class, semantics) =
decide_slot_storage(slot, input, var_sharedcow_enabled);
slot_classes.insert(slot, storage_class);
slot_semantics.insert(slot, semantics);
}
let inline_array_sizes = detect_inline_array_candidates(input, &slot_semantics);
let non_escaping_closure_slots =
detect_non_escaping_closure_slots(input, &slot_classes);
promote_local_mutable_ptr_slots(
input,
&non_escaping_closure_slots,
&mut slot_classes,
&mut slot_semantics,
);
StoragePlan {
slot_classes,
slot_semantics,
inline_array_sizes,
non_escaping_closure_slots,
}
}
fn promote_local_mutable_ptr_slots(
input: &StoragePlannerInput<'_>,
non_escaping_closure_slots: &HashSet<SlotId>,
slot_classes: &mut HashMap<SlotId, BindingStorageClass>,
slot_semantics: &mut HashMap<SlotId, BindingSemantics>,
) {
for block in input.mir.iter_blocks() {
for stmt in &block.statements {
let StatementKind::ClosureCapture {
closure_slot,
operands,
..
} = &stmt.kind
else {
continue;
};
if !non_escaping_closure_slots.contains(closure_slot) {
continue;
}
for op in operands {
let Some(root) = operand_root_slot(op) else {
continue;
};
let Some(current) = slot_classes.get(&root).copied() else {
continue;
};
match current {
BindingStorageClass::UniqueHeap
| BindingStorageClass::Direct
| BindingStorageClass::Deferred => {
slot_classes.insert(root, BindingStorageClass::LocalMutablePtr);
if let Some(sem) = slot_semantics.get_mut(&root) {
sem.storage_class = BindingStorageClass::LocalMutablePtr;
}
}
_ => {}
}
}
}
}
}
fn detect_inline_array_candidates(
input: &StoragePlannerInput<'_>,
slot_semantics: &HashMap<SlotId, BindingSemantics>,
) -> HashMap<SlotId, usize> {
let mut aggregate_sizes: HashMap<SlotId, usize> = HashMap::new();
let mut disqualified: HashSet<SlotId> = HashSet::new();
for block in input.mir.iter_blocks() {
for stmt in &block.statements {
let StatementKind::Assign(place, rvalue) = &stmt.kind else {
continue;
};
let Place::Local(slot) = place else {
disqualified.insert(place.root_local());
continue;
};
match rvalue {
Rvalue::Aggregate(ops) => {
if aggregate_sizes.contains_key(slot) {
disqualified.insert(*slot);
} else {
aggregate_sizes.insert(*slot, ops.len());
}
}
_ => {
if aggregate_sizes.contains_key(slot) {
disqualified.insert(*slot);
}
}
}
}
}
let mut hints = HashMap::new();
for (slot, size) in aggregate_sizes {
if disqualified.contains(&slot) {
continue;
}
if size == 0 || size > INLINE_ARRAY_MAX_ELEMENTS {
continue;
}
let Some(sem) = slot_semantics.get(&slot) else {
continue;
};
if sem.escape_status != EscapeStatus::Local {
continue;
}
if sem.aliasability != Aliasability::Unique {
continue;
}
if input.closure_captures.contains(&slot) {
continue;
}
hints.insert(slot, size);
}
hints
}
fn collect_closure_slots(mir: &MirFunction) -> HashSet<SlotId> {
let mut slots = HashSet::new();
for block in mir.iter_blocks() {
for stmt in &block.statements {
if let StatementKind::ClosureCapture { closure_slot, .. } = &stmt.kind {
slots.insert(*closure_slot);
}
}
}
slots
}
fn build_closure_capture_graph(
mir: &MirFunction,
closure_slots: &HashSet<SlotId>,
) -> HashMap<SlotId, HashSet<SlotId>> {
let mut graph: HashMap<SlotId, HashSet<SlotId>> = HashMap::new();
for block in mir.iter_blocks() {
for stmt in &block.statements {
if let StatementKind::ClosureCapture {
closure_slot,
operands,
..
} = &stmt.kind
{
for op in operands {
let Some(root) = operand_root_slot(op) else {
continue;
};
if closure_slots.contains(&root) {
graph
.entry(*closure_slot)
.or_default()
.insert(root);
}
}
}
}
}
graph
}
fn closure_slot_escapes_direct(
c: SlotId,
input: &StoragePlannerInput<'_>,
) -> bool {
let mir = input.mir;
if let Some(sem) = input.binding_semantics.get(&c.0) {
match sem.storage_class {
BindingStorageClass::UniqueHeap | BindingStorageClass::SharedCow => {
return true;
}
_ => {}
}
}
let mut tracked: HashSet<SlotId> = HashSet::new();
tracked.insert(c);
let mut changed = true;
while changed {
changed = false;
for block in mir.iter_blocks() {
for stmt in &block.statements {
match &stmt.kind {
StatementKind::Assign(place, rvalue) => {
let reads_tracked = rvalue_uses_any_slot(rvalue, &tracked);
match place {
Place::Local(dest) => {
if *dest == SlotId(0) && reads_tracked {
return true;
}
if reads_tracked && tracked.insert(*dest) {
changed = true;
}
}
Place::Field(..) => {
if reads_tracked {
return true;
}
}
Place::Index(..) | Place::Deref(..) => {
if reads_tracked {
return true;
}
}
}
if let Rvalue::Aggregate(ops) = rvalue {
if ops.iter().any(|op| operand_uses_any_slot(op, &tracked)) {
return true;
}
}
}
StatementKind::ArrayStore { operands, .. }
| StatementKind::ObjectStore { operands, .. }
| StatementKind::EnumStore { operands, .. } => {
if operands.iter().any(|op| operand_uses_any_slot(op, &tracked)) {
return true;
}
}
StatementKind::TaskBoundary(operands, _) => {
if operands.iter().any(|op| operand_uses_any_slot(op, &tracked)) {
return true;
}
}
StatementKind::ClosureCapture { .. }
| StatementKind::Drop(_)
| StatementKind::Nop => {}
}
}
if let TerminatorKind::Call { func, args, .. } = &block.terminator.kind {
let callee_name = match func {
Operand::Constant(MirConstant::Function(name)) => Some(name.as_str()),
_ => None,
};
if callee_name == Some("snapshot") {
if args.iter().any(|op| operand_uses_any_slot(op, &tracked)) {
return true;
}
}
let callee_summary = callee_name
.and_then(|n| input.callee_summaries.and_then(|m| m.get(n)));
for (arg_idx, arg) in args.iter().enumerate() {
if !operand_uses_any_slot(arg, &tracked) {
continue;
}
match callee_summary {
Some(summary) if arg_idx < summary.closure_param_escapes.len() => {
if summary.closure_param_escapes[arg_idx] {
return true;
}
}
_ => return true,
}
}
let _ = func; }
}
}
false
}
fn propagate_transitive_closure_escape(
closure_slots: &HashSet<SlotId>,
capture_graph: &HashMap<SlotId, HashSet<SlotId>>,
escaping: &mut HashSet<SlotId>,
) {
let mut changed = true;
while changed {
changed = false;
for &a in closure_slots {
if !escaping.contains(&a) {
continue;
}
let Some(captured_closures) = capture_graph.get(&a) else {
continue;
};
for &b in captured_closures {
if escaping.insert(b) {
changed = true;
}
}
}
}
}
fn mir_contains_snapshot_call(mir: &MirFunction) -> bool {
for block in mir.iter_blocks() {
if let TerminatorKind::Call { func, .. } = &block.terminator.kind {
if let Operand::Constant(MirConstant::Function(name)) = func {
if name == "snapshot" {
return true;
}
}
}
}
false
}
fn detect_non_escaping_closure_slots(
input: &StoragePlannerInput<'_>,
_slot_classes: &HashMap<SlotId, BindingStorageClass>,
) -> HashSet<SlotId> {
let closure_slots = collect_closure_slots(input.mir);
if closure_slots.is_empty() {
return HashSet::new();
}
if mir_contains_snapshot_call(input.mir) {
return HashSet::new();
}
let mut escaping: HashSet<SlotId> = HashSet::new();
for &c in &closure_slots {
if closure_slot_escapes_direct(c, input) {
escaping.insert(c);
}
}
let capture_graph = build_closure_capture_graph(input.mir, &closure_slots);
propagate_transitive_closure_escape(&closure_slots, &capture_graph, &mut escaping);
closure_slots
.difference(&escaping)
.copied()
.collect()
}
fn rvalue_uses_any_slot(rvalue: &Rvalue, slots: &HashSet<SlotId>) -> bool {
match rvalue {
Rvalue::Use(op) | Rvalue::Clone(op) | Rvalue::UnaryOp(_, op) => {
operand_uses_any_slot(op, slots)
}
Rvalue::Borrow(_, place) => slots.contains(&place.root_local()),
Rvalue::BinaryOp(_, lhs, rhs) => {
operand_uses_any_slot(lhs, slots) || operand_uses_any_slot(rhs, slots)
}
Rvalue::Aggregate(ops) => ops.iter().any(|op| operand_uses_any_slot(op, slots)),
Rvalue::EnumTest { operand, .. }
| Rvalue::EnumPayload { operand, .. }
| Rvalue::TypePatternTest { operand, .. }
| Rvalue::EnumDiscriminantTest { operand, .. } => operand_uses_any_slot(operand, slots),
}
}
fn operand_uses_any_slot(op: &Operand, slots: &HashSet<SlotId>) -> bool {
match op {
Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
slots.contains(&place.root_local())
}
Operand::Constant(_) => false,
}
}
fn decide_slot_storage(
slot: SlotId,
input: &StoragePlannerInput<'_>,
var_sharedcow_enabled: bool,
) -> (BindingStorageClass, BindingSemantics) {
let is_captured = input.closure_captures.contains(&slot);
let is_mutably_captured = input.mutable_captures.contains(&slot);
let _has_loans = slot_has_active_loans(slot, input.analysis);
let is_mutated = slot_is_mutated(slot, input.mir);
let is_aliased = slot_is_aliased(slot, input.mir, input.closure_captures);
let ownership = input
.binding_semantics
.get(&slot.0)
.map(|s| s.ownership_class);
let explicit_storage = input
.binding_semantics
.get(&slot.0)
.map(|s| s.storage_class);
let is_escaped = detect_escape_status(slot, input.mir, input.closure_captures)
== EscapeStatus::Escaped;
let storage_class = if let Some(BindingStorageClass::Reference) = explicit_storage {
BindingStorageClass::Reference
} else if slot_holds_reference(slot, input.mir) {
BindingStorageClass::Reference
} else if matches!(ownership, Some(BindingOwnershipClass::Flexible)) && var_sharedcow_enabled {
BindingStorageClass::SharedCow
} else if is_mutably_captured {
BindingStorageClass::UniqueHeap
} else if matches!(ownership, Some(BindingOwnershipClass::Flexible))
&& is_aliased
&& is_mutated
{
BindingStorageClass::SharedCow
} else if is_escaped && is_aliased && is_mutated {
BindingStorageClass::SharedCow
} else {
BindingStorageClass::Direct
};
let aliasability = if is_captured || is_aliased {
if is_mutated {
Aliasability::SharedMutable
} else {
Aliasability::SharedImmutable
}
} else {
Aliasability::Unique
};
let mutation_capability = match (ownership, is_mutated) {
(Some(BindingOwnershipClass::OwnedImmutable), _) => MutationCapability::Immutable,
(Some(BindingOwnershipClass::OwnedMutable), _) => MutationCapability::LocalMutable,
(Some(BindingOwnershipClass::Flexible), true) => MutationCapability::SharedMutable,
(Some(BindingOwnershipClass::Flexible), false) => MutationCapability::Immutable,
(None, true) => MutationCapability::LocalMutable,
(None, false) => MutationCapability::Immutable,
};
let escape_status = detect_escape_status(slot, input.mir, input.closure_captures);
let return_ownership_hint = input
.binding_semantics
.get(&slot.0)
.and_then(|s| s.return_ownership_hint);
let enriched = BindingSemantics {
ownership_class: ownership.unwrap_or(BindingOwnershipClass::OwnedImmutable),
storage_class: storage_class,
aliasability,
mutation_capability,
escape_status,
return_ownership_hint,
};
(storage_class, enriched)
}
pub fn detect_escape_status(
slot: SlotId,
mir: &MirFunction,
closure_captures: &HashSet<SlotId>,
) -> EscapeStatus {
if slot != SlotId(0) {
let mut visited = HashSet::new();
if slot_flows_to_return(slot, mir, &mut visited) {
return EscapeStatus::Escaped;
}
}
if closure_captures.contains(&slot) {
EscapeStatus::Captured
} else {
EscapeStatus::Local
}
}
fn slot_flows_to_return(
slot: SlotId,
mir: &MirFunction,
visited: &mut HashSet<SlotId>,
) -> bool {
if !visited.insert(slot) {
return false;
}
let return_slot = SlotId(0);
for block in mir.iter_blocks() {
for stmt in &block.statements {
let StatementKind::Assign(Place::Local(dest), rvalue) = &stmt.kind else {
continue;
};
if !rvalue_uses_slot(rvalue, slot) {
continue;
}
if *dest == return_slot {
return true;
}
if *dest != slot && slot_flows_to_return(*dest, mir, visited) {
return true;
}
}
}
false
}
fn slot_holds_reference(slot: SlotId, mir: &MirFunction) -> bool {
for block in mir.iter_blocks() {
for stmt in &block.statements {
if let StatementKind::Assign(Place::Local(s), Rvalue::Borrow(_, _)) = &stmt.kind {
if *s == slot {
return true;
}
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::analysis::BorrowAnalysis;
use crate::mir::liveness::LivenessResult;
use crate::mir::types::*;
use crate::type_tracking::{
Aliasability, BindingOwnershipClass, BindingSemantics, BindingStorageClass, EscapeStatus,
MutationCapability,
};
fn span() -> shape_ast::ast::Span {
shape_ast::ast::Span { start: 0, end: 1 }
}
fn make_stmt(kind: StatementKind, point: u32) -> MirStatement {
MirStatement {
kind,
span: span(),
point: Point(point),
}
}
fn make_terminator(kind: TerminatorKind) -> Terminator {
Terminator { kind, span: span() }
}
fn empty_analysis() -> BorrowAnalysis {
BorrowAnalysis::empty()
}
fn make_mir(name: &str, blocks: Vec<BasicBlock>, num_locals: u16) -> MirFunction {
MirFunction {
name: name.to_string(),
blocks,
num_locals,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: (0..num_locals).map(|_| LocalTypeInfo::Unknown).collect(),
span: span(),
field_name_table: std::collections::HashMap::new(),
local_struct_type_names: std::collections::HashMap::new(),
local_typed_array_element_types: std::collections::HashMap::new(),
local_declared_scalar_types: std::collections::HashMap::new(),
}
}
#[test]
fn test_simple_binding_gets_direct() {
let mir = make_mir(
"test_direct",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
),
0,
)],
terminator: make_terminator(TerminatorKind::Return),
}],
1,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.slot_classes.get(&SlotId(0)),
Some(&BindingStorageClass::Direct)
);
}
#[test]
fn test_fallback_gives_deferred() {
let mir = make_mir(
"test_deferred",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: true,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.slot_classes.get(&SlotId(0)),
Some(&BindingStorageClass::Deferred)
);
assert_eq!(
plan.slot_classes.get(&SlotId(1)),
Some(&BindingStorageClass::Deferred)
);
}
#[test]
fn test_mutable_capture_gets_local_mutable_ptr() {
let mir = make_mir(
"test_unique_heap",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
),
0,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(0),
operands: vec![Operand::Copy(Place::Local(SlotId(0)))],
function_id: None,
},
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
2,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
1,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let mut closure_captures = HashSet::new();
closure_captures.insert(SlotId(0));
let mut mutable_captures = HashSet::new();
mutable_captures.insert(SlotId(0));
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.slot_classes.get(&SlotId(0)),
Some(&BindingStorageClass::LocalMutablePtr)
);
}
#[test]
fn test_aliased_mutated_var_gets_shared_cow() {
let mir = make_mir(
"test_shared_cow",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::StringId(0))),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
),
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
),
2,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::StringId(1))),
),
3,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
3,
);
let analysis = empty_analysis();
let mut binding_semantics = HashMap::new();
binding_semantics.insert(
0u16,
BindingSemantics::deferred(BindingOwnershipClass::Flexible),
);
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.slot_classes.get(&SlotId(0)),
Some(&BindingStorageClass::SharedCow),
"aliased + mutated + Flexible => SharedCow"
);
}
#[test]
fn test_borrow_holder_gets_reference() {
let mir = make_mir(
"test_reference",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
),
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let mut analysis = empty_analysis();
analysis.loans.insert(
LoanId(0),
crate::mir::analysis::LoanInfo {
id: LoanId(0),
borrowed_place: Place::Local(SlotId(0)),
kind: BorrowKind::Shared,
issued_at: Point(1),
span: span(),
region_depth: 1,
},
);
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.slot_classes.get(&SlotId(1)),
Some(&BindingStorageClass::Reference),
"_1 holds &_0 borrow → Reference"
);
}
#[test]
fn test_explicit_reference_preserved() {
let mir = make_mir(
"test_explicit_ref",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![],
terminator: make_terminator(TerminatorKind::Return),
}],
1,
);
let analysis = empty_analysis();
let mut binding_semantics = HashMap::new();
binding_semantics.insert(
0u16,
BindingSemantics {
ownership_class: BindingOwnershipClass::OwnedImmutable,
storage_class: BindingStorageClass::Reference,
aliasability: Aliasability::Unique,
mutation_capability: MutationCapability::Immutable,
escape_status: EscapeStatus::Local,
return_ownership_hint: None,
},
);
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.slot_classes.get(&SlotId(0)),
Some(&BindingStorageClass::Reference),
"explicit Reference annotation preserved"
);
}
#[test]
fn test_collect_closure_captures() {
let mir = make_mir(
"test_collect",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
),
1,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(2),
operands: vec![
Operand::Copy(Place::Local(SlotId(0))),
Operand::Copy(Place::Local(SlotId(1))),
],
function_id: None,
},
2,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(3))),
),
3,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let (captures, mutable) = collect_closure_captures(&mir);
assert!(captures.contains(&SlotId(0)));
assert!(captures.contains(&SlotId(1)));
assert!(mutable.contains(&SlotId(0)));
assert!(!mutable.contains(&SlotId(1)));
}
#[test]
fn test_immutable_capture_stays_direct() {
let mir = make_mir(
"test_immutable_capture",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
0,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(0),
operands: vec![Operand::Copy(Place::Local(SlotId(0)))],
function_id: None,
},
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
1,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let mut closure_captures = HashSet::new();
closure_captures.insert(SlotId(0));
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
let class = plan.slot_classes.get(&SlotId(0)).copied();
assert!(
matches!(
class,
Some(BindingStorageClass::Direct)
| Some(BindingStorageClass::LocalMutablePtr)
),
"immutable capture stays on stack (Direct or LocalMutablePtr), got {:?}",
class
);
}
#[test]
fn test_owned_mutable_aliased_mutated_stays_direct() {
let mir = make_mir(
"test_let_mut_no_cow",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
),
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
),
2,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(99))),
),
3,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
3,
);
let analysis = empty_analysis();
let mut binding_semantics = HashMap::new();
binding_semantics.insert(
0u16,
BindingSemantics::deferred(BindingOwnershipClass::OwnedMutable),
);
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.slot_classes.get(&SlotId(0)),
Some(&BindingStorageClass::Direct),
"OwnedMutable (let mut) stays Direct even when aliased+mutated"
);
}
#[test]
fn test_all_slots_planned() {
let mir = make_mir(
"test_all_planned",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![],
terminator: make_terminator(TerminatorKind::Return),
}],
5,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(plan.slot_classes.len(), 5, "all slots must be planned");
for i in 0..5 {
assert!(
plan.slot_classes.contains_key(&SlotId(i)),
"slot {} must be in plan",
i
);
}
}
#[test]
fn test_mutable_capture_beats_shared_cow() {
let mir = make_mir(
"test_priority",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
),
0,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(0),
operands: vec![Operand::Copy(Place::Local(SlotId(0)))],
function_id: None,
},
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
2,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
1,
);
let analysis = empty_analysis();
let mut binding_semantics = HashMap::new();
binding_semantics.insert(
0u16,
BindingSemantics::deferred(BindingOwnershipClass::Flexible),
);
let mut closure_captures = HashSet::new();
closure_captures.insert(SlotId(0));
let mut mutable_captures = HashSet::new();
mutable_captures.insert(SlotId(0));
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
let actual = plan.slot_classes.get(&SlotId(0)).copied();
if var_sharedcow_default_enabled() {
assert_eq!(
actual,
Some(BindingStorageClass::SharedCow),
"Phase V0.a flag on: mutably-captured `var` is SharedCow"
);
assert_eq!(
decide_slot_storage(SlotId(0), &input, false).0,
BindingStorageClass::UniqueHeap,
"legacy path (flag off) still assigns UniqueHeap before Phase D demotion"
);
} else {
assert_eq!(
actual,
Some(BindingStorageClass::LocalMutablePtr),
"Phase V0.a flag off: non-escaping mutable capture → LocalMutablePtr (Phase D)"
);
}
}
#[test]
fn test_escape_status_local() {
let mir = make_mir(
"test_local_escape",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
),
0,
)],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let captures = HashSet::new();
assert_eq!(
detect_escape_status(SlotId(1), &mir, &captures),
EscapeStatus::Local,
"slot that doesn't escape should be Local"
);
}
#[test]
fn test_escape_status_escaped_via_return() {
let mir = make_mir(
"test_escaped",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let captures = HashSet::new();
assert_eq!(
detect_escape_status(SlotId(1), &mir, &captures),
EscapeStatus::Escaped,
"slot assigned to return slot should be Escaped"
);
}
#[test]
fn test_escape_status_escaped_via_local_alias_chain() {
let mir = make_mir(
"test_transitive_escape",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
),
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
2,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
3,
);
let captures = HashSet::new();
assert_eq!(
detect_escape_status(SlotId(2), &mir, &captures),
EscapeStatus::Escaped,
"slot flowing into a returned local alias should be Escaped"
);
}
#[test]
fn test_escape_status_captured() {
let mir = make_mir(
"test_captured",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
),
0,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
function_id: None,
},
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let mut captures = HashSet::new();
captures.insert(SlotId(1));
assert_eq!(
detect_escape_status(SlotId(1), &mir, &captures),
EscapeStatus::Captured,
"slot captured by closure should be Captured"
);
}
#[test]
fn test_escape_status_escaped_beats_captured() {
let mir = make_mir(
"test_escaped_captured",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
),
0,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
function_id: None,
},
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
2,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let mut captures = HashSet::new();
captures.insert(SlotId(1));
assert_eq!(
detect_escape_status(SlotId(1), &mir, &captures),
EscapeStatus::Escaped,
"Escaped takes priority over Captured"
);
}
#[test]
fn test_escape_semantics_in_plan() {
let mir = make_mir(
"test_escape_in_plan",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.slot_semantics.get(&SlotId(1)).map(|s| s.escape_status),
Some(EscapeStatus::Escaped),
"slot flowing to return should have Escaped status in plan"
);
}
#[test]
fn test_inline_hint_for_small_local_aggregate() {
let mir = make_mir(
"test_inline_small_local",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(vec![
Operand::Constant(MirConstant::Int(1)),
Operand::Constant(MirConstant::Int(2)),
Operand::Constant(MirConstant::Int(3)),
]),
),
0,
)],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.inline_array_sizes.get(&SlotId(1)),
Some(&3),
"3-element non-escaping aggregate should be hinted"
);
}
#[test]
fn test_no_inline_hint_when_aggregate_escapes() {
let mir = make_mir(
"test_no_inline_escape",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(vec![
Operand::Constant(MirConstant::Int(1)),
Operand::Constant(MirConstant::Int(2)),
]),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert!(
!plan.inline_array_sizes.contains_key(&SlotId(1)),
"escaping aggregate must not be hinted"
);
}
#[test]
fn test_no_inline_hint_when_aggregate_too_large() {
let big_ops: Vec<Operand> = (0..9)
.map(|_| Operand::Constant(MirConstant::Int(0)))
.collect();
let mir = make_mir(
"test_no_inline_too_large",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(big_ops),
),
0,
)],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert!(
!plan.inline_array_sizes.contains_key(&SlotId(1)),
"oversize aggregate must not be hinted"
);
}
#[test]
fn test_no_inline_hint_when_aggregate_captured() {
let mir = make_mir(
"test_no_inline_captured",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(vec![
Operand::Constant(MirConstant::Int(1)),
Operand::Constant(MirConstant::Int(2)),
]),
),
0,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
function_id: None,
},
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let mut closure_captures = HashSet::new();
closure_captures.insert(SlotId(1));
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert!(
!plan.inline_array_sizes.contains_key(&SlotId(1)),
"captured aggregate must not be hinted"
);
}
#[test]
fn test_no_inline_hint_when_reassigned() {
let mir = make_mir(
"test_no_inline_reassigned",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(vec![
Operand::Constant(MirConstant::Int(1)),
Operand::Constant(MirConstant::Int(2)),
]),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(vec![
Operand::Constant(MirConstant::Int(3)),
Operand::Constant(MirConstant::Int(4)),
]),
),
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert!(
!plan.inline_array_sizes.contains_key(&SlotId(1)),
"re-assigned slot must not be hinted"
);
}
#[test]
fn test_inline_hint_at_boundary_size() {
let ops: Vec<Operand> = (0..INLINE_ARRAY_MAX_ELEMENTS)
.map(|_| Operand::Constant(MirConstant::Int(0)))
.collect();
let mir = make_mir(
"test_inline_boundary",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(ops),
),
0,
)],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.inline_array_sizes.get(&SlotId(1)),
Some(&INLINE_ARRAY_MAX_ELEMENTS),
"boundary-size aggregate should be hinted"
);
}
#[test]
fn test_no_inline_hint_with_fallbacks() {
let mir = make_mir(
"test_no_inline_fallback",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(vec![
Operand::Constant(MirConstant::Int(1)),
]),
),
0,
)],
terminator: make_terminator(TerminatorKind::Return),
}],
2,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: true,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert!(
plan.inline_array_sizes.is_empty(),
"fallback path must not record any hints"
);
}
fn single_block_mir(name: &str, statements: Vec<MirStatement>, num_locals: u16) -> MirFunction {
make_mir(
name,
vec![BasicBlock {
id: BasicBlockId(0),
statements,
terminator: make_terminator(TerminatorKind::Return),
}],
num_locals,
)
}
fn run_planner(mir: &MirFunction) -> StoragePlan {
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
plan_storage(&input)
}
#[test]
fn test_phase_b_pure_local_closure_is_non_escaping() {
let mir = single_block_mir(
"phase_b_pure_local",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
],
3,
);
let plan = run_planner(&mir);
assert!(
plan.non_escaping_closure_slots.contains(&SlotId(1)),
"pure let f = || 1 should be non-escaping; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_closure_returned_is_escaping() {
let mir = single_block_mir(
"phase_b_returned",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
2,
),
],
2,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"returned closure must be escaping; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_closure_in_array_literal_is_escaping() {
let mir = single_block_mir(
"phase_b_in_array_literal",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Aggregate(vec![Operand::Copy(Place::Local(SlotId(1)))]),
),
2,
),
],
3,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"closure stored in array literal must be escaping; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_closure_pushed_via_array_store_is_escaping() {
let mir = single_block_mir(
"phase_b_array_store",
vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Aggregate(vec![]),
),
0,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(2),
operands: vec![],
function_id: None,
},
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
2,
),
make_stmt(
StatementKind::ArrayStore {
container_slot: SlotId(1),
operands: vec![Operand::Copy(Place::Local(SlotId(2)))],
},
3,
),
],
3,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(2)),
"closure pushed into array via ArrayStore must be escaping; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_closure_stored_in_object_field_is_escaping() {
let mir = single_block_mir(
"phase_b_field_store",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(2),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::Assign(
Place::Field(Box::new(Place::Local(SlotId(1))), FieldIdx(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
),
2,
),
],
3,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(2)),
"closure stored in struct field must be escaping; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_closure_across_detached_task_is_escaping() {
let mir = single_block_mir(
"phase_b_detached_task",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::TaskBoundary(
vec![Operand::Copy(Place::Local(SlotId(1)))],
TaskBoundaryKind::Detached,
),
2,
),
],
2,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"closure crossing detached task boundary must be escaping; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_closure_across_structured_task_is_escaping() {
let mir = single_block_mir(
"phase_b_structured_task",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::TaskBoundary(
vec![Operand::Copy(Place::Local(SlotId(1)))],
TaskBoundaryKind::Structured,
),
2,
),
],
2,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"closure crossing structured task boundary must be escaping (conservative); got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_closure_written_through_deref_is_escaping() {
let mir = single_block_mir(
"phase_b_deref_write",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(2),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::Assign(
Place::Deref(Box::new(Place::Local(SlotId(1)))),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
),
2,
),
],
3,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(2)),
"closure written through deref must be escaping; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_closure_promoted_to_shared_cow_is_escaping() {
let mir = single_block_mir(
"phase_b_shared_cow_promotion",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
],
2,
);
let analysis = empty_analysis();
let mut binding_semantics = HashMap::new();
binding_semantics.insert(
1u16,
BindingSemantics {
ownership_class: BindingOwnershipClass::Flexible,
storage_class: BindingStorageClass::SharedCow,
aliasability: Aliasability::SharedMutable,
mutation_capability: MutationCapability::SharedMutable,
escape_status: EscapeStatus::Local,
return_ownership_hint: None,
},
);
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"SharedCow-promoted closure slot must be escaping; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_transitive_closure_capture_escapes_together() {
let mir = single_block_mir(
"phase_b_transitive_escape",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(2),
operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
function_id: None,
},
2,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
3,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
),
4,
),
],
3,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(2)),
"escaping g must not be classified non-escaping"
);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"f is captured by escaping g → f must also escape (§2.4); got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_transitive_capture_both_non_escaping() {
let mir = single_block_mir(
"phase_b_transitive_non_escape",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(2),
operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
function_id: None,
},
2,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
3,
),
],
3,
);
let plan = run_planner(&mir);
assert!(
plan.non_escaping_closure_slots.contains(&SlotId(1)),
"non-escaping g keeps f non-escaping; got {:?}",
plan.non_escaping_closure_slots
);
assert!(
plan.non_escaping_closure_slots.contains(&SlotId(2)),
"g itself is non-escaping"
);
}
#[test]
fn test_phase_b_call_arg_conservative_without_summary() {
let mir = make_mir(
"phase_b_call_arg_conservative",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Function("map".to_string())),
args: vec![
Operand::Copy(Place::Local(SlotId(3))),
Operand::Copy(Place::Local(SlotId(1))),
],
destination: Place::Local(SlotId(2)),
next: BasicBlockId(0),
},
span: span(),
},
}],
4,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"closure passed as call arg without a callee summary must be conservative = escaping"
);
}
#[test]
fn test_phase_b_call_arg_with_non_escaping_summary() {
use crate::mir::analysis::{FunctionBorrowSummary, ReturnOwnershipMode};
let mir = make_mir(
"phase_b_call_arg_with_summary",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Function(
"trusted_non_escaping".to_string(),
)),
args: vec![
Operand::Copy(Place::Local(SlotId(3))),
Operand::Copy(Place::Local(SlotId(1))),
],
destination: Place::Local(SlotId(2)),
next: BasicBlockId(0),
},
span: span(),
},
}],
4,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let mut summaries: HashMap<String, FunctionBorrowSummary> = HashMap::new();
summaries.insert(
"trusted_non_escaping".to_string(),
FunctionBorrowSummary {
param_borrows: vec![None, None],
conflict_pairs: vec![],
return_summary: None,
return_ownership_mode: ReturnOwnershipMode::Unknown,
closure_param_escapes: vec![false, false],
},
);
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: Some(&summaries),
};
let plan = plan_storage(&input);
assert!(
plan.non_escaping_closure_slots.contains(&SlotId(1)),
"closure passed to a callee with a non-escaping param summary is non-escaping; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_b_snapshot_call_forces_escape() {
let mir = make_mir(
"phase_b_snapshot_escape",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Function("snapshot".to_string())),
args: vec![Operand::Copy(Place::Local(SlotId(1)))],
destination: Place::Local(SlotId(2)),
next: BasicBlockId(0),
},
span: span(),
},
}],
3,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"closure fed to snapshot() must be escaping"
);
}
#[test]
fn test_phase_b_enum_store_is_escaping() {
let mir = single_block_mir(
"phase_b_enum_store",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::EnumStore {
container_slot: SlotId(2),
operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
variant_name: None,
},
2,
),
],
3,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"closure stored in enum payload must be escaping"
);
}
#[test]
fn test_phase_b_object_store_is_escaping() {
let mir = single_block_mir(
"phase_b_object_store",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::ObjectStore {
container_slot: SlotId(2),
operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
field_names: vec!["f".to_string()],
schema_id: None,
},
2,
),
],
3,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"closure stored in object literal must be escaping"
);
}
#[test]
fn test_phase_b_two_independent_closures() {
let mir = single_block_mir(
"phase_b_two_independent",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(2),
operands: vec![],
function_id: None,
},
2,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
3,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
4,
),
],
3,
);
let plan = run_planner(&mir);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(1)),
"first closure escapes via return"
);
assert!(
plan.non_escaping_closure_slots.contains(&SlotId(2)),
"second closure is independent and does not escape; got {:?}",
plan.non_escaping_closure_slots
);
}
fn phase_d_basic_mir() -> MirFunction {
single_block_mir(
"phase_d_local_ptr",
vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
),
0,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(2),
operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
function_id: None,
},
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
2,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Int(5))),
),
3,
),
],
3,
)
}
#[test]
fn test_phase_d_non_escaping_closure_promotes_outer_slot_to_local_mutable_ptr() {
let mir = phase_d_basic_mir();
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let mut closure_captures = HashSet::new();
closure_captures.insert(SlotId(1));
let mut mutable_captures = HashSet::new();
mutable_captures.insert(SlotId(1));
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.slot_classes.get(&SlotId(1)),
Some(&BindingStorageClass::LocalMutablePtr),
"non-escaping mutable capture → LocalMutablePtr"
);
assert!(
plan.non_escaping_closure_slots.contains(&SlotId(2)),
"closure in _2 is non-escaping"
);
}
#[test]
fn test_phase_d_escaping_closure_leaves_outer_slot_as_unique_heap() {
let mut mir = phase_d_basic_mir();
let last_block_idx = mir.blocks.len() - 1;
let stmts = &mut mir.blocks[last_block_idx].statements;
stmts.pop();
stmts.push(make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
),
3,
));
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let mut closure_captures = HashSet::new();
closure_captures.insert(SlotId(1));
let mut mutable_captures = HashSet::new();
mutable_captures.insert(SlotId(1));
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert!(
!plan.non_escaping_closure_slots.contains(&SlotId(2)),
"closure is escaping via return"
);
assert_eq!(
plan.slot_classes.get(&SlotId(1)),
Some(&BindingStorageClass::UniqueHeap),
"escaping closure → outer slot stays UniqueHeap"
);
}
#[test]
fn test_phase_d_multiple_disjoint_captures_all_local_mutable_ptr() {
let mir = single_block_mir(
"phase_d_disjoint",
vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
),
1,
),
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(3),
operands: vec![
Operand::Copy(Place::Local(SlotId(1))),
Operand::Copy(Place::Local(SlotId(2))),
],
function_id: None,
},
2,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(3)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
3,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
4,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
),
5,
),
],
4,
);
let analysis = empty_analysis();
let binding_semantics = HashMap::new();
let mut closure_captures = HashSet::new();
closure_captures.insert(SlotId(1));
closure_captures.insert(SlotId(2));
let mut mutable_captures = HashSet::new();
mutable_captures.insert(SlotId(1));
mutable_captures.insert(SlotId(2));
let input = StoragePlannerInput {
mir: &mir,
analysis: &analysis,
binding_semantics: &binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
let plan = plan_storage(&input);
assert_eq!(
plan.slot_classes.get(&SlotId(1)),
Some(&BindingStorageClass::LocalMutablePtr)
);
assert_eq!(
plan.slot_classes.get(&SlotId(2)),
Some(&BindingStorageClass::LocalMutablePtr)
);
}
#[test]
fn test_phase_g_snapshot_call_forces_all_closures_escaping() {
let bb0 = BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Function(
"snapshot".to_string(),
)),
args: vec![],
destination: Place::Local(SlotId(2)),
next: BasicBlockId(1),
},
span: span(),
},
};
let bb1 = BasicBlock {
id: BasicBlockId(1),
statements: vec![],
terminator: make_terminator(TerminatorKind::Return),
};
let mir = make_mir("phase_g_snapshot_escape", vec![bb0, bb1], 3);
let plan = run_planner(&mir);
assert!(
plan.non_escaping_closure_slots.is_empty(),
"snapshot() in function body must force every closure to heap; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_g_no_snapshot_keeps_closure_non_escaping() {
let mir = single_block_mir(
"phase_g_no_snapshot",
vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
],
2,
);
let plan = run_planner(&mir);
assert!(
plan.non_escaping_closure_slots.contains(&SlotId(1)),
"closure with no escape vector (and no snapshot) must be non-escaping; got {:?}",
plan.non_escaping_closure_slots
);
}
#[test]
fn test_phase_g_snapshot_forces_aliased_closure_to_heap() {
let bb0 = BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::ClosureCapture {
closure_slot: SlotId(1),
operands: vec![],
function_id: None,
},
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
),
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
),
2,
),
],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Function(
"snapshot".to_string(),
)),
args: vec![],
destination: Place::Local(SlotId(3)),
next: BasicBlockId(1),
},
span: span(),
},
};
let bb1 = BasicBlock {
id: BasicBlockId(1),
statements: vec![],
terminator: make_terminator(TerminatorKind::Return),
};
let mir = make_mir("phase_g_snapshot_aliased", vec![bb0, bb1], 4);
let plan = run_planner(&mir);
assert!(
plan.non_escaping_closure_slots.is_empty(),
"snapshot() with aliased closure must still force every closure slot to heap; \
got {:?}",
plan.non_escaping_closure_slots
);
}
fn run_decide(
slot: SlotId,
mir: &MirFunction,
binding_semantics: &HashMap<u16, BindingSemantics>,
flag_on: bool,
) -> BindingStorageClass {
let analysis = empty_analysis();
let closure_captures = HashSet::new();
let mutable_captures = HashSet::new();
let input = StoragePlannerInput {
mir,
analysis: &analysis,
binding_semantics,
closure_captures: &closure_captures,
mutable_captures: &mutable_captures,
had_fallbacks: false,
callee_summaries: None,
};
decide_slot_storage(slot, &input, flag_on).0
}
#[test]
fn test_v0a_var_gets_sharedcow_when_flag_on() {
let mir = make_mir(
"v0a_var_flag_on",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::BinaryOp(
BinOp::Add,
Operand::Copy(Place::Local(SlotId(0))),
Operand::Constant(MirConstant::Int(1)),
),
),
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
1,
);
let mut binding_semantics = HashMap::new();
binding_semantics.insert(
0u16,
BindingSemantics::deferred(BindingOwnershipClass::Flexible),
);
assert_eq!(
run_decide(SlotId(0), &mir, &binding_semantics, true),
BindingStorageClass::SharedCow,
"Phase V0.a: `var` binding with flag on must be SharedCow"
);
assert_eq!(
run_decide(SlotId(0), &mir, &binding_semantics, false),
BindingStorageClass::Direct,
"Phase V0.a: `var` binding with flag off falls back to Direct when not aliased+mutated"
);
}
#[test]
fn test_v0a_let_stays_direct_regardless_of_flag() {
let mir = make_mir(
"v0a_let_direct",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
),
0,
)],
terminator: make_terminator(TerminatorKind::Return),
}],
1,
);
let mut binding_semantics = HashMap::new();
binding_semantics.insert(
0u16,
BindingSemantics::deferred(BindingOwnershipClass::OwnedImmutable),
);
for flag_on in [true, false] {
assert_eq!(
run_decide(SlotId(0), &mir, &binding_semantics, flag_on),
BindingStorageClass::Direct,
"Phase V0.a: `let` binding stays Direct (flag_on = {flag_on})"
);
}
}
#[test]
fn test_v0a_let_mut_not_sharedcow_under_flag() {
let mir = make_mir(
"v0a_let_mut_no_cow",
vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
),
0,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::BinaryOp(
BinOp::Add,
Operand::Copy(Place::Local(SlotId(0))),
Operand::Constant(MirConstant::Int(1)),
),
),
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
1,
);
let mut binding_semantics = HashMap::new();
binding_semantics.insert(
0u16,
BindingSemantics::deferred(BindingOwnershipClass::OwnedMutable),
);
for flag_on in [true, false] {
let class = run_decide(SlotId(0), &mir, &binding_semantics, flag_on);
assert!(
matches!(
class,
BindingStorageClass::Direct | BindingStorageClass::UniqueHeap
),
"Phase V0.a: `let mut` must not become SharedCow under V0.a \
flag (flag_on = {flag_on}); got {class:?}"
);
assert_ne!(
class,
BindingStorageClass::SharedCow,
"Phase V0.a: `let mut` must NEVER become SharedCow (flag_on = {flag_on})"
);
}
}
}