1use std::{collections::VecDeque, fmt, iter, ops::Range};
21
22use flux_common::tracked_span_bug;
23use flux_middle::{
24 global_env::GlobalEnv,
25 queries::QueryResult,
26 rty::{self, Loc},
27};
28use rustc_abi::FieldIdx;
29use rustc_data_structures::stack::ensure_sufficient_stack;
30use rustc_hash::FxHashMap;
31use rustc_index::{IndexSlice, IndexVec, bit_set::DenseBitSet};
32use rustc_middle::{
33 mir::{self, BasicBlock, TerminatorEdges, visit::Visitor},
34 ty,
35};
36use rustc_mir_dataflow::{
37 Analysis, JoinSemiLattice, ResultsVisitor,
38 fmt::DebugWithContext,
39 lattice::{FlatSet, HasBottom, HasTop},
40};
41
42use super::GhostStatements;
43use crate::ghost_statements::{GhostStatement, Point};
44
45pub(crate) fn add_ghost_statements<'tcx>(
46 stmts: &mut GhostStatements,
47 genv: GlobalEnv<'_, 'tcx>,
48 body: &mir::Body<'tcx>,
49 fn_sig: Option<&rty::EarlyBinder<rty::PolyFnSig>>,
50) -> QueryResult {
51 let map = Map::new(body);
52
53 let mut visitor = CollectPointerToBorrows::new(&map, stmts);
54
55 PointsToAnalysis::new(&map, fn_sig)
56 .iterate_to_fixpoint(genv.tcx(), body, None)
57 .visit_reachable_with(body, &mut visitor);
58
59 Ok(())
60}
61
62type Results<'a, 'tcx> = rustc_mir_dataflow::Results<'tcx, PointsToAnalysis<'a>>;
63
64struct PointsToAnalysis<'a> {
69 fn_sig: Option<&'a rty::EarlyBinder<rty::PolyFnSig>>,
70 map: &'a Map,
71}
72
73impl<'a> PointsToAnalysis<'a> {
74 fn new(map: &'a Map, fn_sig: Option<&'a rty::EarlyBinder<rty::PolyFnSig>>) -> Self {
75 Self { fn_sig, map }
76 }
77
78 fn handle_statement(&self, statement: &mir::Statement, state: &mut State) {
79 match &statement.kind {
80 mir::StatementKind::Assign(box (target, rvalue)) => {
81 self.handle_assign(*target, rvalue, state);
82 }
83 mir::StatementKind::StorageLive(local) | mir::StatementKind::StorageDead(local) => {
84 state.flood_with(mir::Place::from(*local).as_ref(), self.map, FlatSet::BOTTOM);
87 }
88 mir::StatementKind::Deinit(box place) => {
89 state.flood_with(place.as_ref(), self.map, FlatSet::BOTTOM);
91 }
92 mir::StatementKind::Retag(..)
93 | mir::StatementKind::Intrinsic(..)
94 | mir::StatementKind::SetDiscriminant { .. }
95 | mir::StatementKind::ConstEvalCounter
96 | mir::StatementKind::Nop
97 | mir::StatementKind::FakeRead(..)
98 | mir::StatementKind::PlaceMention(..)
99 | mir::StatementKind::Coverage(..)
100 | mir::StatementKind::AscribeUserType(..)
101 | mir::StatementKind::BackwardIncompatibleDropHint { .. } => {}
102 }
103 }
104
105 fn handle_assign(&self, target: mir::Place, rvalue: &mir::Rvalue, state: &mut State) {
106 match rvalue {
107 mir::Rvalue::Use(operand) => {
108 let result = self
109 .handle_operand(operand)
110 .map_or(PlaceOrValue::TOP, PlaceOrValue::Place);
111 state.assign(target.as_ref(), result, self.map);
112 }
113 mir::Rvalue::Ref(_, _, place) => {
114 let result = PlaceOrValue::Value(self.handle_ref(place, state));
115 state.assign(target.as_ref(), result, self.map);
116 }
117 mir::Rvalue::Aggregate(box mir::AggregateKind::Tuple, operands) => {
118 state.flood(target.as_ref(), self.map);
119 let Some(target_idx) = self.map.find(target.as_ref()) else { return };
120 for (elem, operand) in operands.iter_enumerated() {
121 let Some(rhs_idx) = self.handle_operand(operand) else { continue };
122 if let Some(field) = self.map.apply(target_idx, elem) {
123 state.insert_place_idx(field, rhs_idx, self.map);
124 }
125 }
126 }
127 _ => {}
128 }
129 }
130
131 fn handle_ref(&self, place: &mir::Place, state: &State) -> FlatSet<Loc> {
132 match &place.projection[..] {
133 [] => FlatSet::Elem(Loc::Local(place.local)),
134 [mir::PlaceElem::Deref] => state.get(place.local.into(), self.map),
135 _ => FlatSet::Top,
136 }
137 }
138
139 fn handle_operand(&self, operand: &mir::Operand) -> Option<PlaceIndex> {
140 match operand {
141 mir::Operand::Constant(..) => None,
142 mir::Operand::Copy(place) | mir::Operand::Move(place) => {
143 self.map.find(place.as_ref())
146 }
147 }
148 }
149
150 fn handle_terminator<'mir, 'tcx>(
153 &self,
154 terminator: &'mir mir::Terminator<'tcx>,
155 state: &mut State,
156 ) -> TerminatorEdges<'mir, 'tcx> {
157 match &terminator.kind {
158 mir::TerminatorKind::TailCall { .. }
159 | mir::TerminatorKind::Call { .. }
160 | mir::TerminatorKind::InlineAsm { .. } => {
161 }
163 mir::TerminatorKind::Drop { place, .. } => {
164 state.flood_with(place.as_ref(), self.map, FlatSet::BOTTOM);
165 }
166 mir::TerminatorKind::SwitchInt { .. }
167 | mir::TerminatorKind::Yield { .. }
168 | mir::TerminatorKind::Goto { .. }
169 | mir::TerminatorKind::UnwindResume
170 | mir::TerminatorKind::UnwindTerminate(_)
171 | mir::TerminatorKind::Return
172 | mir::TerminatorKind::Unreachable
173 | mir::TerminatorKind::Assert { .. }
174 | mir::TerminatorKind::CoroutineDrop
175 | mir::TerminatorKind::FalseEdge { .. }
176 | mir::TerminatorKind::FalseUnwind { .. } => {
177 }
179 }
180 terminator.edges()
181 }
182
183 fn handle_call_return(&self, return_places: mir::CallReturnPlaces, state: &mut State) {
184 return_places.for_each(|place| {
185 state.flood(place.as_ref(), self.map);
186 });
187 }
188}
189
190impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for PointsToAnalysis<'_> {
191 type Domain = State;
192
193 const NAME: &'static str = "PointsToAnalysis";
194
195 fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
196 State { values: IndexVec::from_elem_n(FlatSet::BOTTOM, self.map.value_count) }
197 }
198
199 fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
200 if let Some(fn_sig) = self.fn_sig {
204 let fn_sig = fn_sig.as_ref().skip_binder().as_ref().skip_binder();
205 for (local, ty) in iter::zip(body.args_iter(), fn_sig.inputs()) {
206 if let rty::TyKind::Ptr(_, path) = ty.kind() {
207 let loc = FlatSet::Elem(path.to_loc().unwrap_or_else(|| tracked_span_bug!()));
208 state.flood_with(mir::PlaceRef { local, projection: &[] }, self.map, loc);
209 } else {
210 state.flood(mir::PlaceRef { local, projection: &[] }, self.map);
211 }
212 }
213 } else {
214 for local in body.args_iter() {
215 state.flood(mir::PlaceRef { local, projection: &[] }, self.map);
216 }
217 }
218 }
219
220 fn apply_primary_statement_effect(
221 &mut self,
222 state: &mut Self::Domain,
223 statement: &mir::Statement<'tcx>,
224 _location: mir::Location,
225 ) {
226 self.handle_statement(statement, state);
227 }
228
229 fn apply_primary_terminator_effect<'mir>(
230 &mut self,
231 state: &mut Self::Domain,
232 terminator: &'mir mir::Terminator<'tcx>,
233 _location: mir::Location,
234 ) -> mir::TerminatorEdges<'mir, 'tcx> {
235 self.handle_terminator(terminator, state)
236 }
237
238 fn apply_call_return_effect(
239 &mut self,
240 state: &mut Self::Domain,
241 _block: BasicBlock,
242 return_places: mir::CallReturnPlaces<'_, 'tcx>,
243 ) {
244 self.handle_call_return(return_places, state);
245 }
246}
247
248struct CollectPointerToBorrows<'a> {
249 map: &'a Map,
250 tracked_places: FxHashMap<PlaceIndex, flux_rustc_bridge::mir::Place>,
251 stmts: &'a mut GhostStatements,
252 before_state: Vec<(PlaceIndex, FlatSet<Loc>)>,
253}
254
255impl<'a> CollectPointerToBorrows<'a> {
256 fn new(map: &'a Map, stmts: &'a mut GhostStatements) -> Self {
257 let mut tracked_places = FxHashMap::default();
258 map.for_each_tracked_place(|place_idx, local, projection| {
259 let projection = projection
260 .iter()
261 .copied()
262 .map(flux_rustc_bridge::mir::PlaceElem::Field)
263 .collect();
264 tracked_places.insert(place_idx, flux_rustc_bridge::mir::Place::new(local, projection));
265 });
266 Self { map, tracked_places, stmts, before_state: vec![] }
267 }
268}
269
270impl<'a, 'mir, 'tcx> ResultsVisitor<'mir, 'tcx, PointsToAnalysis<'a>>
271 for CollectPointerToBorrows<'_>
272{
273 fn visit_block_start(&mut self, state: &State) {
274 self.before_state.clear();
275 for place_idx in self.tracked_places.keys() {
276 let value = state.get_idx(*place_idx, self.map);
277 self.before_state.push((*place_idx, value));
278 }
279 }
280
281 fn visit_after_primary_statement_effect(
282 &mut self,
283 _results: &mut Results<'a, 'tcx>,
284 state: &State,
285 _statement: &'mir mir::Statement<'tcx>,
286 location: mir::Location,
287 ) {
288 let point = Point::BeforeLocation(location);
289 for (place_idx, old_value) in &mut self.before_state {
290 let new_value = state.get_idx(*place_idx, self.map);
291 if let (FlatSet::Elem(_), FlatSet::Top) = (&old_value, &new_value) {
292 let place = self
293 .tracked_places
294 .get(place_idx)
295 .unwrap_or_else(|| tracked_span_bug!())
296 .clone();
297 self.stmts.insert_at(point, GhostStatement::PtrToRef(place));
298 }
299 *old_value = new_value;
300 }
301 }
302
303 fn visit_after_primary_terminator_effect(
304 &mut self,
305 results: &mut Results<'a, 'tcx>,
306 _state: &State,
307 terminator: &'mir mir::Terminator<'tcx>,
308 location: mir::Location,
309 ) {
310 let block = location.block;
311 for target in terminator.successors() {
312 let point = Point::Edge(block, target);
313 let target_state = results.entry_set_for_block(target);
314
315 for (place_idx, old_value) in &self.before_state {
316 let new_value = target_state.get_idx(*place_idx, self.map);
317 if let (FlatSet::Elem(_), FlatSet::Top) = (&old_value, new_value) {
318 let place = self
319 .tracked_places
320 .get(place_idx)
321 .unwrap_or_else(|| tracked_span_bug!())
322 .clone();
323 self.stmts.insert_at(point, GhostStatement::PtrToRef(place));
324 }
325 }
326 }
327 }
328}
329
330#[derive(Debug)]
337pub struct Map {
338 locals: IndexVec<mir::Local, Option<PlaceIndex>>,
339 projections: FxHashMap<(PlaceIndex, FieldIdx), PlaceIndex>,
340 places: IndexVec<PlaceIndex, PlaceInfo>,
341 value_count: usize,
342 inner_values: IndexVec<PlaceIndex, Range<usize>>,
344 inner_values_buffer: Vec<ValueIndex>,
345}
346
347impl Map {
348 fn new(body: &mir::Body) -> Self {
354 let mut map = Self {
355 locals: IndexVec::new(),
356 projections: FxHashMap::default(),
357 places: IndexVec::new(),
358 value_count: 0,
359 inner_values: IndexVec::new(),
360 inner_values_buffer: Vec::new(),
361 };
362 let exclude = excluded_locals(body);
363 map.register(body, exclude);
364 map
365 }
366
367 fn register(&mut self, body: &mir::Body, exclude: DenseBitSet<mir::Local>) {
369 let mut worklist = VecDeque::with_capacity(body.local_decls.len());
370
371 self.locals = IndexVec::from_elem(None, &body.local_decls);
373 for (local, decl) in body.local_decls.iter_enumerated() {
374 if exclude.contains(local) {
375 continue;
376 }
377
378 debug_assert!(self.locals[local].is_none());
380 let place = self.places.push(PlaceInfo::new(None));
381 self.locals[local] = Some(place);
382
383 self.register_children(place, decl.ty, &mut worklist);
385 }
386
387 while let Some((mut place, elem, ty)) = worklist.pop_front() {
389 place = *self.projections.entry((place, elem)).or_insert_with(|| {
391 let next = self.places.push(PlaceInfo::new(Some(elem)));
393 self.places[next].next_sibling = self.places[place].first_child;
394 self.places[place].first_child = Some(next);
395 next
396 });
397
398 self.register_children(place, ty, &mut worklist);
400 }
401
402 self.inner_values_buffer = Vec::with_capacity(self.value_count);
406 self.inner_values = IndexVec::from_elem(0..0, &self.places);
407 for local in body.local_decls.indices() {
408 if let Some(place) = self.locals[local] {
409 self.cache_preorder_invoke(place);
410 }
411 }
412
413 for opt_place in &mut self.locals {
415 if let Some(place) = *opt_place
416 && self.inner_values[place].is_empty()
417 {
418 *opt_place = None;
419 }
420 }
421 self.projections
422 .retain(|_, child| !self.inner_values[*child].is_empty());
423 }
424
425 fn register_children<'tcx>(
429 &mut self,
430 place: PlaceIndex,
431 ty: ty::Ty<'tcx>,
432 worklist: &mut VecDeque<(PlaceIndex, FieldIdx, ty::Ty<'tcx>)>,
433 ) {
434 assert!(self.places[place].value_index.is_none());
436 if let ty::TyKind::Ref(.., mir::Mutability::Mut) = ty.kind() {
437 self.places[place].value_index = Some(self.value_count.into());
438 self.value_count += 1;
439 }
440
441 if let ty::Tuple(list) = ty.kind() {
443 for (field, ty) in list.iter().enumerate() {
444 worklist.push_back((place, field.into(), ty));
445 }
446 }
447 }
448
449 fn cache_preorder_invoke(&mut self, root: PlaceIndex) {
452 let start = self.inner_values_buffer.len();
453 if let Some(vi) = self.places[root].value_index {
454 self.inner_values_buffer.push(vi);
455 }
456
457 let mut next_child = self.places[root].first_child;
459 while let Some(child) = next_child {
460 ensure_sufficient_stack(|| self.cache_preorder_invoke(child));
461 next_child = self.places[child].next_sibling;
462 }
463
464 let end = self.inner_values_buffer.len();
465 self.inner_values[root] = start..end;
466 }
467
468 fn find(&self, place: mir::PlaceRef<'_>) -> Option<PlaceIndex> {
470 let mut index = *self.locals[place.local].as_ref()?;
471
472 for &elem in place.projection {
473 let mir::ProjectionElem::Field(elem, _) = elem else { return None };
474 index = self.apply(index, elem)?;
475 }
476
477 Some(index)
478 }
479
480 fn children(&self, parent: PlaceIndex) -> impl Iterator<Item = PlaceIndex> + '_ {
482 Children::new(self, parent)
483 }
484
485 fn apply(&self, place: PlaceIndex, elem: FieldIdx) -> Option<PlaceIndex> {
487 self.projections.get(&(place, elem)).copied()
488 }
489
490 fn for_each_aliasing_place(&self, place: mir::PlaceRef<'_>, f: &mut impl FnMut(ValueIndex)) {
492 let Some(mut index) = self.locals[place.local] else {
493 return;
495 };
496 for elem in place.projection {
497 let mir::ProjectionElem::Field(elem, _) = *elem else {
498 return;
499 };
500 if let Some(vi) = self.places[index].value_index {
502 f(vi);
503 }
504
505 let Some(sub) = self.apply(index, elem) else {
506 return;
507 };
508 index = sub;
509 }
510 self.for_each_value_inside(index, f);
511 }
512
513 fn for_each_value_inside(&self, root: PlaceIndex, f: &mut impl FnMut(ValueIndex)) {
515 let range = self.inner_values[root].clone();
516 let values = &self.inner_values_buffer[range];
517 for &v in values {
518 f(v);
519 }
520 }
521
522 fn for_each_tracked_place(&self, mut f: impl FnMut(PlaceIndex, mir::Local, &[FieldIdx])) {
523 let mut projection = Vec::new();
524 for (local, place) in self.locals.iter_enumerated() {
525 if let Some(place) = place {
526 self.for_each_tracked_place_rec(
527 *place,
528 &mut projection,
529 &mut |place, projection| {
530 f(place, local, projection);
531 },
532 );
533 }
534 }
535 }
536
537 fn for_each_tracked_place_rec(
538 &self,
539 root: PlaceIndex,
540 projection: &mut Vec<FieldIdx>,
541 f: &mut impl FnMut(PlaceIndex, &[FieldIdx]),
542 ) {
543 if self.inner_values[root].is_empty() {
545 return;
546 }
547
548 if self.places[root].value_index.is_some() {
549 f(root, projection);
550 }
551
552 for child in self.children(root) {
553 let elem = self.places[child]
554 .proj_elem
555 .unwrap_or_else(|| tracked_span_bug!());
556 projection.push(elem);
557 self.for_each_tracked_place_rec(child, projection, f);
558 projection.pop();
559 }
560 }
561}
562
563#[derive(Debug)]
568struct PlaceInfo {
569 value_index: Option<ValueIndex>,
571
572 proj_elem: Option<FieldIdx>,
574
575 first_child: Option<PlaceIndex>,
577
578 next_sibling: Option<PlaceIndex>,
580}
581
582impl PlaceInfo {
583 fn new(proj_elem: Option<FieldIdx>) -> Self {
584 Self { next_sibling: None, proj_elem, first_child: None, value_index: None }
585 }
586}
587
588struct Children<'a> {
589 map: &'a Map,
590 next: Option<PlaceIndex>,
591}
592
593impl<'a> Children<'a> {
594 fn new(map: &'a Map, parent: PlaceIndex) -> Self {
595 Self { map, next: map.places[parent].first_child }
596 }
597}
598
599impl Iterator for Children<'_> {
600 type Item = PlaceIndex;
601
602 fn next(&mut self) -> Option<Self::Item> {
603 match self.next {
604 Some(child) => {
605 self.next = self.map.places[child].next_sibling;
606 Some(child)
607 }
608 None => None,
609 }
610 }
611}
612
613fn excluded_locals(body: &mir::Body<'_>) -> DenseBitSet<mir::Local> {
615 struct Collector {
616 result: DenseBitSet<mir::Local>,
617 }
618
619 impl<'tcx> mir::visit::Visitor<'tcx> for Collector {
620 fn visit_place(
621 &mut self,
622 place: &mir::Place<'tcx>,
623 context: mir::visit::PlaceContext,
624 _location: mir::Location,
625 ) {
626 if (context.is_borrow()
627 || context.is_address_of()
628 || context.is_drop()
629 || context
630 == mir::visit::PlaceContext::MutatingUse(
631 mir::visit::MutatingUseContext::AsmOutput,
632 ))
633 && !place.is_indirect()
634 {
635 self.result.insert(place.local);
638 }
639 }
640 }
641
642 let mut collector = Collector { result: DenseBitSet::new_empty(body.local_decls.len()) };
643 collector.visit_body(body);
644 collector.result
645}
646
647rustc_index::newtype_index!(
648 struct PlaceIndex {}
653);
654
655rustc_index::newtype_index!(
656 struct ValueIndex {}
660);
661
662enum PlaceOrValue {
664 Value(FlatSet<Loc>),
665 Place(PlaceIndex),
666}
667
668impl PlaceOrValue {
669 const TOP: Self = PlaceOrValue::Value(FlatSet::TOP);
670}
671
672#[derive(PartialEq, Eq, Debug)]
683struct State {
684 values: IndexVec<ValueIndex, FlatSet<Loc>>,
685}
686
687impl Clone for State {
688 fn clone(&self) -> Self {
689 Self { values: self.values.clone() }
690 }
691
692 fn clone_from(&mut self, source: &Self) {
693 self.values.clone_from(&source.values);
694 }
695}
696
697impl JoinSemiLattice for State {
698 fn join(&mut self, other: &Self) -> bool {
699 assert_eq!(self.values.len(), other.values.len());
700 let mut changed = false;
701 for (a, b) in iter::zip(&mut self.values, &other.values) {
702 changed |= a.join(b);
703 }
704 changed
705 }
706}
707
708impl State {
709 fn flood(&mut self, place: mir::PlaceRef<'_>, map: &Map) {
710 self.flood_with(place, map, FlatSet::TOP);
711 }
712
713 fn flood_with(&mut self, place: mir::PlaceRef<'_>, map: &Map, value: FlatSet<Loc>) {
714 map.for_each_aliasing_place(place, &mut |vi| {
715 self.values[vi] = value;
716 });
717 }
718
719 fn assign(&mut self, target: mir::PlaceRef<'_>, result: PlaceOrValue, map: &Map) {
721 self.flood(target, map);
722 if let Some(target) = map.find(target) {
723 self.insert_idx(target, result, map);
724 }
725 }
726
727 fn insert_idx(&mut self, target: PlaceIndex, result: PlaceOrValue, map: &Map) {
732 match result {
733 PlaceOrValue::Value(value) => self.insert_value_idx(target, value, map),
734 PlaceOrValue::Place(source) => self.insert_place_idx(target, source, map),
735 }
736 }
737
738 fn insert_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map) {
746 if let Some(target_value) = map.places[target].value_index {
750 if let Some(source_value) = map.places[source].value_index {
751 self.values[target_value] = self.values[source_value];
752 }
753 }
754 for target_child in map.children(target) {
755 let projection = map.places[target_child]
757 .proj_elem
758 .unwrap_or_else(|| tracked_span_bug!());
759 if let Some(source_child) = map.projections.get(&(source, projection)) {
760 self.insert_place_idx(target_child, *source_child, map);
761 }
762 }
763 }
764
765 fn insert_value_idx(&mut self, target: PlaceIndex, value: FlatSet<Loc>, map: &Map) {
770 if let Some(value_index) = map.places[target].value_index {
771 self.values[value_index] = value;
772 }
773 }
774
775 fn get(&self, place: mir::PlaceRef<'_>, map: &Map) -> FlatSet<Loc> {
777 map.find(place)
778 .map_or(FlatSet::TOP, |place| self.get_idx(place, map))
779 }
780
781 fn get_idx(&self, place: PlaceIndex, map: &Map) -> FlatSet<Loc> {
783 self.get_tracked_idx(place, map).unwrap_or(FlatSet::Top)
784 }
785
786 fn get_tracked_idx(&self, place: PlaceIndex, map: &Map) -> Option<FlatSet<Loc>> {
788 map.places[place].value_index.map(|v| self.values[v])
789 }
790}
791
792impl DebugWithContext<PointsToAnalysis<'_>> for State {
794 fn fmt_with(&self, ctxt: &PointsToAnalysis, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
795 debug_with_context(&self.values, None, ctxt.map, f)
796 }
797
798 fn fmt_diff_with(
799 &self,
800 old: &Self,
801 ctxt: &PointsToAnalysis,
802 f: &mut fmt::Formatter<'_>,
803 ) -> std::fmt::Result {
804 debug_with_context(&self.values, Some(&old.values), ctxt.map, f)
805 }
806}
807
808fn debug_with_context_rec<V: fmt::Debug + Eq>(
809 place: PlaceIndex,
810 place_str: &str,
811 new: &IndexSlice<ValueIndex, V>,
812 old: Option<&IndexSlice<ValueIndex, V>>,
813 map: &Map,
814 f: &mut fmt::Formatter<'_>,
815) -> std::fmt::Result {
816 if let Some(value) = map.places[place].value_index {
817 match old {
818 None => writeln!(f, "{}: {:?}", place_str, new[value])?,
819 Some(old) => {
820 if new[value] != old[value] {
821 writeln!(f, "\u{001f}-{}: {:?}", place_str, old[value])?;
822 writeln!(f, "\u{001f}+{}: {:?}", place_str, new[value])?;
823 }
824 }
825 }
826 }
827
828 for child in map.children(place) {
829 let info_elem = map.places[child]
830 .proj_elem
831 .unwrap_or_else(|| tracked_span_bug!());
832 let child_place_str = format!("{}.{}", place_str, info_elem.index());
833 debug_with_context_rec(child, &child_place_str, new, old, map, f)?;
834 }
835
836 Ok(())
837}
838
839fn debug_with_context<V: fmt::Debug + Eq>(
840 new: &IndexSlice<ValueIndex, V>,
841 old: Option<&IndexSlice<ValueIndex, V>>,
842 map: &Map,
843 f: &mut fmt::Formatter<'_>,
844) -> std::fmt::Result {
845 for (local, place) in map.locals.iter_enumerated() {
846 if let Some(place) = place {
847 debug_with_context_rec(*place, &format!("{local:?}"), new, old, map, f)?;
848 }
849 }
850 Ok(())
851}