1use std::collections::{HashMap, HashSet};
25use std::sync::OnceLock;
26
27use crate::mir::analysis::{BorrowAnalysis, FunctionBorrowSummary};
28use crate::mir::types::*;
29use crate::type_tracking::{
30 Aliasability, BindingOwnershipClass, BindingSemantics, BindingStorageClass, EscapeStatus,
31 MutationCapability,
32};
33
34fn var_sharedcow_default_enabled() -> bool {
51 static CACHED: OnceLock<bool> = OnceLock::new();
52 *CACHED.get_or_init(|| match std::env::var("SHAPE_V2_VAR_SHAREDCOW") {
53 Ok(v) => !matches!(
55 v.trim(),
56 "0" | "false" | "FALSE" | "False" | "off" | "OFF" | "Off" | "no" | "NO" | "No" | ""
57 ),
58 Err(_) => true,
60 })
61}
62
63pub const INLINE_ARRAY_MAX_ELEMENTS: usize = 8;
69
70#[derive(Debug, Clone)]
72pub struct StoragePlan {
73 pub slot_classes: HashMap<SlotId, BindingStorageClass>,
75 pub slot_semantics: HashMap<SlotId, BindingSemantics>,
77 pub inline_array_sizes: HashMap<SlotId, usize>,
91 pub non_escaping_closure_slots: HashSet<SlotId>,
108}
109
110pub struct StoragePlannerInput<'a> {
112 pub mir: &'a MirFunction,
114 pub analysis: &'a BorrowAnalysis,
116 pub binding_semantics: &'a HashMap<u16, BindingSemantics>,
118 pub closure_captures: &'a HashSet<SlotId>,
120 pub mutable_captures: &'a HashSet<SlotId>,
122 pub had_fallbacks: bool,
124 pub callee_summaries: Option<&'a HashMap<String, FunctionBorrowSummary>>,
131}
132
133pub fn collect_closure_captures(mir: &MirFunction) -> (HashSet<SlotId>, HashSet<SlotId>) {
141 let mut all_captures = HashSet::new();
142 let mut assign_counts: HashMap<SlotId, u32> = HashMap::new();
143
144 for block in mir.iter_blocks() {
145 for stmt in &block.statements {
146 match &stmt.kind {
147 StatementKind::ClosureCapture { operands, .. } => {
148 for op in operands {
149 if let Some(slot) = operand_root_slot(op) {
150 all_captures.insert(slot);
151 }
152 }
153 }
154 StatementKind::Assign(place, _) => {
155 if let Place::Local(slot) = place {
156 *assign_counts.entry(*slot).or_insert(0) += 1;
157 }
158 }
159 _ => {}
160 }
161 }
162 }
163
164 let mutable_captures: HashSet<SlotId> = all_captures
167 .iter()
168 .filter(|slot| assign_counts.get(slot).copied().unwrap_or(0) > 1)
169 .copied()
170 .collect();
171
172 (all_captures, mutable_captures)
173}
174
175fn operand_root_slot(op: &Operand) -> Option<SlotId> {
177 match op {
178 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
179 Some(place.root_local())
180 }
181 Operand::Constant(_) => None,
182 }
183}
184
185fn slot_has_active_loans(slot: SlotId, analysis: &BorrowAnalysis) -> bool {
188 for loan_info in analysis.loans.values() {
189 if loan_info.borrowed_place.root_local() == slot {
190 return true;
191 }
192 }
193 false
194}
195
196fn slot_is_aliased(slot: SlotId, mir: &MirFunction, closure_captures: &HashSet<SlotId>) -> bool {
199 if closure_captures.contains(&slot) {
200 return true;
201 }
202
203 let mut use_count = 0u32;
204 for block in mir.iter_blocks() {
205 for stmt in &block.statements {
206 if let StatementKind::Assign(_, rvalue) = &stmt.kind {
207 if rvalue_uses_slot(rvalue, slot) {
208 use_count += 1;
209 if use_count > 1 {
210 return true;
211 }
212 }
213 }
214 }
215 if let TerminatorKind::Call { func, args, .. } = &block.terminator.kind {
217 if operand_uses_slot(func, slot) {
218 use_count += 1;
219 }
220 for arg in args {
221 if operand_uses_slot(arg, slot) {
222 use_count += 1;
223 }
224 }
225 if use_count > 1 {
226 return true;
227 }
228 }
229 }
230 false
231}
232
233fn slot_is_mutated(slot: SlotId, mir: &MirFunction) -> bool {
235 let mut assign_count = 0u32;
236 for block in mir.iter_blocks() {
237 for stmt in &block.statements {
238 if let StatementKind::Assign(Place::Local(s), _) = &stmt.kind {
239 if *s == slot {
240 assign_count += 1;
241 if assign_count > 1 {
242 return true;
243 }
244 }
245 }
246 }
247 }
248 false
249}
250
251fn rvalue_uses_slot(rvalue: &Rvalue, slot: SlotId) -> bool {
253 match rvalue {
254 Rvalue::Use(op) | Rvalue::Clone(op) | Rvalue::UnaryOp(_, op) => {
255 operand_uses_slot(op, slot)
256 }
257 Rvalue::Borrow(_, place) => place.root_local() == slot,
258 Rvalue::BinaryOp(_, lhs, rhs) => {
259 operand_uses_slot(lhs, slot) || operand_uses_slot(rhs, slot)
260 }
261 Rvalue::Aggregate(ops) => ops.iter().any(|op| operand_uses_slot(op, slot)),
262 Rvalue::EnumTest { operand, .. }
263 | Rvalue::EnumPayload { operand, .. }
264 | Rvalue::TypePatternTest { operand, .. }
265 | Rvalue::EnumDiscriminantTest { operand, .. } => operand_uses_slot(operand, slot),
266 }
267}
268
269fn operand_uses_slot(op: &Operand, slot: SlotId) -> bool {
271 match op {
272 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
273 place.root_local() == slot
274 }
275 Operand::Constant(_) => false,
276 }
277}
278
279pub fn plan_storage(input: &StoragePlannerInput<'_>) -> StoragePlan {
287 let mut slot_classes = HashMap::new();
288 let mut slot_semantics = HashMap::new();
289
290 if input.had_fallbacks {
293 for slot_idx in 0..input.mir.num_locals {
294 let slot = SlotId(slot_idx);
295 slot_classes.insert(slot, BindingStorageClass::Deferred);
296 slot_semantics.insert(
297 slot,
298 BindingSemantics {
299 ownership_class: BindingOwnershipClass::OwnedImmutable,
300 storage_class: BindingStorageClass::Deferred,
301 aliasability: Aliasability::Unique,
302 mutation_capability: MutationCapability::Immutable,
303 escape_status: EscapeStatus::Local,
304 return_ownership_hint: None,
305 },
306 );
307 }
308 return StoragePlan {
309 slot_classes,
310 slot_semantics,
311 inline_array_sizes: HashMap::new(),
312 non_escaping_closure_slots: HashSet::new(),
313 };
314 }
315
316 let var_sharedcow_enabled = var_sharedcow_default_enabled();
317 for slot_idx in 0..input.mir.num_locals {
318 let slot = SlotId(slot_idx);
319 let (storage_class, semantics) =
320 decide_slot_storage(slot, input, var_sharedcow_enabled);
321 slot_classes.insert(slot, storage_class);
322 slot_semantics.insert(slot, semantics);
323 }
324
325 let inline_array_sizes = detect_inline_array_candidates(input, &slot_semantics);
329
330 let non_escaping_closure_slots =
335 detect_non_escaping_closure_slots(input, &slot_classes);
336
337 promote_local_mutable_ptr_slots(
357 input,
358 &non_escaping_closure_slots,
359 &mut slot_classes,
360 &mut slot_semantics,
361 );
362
363 StoragePlan {
364 slot_classes,
365 slot_semantics,
366 inline_array_sizes,
367 non_escaping_closure_slots,
368 }
369}
370
371fn promote_local_mutable_ptr_slots(
391 input: &StoragePlannerInput<'_>,
392 non_escaping_closure_slots: &HashSet<SlotId>,
393 slot_classes: &mut HashMap<SlotId, BindingStorageClass>,
394 slot_semantics: &mut HashMap<SlotId, BindingSemantics>,
395) {
396 for block in input.mir.iter_blocks() {
397 for stmt in &block.statements {
398 let StatementKind::ClosureCapture {
399 closure_slot,
400 operands,
401 ..
402 } = &stmt.kind
403 else {
404 continue;
405 };
406
407 if !non_escaping_closure_slots.contains(closure_slot) {
408 continue;
409 }
410
411 for op in operands {
412 let Some(root) = operand_root_slot(op) else {
413 continue;
414 };
415 let Some(current) = slot_classes.get(&root).copied() else {
416 continue;
417 };
418 match current {
419 BindingStorageClass::UniqueHeap
420 | BindingStorageClass::Direct
421 | BindingStorageClass::Deferred => {
422 slot_classes.insert(root, BindingStorageClass::LocalMutablePtr);
423 if let Some(sem) = slot_semantics.get_mut(&root) {
424 sem.storage_class = BindingStorageClass::LocalMutablePtr;
425 }
426 }
427 _ => {}
429 }
430 }
431 }
432 }
433}
434
435fn detect_inline_array_candidates(
450 input: &StoragePlannerInput<'_>,
451 slot_semantics: &HashMap<SlotId, BindingSemantics>,
452) -> HashMap<SlotId, usize> {
453 let mut aggregate_sizes: HashMap<SlotId, usize> = HashMap::new();
454 let mut disqualified: HashSet<SlotId> = HashSet::new();
455
456 for block in input.mir.iter_blocks() {
457 for stmt in &block.statements {
458 let StatementKind::Assign(place, rvalue) = &stmt.kind else {
459 continue;
460 };
461 let Place::Local(slot) = place else {
462 disqualified.insert(place.root_local());
465 continue;
466 };
467
468 match rvalue {
469 Rvalue::Aggregate(ops) => {
470 if aggregate_sizes.contains_key(slot) {
474 disqualified.insert(*slot);
475 } else {
476 aggregate_sizes.insert(*slot, ops.len());
477 }
478 }
479 _ => {
480 if aggregate_sizes.contains_key(slot) {
483 disqualified.insert(*slot);
484 }
485 }
486 }
487 }
488 }
489
490 let mut hints = HashMap::new();
491 for (slot, size) in aggregate_sizes {
492 if disqualified.contains(&slot) {
493 continue;
494 }
495 if size == 0 || size > INLINE_ARRAY_MAX_ELEMENTS {
496 continue;
497 }
498 let Some(sem) = slot_semantics.get(&slot) else {
499 continue;
500 };
501 if sem.escape_status != EscapeStatus::Local {
502 continue;
503 }
504 if sem.aliasability != Aliasability::Unique {
508 continue;
509 }
510 if input.closure_captures.contains(&slot) {
511 continue;
512 }
513 hints.insert(slot, size);
514 }
515 hints
516}
517
518fn collect_closure_slots(mir: &MirFunction) -> HashSet<SlotId> {
537 let mut slots = HashSet::new();
538 for block in mir.iter_blocks() {
539 for stmt in &block.statements {
540 if let StatementKind::ClosureCapture { closure_slot, .. } = &stmt.kind {
541 slots.insert(*closure_slot);
542 }
543 }
544 }
545 slots
546}
547
548fn build_closure_capture_graph(
552 mir: &MirFunction,
553 closure_slots: &HashSet<SlotId>,
554) -> HashMap<SlotId, HashSet<SlotId>> {
555 let mut graph: HashMap<SlotId, HashSet<SlotId>> = HashMap::new();
556 for block in mir.iter_blocks() {
557 for stmt in &block.statements {
558 if let StatementKind::ClosureCapture {
559 closure_slot,
560 operands,
561 ..
562 } = &stmt.kind
563 {
564 for op in operands {
565 let Some(root) = operand_root_slot(op) else {
566 continue;
567 };
568 if closure_slots.contains(&root) {
569 graph
570 .entry(*closure_slot)
571 .or_default()
572 .insert(root);
573 }
574 }
575 }
576 }
577 }
578 graph
579}
580
581fn closure_slot_escapes_direct(
594 c: SlotId,
595 input: &StoragePlannerInput<'_>,
596) -> bool {
597 let mir = input.mir;
598
599 if let Some(sem) = input.binding_semantics.get(&c.0) {
602 match sem.storage_class {
603 BindingStorageClass::UniqueHeap | BindingStorageClass::SharedCow => {
604 return true;
605 }
606 _ => {}
607 }
608 }
609
610 let mut tracked: HashSet<SlotId> = HashSet::new();
614 tracked.insert(c);
615
616 let mut changed = true;
617 while changed {
618 changed = false;
619
620 for block in mir.iter_blocks() {
621 for stmt in &block.statements {
622 match &stmt.kind {
623 StatementKind::Assign(place, rvalue) => {
626 let reads_tracked = rvalue_uses_any_slot(rvalue, &tracked);
627 match place {
628 Place::Local(dest) => {
629 if *dest == SlotId(0) && reads_tracked {
631 return true;
632 }
633 if reads_tracked && tracked.insert(*dest) {
636 changed = true;
637 }
638 }
639 Place::Field(..) => {
641 if reads_tracked {
642 return true;
643 }
644 }
645 Place::Index(..) | Place::Deref(..) => {
649 if reads_tracked {
650 return true;
651 }
652 }
653 }
654
655 if let Rvalue::Aggregate(ops) = rvalue {
658 if ops.iter().any(|op| operand_uses_any_slot(op, &tracked)) {
659 return true;
660 }
661 }
662 }
663 StatementKind::ArrayStore { operands, .. }
665 | StatementKind::ObjectStore { operands, .. }
666 | StatementKind::EnumStore { operands, .. } => {
667 if operands.iter().any(|op| operand_uses_any_slot(op, &tracked)) {
668 return true;
669 }
670 }
671 StatementKind::TaskBoundary(operands, _) => {
676 if operands.iter().any(|op| operand_uses_any_slot(op, &tracked)) {
677 return true;
678 }
679 }
680 StatementKind::ClosureCapture { .. }
686 | StatementKind::Drop(_)
687 | StatementKind::Nop => {}
688 }
689 }
690
691 if let TerminatorKind::Call { func, args, .. } = &block.terminator.kind {
696 let callee_name = match func {
701 Operand::Constant(MirConstant::Function(name)) => Some(name.as_str()),
702 _ => None,
703 };
704 if callee_name == Some("snapshot") {
705 if args.iter().any(|op| operand_uses_any_slot(op, &tracked)) {
706 return true;
707 }
708 }
709
710 let callee_summary = callee_name
711 .and_then(|n| input.callee_summaries.and_then(|m| m.get(n)));
712
713 for (arg_idx, arg) in args.iter().enumerate() {
714 if !operand_uses_any_slot(arg, &tracked) {
715 continue;
716 }
717 match callee_summary {
720 Some(summary) if arg_idx < summary.closure_param_escapes.len() => {
721 if summary.closure_param_escapes[arg_idx] {
722 return true;
723 }
724 }
727 _ => return true,
728 }
729 }
730
731 let _ = func; }
736 }
737 }
738
739 false
740}
741
742fn propagate_transitive_closure_escape(
747 closure_slots: &HashSet<SlotId>,
748 capture_graph: &HashMap<SlotId, HashSet<SlotId>>,
749 escaping: &mut HashSet<SlotId>,
750) {
751 let mut changed = true;
752 while changed {
753 changed = false;
754 for &a in closure_slots {
755 if !escaping.contains(&a) {
756 continue;
757 }
758 let Some(captured_closures) = capture_graph.get(&a) else {
759 continue;
760 };
761 for &b in captured_closures {
762 if escaping.insert(b) {
763 changed = true;
764 }
765 }
766 }
767 }
768}
769
770fn mir_contains_snapshot_call(mir: &MirFunction) -> bool {
783 for block in mir.iter_blocks() {
784 if let TerminatorKind::Call { func, .. } = &block.terminator.kind {
785 if let Operand::Constant(MirConstant::Function(name)) = func {
786 if name == "snapshot" {
787 return true;
788 }
789 }
790 }
791 }
792 false
793}
794
795fn detect_non_escaping_closure_slots(
800 input: &StoragePlannerInput<'_>,
801 _slot_classes: &HashMap<SlotId, BindingStorageClass>,
802) -> HashSet<SlotId> {
803 let closure_slots = collect_closure_slots(input.mir);
804 if closure_slots.is_empty() {
805 return HashSet::new();
806 }
807
808 if mir_contains_snapshot_call(input.mir) {
817 return HashSet::new();
819 }
820
821 let mut escaping: HashSet<SlotId> = HashSet::new();
823 for &c in &closure_slots {
824 if closure_slot_escapes_direct(c, input) {
825 escaping.insert(c);
826 }
827 }
828
829 let capture_graph = build_closure_capture_graph(input.mir, &closure_slots);
831 propagate_transitive_closure_escape(&closure_slots, &capture_graph, &mut escaping);
832
833 closure_slots
835 .difference(&escaping)
836 .copied()
837 .collect()
838}
839
840fn rvalue_uses_any_slot(rvalue: &Rvalue, slots: &HashSet<SlotId>) -> bool {
842 match rvalue {
843 Rvalue::Use(op) | Rvalue::Clone(op) | Rvalue::UnaryOp(_, op) => {
844 operand_uses_any_slot(op, slots)
845 }
846 Rvalue::Borrow(_, place) => slots.contains(&place.root_local()),
847 Rvalue::BinaryOp(_, lhs, rhs) => {
848 operand_uses_any_slot(lhs, slots) || operand_uses_any_slot(rhs, slots)
849 }
850 Rvalue::Aggregate(ops) => ops.iter().any(|op| operand_uses_any_slot(op, slots)),
851 Rvalue::EnumTest { operand, .. }
852 | Rvalue::EnumPayload { operand, .. }
853 | Rvalue::TypePatternTest { operand, .. }
854 | Rvalue::EnumDiscriminantTest { operand, .. } => operand_uses_any_slot(operand, slots),
855 }
856}
857
858fn operand_uses_any_slot(op: &Operand, slots: &HashSet<SlotId>) -> bool {
860 match op {
861 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
862 slots.contains(&place.root_local())
863 }
864 Operand::Constant(_) => false,
865 }
866}
867
868fn decide_slot_storage(
902 slot: SlotId,
903 input: &StoragePlannerInput<'_>,
904 var_sharedcow_enabled: bool,
905) -> (BindingStorageClass, BindingSemantics) {
906 let is_captured = input.closure_captures.contains(&slot);
907 let is_mutably_captured = input.mutable_captures.contains(&slot);
908 let _has_loans = slot_has_active_loans(slot, input.analysis);
909 let is_mutated = slot_is_mutated(slot, input.mir);
910 let is_aliased = slot_is_aliased(slot, input.mir, input.closure_captures);
911
912 let ownership = input
914 .binding_semantics
915 .get(&slot.0)
916 .map(|s| s.ownership_class);
917
918 let explicit_storage = input
920 .binding_semantics
921 .get(&slot.0)
922 .map(|s| s.storage_class);
923
924 let is_escaped = detect_escape_status(slot, input.mir, input.closure_captures)
925 == EscapeStatus::Escaped;
926
927 let storage_class = if let Some(BindingStorageClass::Reference) = explicit_storage {
928 BindingStorageClass::Reference
930 } else if slot_holds_reference(slot, input.mir) {
931 BindingStorageClass::Reference
933 } else if matches!(ownership, Some(BindingOwnershipClass::Flexible)) && var_sharedcow_enabled {
934 BindingStorageClass::SharedCow
941 } else if is_mutably_captured {
942 BindingStorageClass::UniqueHeap
944 } else if matches!(ownership, Some(BindingOwnershipClass::Flexible))
945 && is_aliased
946 && is_mutated
947 {
948 BindingStorageClass::SharedCow
952 } else if is_escaped && is_aliased && is_mutated {
953 BindingStorageClass::SharedCow
956 } else {
957 BindingStorageClass::Direct
960 };
961
962 let aliasability = if is_captured || is_aliased {
964 if is_mutated {
965 Aliasability::SharedMutable
966 } else {
967 Aliasability::SharedImmutable
968 }
969 } else {
970 Aliasability::Unique
971 };
972
973 let mutation_capability = match (ownership, is_mutated) {
974 (Some(BindingOwnershipClass::OwnedImmutable), _) => MutationCapability::Immutable,
975 (Some(BindingOwnershipClass::OwnedMutable), _) => MutationCapability::LocalMutable,
976 (Some(BindingOwnershipClass::Flexible), true) => MutationCapability::SharedMutable,
977 (Some(BindingOwnershipClass::Flexible), false) => MutationCapability::Immutable,
978 (None, true) => MutationCapability::LocalMutable,
979 (None, false) => MutationCapability::Immutable,
980 };
981
982 let escape_status = detect_escape_status(slot, input.mir, input.closure_captures);
983
984 let return_ownership_hint = input
988 .binding_semantics
989 .get(&slot.0)
990 .and_then(|s| s.return_ownership_hint);
991
992 let enriched = BindingSemantics {
993 ownership_class: ownership.unwrap_or(BindingOwnershipClass::OwnedImmutable),
994 storage_class: storage_class,
995 aliasability,
996 mutation_capability,
997 escape_status,
998 return_ownership_hint,
999 };
1000
1001 (storage_class, enriched)
1002}
1003
1004pub fn detect_escape_status(
1011 slot: SlotId,
1012 mir: &MirFunction,
1013 closure_captures: &HashSet<SlotId>,
1014) -> EscapeStatus {
1015 if slot != SlotId(0) {
1016 let mut visited = HashSet::new();
1017 if slot_flows_to_return(slot, mir, &mut visited) {
1018 return EscapeStatus::Escaped;
1019 }
1020 }
1021
1022 if closure_captures.contains(&slot) {
1023 EscapeStatus::Captured
1024 } else {
1025 EscapeStatus::Local
1026 }
1027}
1028
1029fn slot_flows_to_return(
1030 slot: SlotId,
1031 mir: &MirFunction,
1032 visited: &mut HashSet<SlotId>,
1033) -> bool {
1034 if !visited.insert(slot) {
1035 return false;
1036 }
1037
1038 let return_slot = SlotId(0);
1039 for block in mir.iter_blocks() {
1040 for stmt in &block.statements {
1041 let StatementKind::Assign(Place::Local(dest), rvalue) = &stmt.kind else {
1042 continue;
1043 };
1044 if !rvalue_uses_slot(rvalue, slot) {
1045 continue;
1046 }
1047 if *dest == return_slot {
1048 return true;
1049 }
1050 if *dest != slot && slot_flows_to_return(*dest, mir, visited) {
1051 return true;
1052 }
1053 }
1054 }
1055
1056 false
1057}
1058
1059fn slot_holds_reference(slot: SlotId, mir: &MirFunction) -> bool {
1061 for block in mir.iter_blocks() {
1062 for stmt in &block.statements {
1063 if let StatementKind::Assign(Place::Local(s), Rvalue::Borrow(_, _)) = &stmt.kind {
1064 if *s == slot {
1065 return true;
1066 }
1067 }
1068 }
1069 }
1070 false
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 use super::*;
1076 use crate::mir::analysis::BorrowAnalysis;
1077 use crate::mir::liveness::LivenessResult;
1078 use crate::mir::types::*;
1079 use crate::type_tracking::{
1080 Aliasability, BindingOwnershipClass, BindingSemantics, BindingStorageClass, EscapeStatus,
1081 MutationCapability,
1082 };
1083
1084 fn span() -> shape_ast::ast::Span {
1085 shape_ast::ast::Span { start: 0, end: 1 }
1086 }
1087
1088 fn make_stmt(kind: StatementKind, point: u32) -> MirStatement {
1089 MirStatement {
1090 kind,
1091 span: span(),
1092 point: Point(point),
1093 }
1094 }
1095
1096 fn make_terminator(kind: TerminatorKind) -> Terminator {
1097 Terminator { kind, span: span() }
1098 }
1099
1100 fn empty_analysis() -> BorrowAnalysis {
1101 BorrowAnalysis::empty()
1102 }
1103
1104 fn make_mir(name: &str, blocks: Vec<BasicBlock>, num_locals: u16) -> MirFunction {
1106 MirFunction {
1107 name: name.to_string(),
1108 blocks,
1109 num_locals,
1110 param_slots: vec![],
1111 param_reference_kinds: vec![],
1112 local_types: (0..num_locals).map(|_| LocalTypeInfo::Unknown).collect(),
1113 span: span(),
1114 field_name_table: std::collections::HashMap::new(),
1115 local_struct_type_names: std::collections::HashMap::new(),
1116 local_typed_array_element_types: std::collections::HashMap::new(),
1117 local_declared_scalar_types: std::collections::HashMap::new(),
1118 }
1119 }
1120
1121 #[test]
1124 fn test_simple_binding_gets_direct() {
1125 let mir = make_mir(
1127 "test_direct",
1128 vec![BasicBlock {
1129 id: BasicBlockId(0),
1130 statements: vec![make_stmt(
1131 StatementKind::Assign(
1132 Place::Local(SlotId(0)),
1133 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1134 ),
1135 0,
1136 )],
1137 terminator: make_terminator(TerminatorKind::Return),
1138 }],
1139 1,
1140 );
1141
1142 let analysis = empty_analysis();
1143 let binding_semantics = HashMap::new();
1144 let closure_captures = HashSet::new();
1145 let mutable_captures = HashSet::new();
1146
1147 let input = StoragePlannerInput {
1148 mir: &mir,
1149 analysis: &analysis,
1150 binding_semantics: &binding_semantics,
1151 closure_captures: &closure_captures,
1152 mutable_captures: &mutable_captures,
1153 had_fallbacks: false,
1154 callee_summaries: None,
1155 };
1156
1157 let plan = plan_storage(&input);
1158 assert_eq!(
1159 plan.slot_classes.get(&SlotId(0)),
1160 Some(&BindingStorageClass::Direct)
1161 );
1162 }
1163
1164 #[test]
1167 fn test_fallback_gives_deferred() {
1168 let mir = make_mir(
1169 "test_deferred",
1170 vec![BasicBlock {
1171 id: BasicBlockId(0),
1172 statements: vec![],
1173 terminator: make_terminator(TerminatorKind::Return),
1174 }],
1175 2,
1176 );
1177
1178 let analysis = empty_analysis();
1179 let binding_semantics = HashMap::new();
1180 let closure_captures = HashSet::new();
1181 let mutable_captures = HashSet::new();
1182
1183 let input = StoragePlannerInput {
1184 mir: &mir,
1185 analysis: &analysis,
1186 binding_semantics: &binding_semantics,
1187 closure_captures: &closure_captures,
1188 mutable_captures: &mutable_captures,
1189 had_fallbacks: true,
1190 callee_summaries: None,
1191 };
1192
1193 let plan = plan_storage(&input);
1194 assert_eq!(
1195 plan.slot_classes.get(&SlotId(0)),
1196 Some(&BindingStorageClass::Deferred)
1197 );
1198 assert_eq!(
1199 plan.slot_classes.get(&SlotId(1)),
1200 Some(&BindingStorageClass::Deferred)
1201 );
1202 }
1203
1204 #[test]
1213 fn test_mutable_capture_gets_local_mutable_ptr() {
1214 let mir = make_mir(
1216 "test_unique_heap",
1217 vec![BasicBlock {
1218 id: BasicBlockId(0),
1219 statements: vec![
1220 make_stmt(
1221 StatementKind::Assign(
1222 Place::Local(SlotId(0)),
1223 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
1224 ),
1225 0,
1226 ),
1227 make_stmt(
1228 StatementKind::ClosureCapture {
1229 closure_slot: SlotId(0),
1230 operands: vec![Operand::Copy(Place::Local(SlotId(0)))],
1231 function_id: None,
1232 },
1233 1,
1234 ),
1235 make_stmt(
1236 StatementKind::Assign(
1237 Place::Local(SlotId(0)),
1238 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
1239 ),
1240 2,
1241 ),
1242 ],
1243 terminator: make_terminator(TerminatorKind::Return),
1244 }],
1245 1,
1246 );
1247
1248 let analysis = empty_analysis();
1249 let binding_semantics = HashMap::new();
1250
1251 let mut closure_captures = HashSet::new();
1253 closure_captures.insert(SlotId(0));
1254 let mut mutable_captures = HashSet::new();
1255 mutable_captures.insert(SlotId(0));
1256
1257 let input = StoragePlannerInput {
1258 mir: &mir,
1259 analysis: &analysis,
1260 binding_semantics: &binding_semantics,
1261 closure_captures: &closure_captures,
1262 mutable_captures: &mutable_captures,
1263 had_fallbacks: false,
1264 callee_summaries: None,
1265 };
1266
1267 let plan = plan_storage(&input);
1268 assert_eq!(
1273 plan.slot_classes.get(&SlotId(0)),
1274 Some(&BindingStorageClass::LocalMutablePtr)
1275 );
1276 }
1277
1278 #[test]
1281 fn test_aliased_mutated_var_gets_shared_cow() {
1282 let mir = make_mir(
1284 "test_shared_cow",
1285 vec![BasicBlock {
1286 id: BasicBlockId(0),
1287 statements: vec![
1288 make_stmt(
1289 StatementKind::Assign(
1290 Place::Local(SlotId(0)),
1291 Rvalue::Use(Operand::Constant(MirConstant::StringId(0))),
1292 ),
1293 0,
1294 ),
1295 make_stmt(
1296 StatementKind::Assign(
1297 Place::Local(SlotId(1)),
1298 Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
1299 ),
1300 1,
1301 ),
1302 make_stmt(
1303 StatementKind::Assign(
1304 Place::Local(SlotId(2)),
1305 Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
1306 ),
1307 2,
1308 ),
1309 make_stmt(
1310 StatementKind::Assign(
1311 Place::Local(SlotId(0)),
1312 Rvalue::Use(Operand::Constant(MirConstant::StringId(1))),
1313 ),
1314 3,
1315 ),
1316 ],
1317 terminator: make_terminator(TerminatorKind::Return),
1318 }],
1319 3,
1320 );
1321
1322 let analysis = empty_analysis();
1323 let mut binding_semantics = HashMap::new();
1324 binding_semantics.insert(
1326 0u16,
1327 BindingSemantics::deferred(BindingOwnershipClass::Flexible),
1328 );
1329
1330 let closure_captures = HashSet::new();
1331 let mutable_captures = HashSet::new();
1332
1333 let input = StoragePlannerInput {
1334 mir: &mir,
1335 analysis: &analysis,
1336 binding_semantics: &binding_semantics,
1337 closure_captures: &closure_captures,
1338 mutable_captures: &mutable_captures,
1339 had_fallbacks: false,
1340 callee_summaries: None,
1341 };
1342
1343 let plan = plan_storage(&input);
1344 assert_eq!(
1345 plan.slot_classes.get(&SlotId(0)),
1346 Some(&BindingStorageClass::SharedCow),
1347 "aliased + mutated + Flexible => SharedCow"
1348 );
1349 }
1350
1351 #[test]
1354 fn test_borrow_holder_gets_reference() {
1355 let mir = make_mir(
1357 "test_reference",
1358 vec![BasicBlock {
1359 id: BasicBlockId(0),
1360 statements: vec![
1361 make_stmt(
1362 StatementKind::Assign(
1363 Place::Local(SlotId(0)),
1364 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1365 ),
1366 0,
1367 ),
1368 make_stmt(
1369 StatementKind::Assign(
1370 Place::Local(SlotId(1)),
1371 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
1372 ),
1373 1,
1374 ),
1375 ],
1376 terminator: make_terminator(TerminatorKind::Return),
1377 }],
1378 2,
1379 );
1380
1381 let mut analysis = empty_analysis();
1383 analysis.loans.insert(
1384 LoanId(0),
1385 crate::mir::analysis::LoanInfo {
1386 id: LoanId(0),
1387 borrowed_place: Place::Local(SlotId(0)),
1388 kind: BorrowKind::Shared,
1389 issued_at: Point(1),
1390 span: span(),
1391 region_depth: 1,
1392 },
1393 );
1394
1395 let binding_semantics = HashMap::new();
1396 let closure_captures = HashSet::new();
1397 let mutable_captures = HashSet::new();
1398
1399 let input = StoragePlannerInput {
1400 mir: &mir,
1401 analysis: &analysis,
1402 binding_semantics: &binding_semantics,
1403 closure_captures: &closure_captures,
1404 mutable_captures: &mutable_captures,
1405 had_fallbacks: false,
1406 callee_summaries: None,
1407 };
1408
1409 let plan = plan_storage(&input);
1410 assert_eq!(
1412 plan.slot_classes.get(&SlotId(1)),
1413 Some(&BindingStorageClass::Reference),
1414 "_1 holds &_0 borrow → Reference"
1415 );
1416 }
1417
1418 #[test]
1421 fn test_explicit_reference_preserved() {
1422 let mir = make_mir(
1423 "test_explicit_ref",
1424 vec![BasicBlock {
1425 id: BasicBlockId(0),
1426 statements: vec![],
1427 terminator: make_terminator(TerminatorKind::Return),
1428 }],
1429 1,
1430 );
1431
1432 let analysis = empty_analysis();
1433 let mut binding_semantics = HashMap::new();
1434 binding_semantics.insert(
1435 0u16,
1436 BindingSemantics {
1437 ownership_class: BindingOwnershipClass::OwnedImmutable,
1438 storage_class: BindingStorageClass::Reference,
1439 aliasability: Aliasability::Unique,
1440 mutation_capability: MutationCapability::Immutable,
1441 escape_status: EscapeStatus::Local,
1442 return_ownership_hint: None,
1443 },
1444 );
1445
1446 let closure_captures = HashSet::new();
1447 let mutable_captures = HashSet::new();
1448
1449 let input = StoragePlannerInput {
1450 mir: &mir,
1451 analysis: &analysis,
1452 binding_semantics: &binding_semantics,
1453 closure_captures: &closure_captures,
1454 mutable_captures: &mutable_captures,
1455 had_fallbacks: false,
1456 callee_summaries: None,
1457 };
1458
1459 let plan = plan_storage(&input);
1460 assert_eq!(
1461 plan.slot_classes.get(&SlotId(0)),
1462 Some(&BindingStorageClass::Reference),
1463 "explicit Reference annotation preserved"
1464 );
1465 }
1466
1467 #[test]
1470 fn test_collect_closure_captures() {
1471 let mir = make_mir(
1473 "test_collect",
1474 vec![BasicBlock {
1475 id: BasicBlockId(0),
1476 statements: vec![
1477 make_stmt(
1478 StatementKind::Assign(
1479 Place::Local(SlotId(0)),
1480 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
1481 ),
1482 0,
1483 ),
1484 make_stmt(
1485 StatementKind::Assign(
1486 Place::Local(SlotId(1)),
1487 Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
1488 ),
1489 1,
1490 ),
1491 make_stmt(
1492 StatementKind::ClosureCapture {
1493 closure_slot: SlotId(2),
1494 operands: vec![
1495 Operand::Copy(Place::Local(SlotId(0))),
1496 Operand::Copy(Place::Local(SlotId(1))),
1497 ],
1498 function_id: None,
1499 },
1500 2,
1501 ),
1502 make_stmt(
1503 StatementKind::Assign(
1504 Place::Local(SlotId(0)),
1505 Rvalue::Use(Operand::Constant(MirConstant::Int(3))),
1506 ),
1507 3,
1508 ),
1509 ],
1510 terminator: make_terminator(TerminatorKind::Return),
1511 }],
1512 2,
1513 );
1514
1515 let (captures, mutable) = collect_closure_captures(&mir);
1516 assert!(captures.contains(&SlotId(0)));
1517 assert!(captures.contains(&SlotId(1)));
1518 assert!(mutable.contains(&SlotId(0)));
1520 assert!(!mutable.contains(&SlotId(1)));
1523 }
1524
1525 #[test]
1528 fn test_immutable_capture_stays_direct() {
1529 let mir = make_mir(
1531 "test_immutable_capture",
1532 vec![BasicBlock {
1533 id: BasicBlockId(0),
1534 statements: vec![
1535 make_stmt(
1536 StatementKind::Assign(
1537 Place::Local(SlotId(0)),
1538 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
1539 ),
1540 0,
1541 ),
1542 make_stmt(
1543 StatementKind::ClosureCapture {
1544 closure_slot: SlotId(0),
1545 operands: vec![Operand::Copy(Place::Local(SlotId(0)))],
1546 function_id: None,
1547 },
1548 1,
1549 ),
1550 ],
1551 terminator: make_terminator(TerminatorKind::Return),
1552 }],
1553 1,
1554 );
1555
1556 let analysis = empty_analysis();
1557 let binding_semantics = HashMap::new();
1558 let mut closure_captures = HashSet::new();
1559 closure_captures.insert(SlotId(0));
1560 let mutable_captures = HashSet::new();
1561
1562 let input = StoragePlannerInput {
1563 mir: &mir,
1564 analysis: &analysis,
1565 binding_semantics: &binding_semantics,
1566 closure_captures: &closure_captures,
1567 mutable_captures: &mutable_captures,
1568 had_fallbacks: false,
1569 callee_summaries: None,
1570 };
1571
1572 let plan = plan_storage(&input);
1573 let class = plan.slot_classes.get(&SlotId(0)).copied();
1580 assert!(
1581 matches!(
1582 class,
1583 Some(BindingStorageClass::Direct)
1584 | Some(BindingStorageClass::LocalMutablePtr)
1585 ),
1586 "immutable capture stays on stack (Direct or LocalMutablePtr), got {:?}",
1587 class
1588 );
1589 }
1590
1591 #[test]
1594 fn test_owned_mutable_aliased_mutated_stays_direct() {
1595 let mir = make_mir(
1598 "test_let_mut_no_cow",
1599 vec![BasicBlock {
1600 id: BasicBlockId(0),
1601 statements: vec![
1602 make_stmt(
1603 StatementKind::Assign(
1604 Place::Local(SlotId(0)),
1605 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
1606 ),
1607 0,
1608 ),
1609 make_stmt(
1610 StatementKind::Assign(
1611 Place::Local(SlotId(1)),
1612 Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
1613 ),
1614 1,
1615 ),
1616 make_stmt(
1617 StatementKind::Assign(
1618 Place::Local(SlotId(2)),
1619 Rvalue::Use(Operand::Copy(Place::Local(SlotId(0)))),
1620 ),
1621 2,
1622 ),
1623 make_stmt(
1624 StatementKind::Assign(
1625 Place::Local(SlotId(0)),
1626 Rvalue::Use(Operand::Constant(MirConstant::Int(99))),
1627 ),
1628 3,
1629 ),
1630 ],
1631 terminator: make_terminator(TerminatorKind::Return),
1632 }],
1633 3,
1634 );
1635
1636 let analysis = empty_analysis();
1637 let mut binding_semantics = HashMap::new();
1638 binding_semantics.insert(
1639 0u16,
1640 BindingSemantics::deferred(BindingOwnershipClass::OwnedMutable),
1641 );
1642
1643 let closure_captures = HashSet::new();
1644 let mutable_captures = HashSet::new();
1645
1646 let input = StoragePlannerInput {
1647 mir: &mir,
1648 analysis: &analysis,
1649 binding_semantics: &binding_semantics,
1650 closure_captures: &closure_captures,
1651 mutable_captures: &mutable_captures,
1652 had_fallbacks: false,
1653 callee_summaries: None,
1654 };
1655
1656 let plan = plan_storage(&input);
1657 assert_eq!(
1658 plan.slot_classes.get(&SlotId(0)),
1659 Some(&BindingStorageClass::Direct),
1660 "OwnedMutable (let mut) stays Direct even when aliased+mutated"
1661 );
1662 }
1663
1664 #[test]
1667 fn test_all_slots_planned() {
1668 let mir = make_mir(
1669 "test_all_planned",
1670 vec![BasicBlock {
1671 id: BasicBlockId(0),
1672 statements: vec![],
1673 terminator: make_terminator(TerminatorKind::Return),
1674 }],
1675 5,
1676 );
1677
1678 let analysis = empty_analysis();
1679 let binding_semantics = HashMap::new();
1680 let closure_captures = HashSet::new();
1681 let mutable_captures = HashSet::new();
1682
1683 let input = StoragePlannerInput {
1684 mir: &mir,
1685 analysis: &analysis,
1686 binding_semantics: &binding_semantics,
1687 closure_captures: &closure_captures,
1688 mutable_captures: &mutable_captures,
1689 had_fallbacks: false,
1690 callee_summaries: None,
1691 };
1692
1693 let plan = plan_storage(&input);
1694 assert_eq!(plan.slot_classes.len(), 5, "all slots must be planned");
1695 for i in 0..5 {
1696 assert!(
1697 plan.slot_classes.contains_key(&SlotId(i)),
1698 "slot {} must be in plan",
1699 i
1700 );
1701 }
1702 }
1703
1704 #[test]
1707 fn test_mutable_capture_beats_shared_cow() {
1708 let mir = make_mir(
1711 "test_priority",
1712 vec![BasicBlock {
1713 id: BasicBlockId(0),
1714 statements: vec![
1715 make_stmt(
1716 StatementKind::Assign(
1717 Place::Local(SlotId(0)),
1718 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
1719 ),
1720 0,
1721 ),
1722 make_stmt(
1723 StatementKind::ClosureCapture {
1724 closure_slot: SlotId(0),
1725 operands: vec![Operand::Copy(Place::Local(SlotId(0)))],
1726 function_id: None,
1727 },
1728 1,
1729 ),
1730 make_stmt(
1731 StatementKind::Assign(
1732 Place::Local(SlotId(0)),
1733 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
1734 ),
1735 2,
1736 ),
1737 ],
1738 terminator: make_terminator(TerminatorKind::Return),
1739 }],
1740 1,
1741 );
1742
1743 let analysis = empty_analysis();
1744 let mut binding_semantics = HashMap::new();
1745 binding_semantics.insert(
1746 0u16,
1747 BindingSemantics::deferred(BindingOwnershipClass::Flexible),
1748 );
1749
1750 let mut closure_captures = HashSet::new();
1751 closure_captures.insert(SlotId(0));
1752 let mut mutable_captures = HashSet::new();
1753 mutable_captures.insert(SlotId(0));
1754
1755 let input = StoragePlannerInput {
1756 mir: &mir,
1757 analysis: &analysis,
1758 binding_semantics: &binding_semantics,
1759 closure_captures: &closure_captures,
1760 mutable_captures: &mutable_captures,
1761 had_fallbacks: false,
1762 callee_summaries: None,
1763 };
1764
1765 let plan = plan_storage(&input);
1779 let actual = plan.slot_classes.get(&SlotId(0)).copied();
1780 if var_sharedcow_default_enabled() {
1781 assert_eq!(
1782 actual,
1783 Some(BindingStorageClass::SharedCow),
1784 "Phase V0.a flag on: mutably-captured `var` is SharedCow"
1785 );
1786 assert_eq!(
1789 decide_slot_storage(SlotId(0), &input, false).0,
1790 BindingStorageClass::UniqueHeap,
1791 "legacy path (flag off) still assigns UniqueHeap before Phase D demotion"
1792 );
1793 } else {
1794 assert_eq!(
1795 actual,
1796 Some(BindingStorageClass::LocalMutablePtr),
1797 "Phase V0.a flag off: non-escaping mutable capture → LocalMutablePtr (Phase D)"
1798 );
1799 }
1800 }
1801
1802 #[test]
1805 fn test_escape_status_local() {
1806 let mir = make_mir(
1809 "test_local_escape",
1810 vec![BasicBlock {
1811 id: BasicBlockId(0),
1812 statements: vec![make_stmt(
1813 StatementKind::Assign(
1814 Place::Local(SlotId(1)),
1815 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1816 ),
1817 0,
1818 )],
1819 terminator: make_terminator(TerminatorKind::Return),
1820 }],
1821 2,
1822 );
1823
1824 let captures = HashSet::new();
1825 assert_eq!(
1826 detect_escape_status(SlotId(1), &mir, &captures),
1827 EscapeStatus::Local,
1828 "slot that doesn't escape should be Local"
1829 );
1830 }
1831
1832 #[test]
1833 fn test_escape_status_escaped_via_return() {
1834 let mir = make_mir(
1837 "test_escaped",
1838 vec![BasicBlock {
1839 id: BasicBlockId(0),
1840 statements: vec![
1841 make_stmt(
1842 StatementKind::Assign(
1843 Place::Local(SlotId(1)),
1844 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1845 ),
1846 0,
1847 ),
1848 make_stmt(
1849 StatementKind::Assign(
1850 Place::Local(SlotId(0)),
1851 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
1852 ),
1853 1,
1854 ),
1855 ],
1856 terminator: make_terminator(TerminatorKind::Return),
1857 }],
1858 2,
1859 );
1860
1861 let captures = HashSet::new();
1862 assert_eq!(
1863 detect_escape_status(SlotId(1), &mir, &captures),
1864 EscapeStatus::Escaped,
1865 "slot assigned to return slot should be Escaped"
1866 );
1867 }
1868
1869 #[test]
1870 fn test_escape_status_escaped_via_local_alias_chain() {
1871 let mir = make_mir(
1874 "test_transitive_escape",
1875 vec![BasicBlock {
1876 id: BasicBlockId(0),
1877 statements: vec![
1878 make_stmt(
1879 StatementKind::Assign(
1880 Place::Local(SlotId(2)),
1881 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1882 ),
1883 0,
1884 ),
1885 make_stmt(
1886 StatementKind::Assign(
1887 Place::Local(SlotId(1)),
1888 Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
1889 ),
1890 1,
1891 ),
1892 make_stmt(
1893 StatementKind::Assign(
1894 Place::Local(SlotId(0)),
1895 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
1896 ),
1897 2,
1898 ),
1899 ],
1900 terminator: make_terminator(TerminatorKind::Return),
1901 }],
1902 3,
1903 );
1904
1905 let captures = HashSet::new();
1906 assert_eq!(
1907 detect_escape_status(SlotId(2), &mir, &captures),
1908 EscapeStatus::Escaped,
1909 "slot flowing into a returned local alias should be Escaped"
1910 );
1911 }
1912
1913 #[test]
1914 fn test_escape_status_captured() {
1915 let mir = make_mir(
1917 "test_captured",
1918 vec![BasicBlock {
1919 id: BasicBlockId(0),
1920 statements: vec![
1921 make_stmt(
1922 StatementKind::Assign(
1923 Place::Local(SlotId(1)),
1924 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1925 ),
1926 0,
1927 ),
1928 make_stmt(
1929 StatementKind::ClosureCapture {
1930 closure_slot: SlotId(1),
1931 operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
1932 function_id: None,
1933 },
1934 1,
1935 ),
1936 ],
1937 terminator: make_terminator(TerminatorKind::Return),
1938 }],
1939 2,
1940 );
1941
1942 let mut captures = HashSet::new();
1943 captures.insert(SlotId(1));
1944 assert_eq!(
1945 detect_escape_status(SlotId(1), &mir, &captures),
1946 EscapeStatus::Captured,
1947 "slot captured by closure should be Captured"
1948 );
1949 }
1950
1951 #[test]
1952 fn test_escape_status_escaped_beats_captured() {
1953 let mir = make_mir(
1956 "test_escaped_captured",
1957 vec![BasicBlock {
1958 id: BasicBlockId(0),
1959 statements: vec![
1960 make_stmt(
1961 StatementKind::Assign(
1962 Place::Local(SlotId(1)),
1963 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
1964 ),
1965 0,
1966 ),
1967 make_stmt(
1968 StatementKind::ClosureCapture {
1969 closure_slot: SlotId(1),
1970 operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
1971 function_id: None,
1972 },
1973 1,
1974 ),
1975 make_stmt(
1976 StatementKind::Assign(
1977 Place::Local(SlotId(0)),
1978 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
1979 ),
1980 2,
1981 ),
1982 ],
1983 terminator: make_terminator(TerminatorKind::Return),
1984 }],
1985 2,
1986 );
1987
1988 let mut captures = HashSet::new();
1989 captures.insert(SlotId(1));
1990 assert_eq!(
1991 detect_escape_status(SlotId(1), &mir, &captures),
1992 EscapeStatus::Escaped,
1993 "Escaped takes priority over Captured"
1994 );
1995 }
1996
1997 #[test]
1998 fn test_escape_semantics_in_plan() {
1999 let mir = make_mir(
2002 "test_escape_in_plan",
2003 vec![BasicBlock {
2004 id: BasicBlockId(0),
2005 statements: vec![
2006 make_stmt(
2007 StatementKind::Assign(
2008 Place::Local(SlotId(1)),
2009 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
2010 ),
2011 0,
2012 ),
2013 make_stmt(
2014 StatementKind::Assign(
2015 Place::Local(SlotId(0)),
2016 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2017 ),
2018 1,
2019 ),
2020 ],
2021 terminator: make_terminator(TerminatorKind::Return),
2022 }],
2023 2,
2024 );
2025
2026 let analysis = empty_analysis();
2027 let binding_semantics = HashMap::new();
2028 let closure_captures = HashSet::new();
2029 let mutable_captures = HashSet::new();
2030
2031 let input = StoragePlannerInput {
2032 mir: &mir,
2033 analysis: &analysis,
2034 binding_semantics: &binding_semantics,
2035 closure_captures: &closure_captures,
2036 mutable_captures: &mutable_captures,
2037 had_fallbacks: false,
2038 callee_summaries: None,
2039 };
2040
2041 let plan = plan_storage(&input);
2042 assert_eq!(
2043 plan.slot_semantics.get(&SlotId(1)).map(|s| s.escape_status),
2044 Some(EscapeStatus::Escaped),
2045 "slot flowing to return should have Escaped status in plan"
2046 );
2047 }
2048
2049 #[test]
2052 fn test_inline_hint_for_small_local_aggregate() {
2053 let mir = make_mir(
2056 "test_inline_small_local",
2057 vec![BasicBlock {
2058 id: BasicBlockId(0),
2059 statements: vec![make_stmt(
2060 StatementKind::Assign(
2061 Place::Local(SlotId(1)),
2062 Rvalue::Aggregate(vec![
2063 Operand::Constant(MirConstant::Int(1)),
2064 Operand::Constant(MirConstant::Int(2)),
2065 Operand::Constant(MirConstant::Int(3)),
2066 ]),
2067 ),
2068 0,
2069 )],
2070 terminator: make_terminator(TerminatorKind::Return),
2071 }],
2072 2,
2073 );
2074
2075 let analysis = empty_analysis();
2076 let binding_semantics = HashMap::new();
2077 let closure_captures = HashSet::new();
2078 let mutable_captures = HashSet::new();
2079
2080 let input = StoragePlannerInput {
2081 mir: &mir,
2082 analysis: &analysis,
2083 binding_semantics: &binding_semantics,
2084 closure_captures: &closure_captures,
2085 mutable_captures: &mutable_captures,
2086 had_fallbacks: false,
2087 callee_summaries: None,
2088 };
2089
2090 let plan = plan_storage(&input);
2091 assert_eq!(
2092 plan.inline_array_sizes.get(&SlotId(1)),
2093 Some(&3),
2094 "3-element non-escaping aggregate should be hinted"
2095 );
2096 }
2097
2098 #[test]
2099 fn test_no_inline_hint_when_aggregate_escapes() {
2100 let mir = make_mir(
2103 "test_no_inline_escape",
2104 vec![BasicBlock {
2105 id: BasicBlockId(0),
2106 statements: vec![
2107 make_stmt(
2108 StatementKind::Assign(
2109 Place::Local(SlotId(1)),
2110 Rvalue::Aggregate(vec![
2111 Operand::Constant(MirConstant::Int(1)),
2112 Operand::Constant(MirConstant::Int(2)),
2113 ]),
2114 ),
2115 0,
2116 ),
2117 make_stmt(
2118 StatementKind::Assign(
2119 Place::Local(SlotId(0)),
2120 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2121 ),
2122 1,
2123 ),
2124 ],
2125 terminator: make_terminator(TerminatorKind::Return),
2126 }],
2127 2,
2128 );
2129
2130 let analysis = empty_analysis();
2131 let binding_semantics = HashMap::new();
2132 let closure_captures = HashSet::new();
2133 let mutable_captures = HashSet::new();
2134
2135 let input = StoragePlannerInput {
2136 mir: &mir,
2137 analysis: &analysis,
2138 binding_semantics: &binding_semantics,
2139 closure_captures: &closure_captures,
2140 mutable_captures: &mutable_captures,
2141 had_fallbacks: false,
2142 callee_summaries: None,
2143 };
2144
2145 let plan = plan_storage(&input);
2146 assert!(
2147 !plan.inline_array_sizes.contains_key(&SlotId(1)),
2148 "escaping aggregate must not be hinted"
2149 );
2150 }
2151
2152 #[test]
2153 fn test_no_inline_hint_when_aggregate_too_large() {
2154 let big_ops: Vec<Operand> = (0..9)
2157 .map(|_| Operand::Constant(MirConstant::Int(0)))
2158 .collect();
2159 let mir = make_mir(
2160 "test_no_inline_too_large",
2161 vec![BasicBlock {
2162 id: BasicBlockId(0),
2163 statements: vec![make_stmt(
2164 StatementKind::Assign(
2165 Place::Local(SlotId(1)),
2166 Rvalue::Aggregate(big_ops),
2167 ),
2168 0,
2169 )],
2170 terminator: make_terminator(TerminatorKind::Return),
2171 }],
2172 2,
2173 );
2174
2175 let analysis = empty_analysis();
2176 let binding_semantics = HashMap::new();
2177 let closure_captures = HashSet::new();
2178 let mutable_captures = HashSet::new();
2179
2180 let input = StoragePlannerInput {
2181 mir: &mir,
2182 analysis: &analysis,
2183 binding_semantics: &binding_semantics,
2184 closure_captures: &closure_captures,
2185 mutable_captures: &mutable_captures,
2186 had_fallbacks: false,
2187 callee_summaries: None,
2188 };
2189
2190 let plan = plan_storage(&input);
2191 assert!(
2192 !plan.inline_array_sizes.contains_key(&SlotId(1)),
2193 "oversize aggregate must not be hinted"
2194 );
2195 }
2196
2197 #[test]
2198 fn test_no_inline_hint_when_aggregate_captured() {
2199 let mir = make_mir(
2201 "test_no_inline_captured",
2202 vec![BasicBlock {
2203 id: BasicBlockId(0),
2204 statements: vec![
2205 make_stmt(
2206 StatementKind::Assign(
2207 Place::Local(SlotId(1)),
2208 Rvalue::Aggregate(vec![
2209 Operand::Constant(MirConstant::Int(1)),
2210 Operand::Constant(MirConstant::Int(2)),
2211 ]),
2212 ),
2213 0,
2214 ),
2215 make_stmt(
2216 StatementKind::ClosureCapture {
2217 closure_slot: SlotId(1),
2218 operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
2219 function_id: None,
2220 },
2221 1,
2222 ),
2223 ],
2224 terminator: make_terminator(TerminatorKind::Return),
2225 }],
2226 2,
2227 );
2228
2229 let analysis = empty_analysis();
2230 let binding_semantics = HashMap::new();
2231 let mut closure_captures = HashSet::new();
2232 closure_captures.insert(SlotId(1));
2233 let mutable_captures = HashSet::new();
2234
2235 let input = StoragePlannerInput {
2236 mir: &mir,
2237 analysis: &analysis,
2238 binding_semantics: &binding_semantics,
2239 closure_captures: &closure_captures,
2240 mutable_captures: &mutable_captures,
2241 had_fallbacks: false,
2242 callee_summaries: None,
2243 };
2244
2245 let plan = plan_storage(&input);
2246 assert!(
2247 !plan.inline_array_sizes.contains_key(&SlotId(1)),
2248 "captured aggregate must not be hinted"
2249 );
2250 }
2251
2252 #[test]
2253 fn test_no_inline_hint_when_reassigned() {
2254 let mir = make_mir(
2257 "test_no_inline_reassigned",
2258 vec![BasicBlock {
2259 id: BasicBlockId(0),
2260 statements: vec![
2261 make_stmt(
2262 StatementKind::Assign(
2263 Place::Local(SlotId(1)),
2264 Rvalue::Aggregate(vec![
2265 Operand::Constant(MirConstant::Int(1)),
2266 Operand::Constant(MirConstant::Int(2)),
2267 ]),
2268 ),
2269 0,
2270 ),
2271 make_stmt(
2272 StatementKind::Assign(
2273 Place::Local(SlotId(1)),
2274 Rvalue::Aggregate(vec![
2275 Operand::Constant(MirConstant::Int(3)),
2276 Operand::Constant(MirConstant::Int(4)),
2277 ]),
2278 ),
2279 1,
2280 ),
2281 ],
2282 terminator: make_terminator(TerminatorKind::Return),
2283 }],
2284 2,
2285 );
2286
2287 let analysis = empty_analysis();
2288 let binding_semantics = HashMap::new();
2289 let closure_captures = HashSet::new();
2290 let mutable_captures = HashSet::new();
2291
2292 let input = StoragePlannerInput {
2293 mir: &mir,
2294 analysis: &analysis,
2295 binding_semantics: &binding_semantics,
2296 closure_captures: &closure_captures,
2297 mutable_captures: &mutable_captures,
2298 had_fallbacks: false,
2299 callee_summaries: None,
2300 };
2301
2302 let plan = plan_storage(&input);
2303 assert!(
2304 !plan.inline_array_sizes.contains_key(&SlotId(1)),
2305 "re-assigned slot must not be hinted"
2306 );
2307 }
2308
2309 #[test]
2310 fn test_inline_hint_at_boundary_size() {
2311 let ops: Vec<Operand> = (0..INLINE_ARRAY_MAX_ELEMENTS)
2313 .map(|_| Operand::Constant(MirConstant::Int(0)))
2314 .collect();
2315 let mir = make_mir(
2316 "test_inline_boundary",
2317 vec![BasicBlock {
2318 id: BasicBlockId(0),
2319 statements: vec![make_stmt(
2320 StatementKind::Assign(
2321 Place::Local(SlotId(1)),
2322 Rvalue::Aggregate(ops),
2323 ),
2324 0,
2325 )],
2326 terminator: make_terminator(TerminatorKind::Return),
2327 }],
2328 2,
2329 );
2330
2331 let analysis = empty_analysis();
2332 let binding_semantics = HashMap::new();
2333 let closure_captures = HashSet::new();
2334 let mutable_captures = HashSet::new();
2335
2336 let input = StoragePlannerInput {
2337 mir: &mir,
2338 analysis: &analysis,
2339 binding_semantics: &binding_semantics,
2340 closure_captures: &closure_captures,
2341 mutable_captures: &mutable_captures,
2342 had_fallbacks: false,
2343 callee_summaries: None,
2344 };
2345
2346 let plan = plan_storage(&input);
2347 assert_eq!(
2348 plan.inline_array_sizes.get(&SlotId(1)),
2349 Some(&INLINE_ARRAY_MAX_ELEMENTS),
2350 "boundary-size aggregate should be hinted"
2351 );
2352 }
2353
2354 #[test]
2355 fn test_no_inline_hint_with_fallbacks() {
2356 let mir = make_mir(
2358 "test_no_inline_fallback",
2359 vec![BasicBlock {
2360 id: BasicBlockId(0),
2361 statements: vec![make_stmt(
2362 StatementKind::Assign(
2363 Place::Local(SlotId(1)),
2364 Rvalue::Aggregate(vec![
2365 Operand::Constant(MirConstant::Int(1)),
2366 ]),
2367 ),
2368 0,
2369 )],
2370 terminator: make_terminator(TerminatorKind::Return),
2371 }],
2372 2,
2373 );
2374
2375 let analysis = empty_analysis();
2376 let binding_semantics = HashMap::new();
2377 let closure_captures = HashSet::new();
2378 let mutable_captures = HashSet::new();
2379
2380 let input = StoragePlannerInput {
2381 mir: &mir,
2382 analysis: &analysis,
2383 binding_semantics: &binding_semantics,
2384 closure_captures: &closure_captures,
2385 mutable_captures: &mutable_captures,
2386 had_fallbacks: true,
2387 callee_summaries: None,
2388 };
2389
2390 let plan = plan_storage(&input);
2391 assert!(
2392 plan.inline_array_sizes.is_empty(),
2393 "fallback path must not record any hints"
2394 );
2395 }
2396
2397 fn single_block_mir(name: &str, statements: Vec<MirStatement>, num_locals: u16) -> MirFunction {
2412 make_mir(
2413 name,
2414 vec![BasicBlock {
2415 id: BasicBlockId(0),
2416 statements,
2417 terminator: make_terminator(TerminatorKind::Return),
2418 }],
2419 num_locals,
2420 )
2421 }
2422
2423 fn run_planner(mir: &MirFunction) -> StoragePlan {
2424 let analysis = empty_analysis();
2425 let binding_semantics = HashMap::new();
2426 let closure_captures = HashSet::new();
2427 let mutable_captures = HashSet::new();
2428 let input = StoragePlannerInput {
2429 mir,
2430 analysis: &analysis,
2431 binding_semantics: &binding_semantics,
2432 closure_captures: &closure_captures,
2433 mutable_captures: &mutable_captures,
2434 had_fallbacks: false,
2435 callee_summaries: None,
2436 };
2437 plan_storage(&input)
2438 }
2439
2440 #[test]
2441 fn test_phase_b_pure_local_closure_is_non_escaping() {
2442 let mir = single_block_mir(
2447 "phase_b_pure_local",
2448 vec![
2449 make_stmt(
2450 StatementKind::ClosureCapture {
2451 closure_slot: SlotId(1),
2452 operands: vec![],
2453 function_id: None,
2454 },
2455 0,
2456 ),
2457 make_stmt(
2458 StatementKind::Assign(
2459 Place::Local(SlotId(1)),
2460 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2461 ),
2462 1,
2463 ),
2464 ],
2465 3,
2466 );
2467
2468 let plan = run_planner(&mir);
2469 assert!(
2470 plan.non_escaping_closure_slots.contains(&SlotId(1)),
2471 "pure let f = || 1 should be non-escaping; got {:?}",
2472 plan.non_escaping_closure_slots
2473 );
2474 }
2475
2476 #[test]
2477 fn test_phase_b_closure_returned_is_escaping() {
2478 let mir = single_block_mir(
2481 "phase_b_returned",
2482 vec![
2483 make_stmt(
2484 StatementKind::ClosureCapture {
2485 closure_slot: SlotId(1),
2486 operands: vec![],
2487 function_id: None,
2488 },
2489 0,
2490 ),
2491 make_stmt(
2492 StatementKind::Assign(
2493 Place::Local(SlotId(1)),
2494 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2495 ),
2496 1,
2497 ),
2498 make_stmt(
2499 StatementKind::Assign(
2500 Place::Local(SlotId(0)),
2501 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
2502 ),
2503 2,
2504 ),
2505 ],
2506 2,
2507 );
2508
2509 let plan = run_planner(&mir);
2510 assert!(
2511 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
2512 "returned closure must be escaping; got {:?}",
2513 plan.non_escaping_closure_slots
2514 );
2515 }
2516
2517 #[test]
2518 fn test_phase_b_closure_in_array_literal_is_escaping() {
2519 let mir = single_block_mir(
2522 "phase_b_in_array_literal",
2523 vec![
2524 make_stmt(
2525 StatementKind::ClosureCapture {
2526 closure_slot: SlotId(1),
2527 operands: vec![],
2528 function_id: None,
2529 },
2530 0,
2531 ),
2532 make_stmt(
2533 StatementKind::Assign(
2534 Place::Local(SlotId(1)),
2535 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2536 ),
2537 1,
2538 ),
2539 make_stmt(
2540 StatementKind::Assign(
2541 Place::Local(SlotId(2)),
2542 Rvalue::Aggregate(vec![Operand::Copy(Place::Local(SlotId(1)))]),
2543 ),
2544 2,
2545 ),
2546 ],
2547 3,
2548 );
2549
2550 let plan = run_planner(&mir);
2551 assert!(
2552 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
2553 "closure stored in array literal must be escaping; got {:?}",
2554 plan.non_escaping_closure_slots
2555 );
2556 }
2557
2558 #[test]
2559 fn test_phase_b_closure_pushed_via_array_store_is_escaping() {
2560 let mir = single_block_mir(
2563 "phase_b_array_store",
2564 vec![
2565 make_stmt(
2566 StatementKind::Assign(
2567 Place::Local(SlotId(1)),
2568 Rvalue::Aggregate(vec![]),
2569 ),
2570 0,
2571 ),
2572 make_stmt(
2573 StatementKind::ClosureCapture {
2574 closure_slot: SlotId(2),
2575 operands: vec![],
2576 function_id: None,
2577 },
2578 1,
2579 ),
2580 make_stmt(
2581 StatementKind::Assign(
2582 Place::Local(SlotId(2)),
2583 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2584 ),
2585 2,
2586 ),
2587 make_stmt(
2588 StatementKind::ArrayStore {
2589 container_slot: SlotId(1),
2590 operands: vec![Operand::Copy(Place::Local(SlotId(2)))],
2591 },
2592 3,
2593 ),
2594 ],
2595 3,
2596 );
2597
2598 let plan = run_planner(&mir);
2599 assert!(
2600 !plan.non_escaping_closure_slots.contains(&SlotId(2)),
2601 "closure pushed into array via ArrayStore must be escaping; got {:?}",
2602 plan.non_escaping_closure_slots
2603 );
2604 }
2605
2606 #[test]
2607 fn test_phase_b_closure_stored_in_object_field_is_escaping() {
2608 let mir = single_block_mir(
2611 "phase_b_field_store",
2612 vec![
2613 make_stmt(
2614 StatementKind::ClosureCapture {
2615 closure_slot: SlotId(2),
2616 operands: vec![],
2617 function_id: None,
2618 },
2619 0,
2620 ),
2621 make_stmt(
2622 StatementKind::Assign(
2623 Place::Local(SlotId(2)),
2624 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2625 ),
2626 1,
2627 ),
2628 make_stmt(
2629 StatementKind::Assign(
2630 Place::Field(Box::new(Place::Local(SlotId(1))), FieldIdx(0)),
2631 Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
2632 ),
2633 2,
2634 ),
2635 ],
2636 3,
2637 );
2638
2639 let plan = run_planner(&mir);
2640 assert!(
2641 !plan.non_escaping_closure_slots.contains(&SlotId(2)),
2642 "closure stored in struct field must be escaping; got {:?}",
2643 plan.non_escaping_closure_slots
2644 );
2645 }
2646
2647 #[test]
2648 fn test_phase_b_closure_across_detached_task_is_escaping() {
2649 let mir = single_block_mir(
2651 "phase_b_detached_task",
2652 vec![
2653 make_stmt(
2654 StatementKind::ClosureCapture {
2655 closure_slot: SlotId(1),
2656 operands: vec![],
2657 function_id: None,
2658 },
2659 0,
2660 ),
2661 make_stmt(
2662 StatementKind::Assign(
2663 Place::Local(SlotId(1)),
2664 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2665 ),
2666 1,
2667 ),
2668 make_stmt(
2669 StatementKind::TaskBoundary(
2670 vec![Operand::Copy(Place::Local(SlotId(1)))],
2671 TaskBoundaryKind::Detached,
2672 ),
2673 2,
2674 ),
2675 ],
2676 2,
2677 );
2678
2679 let plan = run_planner(&mir);
2680 assert!(
2681 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
2682 "closure crossing detached task boundary must be escaping; got {:?}",
2683 plan.non_escaping_closure_slots
2684 );
2685 }
2686
2687 #[test]
2688 fn test_phase_b_closure_across_structured_task_is_escaping() {
2689 let mir = single_block_mir(
2692 "phase_b_structured_task",
2693 vec![
2694 make_stmt(
2695 StatementKind::ClosureCapture {
2696 closure_slot: SlotId(1),
2697 operands: vec![],
2698 function_id: None,
2699 },
2700 0,
2701 ),
2702 make_stmt(
2703 StatementKind::Assign(
2704 Place::Local(SlotId(1)),
2705 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2706 ),
2707 1,
2708 ),
2709 make_stmt(
2710 StatementKind::TaskBoundary(
2711 vec![Operand::Copy(Place::Local(SlotId(1)))],
2712 TaskBoundaryKind::Structured,
2713 ),
2714 2,
2715 ),
2716 ],
2717 2,
2718 );
2719
2720 let plan = run_planner(&mir);
2721 assert!(
2722 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
2723 "closure crossing structured task boundary must be escaping (conservative); got {:?}",
2724 plan.non_escaping_closure_slots
2725 );
2726 }
2727
2728 #[test]
2729 fn test_phase_b_closure_written_through_deref_is_escaping() {
2730 let mir = single_block_mir(
2733 "phase_b_deref_write",
2734 vec![
2735 make_stmt(
2736 StatementKind::ClosureCapture {
2737 closure_slot: SlotId(2),
2738 operands: vec![],
2739 function_id: None,
2740 },
2741 0,
2742 ),
2743 make_stmt(
2744 StatementKind::Assign(
2745 Place::Local(SlotId(2)),
2746 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2747 ),
2748 1,
2749 ),
2750 make_stmt(
2751 StatementKind::Assign(
2752 Place::Deref(Box::new(Place::Local(SlotId(1)))),
2753 Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
2754 ),
2755 2,
2756 ),
2757 ],
2758 3,
2759 );
2760
2761 let plan = run_planner(&mir);
2762 assert!(
2763 !plan.non_escaping_closure_slots.contains(&SlotId(2)),
2764 "closure written through deref must be escaping; got {:?}",
2765 plan.non_escaping_closure_slots
2766 );
2767 }
2768
2769 #[test]
2770 fn test_phase_b_closure_promoted_to_shared_cow_is_escaping() {
2771 let mir = single_block_mir(
2775 "phase_b_shared_cow_promotion",
2776 vec![
2777 make_stmt(
2778 StatementKind::ClosureCapture {
2779 closure_slot: SlotId(1),
2780 operands: vec![],
2781 function_id: None,
2782 },
2783 0,
2784 ),
2785 make_stmt(
2786 StatementKind::Assign(
2787 Place::Local(SlotId(1)),
2788 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2789 ),
2790 1,
2791 ),
2792 ],
2793 2,
2794 );
2795
2796 let analysis = empty_analysis();
2797 let mut binding_semantics = HashMap::new();
2798 binding_semantics.insert(
2799 1u16,
2800 BindingSemantics {
2801 ownership_class: BindingOwnershipClass::Flexible,
2802 storage_class: BindingStorageClass::SharedCow,
2803 aliasability: Aliasability::SharedMutable,
2804 mutation_capability: MutationCapability::SharedMutable,
2805 escape_status: EscapeStatus::Local,
2806 return_ownership_hint: None,
2807 },
2808 );
2809 let closure_captures = HashSet::new();
2810 let mutable_captures = HashSet::new();
2811 let input = StoragePlannerInput {
2812 mir: &mir,
2813 analysis: &analysis,
2814 binding_semantics: &binding_semantics,
2815 closure_captures: &closure_captures,
2816 mutable_captures: &mutable_captures,
2817 had_fallbacks: false,
2818 callee_summaries: None,
2819 };
2820 let plan = plan_storage(&input);
2821 assert!(
2822 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
2823 "SharedCow-promoted closure slot must be escaping; got {:?}",
2824 plan.non_escaping_closure_slots
2825 );
2826 }
2827
2828 #[test]
2829 fn test_phase_b_transitive_closure_capture_escapes_together() {
2830 let mir = single_block_mir(
2835 "phase_b_transitive_escape",
2836 vec![
2837 make_stmt(
2839 StatementKind::ClosureCapture {
2840 closure_slot: SlotId(1),
2841 operands: vec![],
2842 function_id: None,
2843 },
2844 0,
2845 ),
2846 make_stmt(
2847 StatementKind::Assign(
2848 Place::Local(SlotId(1)),
2849 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2850 ),
2851 1,
2852 ),
2853 make_stmt(
2855 StatementKind::ClosureCapture {
2856 closure_slot: SlotId(2),
2857 operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
2858 function_id: None,
2859 },
2860 2,
2861 ),
2862 make_stmt(
2863 StatementKind::Assign(
2864 Place::Local(SlotId(2)),
2865 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2866 ),
2867 3,
2868 ),
2869 make_stmt(
2871 StatementKind::Assign(
2872 Place::Local(SlotId(0)),
2873 Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
2874 ),
2875 4,
2876 ),
2877 ],
2878 3,
2879 );
2880
2881 let plan = run_planner(&mir);
2882 assert!(
2883 !plan.non_escaping_closure_slots.contains(&SlotId(2)),
2884 "escaping g must not be classified non-escaping"
2885 );
2886 assert!(
2887 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
2888 "f is captured by escaping g → f must also escape (§2.4); got {:?}",
2889 plan.non_escaping_closure_slots
2890 );
2891 }
2892
2893 #[test]
2894 fn test_phase_b_transitive_capture_both_non_escaping() {
2895 let mir = single_block_mir(
2900 "phase_b_transitive_non_escape",
2901 vec![
2902 make_stmt(
2903 StatementKind::ClosureCapture {
2904 closure_slot: SlotId(1),
2905 operands: vec![],
2906 function_id: None,
2907 },
2908 0,
2909 ),
2910 make_stmt(
2911 StatementKind::Assign(
2912 Place::Local(SlotId(1)),
2913 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2914 ),
2915 1,
2916 ),
2917 make_stmt(
2918 StatementKind::ClosureCapture {
2919 closure_slot: SlotId(2),
2920 operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
2921 function_id: None,
2922 },
2923 2,
2924 ),
2925 make_stmt(
2926 StatementKind::Assign(
2927 Place::Local(SlotId(2)),
2928 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2929 ),
2930 3,
2931 ),
2932 ],
2933 3,
2934 );
2935
2936 let plan = run_planner(&mir);
2937 assert!(
2938 plan.non_escaping_closure_slots.contains(&SlotId(1)),
2939 "non-escaping g keeps f non-escaping; got {:?}",
2940 plan.non_escaping_closure_slots
2941 );
2942 assert!(
2943 plan.non_escaping_closure_slots.contains(&SlotId(2)),
2944 "g itself is non-escaping"
2945 );
2946 }
2947
2948 #[test]
2949 fn test_phase_b_call_arg_conservative_without_summary() {
2950 let mir = make_mir(
2955 "phase_b_call_arg_conservative",
2956 vec![BasicBlock {
2957 id: BasicBlockId(0),
2958 statements: vec![
2959 make_stmt(
2960 StatementKind::ClosureCapture {
2961 closure_slot: SlotId(1),
2962 operands: vec![],
2963 function_id: None,
2964 },
2965 0,
2966 ),
2967 make_stmt(
2968 StatementKind::Assign(
2969 Place::Local(SlotId(1)),
2970 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
2971 ),
2972 1,
2973 ),
2974 ],
2975 terminator: Terminator {
2976 kind: TerminatorKind::Call {
2977 func: Operand::Constant(MirConstant::Function("map".to_string())),
2978 args: vec![
2979 Operand::Copy(Place::Local(SlotId(3))),
2980 Operand::Copy(Place::Local(SlotId(1))),
2981 ],
2982 destination: Place::Local(SlotId(2)),
2983 next: BasicBlockId(0),
2984 },
2985 span: span(),
2986 },
2987 }],
2988 4,
2989 );
2990
2991 let plan = run_planner(&mir);
2992 assert!(
2993 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
2994 "closure passed as call arg without a callee summary must be conservative = escaping"
2995 );
2996 }
2997
2998 #[test]
2999 fn test_phase_b_call_arg_with_non_escaping_summary() {
3000 use crate::mir::analysis::{FunctionBorrowSummary, ReturnOwnershipMode};
3004
3005 let mir = make_mir(
3006 "phase_b_call_arg_with_summary",
3007 vec![BasicBlock {
3008 id: BasicBlockId(0),
3009 statements: vec![
3010 make_stmt(
3011 StatementKind::ClosureCapture {
3012 closure_slot: SlotId(1),
3013 operands: vec![],
3014 function_id: None,
3015 },
3016 0,
3017 ),
3018 make_stmt(
3019 StatementKind::Assign(
3020 Place::Local(SlotId(1)),
3021 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3022 ),
3023 1,
3024 ),
3025 ],
3026 terminator: Terminator {
3027 kind: TerminatorKind::Call {
3028 func: Operand::Constant(MirConstant::Function(
3029 "trusted_non_escaping".to_string(),
3030 )),
3031 args: vec![
3032 Operand::Copy(Place::Local(SlotId(3))),
3033 Operand::Copy(Place::Local(SlotId(1))),
3034 ],
3035 destination: Place::Local(SlotId(2)),
3036 next: BasicBlockId(0),
3037 },
3038 span: span(),
3039 },
3040 }],
3041 4,
3042 );
3043
3044 let analysis = empty_analysis();
3045 let binding_semantics = HashMap::new();
3046 let closure_captures = HashSet::new();
3047 let mutable_captures = HashSet::new();
3048 let mut summaries: HashMap<String, FunctionBorrowSummary> = HashMap::new();
3049 summaries.insert(
3050 "trusted_non_escaping".to_string(),
3051 FunctionBorrowSummary {
3052 param_borrows: vec![None, None],
3053 conflict_pairs: vec![],
3054 return_summary: None,
3055 return_ownership_mode: ReturnOwnershipMode::Unknown,
3056 closure_param_escapes: vec![false, false],
3058 },
3059 );
3060
3061 let input = StoragePlannerInput {
3062 mir: &mir,
3063 analysis: &analysis,
3064 binding_semantics: &binding_semantics,
3065 closure_captures: &closure_captures,
3066 mutable_captures: &mutable_captures,
3067 had_fallbacks: false,
3068 callee_summaries: Some(&summaries),
3069 };
3070 let plan = plan_storage(&input);
3071 assert!(
3072 plan.non_escaping_closure_slots.contains(&SlotId(1)),
3073 "closure passed to a callee with a non-escaping param summary is non-escaping; got {:?}",
3074 plan.non_escaping_closure_slots
3075 );
3076 }
3077
3078 #[test]
3079 fn test_phase_b_snapshot_call_forces_escape() {
3080 let mir = make_mir(
3083 "phase_b_snapshot_escape",
3084 vec![BasicBlock {
3085 id: BasicBlockId(0),
3086 statements: vec![
3087 make_stmt(
3088 StatementKind::ClosureCapture {
3089 closure_slot: SlotId(1),
3090 operands: vec![],
3091 function_id: None,
3092 },
3093 0,
3094 ),
3095 make_stmt(
3096 StatementKind::Assign(
3097 Place::Local(SlotId(1)),
3098 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3099 ),
3100 1,
3101 ),
3102 ],
3103 terminator: Terminator {
3104 kind: TerminatorKind::Call {
3105 func: Operand::Constant(MirConstant::Function("snapshot".to_string())),
3106 args: vec![Operand::Copy(Place::Local(SlotId(1)))],
3107 destination: Place::Local(SlotId(2)),
3108 next: BasicBlockId(0),
3109 },
3110 span: span(),
3111 },
3112 }],
3113 3,
3114 );
3115
3116 let plan = run_planner(&mir);
3117 assert!(
3118 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
3119 "closure fed to snapshot() must be escaping"
3120 );
3121 }
3122
3123 #[test]
3124 fn test_phase_b_enum_store_is_escaping() {
3125 let mir = single_block_mir(
3128 "phase_b_enum_store",
3129 vec![
3130 make_stmt(
3131 StatementKind::ClosureCapture {
3132 closure_slot: SlotId(1),
3133 operands: vec![],
3134 function_id: None,
3135 },
3136 0,
3137 ),
3138 make_stmt(
3139 StatementKind::Assign(
3140 Place::Local(SlotId(1)),
3141 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3142 ),
3143 1,
3144 ),
3145 make_stmt(
3146 StatementKind::EnumStore {
3147 container_slot: SlotId(2),
3148 operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
3149 variant_name: None,
3150 },
3151 2,
3152 ),
3153 ],
3154 3,
3155 );
3156
3157 let plan = run_planner(&mir);
3158 assert!(
3159 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
3160 "closure stored in enum payload must be escaping"
3161 );
3162 }
3163
3164 #[test]
3165 fn test_phase_b_object_store_is_escaping() {
3166 let mir = single_block_mir(
3169 "phase_b_object_store",
3170 vec![
3171 make_stmt(
3172 StatementKind::ClosureCapture {
3173 closure_slot: SlotId(1),
3174 operands: vec![],
3175 function_id: None,
3176 },
3177 0,
3178 ),
3179 make_stmt(
3180 StatementKind::Assign(
3181 Place::Local(SlotId(1)),
3182 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3183 ),
3184 1,
3185 ),
3186 make_stmt(
3187 StatementKind::ObjectStore {
3188 container_slot: SlotId(2),
3189 operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
3190 field_names: vec!["f".to_string()],
3191 schema_id: None,
3192 },
3193 2,
3194 ),
3195 ],
3196 3,
3197 );
3198
3199 let plan = run_planner(&mir);
3200 assert!(
3201 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
3202 "closure stored in object literal must be escaping"
3203 );
3204 }
3205
3206 #[test]
3207 fn test_phase_b_two_independent_closures() {
3208 let mir = single_block_mir(
3213 "phase_b_two_independent",
3214 vec![
3215 make_stmt(
3217 StatementKind::ClosureCapture {
3218 closure_slot: SlotId(1),
3219 operands: vec![],
3220 function_id: None,
3221 },
3222 0,
3223 ),
3224 make_stmt(
3225 StatementKind::Assign(
3226 Place::Local(SlotId(1)),
3227 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3228 ),
3229 1,
3230 ),
3231 make_stmt(
3233 StatementKind::ClosureCapture {
3234 closure_slot: SlotId(2),
3235 operands: vec![],
3236 function_id: None,
3237 },
3238 2,
3239 ),
3240 make_stmt(
3241 StatementKind::Assign(
3242 Place::Local(SlotId(2)),
3243 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3244 ),
3245 3,
3246 ),
3247 make_stmt(
3249 StatementKind::Assign(
3250 Place::Local(SlotId(0)),
3251 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
3252 ),
3253 4,
3254 ),
3255 ],
3256 3,
3257 );
3258
3259 let plan = run_planner(&mir);
3260 assert!(
3261 !plan.non_escaping_closure_slots.contains(&SlotId(1)),
3262 "first closure escapes via return"
3263 );
3264 assert!(
3265 plan.non_escaping_closure_slots.contains(&SlotId(2)),
3266 "second closure is independent and does not escape; got {:?}",
3267 plan.non_escaping_closure_slots
3268 );
3269 }
3270
3271 fn phase_d_basic_mir() -> MirFunction {
3277 single_block_mir(
3278 "phase_d_local_ptr",
3279 vec![
3280 make_stmt(
3282 StatementKind::Assign(
3283 Place::Local(SlotId(1)),
3284 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
3285 ),
3286 0,
3287 ),
3288 make_stmt(
3290 StatementKind::ClosureCapture {
3291 closure_slot: SlotId(2),
3292 operands: vec![Operand::Copy(Place::Local(SlotId(1)))],
3293 function_id: None,
3294 },
3295 1,
3296 ),
3297 make_stmt(
3299 StatementKind::Assign(
3300 Place::Local(SlotId(2)),
3301 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3302 ),
3303 2,
3304 ),
3305 make_stmt(
3307 StatementKind::Assign(
3308 Place::Local(SlotId(1)),
3309 Rvalue::Use(Operand::Constant(MirConstant::Int(5))),
3310 ),
3311 3,
3312 ),
3313 ],
3314 3,
3315 )
3316 }
3317
3318 #[test]
3319 fn test_phase_d_non_escaping_closure_promotes_outer_slot_to_local_mutable_ptr() {
3320 let mir = phase_d_basic_mir();
3321 let analysis = empty_analysis();
3322 let binding_semantics = HashMap::new();
3323 let mut closure_captures = HashSet::new();
3324 closure_captures.insert(SlotId(1));
3325 let mut mutable_captures = HashSet::new();
3326 mutable_captures.insert(SlotId(1));
3327
3328 let input = StoragePlannerInput {
3329 mir: &mir,
3330 analysis: &analysis,
3331 binding_semantics: &binding_semantics,
3332 closure_captures: &closure_captures,
3333 mutable_captures: &mutable_captures,
3334 had_fallbacks: false,
3335 callee_summaries: None,
3336 };
3337 let plan = plan_storage(&input);
3338
3339 assert_eq!(
3340 plan.slot_classes.get(&SlotId(1)),
3341 Some(&BindingStorageClass::LocalMutablePtr),
3342 "non-escaping mutable capture → LocalMutablePtr"
3343 );
3344 assert!(
3345 plan.non_escaping_closure_slots.contains(&SlotId(2)),
3346 "closure in _2 is non-escaping"
3347 );
3348 }
3349
3350 #[test]
3351 fn test_phase_d_escaping_closure_leaves_outer_slot_as_unique_heap() {
3352 let mut mir = phase_d_basic_mir();
3355 let last_block_idx = mir.blocks.len() - 1;
3358 let stmts = &mut mir.blocks[last_block_idx].statements;
3359 stmts.pop();
3361 stmts.push(make_stmt(
3362 StatementKind::Assign(
3363 Place::Local(SlotId(0)),
3364 Rvalue::Use(Operand::Copy(Place::Local(SlotId(2)))),
3365 ),
3366 3,
3367 ));
3368
3369 let analysis = empty_analysis();
3370 let binding_semantics = HashMap::new();
3371 let mut closure_captures = HashSet::new();
3372 closure_captures.insert(SlotId(1));
3373 let mut mutable_captures = HashSet::new();
3374 mutable_captures.insert(SlotId(1));
3375
3376 let input = StoragePlannerInput {
3377 mir: &mir,
3378 analysis: &analysis,
3379 binding_semantics: &binding_semantics,
3380 closure_captures: &closure_captures,
3381 mutable_captures: &mutable_captures,
3382 had_fallbacks: false,
3383 callee_summaries: None,
3384 };
3385 let plan = plan_storage(&input);
3386
3387 assert!(
3388 !plan.non_escaping_closure_slots.contains(&SlotId(2)),
3389 "closure is escaping via return"
3390 );
3391 assert_eq!(
3392 plan.slot_classes.get(&SlotId(1)),
3393 Some(&BindingStorageClass::UniqueHeap),
3394 "escaping closure → outer slot stays UniqueHeap"
3395 );
3396 }
3397
3398 #[test]
3399 fn test_phase_d_multiple_disjoint_captures_all_local_mutable_ptr() {
3400 let mir = single_block_mir(
3404 "phase_d_disjoint",
3405 vec![
3406 make_stmt(
3407 StatementKind::Assign(
3408 Place::Local(SlotId(1)),
3409 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
3410 ),
3411 0,
3412 ),
3413 make_stmt(
3414 StatementKind::Assign(
3415 Place::Local(SlotId(2)),
3416 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
3417 ),
3418 1,
3419 ),
3420 make_stmt(
3421 StatementKind::ClosureCapture {
3422 closure_slot: SlotId(3),
3423 operands: vec![
3424 Operand::Copy(Place::Local(SlotId(1))),
3425 Operand::Copy(Place::Local(SlotId(2))),
3426 ],
3427 function_id: None,
3428 },
3429 2,
3430 ),
3431 make_stmt(
3432 StatementKind::Assign(
3433 Place::Local(SlotId(3)),
3434 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3435 ),
3436 3,
3437 ),
3438 make_stmt(
3440 StatementKind::Assign(
3441 Place::Local(SlotId(1)),
3442 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
3443 ),
3444 4,
3445 ),
3446 make_stmt(
3447 StatementKind::Assign(
3448 Place::Local(SlotId(2)),
3449 Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
3450 ),
3451 5,
3452 ),
3453 ],
3454 4,
3455 );
3456
3457 let analysis = empty_analysis();
3458 let binding_semantics = HashMap::new();
3459 let mut closure_captures = HashSet::new();
3460 closure_captures.insert(SlotId(1));
3461 closure_captures.insert(SlotId(2));
3462 let mut mutable_captures = HashSet::new();
3463 mutable_captures.insert(SlotId(1));
3464 mutable_captures.insert(SlotId(2));
3465
3466 let input = StoragePlannerInput {
3467 mir: &mir,
3468 analysis: &analysis,
3469 binding_semantics: &binding_semantics,
3470 closure_captures: &closure_captures,
3471 mutable_captures: &mutable_captures,
3472 had_fallbacks: false,
3473 callee_summaries: None,
3474 };
3475 let plan = plan_storage(&input);
3476 assert_eq!(
3477 plan.slot_classes.get(&SlotId(1)),
3478 Some(&BindingStorageClass::LocalMutablePtr)
3479 );
3480 assert_eq!(
3481 plan.slot_classes.get(&SlotId(2)),
3482 Some(&BindingStorageClass::LocalMutablePtr)
3483 );
3484 }
3485
3486 #[test]
3496 fn test_phase_g_snapshot_call_forces_all_closures_escaping() {
3497 let bb0 = BasicBlock {
3503 id: BasicBlockId(0),
3504 statements: vec![
3505 make_stmt(
3506 StatementKind::ClosureCapture {
3507 closure_slot: SlotId(1),
3508 operands: vec![],
3509 function_id: None,
3510 },
3511 0,
3512 ),
3513 make_stmt(
3514 StatementKind::Assign(
3515 Place::Local(SlotId(1)),
3516 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3517 ),
3518 1,
3519 ),
3520 ],
3521 terminator: Terminator {
3522 kind: TerminatorKind::Call {
3523 func: Operand::Constant(MirConstant::Function(
3524 "snapshot".to_string(),
3525 )),
3526 args: vec![],
3527 destination: Place::Local(SlotId(2)),
3528 next: BasicBlockId(1),
3529 },
3530 span: span(),
3531 },
3532 };
3533 let bb1 = BasicBlock {
3534 id: BasicBlockId(1),
3535 statements: vec![],
3536 terminator: make_terminator(TerminatorKind::Return),
3537 };
3538 let mir = make_mir("phase_g_snapshot_escape", vec![bb0, bb1], 3);
3539
3540 let plan = run_planner(&mir);
3541 assert!(
3542 plan.non_escaping_closure_slots.is_empty(),
3543 "snapshot() in function body must force every closure to heap; got {:?}",
3544 plan.non_escaping_closure_slots
3545 );
3546 }
3547
3548 #[test]
3553 fn test_phase_g_no_snapshot_keeps_closure_non_escaping() {
3554 let mir = single_block_mir(
3555 "phase_g_no_snapshot",
3556 vec![
3557 make_stmt(
3558 StatementKind::ClosureCapture {
3559 closure_slot: SlotId(1),
3560 operands: vec![],
3561 function_id: None,
3562 },
3563 0,
3564 ),
3565 make_stmt(
3566 StatementKind::Assign(
3567 Place::Local(SlotId(1)),
3568 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3569 ),
3570 1,
3571 ),
3572 ],
3573 2,
3574 );
3575
3576 let plan = run_planner(&mir);
3577 assert!(
3578 plan.non_escaping_closure_slots.contains(&SlotId(1)),
3579 "closure with no escape vector (and no snapshot) must be non-escaping; got {:?}",
3580 plan.non_escaping_closure_slots
3581 );
3582 }
3583
3584 #[test]
3589 fn test_phase_g_snapshot_forces_aliased_closure_to_heap() {
3590 let bb0 = BasicBlock {
3592 id: BasicBlockId(0),
3593 statements: vec![
3594 make_stmt(
3595 StatementKind::ClosureCapture {
3596 closure_slot: SlotId(1),
3597 operands: vec![],
3598 function_id: None,
3599 },
3600 0,
3601 ),
3602 make_stmt(
3603 StatementKind::Assign(
3604 Place::Local(SlotId(1)),
3605 Rvalue::Use(Operand::Constant(MirConstant::ClosurePlaceholder)),
3606 ),
3607 1,
3608 ),
3609 make_stmt(
3610 StatementKind::Assign(
3611 Place::Local(SlotId(2)),
3612 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
3613 ),
3614 2,
3615 ),
3616 ],
3617 terminator: Terminator {
3618 kind: TerminatorKind::Call {
3619 func: Operand::Constant(MirConstant::Function(
3620 "snapshot".to_string(),
3621 )),
3622 args: vec![],
3623 destination: Place::Local(SlotId(3)),
3624 next: BasicBlockId(1),
3625 },
3626 span: span(),
3627 },
3628 };
3629 let bb1 = BasicBlock {
3630 id: BasicBlockId(1),
3631 statements: vec![],
3632 terminator: make_terminator(TerminatorKind::Return),
3633 };
3634 let mir = make_mir("phase_g_snapshot_aliased", vec![bb0, bb1], 4);
3635
3636 let plan = run_planner(&mir);
3637 assert!(
3638 plan.non_escaping_closure_slots.is_empty(),
3639 "snapshot() with aliased closure must still force every closure slot to heap; \
3640 got {:?}",
3641 plan.non_escaping_closure_slots
3642 );
3643 }
3644
3645 fn run_decide(
3653 slot: SlotId,
3654 mir: &MirFunction,
3655 binding_semantics: &HashMap<u16, BindingSemantics>,
3656 flag_on: bool,
3657 ) -> BindingStorageClass {
3658 let analysis = empty_analysis();
3659 let closure_captures = HashSet::new();
3660 let mutable_captures = HashSet::new();
3661 let input = StoragePlannerInput {
3662 mir,
3663 analysis: &analysis,
3664 binding_semantics,
3665 closure_captures: &closure_captures,
3666 mutable_captures: &mutable_captures,
3667 had_fallbacks: false,
3668 callee_summaries: None,
3669 };
3670 decide_slot_storage(slot, &input, flag_on).0
3671 }
3672
3673 #[test]
3674 fn test_v0a_var_gets_sharedcow_when_flag_on() {
3675 let mir = make_mir(
3681 "v0a_var_flag_on",
3682 vec![BasicBlock {
3683 id: BasicBlockId(0),
3684 statements: vec![
3685 make_stmt(
3686 StatementKind::Assign(
3687 Place::Local(SlotId(0)),
3688 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
3689 ),
3690 0,
3691 ),
3692 make_stmt(
3693 StatementKind::Assign(
3694 Place::Local(SlotId(0)),
3695 Rvalue::BinaryOp(
3696 BinOp::Add,
3697 Operand::Copy(Place::Local(SlotId(0))),
3698 Operand::Constant(MirConstant::Int(1)),
3699 ),
3700 ),
3701 1,
3702 ),
3703 ],
3704 terminator: make_terminator(TerminatorKind::Return),
3705 }],
3706 1,
3707 );
3708
3709 let mut binding_semantics = HashMap::new();
3710 binding_semantics.insert(
3711 0u16,
3712 BindingSemantics::deferred(BindingOwnershipClass::Flexible),
3713 );
3714
3715 assert_eq!(
3717 run_decide(SlotId(0), &mir, &binding_semantics, true),
3718 BindingStorageClass::SharedCow,
3719 "Phase V0.a: `var` binding with flag on must be SharedCow"
3720 );
3721
3722 assert_eq!(
3726 run_decide(SlotId(0), &mir, &binding_semantics, false),
3727 BindingStorageClass::Direct,
3728 "Phase V0.a: `var` binding with flag off falls back to Direct when not aliased+mutated"
3729 );
3730 }
3731
3732 #[test]
3733 fn test_v0a_let_stays_direct_regardless_of_flag() {
3734 let mir = make_mir(
3737 "v0a_let_direct",
3738 vec![BasicBlock {
3739 id: BasicBlockId(0),
3740 statements: vec![make_stmt(
3741 StatementKind::Assign(
3742 Place::Local(SlotId(0)),
3743 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
3744 ),
3745 0,
3746 )],
3747 terminator: make_terminator(TerminatorKind::Return),
3748 }],
3749 1,
3750 );
3751
3752 let mut binding_semantics = HashMap::new();
3753 binding_semantics.insert(
3754 0u16,
3755 BindingSemantics::deferred(BindingOwnershipClass::OwnedImmutable),
3756 );
3757
3758 for flag_on in [true, false] {
3759 assert_eq!(
3760 run_decide(SlotId(0), &mir, &binding_semantics, flag_on),
3761 BindingStorageClass::Direct,
3762 "Phase V0.a: `let` binding stays Direct (flag_on = {flag_on})"
3763 );
3764 }
3765 }
3766
3767 #[test]
3768 fn test_v0a_let_mut_not_sharedcow_under_flag() {
3769 let mir = make_mir(
3775 "v0a_let_mut_no_cow",
3776 vec![BasicBlock {
3777 id: BasicBlockId(0),
3778 statements: vec![
3779 make_stmt(
3780 StatementKind::Assign(
3781 Place::Local(SlotId(0)),
3782 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
3783 ),
3784 0,
3785 ),
3786 make_stmt(
3787 StatementKind::Assign(
3788 Place::Local(SlotId(0)),
3789 Rvalue::BinaryOp(
3790 BinOp::Add,
3791 Operand::Copy(Place::Local(SlotId(0))),
3792 Operand::Constant(MirConstant::Int(1)),
3793 ),
3794 ),
3795 1,
3796 ),
3797 ],
3798 terminator: make_terminator(TerminatorKind::Return),
3799 }],
3800 1,
3801 );
3802
3803 let mut binding_semantics = HashMap::new();
3804 binding_semantics.insert(
3805 0u16,
3806 BindingSemantics::deferred(BindingOwnershipClass::OwnedMutable),
3807 );
3808
3809 for flag_on in [true, false] {
3810 let class = run_decide(SlotId(0), &mir, &binding_semantics, flag_on);
3811 assert!(
3812 matches!(
3813 class,
3814 BindingStorageClass::Direct | BindingStorageClass::UniqueHeap
3815 ),
3816 "Phase V0.a: `let mut` must not become SharedCow under V0.a \
3817 flag (flag_on = {flag_on}); got {class:?}"
3818 );
3819 assert_ne!(
3820 class,
3821 BindingStorageClass::SharedCow,
3822 "Phase V0.a: `let mut` must NEVER become SharedCow (flag_on = {flag_on})"
3823 );
3824 }
3825 }
3826}