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, Path},
27};
28use rustc_abi::FieldIdx;
29use rustc_hash::FxHashMap;
30use rustc_index::{IndexSlice, IndexVec, bit_set::DenseBitSet};
31use rustc_middle::{
32 mir::{self, BasicBlock, traversal, visit::Visitor},
33 ty,
34};
35use rustc_mir_dataflow::{
36 Analysis, JoinSemiLattice, ResultsVisitor,
37 fmt::DebugWithContext,
38 lattice::{FlatSet, HasBottom, HasTop},
39 visit_results,
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 let points_to = PointsToAnalysis::new(&map, fn_sig);
53 let results = points_to.iterate_to_fixpoint(genv.tcx(), body, None);
54 let mut visitor = CollectPointerToBorrows::new(&map, stmts, &results.entry_states);
55 for (bb, _) in traversal::reachable(body) {
56 visitor.visit_block_start(&results.entry_states[bb]);
57 visit_results(body, [bb], &results, &mut visitor);
58 }
59
60 Ok(())
61}
62
63struct PointsToAnalysis<'a> {
68 fn_sig: Option<&'a rty::EarlyBinder<rty::PolyFnSig>>,
69 map: &'a Map,
70}
71
72impl<'a> PointsToAnalysis<'a> {
73 fn new(map: &'a Map, fn_sig: Option<&'a rty::EarlyBinder<rty::PolyFnSig>>) -> Self {
74 Self { fn_sig, map }
75 }
76
77 fn handle_statement(&self, statement: &mir::Statement, state: &mut State) {
78 match &statement.kind {
79 mir::StatementKind::Assign(box (target, rvalue)) => {
80 self.handle_assign(*target, rvalue, state);
81 }
82 mir::StatementKind::StorageLive(local) | mir::StatementKind::StorageDead(local) => {
83 state.flood_with(mir::Place::from(*local).as_ref(), self.map, FlatSet::BOTTOM);
86 }
87 mir::StatementKind::Intrinsic(..)
88 | mir::StatementKind::SetDiscriminant { .. }
89 | mir::StatementKind::ConstEvalCounter
90 | mir::StatementKind::Nop
91 | mir::StatementKind::FakeRead(..)
92 | mir::StatementKind::PlaceMention(..)
93 | mir::StatementKind::Coverage(..)
94 | mir::StatementKind::AscribeUserType(..)
95 | mir::StatementKind::BackwardIncompatibleDropHint { .. } => {}
96 }
97 }
98
99 fn handle_assign(&self, target: mir::Place, rvalue: &mir::Rvalue, state: &mut State) {
100 match rvalue {
101 mir::Rvalue::Use(operand, _) => {
102 let result = self
103 .handle_operand(operand)
104 .map_or(PlaceOrValue::TOP, PlaceOrValue::Place);
105 state.assign(target.as_ref(), result, self.map);
106 }
107 mir::Rvalue::Ref(_, _, place) => {
108 let result = PlaceOrValue::Value(self.handle_ref(place, state));
109 state.assign(target.as_ref(), result, self.map);
110 }
111 mir::Rvalue::Aggregate(box mir::AggregateKind::Tuple, operands) => {
112 state.flood(target.as_ref(), self.map);
113 let Some(target_idx) = self.map.find(target.as_ref()) else { return };
114 for (elem, operand) in operands.iter_enumerated() {
115 let Some(rhs_idx) = self.handle_operand(operand) else { continue };
116 if let Some(field) = self.map.apply(target_idx, elem) {
117 state.insert_place_idx(field, rhs_idx, self.map);
118 }
119 }
120 }
121 _ => state.flood(target.as_ref(), self.map),
125 }
126 }
127
128 fn handle_ref(&self, place: &mir::Place, state: &State) -> FlatSet<Path> {
131 let mut loc = Loc::Local(place.local);
132 let mut projection = vec![];
133 for (i, elem) in place.projection.iter().enumerate() {
134 match elem {
135 mir::PlaceElem::Deref => {
136 let prefix =
138 mir::PlaceRef { local: place.local, projection: &place.projection[..i] };
139 match state.get(prefix, self.map) {
140 FlatSet::Elem(path) => {
141 loc = path.loc;
142 projection = path.projection().to_vec();
143 }
144 FlatSet::Top => return FlatSet::TOP,
145 FlatSet::Bottom => tracked_span_bug!("dereference of uninitialized place"),
147 }
148 }
149 mir::PlaceElem::Field(field, _) => projection.push(field),
150 mir::PlaceElem::Downcast(..) => {}
151 _ => return FlatSet::TOP,
152 }
153 }
154 FlatSet::Elem(Path::new(loc, projection))
155 }
156
157 fn handle_operand(&self, operand: &mir::Operand) -> Option<PlaceIndex> {
158 match operand {
159 mir::Operand::Copy(place) | mir::Operand::Move(place) => {
160 self.map.find(place.as_ref())
163 }
164 mir::Operand::Constant(..) | mir::Operand::RuntimeChecks(_) => None,
165 }
166 }
167
168 fn handle_terminator<'tcx>(&self, terminator: &mir::Terminator<'tcx>, state: &mut State) {
171 match &terminator.kind {
172 mir::TerminatorKind::TailCall { .. }
173 | mir::TerminatorKind::Call { .. }
174 | mir::TerminatorKind::InlineAsm { .. } => {
175 }
177 mir::TerminatorKind::Drop { place, .. } => {
178 state.flood_with(place.as_ref(), self.map, FlatSet::BOTTOM);
179 }
180 mir::TerminatorKind::SwitchInt { .. }
181 | mir::TerminatorKind::Yield { .. }
182 | mir::TerminatorKind::Goto { .. }
183 | mir::TerminatorKind::UnwindResume
184 | mir::TerminatorKind::UnwindTerminate(_)
185 | mir::TerminatorKind::Return
186 | mir::TerminatorKind::Unreachable
187 | mir::TerminatorKind::Assert { .. }
188 | mir::TerminatorKind::CoroutineDrop
189 | mir::TerminatorKind::FalseEdge { .. }
190 | mir::TerminatorKind::FalseUnwind { .. } => {
191 }
193 }
194 }
195
196 fn handle_call_return(&self, return_places: mir::CallReturnPlaces, state: &mut State) {
197 return_places.for_each(|place| {
198 state.flood(place.as_ref(), self.map);
199 });
200 }
201}
202
203impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for PointsToAnalysis<'_> {
204 type Domain = State;
205
206 const NAME: &'static str = "PointsToAnalysis";
207
208 fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
209 State { values: IndexVec::from_elem_n(FlatSet::BOTTOM, self.map.value_count) }
210 }
211
212 fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
213 if let Some(fn_sig) = self.fn_sig {
217 let fn_sig = fn_sig.as_ref().skip_binder().as_ref().skip_binder();
218 for (local, ty) in iter::zip(body.args_iter(), fn_sig.inputs()) {
219 if let rty::TyKind::Ptr(_, path) = ty.kind() {
220 let path = FlatSet::Elem(path.clone());
221 state.flood_with(mir::PlaceRef { local, projection: &[] }, self.map, path);
222 } else {
223 state.flood(mir::PlaceRef { local, projection: &[] }, self.map);
224 }
225 }
226 } else {
227 for local in body.args_iter() {
228 state.flood(mir::PlaceRef { local, projection: &[] }, self.map);
229 }
230 }
231 }
232
233 fn apply_primary_statement_effect(
234 &self,
235 state: &mut Self::Domain,
236 statement: &mir::Statement<'tcx>,
237 _location: mir::Location,
238 ) {
239 self.handle_statement(statement, state);
240 }
241
242 fn apply_primary_terminator_effect(
243 &self,
244 state: &mut Self::Domain,
245 terminator: &mir::Terminator<'tcx>,
246 _location: mir::Location,
247 ) {
248 self.handle_terminator(terminator, state);
249 }
250
251 fn apply_call_return_effect(
252 &self,
253 state: &mut Self::Domain,
254 _block: BasicBlock,
255 return_places: mir::CallReturnPlaces<'_, 'tcx>,
256 ) {
257 self.handle_call_return(return_places, state);
258 }
259}
260
261struct CollectPointerToBorrows<'a> {
262 map: &'a Map,
263 tracked_places: FxHashMap<PlaceIndex, flux_rustc_bridge::mir::Place>,
264 stmts: &'a mut GhostStatements,
265 before_state: Vec<(PlaceIndex, FlatSet<Path>)>,
266 results: &'a IndexSlice<BasicBlock, State>,
267}
268
269impl<'a> CollectPointerToBorrows<'a> {
270 fn new(
271 map: &'a Map,
272 stmts: &'a mut GhostStatements,
273 results: &'a IndexSlice<BasicBlock, State>,
274 ) -> Self {
275 let mut tracked_places = FxHashMap::default();
276 map.for_each_tracked_place(|place_idx, local, projection| {
277 let projection = projection
278 .iter()
279 .copied()
280 .map(flux_rustc_bridge::mir::PlaceElem::Field)
281 .collect();
282 tracked_places.insert(place_idx, flux_rustc_bridge::mir::Place::new(local, projection));
283 });
284 Self { map, tracked_places, stmts, before_state: vec![], results }
285 }
286}
287
288impl CollectPointerToBorrows<'_> {
289 fn visit_block_start(&mut self, state: &State) {
290 self.before_state.clear();
291 for place_idx in self.tracked_places.keys() {
292 let value = state.get_idx(*place_idx, self.map);
293 self.before_state.push((*place_idx, value));
294 }
295 }
296}
297
298impl<'a, 'tcx> ResultsVisitor<'tcx, PointsToAnalysis<'a>> for CollectPointerToBorrows<'_> {
299 fn visit_after_primary_statement_effect(
300 &mut self,
301 state: &State,
302 _statement: &mir::Statement<'tcx>,
303 location: mir::Location,
304 ) {
305 let point = Point::BeforeLocation(location);
306 for (place_idx, old_value) in &mut self.before_state {
307 let new_value = state.get_idx(*place_idx, self.map);
308 if let (FlatSet::Elem(_), FlatSet::Top) = (&old_value, &new_value) {
309 let place = self
310 .tracked_places
311 .get(place_idx)
312 .unwrap_or_else(|| tracked_span_bug!())
313 .clone();
314 self.stmts.insert_at(point, GhostStatement::PtrToRef(place));
315 }
316 *old_value = new_value;
317 }
318 }
319
320 fn visit_after_primary_terminator_effect(
321 &mut self,
322 _state: &State,
323 terminator: &mir::Terminator<'tcx>,
324 location: mir::Location,
325 ) {
326 let block = location.block;
327 for target in terminator.successors() {
328 let point = Point::Edge(block, target);
329 let target_state = self.results.get(target).unwrap();
330 for (place_idx, old_value) in &self.before_state {
331 let new_value = target_state.get_idx(*place_idx, self.map);
332 if let (FlatSet::Elem(_), FlatSet::Top) = (&old_value, new_value) {
333 let place = self
334 .tracked_places
335 .get(place_idx)
336 .unwrap_or_else(|| tracked_span_bug!())
337 .clone();
338 self.stmts.insert_at(point, GhostStatement::PtrToRef(place));
339 }
340 }
341 }
342 }
343}
344
345#[derive(Debug)]
352pub struct Map {
353 locals: IndexVec<mir::Local, Option<PlaceIndex>>,
354 projections: FxHashMap<(PlaceIndex, FieldIdx), PlaceIndex>,
355 places: IndexVec<PlaceIndex, PlaceInfo>,
356 value_count: usize,
357 inner_values: IndexVec<PlaceIndex, Range<usize>>,
359 inner_values_buffer: Vec<ValueIndex>,
360}
361
362impl Map {
363 fn new(body: &mir::Body) -> Self {
369 let mut map = Self {
370 locals: IndexVec::new(),
371 projections: FxHashMap::default(),
372 places: IndexVec::new(),
373 value_count: 0,
374 inner_values: IndexVec::new(),
375 inner_values_buffer: Vec::new(),
376 };
377 let exclude = excluded_locals(body);
378 map.register(body, exclude);
379 map
380 }
381
382 fn register(&mut self, body: &mir::Body, exclude: DenseBitSet<mir::Local>) {
384 let mut worklist = VecDeque::with_capacity(body.local_decls.len());
385
386 self.locals = IndexVec::from_elem(None, &body.local_decls);
388 for (local, decl) in body.local_decls.iter_enumerated() {
389 if exclude.contains(local) {
390 continue;
391 }
392
393 debug_assert!(self.locals[local].is_none());
395 let place = self.places.push(PlaceInfo::new(None));
396 self.locals[local] = Some(place);
397
398 self.register_children(place, decl.ty, &mut worklist);
400 }
401
402 while let Some((mut place, elem, ty)) = worklist.pop_front() {
404 place = *self.projections.entry((place, elem)).or_insert_with(|| {
406 let next = self.places.push(PlaceInfo::new(Some(elem)));
408 self.places[next].next_sibling = self.places[place].first_child;
409 self.places[place].first_child = Some(next);
410 next
411 });
412
413 self.register_children(place, ty, &mut worklist);
415 }
416
417 self.inner_values_buffer = Vec::with_capacity(self.value_count);
421 self.inner_values = IndexVec::from_elem(0..0, &self.places);
422 for local in body.local_decls.indices() {
423 if let Some(place) = self.locals[local] {
424 self.cache_preorder_invoke(place);
425 }
426 }
427
428 for opt_place in &mut self.locals {
430 if let Some(place) = *opt_place
431 && self.inner_values[place].is_empty()
432 {
433 *opt_place = None;
434 }
435 }
436 self.projections
437 .retain(|_, child| !self.inner_values[*child].is_empty());
438 }
439
440 fn register_children<'tcx>(
444 &mut self,
445 place: PlaceIndex,
446 ty: ty::Ty<'tcx>,
447 worklist: &mut VecDeque<(PlaceIndex, FieldIdx, ty::Ty<'tcx>)>,
448 ) {
449 assert!(self.places[place].value_index.is_none());
451 if let ty::TyKind::Ref(.., mir::Mutability::Mut) = ty.kind() {
452 self.places[place].value_index = Some(self.value_count.into());
453 self.value_count += 1;
454 }
455
456 if let ty::Tuple(list) = ty.kind() {
458 for (field, ty) in list.iter().enumerate() {
459 worklist.push_back((place, field.into(), ty));
460 }
461 }
462 }
463
464 fn cache_preorder_invoke(&mut self, root: PlaceIndex) {
467 let start = self.inner_values_buffer.len();
468 if let Some(vi) = self.places[root].value_index {
469 self.inner_values_buffer.push(vi);
470 }
471
472 let mut next_child = self.places[root].first_child;
474 while let Some(child) = next_child {
475 self.cache_preorder_invoke(child);
476 next_child = self.places[child].next_sibling;
477 }
478
479 let end = self.inner_values_buffer.len();
480 self.inner_values[root] = start..end;
481 }
482
483 fn find(&self, place: mir::PlaceRef<'_>) -> Option<PlaceIndex> {
485 let mut index = *self.locals[place.local].as_ref()?;
486
487 for &elem in place.projection {
488 let mir::ProjectionElem::Field(elem, _) = elem else { return None };
489 index = self.apply(index, elem)?;
490 }
491
492 Some(index)
493 }
494
495 fn children(&self, parent: PlaceIndex) -> impl Iterator<Item = PlaceIndex> + '_ {
497 Children::new(self, parent)
498 }
499
500 fn apply(&self, place: PlaceIndex, elem: FieldIdx) -> Option<PlaceIndex> {
502 self.projections.get(&(place, elem)).copied()
503 }
504
505 fn for_each_aliasing_place(&self, place: mir::PlaceRef<'_>, f: &mut impl FnMut(ValueIndex)) {
507 let Some(mut index) = self.locals[place.local] else {
508 return;
510 };
511 for elem in place.projection {
512 let mir::ProjectionElem::Field(elem, _) = *elem else {
513 return;
514 };
515 if let Some(vi) = self.places[index].value_index {
517 f(vi);
518 }
519
520 let Some(sub) = self.apply(index, elem) else {
521 return;
522 };
523 index = sub;
524 }
525 self.for_each_value_inside(index, f);
526 }
527
528 fn for_each_value_inside(&self, root: PlaceIndex, f: &mut impl FnMut(ValueIndex)) {
530 let range = self.inner_values[root].clone();
531 let values = &self.inner_values_buffer[range];
532 for &v in values {
533 f(v);
534 }
535 }
536
537 fn for_each_tracked_place(&self, mut f: impl FnMut(PlaceIndex, mir::Local, &[FieldIdx])) {
538 let mut projection = Vec::new();
539 for (local, place) in self.locals.iter_enumerated() {
540 if let Some(place) = place {
541 self.for_each_tracked_place_rec(
542 *place,
543 &mut projection,
544 &mut |place, projection| {
545 f(place, local, projection);
546 },
547 );
548 }
549 }
550 }
551
552 fn for_each_tracked_place_rec(
553 &self,
554 root: PlaceIndex,
555 projection: &mut Vec<FieldIdx>,
556 f: &mut impl FnMut(PlaceIndex, &[FieldIdx]),
557 ) {
558 if self.inner_values[root].is_empty() {
560 return;
561 }
562
563 if self.places[root].value_index.is_some() {
564 f(root, projection);
565 }
566
567 for child in self.children(root) {
568 let elem = self.places[child]
569 .proj_elem
570 .unwrap_or_else(|| tracked_span_bug!());
571 projection.push(elem);
572 self.for_each_tracked_place_rec(child, projection, f);
573 projection.pop();
574 }
575 }
576}
577
578#[derive(Debug)]
583struct PlaceInfo {
584 value_index: Option<ValueIndex>,
586
587 proj_elem: Option<FieldIdx>,
589
590 first_child: Option<PlaceIndex>,
592
593 next_sibling: Option<PlaceIndex>,
595}
596
597impl PlaceInfo {
598 fn new(proj_elem: Option<FieldIdx>) -> Self {
599 Self { next_sibling: None, proj_elem, first_child: None, value_index: None }
600 }
601}
602
603struct Children<'a> {
604 map: &'a Map,
605 next: Option<PlaceIndex>,
606}
607
608impl<'a> Children<'a> {
609 fn new(map: &'a Map, parent: PlaceIndex) -> Self {
610 Self { map, next: map.places[parent].first_child }
611 }
612}
613
614impl Iterator for Children<'_> {
615 type Item = PlaceIndex;
616
617 fn next(&mut self) -> Option<Self::Item> {
618 match self.next {
619 Some(child) => {
620 self.next = self.map.places[child].next_sibling;
621 Some(child)
622 }
623 None => None,
624 }
625 }
626}
627
628fn excluded_locals(body: &mir::Body<'_>) -> DenseBitSet<mir::Local> {
630 struct Collector {
631 result: DenseBitSet<mir::Local>,
632 }
633
634 impl<'tcx> mir::visit::Visitor<'tcx> for Collector {
635 fn visit_place(
636 &mut self,
637 place: &mir::Place<'tcx>,
638 context: mir::visit::PlaceContext,
639 _location: mir::Location,
640 ) {
641 if (context.is_borrow()
642 || context.is_address_of()
643 || context.is_drop()
644 || context
645 == mir::visit::PlaceContext::MutatingUse(
646 mir::visit::MutatingUseContext::AsmOutput,
647 ))
648 && !place.is_indirect()
649 {
650 self.result.insert(place.local);
653 }
654 }
655 }
656
657 let mut collector = Collector { result: DenseBitSet::new_empty(body.local_decls.len()) };
658 collector.visit_body(body);
659 collector.result
660}
661
662rustc_index::newtype_index!(
663 struct PlaceIndex {}
668);
669
670rustc_index::newtype_index!(
671 struct ValueIndex {}
675);
676
677enum PlaceOrValue {
679 Value(FlatSet<Path>),
680 Place(PlaceIndex),
681}
682
683impl PlaceOrValue {
684 const TOP: Self = PlaceOrValue::Value(FlatSet::TOP);
685}
686
687#[derive(PartialEq, Eq, Debug)]
698struct State {
699 values: IndexVec<ValueIndex, FlatSet<Path>>,
700}
701
702impl Clone for State {
703 fn clone(&self) -> Self {
704 Self { values: self.values.clone() }
705 }
706
707 fn clone_from(&mut self, source: &Self) {
708 self.values.clone_from(&source.values);
709 }
710}
711
712impl JoinSemiLattice for State {
713 fn join(&mut self, other: &Self) -> bool {
714 assert_eq!(self.values.len(), other.values.len());
715 let mut changed = false;
716 for (a, b) in iter::zip(&mut self.values, &other.values) {
717 changed |= a.join(b);
718 }
719 changed
720 }
721}
722
723impl State {
724 fn flood(&mut self, place: mir::PlaceRef<'_>, map: &Map) {
725 self.flood_with(place, map, FlatSet::TOP);
726 }
727
728 fn flood_with(&mut self, place: mir::PlaceRef<'_>, map: &Map, value: FlatSet<Path>) {
729 map.for_each_aliasing_place(place, &mut |vi| {
730 self.values[vi] = value.clone();
731 });
732 }
733
734 fn assign(&mut self, target: mir::PlaceRef<'_>, result: PlaceOrValue, map: &Map) {
736 self.flood(target, map);
737 if let Some(target) = map.find(target) {
738 self.insert_idx(target, result, map);
739 }
740 }
741
742 fn insert_idx(&mut self, target: PlaceIndex, result: PlaceOrValue, map: &Map) {
747 match result {
748 PlaceOrValue::Value(value) => self.insert_value_idx(target, value, map),
749 PlaceOrValue::Place(source) => self.insert_place_idx(target, source, map),
750 }
751 }
752
753 fn insert_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map) {
761 if let Some(target_value) = map.places[target].value_index
765 && let Some(source_value) = map.places[source].value_index
766 {
767 self.values[target_value] = self.values[source_value].clone();
768 }
769 for target_child in map.children(target) {
770 let projection = map.places[target_child]
772 .proj_elem
773 .unwrap_or_else(|| tracked_span_bug!());
774 if let Some(source_child) = map.projections.get(&(source, projection)) {
775 self.insert_place_idx(target_child, *source_child, map);
776 }
777 }
778 }
779
780 fn insert_value_idx(&mut self, target: PlaceIndex, value: FlatSet<Path>, map: &Map) {
785 if let Some(value_index) = map.places[target].value_index {
786 self.values[value_index] = value;
787 }
788 }
789
790 fn get(&self, place: mir::PlaceRef<'_>, map: &Map) -> FlatSet<Path> {
792 map.find(place)
793 .map_or(FlatSet::TOP, |place| self.get_idx(place, map))
794 }
795
796 fn get_idx(&self, place: PlaceIndex, map: &Map) -> FlatSet<Path> {
798 self.get_tracked_idx(place, map).unwrap_or(FlatSet::Top)
799 }
800
801 fn get_tracked_idx(&self, place: PlaceIndex, map: &Map) -> Option<FlatSet<Path>> {
803 map.places[place]
804 .value_index
805 .map(|v| self.values[v].clone())
806 }
807}
808
809impl DebugWithContext<PointsToAnalysis<'_>> for State {
811 fn fmt_with(&self, ctxt: &PointsToAnalysis, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
812 debug_with_context(&self.values, None, ctxt.map, f)
813 }
814
815 fn fmt_diff_with(
816 &self,
817 old: &Self,
818 ctxt: &PointsToAnalysis,
819 f: &mut fmt::Formatter<'_>,
820 ) -> std::fmt::Result {
821 debug_with_context(&self.values, Some(&old.values), ctxt.map, f)
822 }
823}
824
825fn debug_with_context_rec<V: fmt::Debug + Eq>(
826 place: PlaceIndex,
827 place_str: &str,
828 new: &IndexSlice<ValueIndex, V>,
829 old: Option<&IndexSlice<ValueIndex, V>>,
830 map: &Map,
831 f: &mut fmt::Formatter<'_>,
832) -> std::fmt::Result {
833 if let Some(value) = map.places[place].value_index {
834 match old {
835 None => writeln!(f, "{}: {:?}", place_str, new[value])?,
836 Some(old) => {
837 if new[value] != old[value] {
838 writeln!(f, "\u{001f}-{}: {:?}", place_str, old[value])?;
839 writeln!(f, "\u{001f}+{}: {:?}", place_str, new[value])?;
840 }
841 }
842 }
843 }
844
845 for child in map.children(place) {
846 let info_elem = map.places[child]
847 .proj_elem
848 .unwrap_or_else(|| tracked_span_bug!());
849 let child_place_str = format!("{}.{}", place_str, info_elem.index());
850 debug_with_context_rec(child, &child_place_str, new, old, map, f)?;
851 }
852
853 Ok(())
854}
855
856fn debug_with_context<V: fmt::Debug + Eq>(
857 new: &IndexSlice<ValueIndex, V>,
858 old: Option<&IndexSlice<ValueIndex, V>>,
859 map: &Map,
860 f: &mut fmt::Formatter<'_>,
861) -> std::fmt::Result {
862 for (local, place) in map.locals.iter_enumerated() {
863 if let Some(place) = place {
864 debug_with_context_rec(*place, &format!("{local:?}"), new, old, map, f)?;
865 }
866 }
867 Ok(())
868}