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_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 _ => state.flood(target.as_ref(), self.map),
124 }
125 }
126
127 fn handle_ref(&self, place: &mir::Place, state: &State) -> FlatSet<Path> {
130 let mut loc = Loc::Local(place.local);
131 let mut projection = vec![];
132 for (i, elem) in place.projection.iter().enumerate() {
133 match elem {
134 mir::PlaceElem::Deref => {
135 let prefix =
137 mir::PlaceRef { local: place.local, projection: &place.projection[..i] };
138 match state.get(prefix, self.map) {
139 FlatSet::Elem(path) => {
140 loc = path.loc;
141 projection = path.projection().to_vec();
142 }
143 FlatSet::Top => return FlatSet::TOP,
144 FlatSet::Bottom => tracked_span_bug!("dereference of uninitialized place"),
146 }
147 }
148 mir::PlaceElem::Field(field, _) => projection.push(field),
149 mir::PlaceElem::Downcast(..) => {}
150 _ => return FlatSet::TOP,
151 }
152 }
153 FlatSet::Elem(Path::new(loc, projection))
154 }
155
156 fn handle_operand(&self, operand: &mir::Operand) -> Option<PlaceIndex> {
157 match operand {
158 mir::Operand::Copy(place) | mir::Operand::Move(place) => {
159 self.map.find(place.as_ref())
162 }
163 mir::Operand::Constant(..) | mir::Operand::RuntimeChecks(_) => None,
164 }
165 }
166
167 fn handle_terminator<'mir, 'tcx>(
170 &self,
171 terminator: &'mir mir::Terminator<'tcx>,
172 state: &mut State,
173 ) -> TerminatorEdges<'mir, 'tcx> {
174 match &terminator.kind {
175 mir::TerminatorKind::TailCall { .. }
176 | mir::TerminatorKind::Call { .. }
177 | mir::TerminatorKind::InlineAsm { .. } => {
178 }
180 mir::TerminatorKind::Drop { place, .. } => {
181 state.flood_with(place.as_ref(), self.map, FlatSet::BOTTOM);
182 }
183 mir::TerminatorKind::SwitchInt { .. }
184 | mir::TerminatorKind::Yield { .. }
185 | mir::TerminatorKind::Goto { .. }
186 | mir::TerminatorKind::UnwindResume
187 | mir::TerminatorKind::UnwindTerminate(_)
188 | mir::TerminatorKind::Return
189 | mir::TerminatorKind::Unreachable
190 | mir::TerminatorKind::Assert { .. }
191 | mir::TerminatorKind::CoroutineDrop
192 | mir::TerminatorKind::FalseEdge { .. }
193 | mir::TerminatorKind::FalseUnwind { .. } => {
194 }
196 }
197 terminator.edges()
198 }
199
200 fn handle_call_return(&self, return_places: mir::CallReturnPlaces, state: &mut State) {
201 return_places.for_each(|place| {
202 state.flood(place.as_ref(), self.map);
203 });
204 }
205}
206
207impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for PointsToAnalysis<'_> {
208 type Domain = State;
209
210 const NAME: &'static str = "PointsToAnalysis";
211
212 fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
213 State { values: IndexVec::from_elem_n(FlatSet::BOTTOM, self.map.value_count) }
214 }
215
216 fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
217 if let Some(fn_sig) = self.fn_sig {
221 let fn_sig = fn_sig.as_ref().skip_binder().as_ref().skip_binder();
222 for (local, ty) in iter::zip(body.args_iter(), fn_sig.inputs()) {
223 if let rty::TyKind::Ptr(_, path) = ty.kind() {
224 let path = FlatSet::Elem(path.clone());
225 state.flood_with(mir::PlaceRef { local, projection: &[] }, self.map, path);
226 } else {
227 state.flood(mir::PlaceRef { local, projection: &[] }, self.map);
228 }
229 }
230 } else {
231 for local in body.args_iter() {
232 state.flood(mir::PlaceRef { local, projection: &[] }, self.map);
233 }
234 }
235 }
236
237 fn apply_primary_statement_effect(
238 &self,
239 state: &mut Self::Domain,
240 statement: &mir::Statement<'tcx>,
241 _location: mir::Location,
242 ) {
243 self.handle_statement(statement, state);
244 }
245
246 fn apply_primary_terminator_effect<'mir>(
247 &self,
248 state: &mut Self::Domain,
249 terminator: &'mir mir::Terminator<'tcx>,
250 _location: mir::Location,
251 ) -> mir::TerminatorEdges<'mir, 'tcx> {
252 self.handle_terminator(terminator, state)
253 }
254
255 fn apply_call_return_effect(
256 &self,
257 state: &mut Self::Domain,
258 _block: BasicBlock,
259 return_places: mir::CallReturnPlaces<'_, 'tcx>,
260 ) {
261 self.handle_call_return(return_places, state);
262 }
263}
264
265struct CollectPointerToBorrows<'a> {
266 map: &'a Map,
267 tracked_places: FxHashMap<PlaceIndex, flux_rustc_bridge::mir::Place>,
268 stmts: &'a mut GhostStatements,
269 before_state: Vec<(PlaceIndex, FlatSet<Path>)>,
270 results: &'a IndexSlice<BasicBlock, State>,
271}
272
273impl<'a> CollectPointerToBorrows<'a> {
274 fn new(
275 map: &'a Map,
276 stmts: &'a mut GhostStatements,
277 results: &'a IndexSlice<BasicBlock, State>,
278 ) -> Self {
279 let mut tracked_places = FxHashMap::default();
280 map.for_each_tracked_place(|place_idx, local, projection| {
281 let projection = projection
282 .iter()
283 .copied()
284 .map(flux_rustc_bridge::mir::PlaceElem::Field)
285 .collect();
286 tracked_places.insert(place_idx, flux_rustc_bridge::mir::Place::new(local, projection));
287 });
288 Self { map, tracked_places, stmts, before_state: vec![], results }
289 }
290}
291
292impl<'a, 'tcx> ResultsVisitor<'tcx, PointsToAnalysis<'a>> for CollectPointerToBorrows<'_> {
293 fn visit_block_start(&mut self, state: &State) {
294 self.before_state.clear();
295 for place_idx in self.tracked_places.keys() {
296 let value = state.get_idx(*place_idx, self.map);
297 self.before_state.push((*place_idx, value));
298 }
299 }
300
301 fn visit_after_primary_statement_effect(
302 &mut self,
303 _analysis: &PointsToAnalysis<'a>,
304 state: &State,
305 _statement: &mir::Statement<'tcx>,
306 location: mir::Location,
307 ) {
308 let point = Point::BeforeLocation(location);
309 for (place_idx, old_value) in &mut self.before_state {
310 let new_value = state.get_idx(*place_idx, self.map);
311 if let (FlatSet::Elem(_), FlatSet::Top) = (&old_value, &new_value) {
312 let place = self
313 .tracked_places
314 .get(place_idx)
315 .unwrap_or_else(|| tracked_span_bug!())
316 .clone();
317 self.stmts.insert_at(point, GhostStatement::PtrToRef(place));
318 }
319 *old_value = new_value;
320 }
321 }
322
323 fn visit_after_primary_terminator_effect(
324 &mut self,
325 _analysis: &PointsToAnalysis<'a>,
326 _state: &State,
327 terminator: &mir::Terminator<'tcx>,
328 location: mir::Location,
329 ) {
330 let block = location.block;
331 for target in terminator.successors() {
332 let point = Point::Edge(block, target);
333 let target_state = self.results.get(target).unwrap();
334 for (place_idx, old_value) in &self.before_state {
335 let new_value = target_state.get_idx(*place_idx, self.map);
336 if let (FlatSet::Elem(_), FlatSet::Top) = (&old_value, new_value) {
337 let place = self
338 .tracked_places
339 .get(place_idx)
340 .unwrap_or_else(|| tracked_span_bug!())
341 .clone();
342 self.stmts.insert_at(point, GhostStatement::PtrToRef(place));
343 }
344 }
345 }
346 }
347}
348
349#[derive(Debug)]
356pub struct Map {
357 locals: IndexVec<mir::Local, Option<PlaceIndex>>,
358 projections: FxHashMap<(PlaceIndex, FieldIdx), PlaceIndex>,
359 places: IndexVec<PlaceIndex, PlaceInfo>,
360 value_count: usize,
361 inner_values: IndexVec<PlaceIndex, Range<usize>>,
363 inner_values_buffer: Vec<ValueIndex>,
364}
365
366impl Map {
367 fn new(body: &mir::Body) -> Self {
373 let mut map = Self {
374 locals: IndexVec::new(),
375 projections: FxHashMap::default(),
376 places: IndexVec::new(),
377 value_count: 0,
378 inner_values: IndexVec::new(),
379 inner_values_buffer: Vec::new(),
380 };
381 let exclude = excluded_locals(body);
382 map.register(body, exclude);
383 map
384 }
385
386 fn register(&mut self, body: &mir::Body, exclude: DenseBitSet<mir::Local>) {
388 let mut worklist = VecDeque::with_capacity(body.local_decls.len());
389
390 self.locals = IndexVec::from_elem(None, &body.local_decls);
392 for (local, decl) in body.local_decls.iter_enumerated() {
393 if exclude.contains(local) {
394 continue;
395 }
396
397 debug_assert!(self.locals[local].is_none());
399 let place = self.places.push(PlaceInfo::new(None));
400 self.locals[local] = Some(place);
401
402 self.register_children(place, decl.ty, &mut worklist);
404 }
405
406 while let Some((mut place, elem, ty)) = worklist.pop_front() {
408 place = *self.projections.entry((place, elem)).or_insert_with(|| {
410 let next = self.places.push(PlaceInfo::new(Some(elem)));
412 self.places[next].next_sibling = self.places[place].first_child;
413 self.places[place].first_child = Some(next);
414 next
415 });
416
417 self.register_children(place, ty, &mut worklist);
419 }
420
421 self.inner_values_buffer = Vec::with_capacity(self.value_count);
425 self.inner_values = IndexVec::from_elem(0..0, &self.places);
426 for local in body.local_decls.indices() {
427 if let Some(place) = self.locals[local] {
428 self.cache_preorder_invoke(place);
429 }
430 }
431
432 for opt_place in &mut self.locals {
434 if let Some(place) = *opt_place
435 && self.inner_values[place].is_empty()
436 {
437 *opt_place = None;
438 }
439 }
440 self.projections
441 .retain(|_, child| !self.inner_values[*child].is_empty());
442 }
443
444 fn register_children<'tcx>(
448 &mut self,
449 place: PlaceIndex,
450 ty: ty::Ty<'tcx>,
451 worklist: &mut VecDeque<(PlaceIndex, FieldIdx, ty::Ty<'tcx>)>,
452 ) {
453 assert!(self.places[place].value_index.is_none());
455 if let ty::TyKind::Ref(.., mir::Mutability::Mut) = ty.kind() {
456 self.places[place].value_index = Some(self.value_count.into());
457 self.value_count += 1;
458 }
459
460 if let ty::Tuple(list) = ty.kind() {
462 for (field, ty) in list.iter().enumerate() {
463 worklist.push_back((place, field.into(), ty));
464 }
465 }
466 }
467
468 fn cache_preorder_invoke(&mut self, root: PlaceIndex) {
471 let start = self.inner_values_buffer.len();
472 if let Some(vi) = self.places[root].value_index {
473 self.inner_values_buffer.push(vi);
474 }
475
476 let mut next_child = self.places[root].first_child;
478 while let Some(child) = next_child {
479 ensure_sufficient_stack(|| self.cache_preorder_invoke(child));
480 next_child = self.places[child].next_sibling;
481 }
482
483 let end = self.inner_values_buffer.len();
484 self.inner_values[root] = start..end;
485 }
486
487 fn find(&self, place: mir::PlaceRef<'_>) -> Option<PlaceIndex> {
489 let mut index = *self.locals[place.local].as_ref()?;
490
491 for &elem in place.projection {
492 let mir::ProjectionElem::Field(elem, _) = elem else { return None };
493 index = self.apply(index, elem)?;
494 }
495
496 Some(index)
497 }
498
499 fn children(&self, parent: PlaceIndex) -> impl Iterator<Item = PlaceIndex> + '_ {
501 Children::new(self, parent)
502 }
503
504 fn apply(&self, place: PlaceIndex, elem: FieldIdx) -> Option<PlaceIndex> {
506 self.projections.get(&(place, elem)).copied()
507 }
508
509 fn for_each_aliasing_place(&self, place: mir::PlaceRef<'_>, f: &mut impl FnMut(ValueIndex)) {
511 let Some(mut index) = self.locals[place.local] else {
512 return;
514 };
515 for elem in place.projection {
516 let mir::ProjectionElem::Field(elem, _) = *elem else {
517 return;
518 };
519 if let Some(vi) = self.places[index].value_index {
521 f(vi);
522 }
523
524 let Some(sub) = self.apply(index, elem) else {
525 return;
526 };
527 index = sub;
528 }
529 self.for_each_value_inside(index, f);
530 }
531
532 fn for_each_value_inside(&self, root: PlaceIndex, f: &mut impl FnMut(ValueIndex)) {
534 let range = self.inner_values[root].clone();
535 let values = &self.inner_values_buffer[range];
536 for &v in values {
537 f(v);
538 }
539 }
540
541 fn for_each_tracked_place(&self, mut f: impl FnMut(PlaceIndex, mir::Local, &[FieldIdx])) {
542 let mut projection = Vec::new();
543 for (local, place) in self.locals.iter_enumerated() {
544 if let Some(place) = place {
545 self.for_each_tracked_place_rec(
546 *place,
547 &mut projection,
548 &mut |place, projection| {
549 f(place, local, projection);
550 },
551 );
552 }
553 }
554 }
555
556 fn for_each_tracked_place_rec(
557 &self,
558 root: PlaceIndex,
559 projection: &mut Vec<FieldIdx>,
560 f: &mut impl FnMut(PlaceIndex, &[FieldIdx]),
561 ) {
562 if self.inner_values[root].is_empty() {
564 return;
565 }
566
567 if self.places[root].value_index.is_some() {
568 f(root, projection);
569 }
570
571 for child in self.children(root) {
572 let elem = self.places[child]
573 .proj_elem
574 .unwrap_or_else(|| tracked_span_bug!());
575 projection.push(elem);
576 self.for_each_tracked_place_rec(child, projection, f);
577 projection.pop();
578 }
579 }
580}
581
582#[derive(Debug)]
587struct PlaceInfo {
588 value_index: Option<ValueIndex>,
590
591 proj_elem: Option<FieldIdx>,
593
594 first_child: Option<PlaceIndex>,
596
597 next_sibling: Option<PlaceIndex>,
599}
600
601impl PlaceInfo {
602 fn new(proj_elem: Option<FieldIdx>) -> Self {
603 Self { next_sibling: None, proj_elem, first_child: None, value_index: None }
604 }
605}
606
607struct Children<'a> {
608 map: &'a Map,
609 next: Option<PlaceIndex>,
610}
611
612impl<'a> Children<'a> {
613 fn new(map: &'a Map, parent: PlaceIndex) -> Self {
614 Self { map, next: map.places[parent].first_child }
615 }
616}
617
618impl Iterator for Children<'_> {
619 type Item = PlaceIndex;
620
621 fn next(&mut self) -> Option<Self::Item> {
622 match self.next {
623 Some(child) => {
624 self.next = self.map.places[child].next_sibling;
625 Some(child)
626 }
627 None => None,
628 }
629 }
630}
631
632fn excluded_locals(body: &mir::Body<'_>) -> DenseBitSet<mir::Local> {
634 struct Collector {
635 result: DenseBitSet<mir::Local>,
636 }
637
638 impl<'tcx> mir::visit::Visitor<'tcx> for Collector {
639 fn visit_place(
640 &mut self,
641 place: &mir::Place<'tcx>,
642 context: mir::visit::PlaceContext,
643 _location: mir::Location,
644 ) {
645 if (context.is_borrow()
646 || context.is_address_of()
647 || context.is_drop()
648 || context
649 == mir::visit::PlaceContext::MutatingUse(
650 mir::visit::MutatingUseContext::AsmOutput,
651 ))
652 && !place.is_indirect()
653 {
654 self.result.insert(place.local);
657 }
658 }
659 }
660
661 let mut collector = Collector { result: DenseBitSet::new_empty(body.local_decls.len()) };
662 collector.visit_body(body);
663 collector.result
664}
665
666rustc_index::newtype_index!(
667 struct PlaceIndex {}
672);
673
674rustc_index::newtype_index!(
675 struct ValueIndex {}
679);
680
681enum PlaceOrValue {
683 Value(FlatSet<Path>),
684 Place(PlaceIndex),
685}
686
687impl PlaceOrValue {
688 const TOP: Self = PlaceOrValue::Value(FlatSet::TOP);
689}
690
691#[derive(PartialEq, Eq, Debug)]
702struct State {
703 values: IndexVec<ValueIndex, FlatSet<Path>>,
704}
705
706impl Clone for State {
707 fn clone(&self) -> Self {
708 Self { values: self.values.clone() }
709 }
710
711 fn clone_from(&mut self, source: &Self) {
712 self.values.clone_from(&source.values);
713 }
714}
715
716impl JoinSemiLattice for State {
717 fn join(&mut self, other: &Self) -> bool {
718 assert_eq!(self.values.len(), other.values.len());
719 let mut changed = false;
720 for (a, b) in iter::zip(&mut self.values, &other.values) {
721 changed |= a.join(b);
722 }
723 changed
724 }
725}
726
727impl State {
728 fn flood(&mut self, place: mir::PlaceRef<'_>, map: &Map) {
729 self.flood_with(place, map, FlatSet::TOP);
730 }
731
732 fn flood_with(&mut self, place: mir::PlaceRef<'_>, map: &Map, value: FlatSet<Path>) {
733 map.for_each_aliasing_place(place, &mut |vi| {
734 self.values[vi] = value.clone();
735 });
736 }
737
738 fn assign(&mut self, target: mir::PlaceRef<'_>, result: PlaceOrValue, map: &Map) {
740 self.flood(target, map);
741 if let Some(target) = map.find(target) {
742 self.insert_idx(target, result, map);
743 }
744 }
745
746 fn insert_idx(&mut self, target: PlaceIndex, result: PlaceOrValue, map: &Map) {
751 match result {
752 PlaceOrValue::Value(value) => self.insert_value_idx(target, value, map),
753 PlaceOrValue::Place(source) => self.insert_place_idx(target, source, map),
754 }
755 }
756
757 fn insert_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map) {
765 if let Some(target_value) = map.places[target].value_index
769 && let Some(source_value) = map.places[source].value_index
770 {
771 self.values[target_value] = self.values[source_value].clone();
772 }
773 for target_child in map.children(target) {
774 let projection = map.places[target_child]
776 .proj_elem
777 .unwrap_or_else(|| tracked_span_bug!());
778 if let Some(source_child) = map.projections.get(&(source, projection)) {
779 self.insert_place_idx(target_child, *source_child, map);
780 }
781 }
782 }
783
784 fn insert_value_idx(&mut self, target: PlaceIndex, value: FlatSet<Path>, map: &Map) {
789 if let Some(value_index) = map.places[target].value_index {
790 self.values[value_index] = value;
791 }
792 }
793
794 fn get(&self, place: mir::PlaceRef<'_>, map: &Map) -> FlatSet<Path> {
796 map.find(place)
797 .map_or(FlatSet::TOP, |place| self.get_idx(place, map))
798 }
799
800 fn get_idx(&self, place: PlaceIndex, map: &Map) -> FlatSet<Path> {
802 self.get_tracked_idx(place, map).unwrap_or(FlatSet::Top)
803 }
804
805 fn get_tracked_idx(&self, place: PlaceIndex, map: &Map) -> Option<FlatSet<Path>> {
807 map.places[place]
808 .value_index
809 .map(|v| self.values[v].clone())
810 }
811}
812
813impl DebugWithContext<PointsToAnalysis<'_>> for State {
815 fn fmt_with(&self, ctxt: &PointsToAnalysis, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
816 debug_with_context(&self.values, None, ctxt.map, f)
817 }
818
819 fn fmt_diff_with(
820 &self,
821 old: &Self,
822 ctxt: &PointsToAnalysis,
823 f: &mut fmt::Formatter<'_>,
824 ) -> std::fmt::Result {
825 debug_with_context(&self.values, Some(&old.values), ctxt.map, f)
826 }
827}
828
829fn debug_with_context_rec<V: fmt::Debug + Eq>(
830 place: PlaceIndex,
831 place_str: &str,
832 new: &IndexSlice<ValueIndex, V>,
833 old: Option<&IndexSlice<ValueIndex, V>>,
834 map: &Map,
835 f: &mut fmt::Formatter<'_>,
836) -> std::fmt::Result {
837 if let Some(value) = map.places[place].value_index {
838 match old {
839 None => writeln!(f, "{}: {:?}", place_str, new[value])?,
840 Some(old) => {
841 if new[value] != old[value] {
842 writeln!(f, "\u{001f}-{}: {:?}", place_str, old[value])?;
843 writeln!(f, "\u{001f}+{}: {:?}", place_str, new[value])?;
844 }
845 }
846 }
847 }
848
849 for child in map.children(place) {
850 let info_elem = map.places[child]
851 .proj_elem
852 .unwrap_or_else(|| tracked_span_bug!());
853 let child_place_str = format!("{}.{}", place_str, info_elem.index());
854 debug_with_context_rec(child, &child_place_str, new, old, map, f)?;
855 }
856
857 Ok(())
858}
859
860fn debug_with_context<V: fmt::Debug + Eq>(
861 new: &IndexSlice<ValueIndex, V>,
862 old: Option<&IndexSlice<ValueIndex, V>>,
863 map: &Map,
864 f: &mut fmt::Formatter<'_>,
865) -> std::fmt::Result {
866 for (local, place) in map.locals.iter_enumerated() {
867 if let Some(place) = place {
868 debug_with_context_rec(*place, &format!("{local:?}"), new, old, map, f)?;
869 }
870 }
871 Ok(())
872}