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