Skip to main content

rustc_mir_transform/
dataflow_const_prop.rs

1//! A constant propagation optimization pass based on dataflow analysis.
2//!
3//! Currently, this pass only propagates scalar values.
4
5use 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
29// These constants are somewhat random guesses and have not been optimized.
30// If `tcx.sess.mir_opt_level() >= 4`, we ignore the limits (this can become very expensive).
31const 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        // Avoid query cycles from coroutines.
44        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        // We want to have a somewhat linear runtime w.r.t. the number of statements/terminators.
55        // Let's call this number `n`. Dataflow analysis has `O(h*n)` transfer function
56        // applications, where `h` is the height of the lattice. Because the height of our lattice
57        // is linear w.r.t. the number of tracked places, this is `O(tracked_places * n)`. However,
58        // because every transfer function application could traverse the whole map, this becomes
59        // `O(num_nodes * tracked_places * n)` in terms of time complexity. Since the number of
60        // map nodes is strongly correlated to the number of tracked places, this becomes more or
61        // less `O(n)` if we place a constant limit on the number of tracked places.
62        let value_limit = if tcx.sess.mir_opt_level() < 4 { Some(PLACE_LIMIT) } else { None };
63
64        // Decide which places to track during the analysis.
65        let map = Map::new(tcx, body, PlaceCollectionMode::Full { value_limit });
66
67        // Perform the actual dataflow analysis.
68        let const_ = debug_span!("analyze")
69            .in_scope(|| ConstAnalysis::new(tcx, body, map).iterate_to_fixpoint(tcx, body, None));
70
71        // Collect results and patch the body afterwards.
72        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
83// Note: Currently, places that have their reference taken cannot be tracked. Although this would
84// be possible, it has to rely on some aliasing model, which we are not ready to commit to yet.
85// Because of that, we can assume that the only way to change the value behind a tracked place is
86// by direct assignment.
87struct 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    // The bottom state denotes uninitialized memory. Because we are only doing a sound
101    // approximation of the actual execution, we can also use this state for places where access
102    // would be UB.
103    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        // The initial state maps all tracked places of argument projections to ⊤ and the rest to ⊥.
109        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                // StorageLive leaves the local in an uninitialized state.
177                // StorageDead makes it UB to access the local afterwards.
178                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                // Could use this, but ignoring it is sound.
198            }
199            NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
200                dst: _,
201                src: _,
202                count: _,
203            }) => {
204                // This statement represents `*dst = *src`, `count` times.
205            }
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                // On move, we would ideally flood the place with bottom. But with the current
221                // framework this is not possible (similar to `InterpCx::eval_operand`).
222                self.map.find(place.as_ref()).map(ValueOrPlace::Place).unwrap_or(ValueOrPlace::TOP)
223            }
224        }
225    }
226
227    /// The effect of a successful function call return should not be
228    /// applied here, see [`Analysis::apply_primary_terminator_effect`].
229    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                // Effect is applied by `handle_call_return`.
237            }
238            TerminatorKind::Drop { place, .. } => {
239                state.flood_with(place.as_ref(), &self.map, FlatSet::<Scalar>::BOTTOM);
240            }
241            TerminatorKind::Yield { .. } => {
242                // They would have an effect, but are not allowed in this phase.
243                bug!("encountered disallowed terminator");
244            }
245            TerminatorKind::SwitchInt { discr, targets } => {
246                return self.handle_switch_int(discr, targets, state);
247            }
248            TerminatorKind::TailCall { .. } => {
249                // FIXME(explicit_tail_calls): determine if we need to do something here (probably
250                // not)
251            }
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                // These terminators have no effect on the analysis.
262            }
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                // If we assign `target = Enum::Variant#0(operand)`,
312                // we must make sure that all `target as Variant#i` are `Top`.
313                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                    // We are assigning the discriminant as part of an aggregate.
344                    // This discriminant can only alias a variant field's value if the operand
345                    // had an invalid value for that type.
346                    // Using invalid values is UB, so we are allowed to perform the assignment
347                    // without extra flooding.
348                    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                // Flood everything now, so we can use `insert_value_idx` directly later.
356                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                        // We have flooded `target` earlier.
368                        state.insert_value_idx(value_target, val, &self.map);
369                    }
370                    if let Some(overflow_target) = overflow_target {
371                        // We have flooded `target` earlier.
372                        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                // Overflows must be ignored here.
444                // The overflowing operators are handled in `handle_assign`.
445                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                // We don't track such places.
470                return ValueOrPlace::TOP;
471            }
472            Rvalue::Repeat(..)
473            | Rvalue::ThreadLocalRef(..)
474            | Rvalue::Cast(..)
475            | Rvalue::BinaryOp(..)
476            | Rvalue::Aggregate(..)
477            | Rvalue::WrapUnsafeBinder(..) => {
478                // No modification is possible through these r-values.
479                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            // We are branching on uninitialized data, this is UB, treat it as unreachable.
508            // This allows the set of visited edges to grow monotonically with the lattice.
509            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    /// The caller must have flooded `place`.
524    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    /// The caller must have flooded `place`.
556    ///
557    /// Perform: `place = operand.projection`.
558    #[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            // Both sides are known, do the actual computation.
630            (FlatSet::Elem(left), FlatSet::Elem(right)) => {
631                match self.ecx.binary_op(op, &left, &right).discard_err() {
632                    // Ideally this would return an Immediate, since it's sometimes
633                    // a pair and sometimes not. But as a hack we always return a pair
634                    // and just make the 2nd component `Bottom` when it does not exist.
635                    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            // Exactly one side is known, attempt some algebraic simplifications.
647            (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
718/// This is used to visualize the dataflow analysis.
719impl<'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(()), // Consider printing something here.
738        }
739    }
740}
741
742struct Patch<'tcx> {
743    tcx: TyCtxt<'tcx>,
744
745    /// For a given MIR location, this stores the values of the operands used by that location. In
746    /// particular, this is before the effect, such that the operands of `_1 = _1 + _2` are
747    /// properly captured. (This may become UB soon, but it is currently emitted even by safe code.)
748    before_effect: FxHashMap<(Location, Place<'tcx>), Const<'tcx>>,
749
750    /// Stores the assigned values for assignments where the Rvalue is constant.
751    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        // Do not attempt to propagate pointers, as we may fail to preserve their identity.
828        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    // Fast path for ZSTs.
846    if layout.is_zst() {
847        return interp_ok(());
848    }
849
850    // Fast path for scalars.
851    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        // ZSTs. Nothing to do.
859        ty::FnDef(..) => {}
860
861        // Those are scalars, must be handled above.
862        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        // Unsupported for now.
916        ty::Array(_, _)
917        | ty::Pat(_, _)
918
919        // Do not attempt to support indirection in constants.
920        | 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                // Don't overwrite the assignment if it already uses a constant (to keep the span).
969            }
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                // Try to propagate into `Index` projections.
1070                self.super_operand(operand, location)
1071            }
1072        }
1073    }
1074}