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