1use std::assert_matches;
6use std::fmt::Formatter;
7
8use rustc_abi::{BackendRepr, FIRST_VARIANT, FieldIdx, Size, VariantIdx};
9use rustc_const_eval::const_eval::{DummyMachine, throw_machine_stop_str};
10use rustc_const_eval::interpret::{
11 ImmTy, Immediate, InterpCx, OpTy, PlaceTy, Projectable, interp_ok,
12};
13use rustc_data_structures::fx::FxHashMap;
14use rustc_hir::def::DefKind;
15use rustc_middle::mir::interpret::{InterpResult, Scalar};
16use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
17use rustc_middle::mir::*;
18use rustc_middle::ty::{self, Ty, TyCtxt};
19use rustc_mir_dataflow::fmt::DebugWithContext;
20use rustc_mir_dataflow::lattice::{FlatSet, HasBottom};
21use rustc_mir_dataflow::value_analysis::{
22 Map, PlaceCollectionMode, PlaceIndex, State, TrackElem, ValueOrPlace, debug_with_context,
23};
24use rustc_mir_dataflow::{Analysis, ResultsVisitor, visit_results};
25use rustc_span::{DUMMY_SP, bug};
26use tracing::{debug, debug_span, instrument};
27
28use crate::PassPolicy;
29
30const BLOCK_LIMIT: usize = 100;
33const PLACE_LIMIT: usize = 100;
34
35pub(super) struct DataflowConstProp;
36
37impl<'tcx> crate::MirPass<'tcx> for DataflowConstProp {
38 fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy {
39 PassPolicy::optional(ctx.mir_opt_level() >= 3)
40 }
41
42 #[instrument(skip_all level = "debug")]
43 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
44 if body.coroutine.is_some() {
46 return;
47 }
48
49 debug!(def_id = ?body.source.def_id());
50 if tcx.sess.mir_opt_level() < 4 && body.basic_blocks.len() > BLOCK_LIMIT {
51 debug!("aborted dataflow const prop due too many basic blocks");
52 return;
53 }
54
55 let value_limit = if tcx.sess.mir_opt_level() < 4 { Some(PLACE_LIMIT) } else { None };
64
65 let map = Map::new(tcx, body, PlaceCollectionMode::Full { value_limit });
67
68 let const_ = debug_span!("analyze")
70 .in_scope(|| ConstAnalysis::new(tcx, body, map).iterate_to_fixpoint(tcx, body, None));
71
72 let mut visitor = Collector::new(tcx, body, &const_.analysis.map);
74 debug_span!("collect").in_scope(|| {
75 visit_results(body, traversal::reachable(body).map(|(bb, _)| bb), &const_, &mut visitor)
76 });
77 let mut patch = visitor.patch;
78 debug_span!("patch").in_scope(|| patch.visit_body_preserves_cfg(body));
79 }
80}
81
82struct ConstAnalysis<'a, 'tcx> {
87 map: Map<'tcx>,
88 tcx: TyCtxt<'tcx>,
89 local_decls: &'a LocalDecls<'tcx>,
90 ecx: InterpCx<'tcx, DummyMachine>,
91 typing_env: ty::TypingEnv<'tcx>,
92}
93
94impl<'tcx> Analysis<'tcx> for ConstAnalysis<'_, 'tcx> {
95 type Domain = State<FlatSet<Scalar>>;
96
97 const NAME: &'static str = "ConstAnalysis";
98
99 fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
103 State::Unreachable
104 }
105
106 fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
107 assert_matches!(state, State::Unreachable);
109 *state = State::new_reachable();
110 for arg in body.args_iter() {
111 state.flood(PlaceRef { local: arg, projection: &[] }, &self.map);
112 }
113 }
114
115 fn apply_primary_statement_effect(
116 &self,
117 state: &mut Self::Domain,
118 statement: &Statement<'tcx>,
119 _location: Location,
120 ) {
121 if state.is_reachable() {
122 match &statement.kind {
123 StatementKind::Assign((place, rvalue)) => {
124 self.handle_assign(*place, rvalue, state);
125 }
126 StatementKind::SetDiscriminant { place, variant_index } => {
127 let place = (**place).as_ref();
128 state.flood_discr(place, &self.map);
129 if let Some(target) = self.map.find_discr(place) {
130 let enum_ty = place.ty(self.local_decls, self.tcx).ty;
131 if let Some(discr) = self.eval_discriminant(enum_ty, *variant_index) {
132 state.insert_value_idx(target, FlatSet::Elem(discr), &self.map);
133 }
134 }
135 }
136 StatementKind::Intrinsic(intrinsic) => {
137 match intrinsic {
138 NonDivergingIntrinsic::Assume(..) => {
139 }
141 NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
142 dst: _,
143 src: _,
144 count: _,
145 }) => {
146 }
148 }
149 }
150 StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
151 state.flood_with(
154 Place::from(*local).as_ref(),
155 &self.map,
156 FlatSet::<Scalar>::BOTTOM,
157 );
158 }
159 StatementKind::ConstEvalCounter
160 | StatementKind::Nop
161 | StatementKind::FakeRead(..)
162 | StatementKind::PlaceMention(..)
163 | StatementKind::Coverage(..)
164 | StatementKind::BackwardIncompatibleDropHint { .. }
165 | StatementKind::AscribeUserType(..) => {}
166 }
167 }
168 }
169
170 fn get_terminator_edges<'mir>(
171 &self,
172 state: &Self::Domain,
173 terminator: &'mir Terminator<'tcx>,
174 _location: Location,
175 ) -> TerminatorEdges<'mir, 'tcx> {
176 if state.is_reachable() {
177 if let TerminatorKind::SwitchInt { discr, targets } = &terminator.kind {
178 self.get_switch_int_edges(discr, targets, state)
179 } else {
180 terminator.edges()
181 }
182 } else {
183 TerminatorEdges::None
184 }
185 }
186
187 fn apply_primary_terminator_effect(
188 &self,
189 state: &mut Self::Domain,
190 terminator: &Terminator<'tcx>,
191 _location: Location,
192 ) {
193 if state.is_reachable() {
194 match &terminator.kind {
195 TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {
196 }
198 TerminatorKind::Drop { place, .. } => {
199 state.flood_with(place.as_ref(), &self.map, FlatSet::<Scalar>::BOTTOM);
200 }
201 TerminatorKind::Yield { .. } => {
202 bug!("encountered disallowed terminator");
204 }
205 TerminatorKind::TailCall { .. } => {
206 }
209 TerminatorKind::SwitchInt { .. }
210 | TerminatorKind::Goto { .. }
211 | TerminatorKind::UnwindResume
212 | TerminatorKind::UnwindTerminate(_)
213 | TerminatorKind::Return
214 | TerminatorKind::Unreachable
215 | TerminatorKind::Assert { .. }
216 | TerminatorKind::CoroutineDrop
217 | TerminatorKind::FalseEdge { .. }
218 | TerminatorKind::FalseUnwind { .. } => {
219 }
221 }
222 }
223 }
224
225 fn apply_call_return_effect(
226 &self,
227 state: &mut Self::Domain,
228 _block: BasicBlock,
229 return_places: CallReturnPlaces<'_, 'tcx>,
230 ) {
231 if state.is_reachable() {
232 return_places.for_each(|place| {
233 state.flood(place.as_ref(), &self.map);
234 })
235 }
236 }
237}
238
239impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
240 fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, map: Map<'tcx>) -> Self {
241 let typing_env = body.typing_env(tcx);
242 Self {
243 map,
244 tcx,
245 local_decls: &body.local_decls,
246 ecx: InterpCx::new(tcx, DUMMY_SP, typing_env, DummyMachine),
247 typing_env,
248 }
249 }
250
251 fn handle_operand(&self, operand: &Operand<'tcx>) -> ValueOrPlace<FlatSet<Scalar>> {
252 match operand {
253 Operand::RuntimeChecks(_) => ValueOrPlace::TOP,
254 Operand::Constant(constant) => ValueOrPlace::Value(
255 constant
256 .const_
257 .try_eval_scalar(self.tcx, self.typing_env)
258 .map_or(FlatSet::Top, FlatSet::Elem),
259 ),
260 Operand::Copy(place) | Operand::Move(place) => {
261 self.map.find(place.as_ref()).map(ValueOrPlace::Place).unwrap_or(ValueOrPlace::TOP)
264 }
265 }
266 }
267
268 fn handle_assign(
269 &self,
270 target: Place<'tcx>,
271 rvalue: &Rvalue<'tcx>,
272 state: &mut State<FlatSet<Scalar>>,
273 ) {
274 let result = match rvalue {
275 Rvalue::Use(operand, _) => {
276 state.flood(target.as_ref(), &self.map);
277 if let Some(target) = self.map.find(target.as_ref()) {
278 self.assign_operand(state, target, operand);
279 }
280 return;
281 }
282 Rvalue::CopyForDeref(_) => bug!("`CopyForDeref` in runtime MIR"),
283 Rvalue::Aggregate(kind, operands) => {
284 state.flood(target.as_ref(), &self.map);
287
288 let Some(target_idx) = self.map.find(target.as_ref()) else { return };
289
290 let (variant_target, variant_index) = match **kind {
291 AggregateKind::Tuple | AggregateKind::Closure(..) => (Some(target_idx), None),
292 AggregateKind::Adt(def_id, variant_index, ..) => {
293 match self.tcx.def_kind(def_id) {
294 DefKind::Struct => (Some(target_idx), None),
295 DefKind::Enum => (
296 self.map.apply(target_idx, TrackElem::Variant(variant_index)),
297 Some(variant_index),
298 ),
299 _ => return,
300 }
301 }
302 _ => return,
303 };
304 if let Some(variant_target_idx) = variant_target {
305 for (field_index, operand) in operands.iter_enumerated() {
306 if let Some(field) =
307 self.map.apply(variant_target_idx, TrackElem::Field(field_index))
308 {
309 self.assign_operand(state, field, operand);
310 }
311 }
312 }
313 if let Some(variant_index) = variant_index
314 && let Some(discr_idx) = self.map.apply(target_idx, TrackElem::Discriminant)
315 {
316 let enum_ty = target.ty(self.local_decls, self.tcx).ty;
322 if let Some(discr_val) = self.eval_discriminant(enum_ty, variant_index) {
323 state.insert_value_idx(discr_idx, FlatSet::Elem(discr_val), &self.map);
324 }
325 }
326 return;
327 }
328 Rvalue::BinaryOp(op, (left, right)) => {
329 if op.is_overflowing() {
330 state.flood(target.as_ref(), &self.map);
332
333 let Some(target) = self.map.find(target.as_ref()) else { return };
334
335 let value_target = self.map.apply(target, TrackElem::Field(0_u32.into()));
336 let overflow_target = self.map.apply(target, TrackElem::Field(1_u32.into()));
337
338 if value_target.is_some() || overflow_target.is_some() {
339 let (val, overflow) = self.binary_op(state, *op, left, right);
340
341 if let Some(value_target) = value_target {
342 state.insert_value_idx(value_target, val, &self.map);
344 }
345 if let Some(overflow_target) = overflow_target {
346 state.insert_value_idx(overflow_target, overflow, &self.map);
348 }
349 }
350 return;
351 } else {
352 let (val, _overflow) = self.binary_op(state, *op, left, right);
354 ValueOrPlace::Value(val)
355 }
356 }
357 Rvalue::Cast(
358 CastKind::PointerCoercion(ty::adjustment::PointerCoercion::Unsize, _),
359 operand,
360 _,
361 ) => {
362 let pointer = self.handle_operand(operand);
363 state.assign(target.as_ref(), pointer, &self.map);
364
365 if let Some(target_len) = self.map.find_len(target.as_ref())
366 && let operand_ty = operand.ty(self.local_decls, self.tcx)
367 && let Some(operand_ty) = operand_ty.builtin_deref(true)
368 && let ty::Array(_, len) = operand_ty.kind()
369 && let Some(len) = Const::Ty(self.tcx.types.usize, *len)
370 .try_eval_scalar_int(self.tcx, self.typing_env)
371 {
372 state.insert_value_idx(target_len, FlatSet::Elem(len.into()), &self.map);
373 }
374 return;
375 }
376 Rvalue::Cast(CastKind::IntToInt | CastKind::IntToFloat, operand, ty) => {
377 ValueOrPlace::Value(
378 if let Ok(layout) = self.tcx.layout_of(self.typing_env.as_query_input(*ty)) {
379 self.eval_operand(operand, state).and_then(|op| {
380 self.ecx
381 .int_to_int_or_float(&op, layout)
382 .discard_err()
383 .map_or(FlatSet::Top, |result| self.wrap_immediate(*result))
384 })
385 } else {
386 FlatSet::Top
387 },
388 )
389 }
390 Rvalue::Cast(CastKind::FloatToInt | CastKind::FloatToFloat, operand, ty) => {
391 ValueOrPlace::Value(
392 if let Ok(layout) = self.tcx.layout_of(self.typing_env.as_query_input(*ty)) {
393 self.eval_operand(operand, state).and_then(|op| {
394 self.ecx
395 .float_to_float_or_int(&op, layout)
396 .discard_err()
397 .map_or(FlatSet::Top, |result| self.wrap_immediate(*result))
398 })
399 } else {
400 FlatSet::Top
401 },
402 )
403 }
404 Rvalue::Cast(CastKind::Transmute | CastKind::Subtype, operand, _) => {
405 ValueOrPlace::Value(
406 self.eval_operand(operand, state).and_then(|op| self.wrap_immediate(*op)),
407 )
408 }
409 Rvalue::UnaryOp(op, operand) => {
410 if let UnOp::PtrMetadata = op
411 && let Some(place) = operand.place()
412 && let Some(len) = self.map.find_len(place.as_ref())
413 {
414 ValueOrPlace::Place(len)
415 } else {
416 ValueOrPlace::Value(self.eval_operand(operand, state).and_then(|value| {
417 self.ecx
418 .unary_op(*op, &value)
419 .discard_err()
420 .map_or(FlatSet::Top, |val| self.wrap_immediate(*val))
421 }))
422 }
423 }
424 Rvalue::Discriminant(place) => {
425 ValueOrPlace::Value(state.get_discr(place.as_ref(), &self.map))
426 }
427 Rvalue::Ref(..) | Rvalue::Reborrow(..) | Rvalue::RawPtr(..) => {
428 ValueOrPlace::TOP
430 }
431 Rvalue::Repeat(..)
432 | Rvalue::ThreadLocalRef(..)
433 | Rvalue::Cast(..)
434 | Rvalue::WrapUnsafeBinder(..) => {
435 ValueOrPlace::TOP
437 }
438 };
439 state.assign(target.as_ref(), result, &self.map);
441 }
442
443 fn get_switch_int_edges<'mir>(
444 &self,
445 discr: &'mir Operand<'tcx>,
446 targets: &'mir SwitchTargets,
447 state: &State<FlatSet<Scalar>>,
448 ) -> TerminatorEdges<'mir, 'tcx> {
449 let value = match self.handle_operand(discr) {
450 ValueOrPlace::Value(value) => value,
451 ValueOrPlace::Place(place) => state.get_idx(place, &self.map),
452 };
453 match value {
454 FlatSet::Bottom => TerminatorEdges::None,
457 FlatSet::Elem(scalar) => {
458 if let Ok(scalar_int) = scalar.try_to_scalar_int() {
459 TerminatorEdges::Single(
460 targets.target_for_value(scalar_int.to_bits_unchecked()),
461 )
462 } else {
463 TerminatorEdges::SwitchInt { discr, targets }
464 }
465 }
466 FlatSet::Top => TerminatorEdges::SwitchInt { discr, targets },
467 }
468 }
469
470 fn assign_operand(
472 &self,
473 state: &mut State<FlatSet<Scalar>>,
474 place: PlaceIndex,
475 operand: &Operand<'tcx>,
476 ) {
477 match operand {
478 Operand::RuntimeChecks(_) => {}
479 Operand::Copy(rhs) | Operand::Move(rhs) => {
480 if let Some(rhs) = self.map.find(rhs.as_ref()) {
481 state.insert_place_idx(place, rhs, &self.map);
482 } else if rhs.projection.first() == Some(&PlaceElem::Deref)
483 && let FlatSet::Elem(pointer) = state.get(rhs.local.into(), &self.map)
484 && let rhs_ty = self.local_decls[rhs.local].ty
485 && let Ok(rhs_layout) =
486 self.tcx.layout_of(self.typing_env.as_query_input(rhs_ty))
487 {
488 let op = ImmTy::from_scalar(pointer, rhs_layout).into();
489 self.assign_constant(state, place, op, rhs.projection);
490 }
491 }
492 Operand::Constant(constant) => {
493 if let Some(constant) =
494 self.ecx.eval_mir_constant(&constant.const_, constant.span, None).discard_err()
495 {
496 self.assign_constant(state, place, constant, &[]);
497 }
498 }
499 }
500 }
501
502 #[instrument(level = "trace", skip(self, state))]
506 fn assign_constant(
507 &self,
508 state: &mut State<FlatSet<Scalar>>,
509 place: PlaceIndex,
510 mut operand: OpTy<'tcx>,
511 projection: &[PlaceElem<'tcx>],
512 ) {
513 for &(mut proj_elem) in projection {
514 if let PlaceElem::Index(index) = proj_elem {
515 if let FlatSet::Elem(index) = state.get(index.into(), &self.map)
516 && let Some(offset) = index.to_target_usize(&self.tcx).discard_err()
517 && let Some(min_length) = offset.checked_add(1)
518 {
519 proj_elem = PlaceElem::ConstantIndex { offset, min_length, from_end: false };
520 } else {
521 return;
522 }
523 }
524 operand = if let Some(operand) = self.ecx.project(&operand, proj_elem).discard_err() {
525 operand
526 } else {
527 return;
528 }
529 }
530
531 self.map.for_each_projection_value(
532 place,
533 operand,
534 &mut |elem, op| match elem {
535 TrackElem::Field(idx) => self.ecx.project_field(op, idx).discard_err(),
536 TrackElem::Variant(idx) => self.ecx.project_downcast(op, idx).discard_err(),
537 TrackElem::Discriminant => {
538 let variant = self.ecx.read_discriminant(op).discard_err()?;
539 let discr_value =
540 self.ecx.discriminant_for_variant(op.layout.ty, variant).discard_err()?;
541 Some(discr_value.into())
542 }
543 TrackElem::DerefLen => {
544 let op: OpTy<'_> = self.ecx.deref_pointer(op).discard_err()?.into();
545 let len_usize = op.len(&self.ecx).discard_err()?;
546 let layout = self
547 .tcx
548 .layout_of(self.typing_env.as_query_input(self.tcx.types.usize))
549 .unwrap();
550 Some(ImmTy::from_uint(len_usize, layout).into())
551 }
552 },
553 &mut |place, op| {
554 if let Some(imm) = self.ecx.read_immediate_raw(op).discard_err()
555 && let Some(imm) = imm.right()
556 {
557 let elem = self.wrap_immediate(*imm);
558 state.insert_value_idx(place, elem, &self.map);
559 }
560 },
561 );
562 }
563
564 fn binary_op(
565 &self,
566 state: &State<FlatSet<Scalar>>,
567 op: BinOp,
568 left: &Operand<'tcx>,
569 right: &Operand<'tcx>,
570 ) -> (FlatSet<Scalar>, FlatSet<Scalar>) {
571 let left = self.eval_operand(left, state);
572 let right = self.eval_operand(right, state);
573
574 match (left, right) {
575 (FlatSet::Bottom, _) | (_, FlatSet::Bottom) => (FlatSet::Bottom, FlatSet::Bottom),
576 (FlatSet::Elem(left), FlatSet::Elem(right)) => {
578 match self.ecx.binary_op(op, &left, &right).discard_err() {
579 Some(val) => {
583 if matches!(val.layout.backend_repr, BackendRepr::ScalarPair { .. }) {
584 let (val, overflow) = val.to_scalar_pair();
585 (FlatSet::Elem(val), FlatSet::Elem(overflow))
586 } else {
587 (FlatSet::Elem(val.to_scalar()), FlatSet::Bottom)
588 }
589 }
590 _ => (FlatSet::Top, FlatSet::Top),
591 }
592 }
593 (FlatSet::Elem(const_arg), _) | (_, FlatSet::Elem(const_arg)) => {
595 let layout = const_arg.layout;
596 if !layout.backend_repr.is_scalar() {
597 return (FlatSet::Top, FlatSet::Top);
598 }
599
600 let arg_scalar = const_arg.to_scalar();
601 let Some(arg_value) = arg_scalar.to_bits(layout.size).discard_err() else {
602 return (FlatSet::Top, FlatSet::Top);
603 };
604
605 match op {
606 BinOp::BitAnd if arg_value == 0 => (FlatSet::Elem(arg_scalar), FlatSet::Bottom),
607 BinOp::BitOr
608 if arg_value == layout.size.truncate(u128::MAX)
609 || (layout.ty.is_bool() && arg_value == 1) =>
610 {
611 (FlatSet::Elem(arg_scalar), FlatSet::Bottom)
612 }
613 BinOp::Mul if layout.ty.is_integral() && arg_value == 0 => {
614 (FlatSet::Elem(arg_scalar), FlatSet::Elem(Scalar::from_bool(false)))
615 }
616 _ => (FlatSet::Top, FlatSet::Top),
617 }
618 }
619 (FlatSet::Top, FlatSet::Top) => (FlatSet::Top, FlatSet::Top),
620 }
621 }
622
623 fn eval_operand(
624 &self,
625 op: &Operand<'tcx>,
626 state: &State<FlatSet<Scalar>>,
627 ) -> FlatSet<ImmTy<'tcx>> {
628 let value = match self.handle_operand(op) {
629 ValueOrPlace::Value(value) => value,
630 ValueOrPlace::Place(place) => state.get_idx(place, &self.map),
631 };
632 value.and_then(|scalar| {
633 let ty = op.ty(self.local_decls, self.tcx);
634 self.tcx
635 .layout_of(self.typing_env.as_query_input(ty))
636 .map_or(FlatSet::Top, |layout| FlatSet::Elem(ImmTy::from_scalar(scalar, layout)))
637 })
638 }
639
640 fn eval_discriminant(&self, enum_ty: Ty<'tcx>, variant_index: VariantIdx) -> Option<Scalar> {
641 if !enum_ty.is_enum() {
642 return None;
643 }
644 let enum_ty_layout = self.tcx.layout_of(self.typing_env.as_query_input(enum_ty)).ok()?;
645 let discr_value =
646 self.ecx.discriminant_for_variant(enum_ty_layout.ty, variant_index).discard_err()?;
647 Some(discr_value.to_scalar())
648 }
649
650 fn wrap_immediate(&self, imm: Immediate) -> FlatSet<Scalar> {
651 match imm {
652 Immediate::Scalar(scalar) => FlatSet::Elem(scalar),
653 Immediate::Uninit => FlatSet::Bottom,
654 _ => FlatSet::Top,
655 }
656 }
657}
658
659impl<'tcx> DebugWithContext<ConstAnalysis<'_, 'tcx>> for State<FlatSet<Scalar>> {
661 fn fmt_with(&self, ctxt: &ConstAnalysis<'_, 'tcx>, f: &mut Formatter<'_>) -> std::fmt::Result {
662 match self {
663 State::Reachable(values) => debug_with_context(values, None, &ctxt.map, f),
664 State::Unreachable => write!(f, "unreachable"),
665 }
666 }
667
668 fn fmt_diff_with(
669 &self,
670 old: &Self,
671 ctxt: &ConstAnalysis<'_, 'tcx>,
672 f: &mut Formatter<'_>,
673 ) -> std::fmt::Result {
674 match (self, old) {
675 (State::Reachable(this), State::Reachable(old)) => {
676 debug_with_context(this, Some(old), &ctxt.map, f)
677 }
678 _ => Ok(()), }
680 }
681}
682
683struct Patch<'tcx> {
684 tcx: TyCtxt<'tcx>,
685
686 before_effect: FxHashMap<(Location, Place<'tcx>), Const<'tcx>>,
690
691 assignments: FxHashMap<Location, Const<'tcx>>,
693}
694
695impl<'tcx> Patch<'tcx> {
696 fn new(tcx: TyCtxt<'tcx>) -> Self {
697 Self { tcx, before_effect: FxHashMap::default(), assignments: FxHashMap::default() }
698 }
699
700 fn make_operand(&self, const_: Const<'tcx>) -> Operand<'tcx> {
701 Operand::Constant(Box::new(ConstOperand { span: DUMMY_SP, user_ty: None, const_ }))
702 }
703}
704
705struct Collector<'a, 'tcx> {
706 patch: Patch<'tcx>,
707 local_decls: &'a LocalDecls<'tcx>,
708 ecx: InterpCx<'tcx, DummyMachine>,
709 map: &'a Map<'tcx>,
710}
711
712impl<'a, 'tcx> Collector<'a, 'tcx> {
713 fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, map: &'a Map<'tcx>) -> Self {
714 Self {
715 patch: Patch::new(tcx),
716 local_decls: &body.local_decls,
717 ecx: InterpCx::new(tcx, DUMMY_SP, body.typing_env(tcx), DummyMachine),
718 map,
719 }
720 }
721
722 #[instrument(level = "trace", skip(self), ret)]
723 fn try_make_constant(
724 &mut self,
725 place: Place<'tcx>,
726 state: &State<FlatSet<Scalar>>,
727 ) -> Option<Const<'tcx>> {
728 let ty = place.ty(self.local_decls, self.patch.tcx).ty;
729 let layout = self.ecx.layout_of(ty).ok()?;
730
731 if layout.is_zst() {
732 return Some(Const::zero_sized(ty));
733 }
734
735 if layout.is_unsized() {
736 return None;
737 }
738
739 let place = self.map.find(place.as_ref())?;
740 if layout.backend_repr.is_scalar()
741 && let Some(value) = propagatable_scalar(place, state, self.map)
742 {
743 return Some(Const::Val(ConstValue::Scalar(value), ty));
744 }
745
746 if matches!(layout.backend_repr, BackendRepr::Scalar(..) | BackendRepr::ScalarPair { .. }) {
747 let alloc_id = self
748 .ecx
749 .intern_with_temp_alloc(layout, |ecx, dest| {
750 try_write_constant(ecx, dest, place, ty, state, self.map)
751 })
752 .discard_err()?;
753 return Some(Const::Val(ConstValue::Indirect { alloc_id, offset: Size::ZERO }, ty));
754 }
755
756 None
757 }
758}
759
760#[instrument(level = "trace", skip(map), ret)]
761fn propagatable_scalar(
762 place: PlaceIndex,
763 state: &State<FlatSet<Scalar>>,
764 map: &Map<'_>,
765) -> Option<Scalar> {
766 if let FlatSet::Elem(value) = state.get_idx(place, map)
767 && value.try_to_scalar_int().is_ok()
768 {
769 Some(value)
771 } else {
772 None
773 }
774}
775
776#[instrument(level = "trace", skip(ecx, state, map), ret)]
777fn try_write_constant<'tcx>(
778 ecx: &mut InterpCx<'tcx, DummyMachine>,
779 dest: &PlaceTy<'tcx>,
780 place: PlaceIndex,
781 ty: Ty<'tcx>,
782 state: &State<FlatSet<Scalar>>,
783 map: &Map<'tcx>,
784) -> InterpResult<'tcx> {
785 let layout = ecx.layout_of(ty)?;
786
787 if layout.is_zst() {
789 return interp_ok(());
790 }
791
792 if layout.backend_repr.is_scalar()
794 && let Some(value) = propagatable_scalar(place, state, map)
795 {
796 return ecx.write_immediate(Immediate::Scalar(value), dest);
797 }
798
799 match ty.kind() {
800 ty::FnDef(..) => {}
802
803 ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => {
805 throw_machine_stop_str!("primitive type with provenance")
806 }
807
808 ty::Tuple(elem_tys) => {
809 for (i, elem) in elem_tys.iter().enumerate() {
810 let i = FieldIdx::from_usize(i);
811 let Some(field) = map.apply(place, TrackElem::Field(i)) else {
812 throw_machine_stop_str!("missing field in tuple")
813 };
814 let field_dest = ecx.project_field(dest, i)?;
815 try_write_constant(ecx, &field_dest, field, elem, state, map)?;
816 }
817 }
818
819 ty::Adt(def, args) => {
820 if def.is_union() {
821 throw_machine_stop_str!("cannot propagate unions")
822 }
823
824 let (variant_idx, variant_def, variant_place, variant_dest) = if def.is_enum() {
825 let Some(discr) = map.apply(place, TrackElem::Discriminant) else {
826 throw_machine_stop_str!("missing discriminant for enum")
827 };
828 let FlatSet::Elem(Scalar::Int(discr)) = state.get_idx(discr, map) else {
829 throw_machine_stop_str!("discriminant with provenance")
830 };
831 let discr_bits = discr.to_bits(discr.size());
832 let Some((variant, _)) =
833 def.discriminants(*ecx.tcx).find(|(_, var)| discr_bits == var.val)
834 else {
835 throw_machine_stop_str!("illegal discriminant for enum")
836 };
837 let Some(variant_place) = map.apply(place, TrackElem::Variant(variant)) else {
838 throw_machine_stop_str!("missing variant for enum")
839 };
840 let variant_dest = ecx.project_downcast(dest, variant)?;
841 (variant, def.variant(variant), variant_place, variant_dest)
842 } else {
843 (FIRST_VARIANT, def.non_enum_variant(), place, dest.clone())
844 };
845
846 for (i, field) in variant_def.fields.iter_enumerated() {
847 let ty = field.ty(*ecx.tcx, args).skip_norm_wip();
848 let Some(field) = map.apply(variant_place, TrackElem::Field(i)) else {
849 throw_machine_stop_str!("missing field in ADT")
850 };
851 let field_dest = ecx.project_field(&variant_dest, i)?;
852 try_write_constant(ecx, &field_dest, field, ty, state, map)?;
853 }
854 ecx.write_discriminant(variant_idx, dest)?;
855 }
856
857 ty::Array(_, _)
859 | ty::Pat(_, _)
860
861 | ty::Ref(..) | ty::RawPtr(..) | ty::FnPtr(..) | ty::Str | ty::Slice(_)
863
864 | ty::Never
865 | ty::Foreign(..)
866 | ty::Alias(..)
867 | ty::Param(_)
868 | ty::Bound(..)
869 | ty::Placeholder(..)
870 | ty::Closure(..)
871 | ty::CoroutineClosure(..)
872 | ty::Coroutine(..)
873 | ty::Dynamic(..)
874 | ty::UnsafeBinder(_) => throw_machine_stop_str!("unsupported type"),
875
876 ty::Error(_) | ty::Infer(..) | ty::CoroutineWitness(..) => bug!(),
877 }
878
879 interp_ok(())
880}
881
882impl<'tcx> ResultsVisitor<'tcx, ConstAnalysis<'_, 'tcx>> for Collector<'_, 'tcx> {
883 #[instrument(level = "trace", skip(self, statement))]
884 fn visit_after_early_statement_effect(
885 &mut self,
886 state: &State<FlatSet<Scalar>>,
887 statement: &Statement<'tcx>,
888 location: Location,
889 ) {
890 match &statement.kind {
891 StatementKind::Assign((_, rvalue)) => {
892 OperandCollector { state, visitor: self }.visit_rvalue(rvalue, location);
893 }
894 _ => (),
895 }
896 }
897
898 #[instrument(level = "trace", skip(self, statement))]
899 fn visit_after_primary_statement_effect(
900 &mut self,
901 state: &State<FlatSet<Scalar>>,
902 statement: &Statement<'tcx>,
903 location: Location,
904 ) {
905 match statement.kind {
906 StatementKind::Assign((_, Rvalue::Use(Operand::Constant(_), _))) => {
907 }
909 StatementKind::Assign((place, _)) => {
910 if let Some(value) = self.try_make_constant(place, state) {
911 self.patch.assignments.insert(location, value);
912 }
913 }
914 _ => (),
915 }
916 }
917
918 fn visit_after_early_terminator_effect(
919 &mut self,
920 state: &State<FlatSet<Scalar>>,
921 terminator: &Terminator<'tcx>,
922 location: Location,
923 ) {
924 OperandCollector { state, visitor: self }.visit_terminator(terminator, location);
925 }
926}
927
928impl<'tcx> MutVisitor<'tcx> for Patch<'tcx> {
929 fn tcx(&self) -> TyCtxt<'tcx> {
930 self.tcx
931 }
932
933 fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
934 if let Some(value) = self.assignments.get(&location) {
935 match &mut statement.kind {
936 StatementKind::Assign((_, rvalue)) => {
937 let old_retag = match rvalue {
938 Rvalue::Use(_, retag) => *retag,
939 _ => WithRetag::Yes,
940 };
941 *rvalue = Rvalue::Use(self.make_operand(*value), old_retag);
942 }
943 _ => bug!("found assignment info for non-assign statement"),
944 }
945 } else {
946 self.super_statement(statement, location);
947 }
948 }
949
950 fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) {
951 match operand {
952 Operand::Copy(place) | Operand::Move(place) => {
953 if let Some(value) = self.before_effect.get(&(location, *place)) {
954 *operand = self.make_operand(*value);
955 } else if !place.projection.is_empty() {
956 self.super_operand(operand, location)
957 }
958 }
959 Operand::Constant(_) | Operand::RuntimeChecks(_) => {}
960 }
961 }
962
963 fn process_projection_elem(
964 &mut self,
965 elem: PlaceElem<'tcx>,
966 location: Location,
967 ) -> Option<PlaceElem<'tcx>> {
968 if let PlaceElem::Index(local) = elem {
969 let offset = self.before_effect.get(&(location, local.into()))?;
970 let offset = offset.try_to_scalar()?;
971 let offset = offset.to_target_usize(&self.tcx).discard_err()?;
972 let min_length = offset.checked_add(1)?;
973 Some(PlaceElem::ConstantIndex { offset, min_length, from_end: false })
974 } else {
975 None
976 }
977 }
978}
979
980struct OperandCollector<'a, 'b, 'tcx> {
981 state: &'a State<FlatSet<Scalar>>,
982 visitor: &'a mut Collector<'b, 'tcx>,
983}
984
985impl<'tcx> Visitor<'tcx> for OperandCollector<'_, '_, 'tcx> {
986 fn visit_projection_elem(
987 &mut self,
988 _: PlaceRef<'tcx>,
989 elem: PlaceElem<'tcx>,
990 _: PlaceContext,
991 location: Location,
992 ) {
993 if let PlaceElem::Index(local) = elem
994 && let Some(value) = self.visitor.try_make_constant(local.into(), self.state)
995 {
996 self.visitor.patch.before_effect.insert((location, local.into()), value);
997 }
998 }
999
1000 fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
1001 if let Some(place) = operand.place() {
1002 if let Some(value) = self.visitor.try_make_constant(place, self.state) {
1003 self.visitor.patch.before_effect.insert((location, place), value);
1004 } else if !place.projection.is_empty() {
1005 self.super_operand(operand, location)
1007 }
1008 }
1009 }
1010}