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
29use crate::PassPolicy;
30
31// These constants are somewhat random guesses and have not been optimized.
32// If `tcx.sess.mir_opt_level() >= 4`, we ignore the limits (this can become very expensive).
33const BLOCK_LIMIT: usize = 100;
34const PLACE_LIMIT: usize = 100;
35
36pub(super) struct DataflowConstProp;
37
38impl<'tcx> crate::MirPass<'tcx> for DataflowConstProp {
39    fn policy(&self, sess: &rustc_session::Session) -> PassPolicy {
40        PassPolicy::optimization(sess.mir_opt_level() >= 3)
41    }
42
43    #[instrument(skip_all level = "debug")]
44    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
45        // Avoid query cycles from coroutines.
46        if body.coroutine.is_some() {
47            return;
48        }
49
50        debug!(def_id = ?body.source.def_id());
51        if tcx.sess.mir_opt_level() < 4 && body.basic_blocks.len() > BLOCK_LIMIT {
52            debug!("aborted dataflow const prop due too many basic blocks");
53            return;
54        }
55
56        // We want to have a somewhat linear runtime w.r.t. the number of statements/terminators.
57        // Let's call this number `n`. Dataflow analysis has `O(h*n)` transfer function
58        // applications, where `h` is the height of the lattice. Because the height of our lattice
59        // is linear w.r.t. the number of tracked places, this is `O(tracked_places * n)`. However,
60        // because every transfer function application could traverse the whole map, this becomes
61        // `O(num_nodes * tracked_places * n)` in terms of time complexity. Since the number of
62        // map nodes is strongly correlated to the number of tracked places, this becomes more or
63        // less `O(n)` if we place a constant limit on the number of tracked places.
64        let value_limit = if tcx.sess.mir_opt_level() < 4 { Some(PLACE_LIMIT) } else { None };
65
66        // Decide which places to track during the analysis.
67        let map = Map::new(tcx, body, PlaceCollectionMode::Full { value_limit });
68
69        // Perform the actual dataflow analysis.
70        let const_ = debug_span!("analyze")
71            .in_scope(|| ConstAnalysis::new(tcx, body, map).iterate_to_fixpoint(tcx, body, None));
72
73        // Collect results and patch the body afterwards.
74        let mut visitor = Collector::new(tcx, body);
75        debug_span!("collect").in_scope(|| visit_reachable_results(body, &const_, &mut visitor));
76        let mut patch = visitor.patch;
77        debug_span!("patch").in_scope(|| patch.visit_body_preserves_cfg(body));
78    }
79}
80
81// Note: Currently, places that have their reference taken cannot be tracked. Although this would
82// be possible, it has to rely on some aliasing model, which we are not ready to commit to yet.
83// Because of that, we can assume that the only way to change the value behind a tracked place is
84// by direct assignment.
85struct ConstAnalysis<'a, 'tcx> {
86    map: Map<'tcx>,
87    tcx: TyCtxt<'tcx>,
88    local_decls: &'a LocalDecls<'tcx>,
89    ecx: InterpCx<'tcx, DummyMachine>,
90    typing_env: ty::TypingEnv<'tcx>,
91}
92
93impl<'tcx> Analysis<'tcx> for ConstAnalysis<'_, 'tcx> {
94    type Domain = State<FlatSet<Scalar>>;
95
96    const NAME: &'static str = "ConstAnalysis";
97
98    // The bottom state denotes uninitialized memory. Because we are only doing a sound
99    // approximation of the actual execution, we can also use this state for places where access
100    // would be UB.
101    fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
102        State::Unreachable
103    }
104
105    fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
106        // The initial state maps all tracked places of argument projections to ⊤ and the rest to ⊥.
107        assert_matches!(state, State::Unreachable);
108        *state = State::new_reachable();
109        for arg in body.args_iter() {
110            state.flood(PlaceRef { local: arg, projection: &[] }, &self.map);
111        }
112    }
113
114    fn apply_primary_statement_effect(
115        &self,
116        state: &mut Self::Domain,
117        statement: &Statement<'tcx>,
118        _location: Location,
119    ) {
120        if state.is_reachable() {
121            self.handle_statement(statement, state);
122        }
123    }
124
125    fn apply_primary_terminator_effect<'mir>(
126        &self,
127        state: &mut Self::Domain,
128        terminator: &'mir Terminator<'tcx>,
129        _location: Location,
130    ) -> TerminatorEdges<'mir, 'tcx> {
131        if state.is_reachable() {
132            self.handle_terminator(terminator, state)
133        } else {
134            TerminatorEdges::None
135        }
136    }
137
138    fn apply_call_return_effect(
139        &self,
140        state: &mut Self::Domain,
141        _block: BasicBlock,
142        return_places: CallReturnPlaces<'_, 'tcx>,
143    ) {
144        if state.is_reachable() {
145            self.handle_call_return(return_places, state)
146        }
147    }
148}
149
150impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
151    fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, map: Map<'tcx>) -> Self {
152        let typing_env = body.typing_env(tcx);
153        Self {
154            map,
155            tcx,
156            local_decls: &body.local_decls,
157            ecx: InterpCx::new(tcx, DUMMY_SP, typing_env, DummyMachine),
158            typing_env,
159        }
160    }
161
162    fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State<FlatSet<Scalar>>) {
163        match &statement.kind {
164            StatementKind::Assign((place, rvalue)) => {
165                self.handle_assign(*place, rvalue, state);
166            }
167            StatementKind::SetDiscriminant { place, variant_index } => {
168                self.handle_set_discriminant(**place, *variant_index, state);
169            }
170            StatementKind::Intrinsic(intrinsic) => {
171                self.handle_intrinsic(intrinsic);
172            }
173            StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
174                // StorageLive leaves the local in an uninitialized state.
175                // StorageDead makes it UB to access the local afterwards.
176                state.flood_with(
177                    Place::from(*local).as_ref(),
178                    &self.map,
179                    FlatSet::<Scalar>::BOTTOM,
180                );
181            }
182            StatementKind::ConstEvalCounter
183            | StatementKind::Nop
184            | StatementKind::FakeRead(..)
185            | StatementKind::PlaceMention(..)
186            | StatementKind::Coverage(..)
187            | StatementKind::BackwardIncompatibleDropHint { .. }
188            | StatementKind::AscribeUserType(..) => {}
189        }
190    }
191
192    fn handle_intrinsic(&self, intrinsic: &NonDivergingIntrinsic<'tcx>) {
193        match intrinsic {
194            NonDivergingIntrinsic::Assume(..) => {
195                // Could use this, but ignoring it is sound.
196            }
197            NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
198                dst: _,
199                src: _,
200                count: _,
201            }) => {
202                // This statement represents `*dst = *src`, `count` times.
203            }
204        }
205    }
206
207    fn handle_operand(
208        &self,
209        operand: &Operand<'tcx>,
210        state: &mut State<FlatSet<Scalar>>,
211    ) -> ValueOrPlace<FlatSet<Scalar>> {
212        match operand {
213            Operand::RuntimeChecks(_) => ValueOrPlace::TOP,
214            Operand::Constant(constant) => {
215                ValueOrPlace::Value(self.handle_constant(constant, state))
216            }
217            Operand::Copy(place) | Operand::Move(place) => {
218                // On move, we would ideally flood the place with bottom. But with the current
219                // framework this is not possible (similar to `InterpCx::eval_operand`).
220                self.map.find(place.as_ref()).map(ValueOrPlace::Place).unwrap_or(ValueOrPlace::TOP)
221            }
222        }
223    }
224
225    /// The effect of a successful function call return should not be
226    /// applied here, see [`Analysis::apply_primary_terminator_effect`].
227    fn handle_terminator<'mir>(
228        &self,
229        terminator: &'mir Terminator<'tcx>,
230        state: &mut State<FlatSet<Scalar>>,
231    ) -> TerminatorEdges<'mir, 'tcx> {
232        match &terminator.kind {
233            TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {
234                // Effect is applied by `handle_call_return`.
235            }
236            TerminatorKind::Drop { place, .. } => {
237                state.flood_with(place.as_ref(), &self.map, FlatSet::<Scalar>::BOTTOM);
238            }
239            TerminatorKind::Yield { .. } => {
240                // They would have an effect, but are not allowed in this phase.
241                bug!("encountered disallowed terminator");
242            }
243            TerminatorKind::SwitchInt { discr, targets } => {
244                return self.handle_switch_int(discr, targets, state);
245            }
246            TerminatorKind::TailCall { .. } => {
247                // FIXME(explicit_tail_calls): determine if we need to do something here (probably
248                // not)
249            }
250            TerminatorKind::Goto { .. }
251            | TerminatorKind::UnwindResume
252            | TerminatorKind::UnwindTerminate(_)
253            | TerminatorKind::Return
254            | TerminatorKind::Unreachable
255            | TerminatorKind::Assert { .. }
256            | TerminatorKind::CoroutineDrop
257            | TerminatorKind::FalseEdge { .. }
258            | TerminatorKind::FalseUnwind { .. } => {
259                // These terminators have no effect on the analysis.
260            }
261        }
262        terminator.edges()
263    }
264
265    fn handle_call_return(
266        &self,
267        return_places: CallReturnPlaces<'_, 'tcx>,
268        state: &mut State<FlatSet<Scalar>>,
269    ) {
270        return_places.for_each(|place| {
271            state.flood(place.as_ref(), &self.map);
272        })
273    }
274
275    fn handle_set_discriminant(
276        &self,
277        place: Place<'tcx>,
278        variant_index: VariantIdx,
279        state: &mut State<FlatSet<Scalar>>,
280    ) {
281        state.flood_discr(place.as_ref(), &self.map);
282        if self.map.find_discr(place.as_ref()).is_some() {
283            let enum_ty = place.ty(self.local_decls, self.tcx).ty;
284            if let Some(discr) = self.eval_discriminant(enum_ty, variant_index) {
285                state.assign_discr(
286                    place.as_ref(),
287                    ValueOrPlace::Value(FlatSet::Elem(discr)),
288                    &self.map,
289                );
290            }
291        }
292    }
293
294    fn handle_assign(
295        &self,
296        target: Place<'tcx>,
297        rvalue: &Rvalue<'tcx>,
298        state: &mut State<FlatSet<Scalar>>,
299    ) {
300        match rvalue {
301            Rvalue::Use(operand, _) => {
302                state.flood(target.as_ref(), &self.map);
303                if let Some(target) = self.map.find(target.as_ref()) {
304                    self.assign_operand(state, target, operand);
305                }
306            }
307            Rvalue::CopyForDeref(_) => bug!("`CopyForDeref` in runtime MIR"),
308            Rvalue::Aggregate(kind, operands) => {
309                // If we assign `target = Enum::Variant#0(operand)`,
310                // we must make sure that all `target as Variant#i` are `Top`.
311                state.flood(target.as_ref(), &self.map);
312
313                let Some(target_idx) = self.map.find(target.as_ref()) else { return };
314
315                let (variant_target, variant_index) = match **kind {
316                    AggregateKind::Tuple | AggregateKind::Closure(..) => (Some(target_idx), None),
317                    AggregateKind::Adt(def_id, variant_index, ..) => {
318                        match self.tcx.def_kind(def_id) {
319                            DefKind::Struct => (Some(target_idx), None),
320                            DefKind::Enum => (
321                                self.map.apply(target_idx, TrackElem::Variant(variant_index)),
322                                Some(variant_index),
323                            ),
324                            _ => return,
325                        }
326                    }
327                    _ => return,
328                };
329                if let Some(variant_target_idx) = variant_target {
330                    for (field_index, operand) in operands.iter_enumerated() {
331                        if let Some(field) =
332                            self.map.apply(variant_target_idx, TrackElem::Field(field_index))
333                        {
334                            self.assign_operand(state, field, operand);
335                        }
336                    }
337                }
338                if let Some(variant_index) = variant_index
339                    && let Some(discr_idx) = self.map.apply(target_idx, TrackElem::Discriminant)
340                {
341                    // We are assigning the discriminant as part of an aggregate.
342                    // This discriminant can only alias a variant field's value if the operand
343                    // had an invalid value for that type.
344                    // Using invalid values is UB, so we are allowed to perform the assignment
345                    // without extra flooding.
346                    let enum_ty = target.ty(self.local_decls, self.tcx).ty;
347                    if let Some(discr_val) = self.eval_discriminant(enum_ty, variant_index) {
348                        state.insert_value_idx(discr_idx, FlatSet::Elem(discr_val), &self.map);
349                    }
350                }
351            }
352            Rvalue::BinaryOp(op, (left, right)) if op.is_overflowing() => {
353                // Flood everything now, so we can use `insert_value_idx` directly later.
354                state.flood(target.as_ref(), &self.map);
355
356                let Some(target) = self.map.find(target.as_ref()) else { return };
357
358                let value_target = self.map.apply(target, TrackElem::Field(0_u32.into()));
359                let overflow_target = self.map.apply(target, TrackElem::Field(1_u32.into()));
360
361                if value_target.is_some() || overflow_target.is_some() {
362                    let (val, overflow) = self.binary_op(state, *op, left, right);
363
364                    if let Some(value_target) = value_target {
365                        // We have flooded `target` earlier.
366                        state.insert_value_idx(value_target, val, &self.map);
367                    }
368                    if let Some(overflow_target) = overflow_target {
369                        // We have flooded `target` earlier.
370                        state.insert_value_idx(overflow_target, overflow, &self.map);
371                    }
372                }
373            }
374            Rvalue::Cast(
375                CastKind::PointerCoercion(ty::adjustment::PointerCoercion::Unsize, _),
376                operand,
377                _,
378            ) => {
379                let pointer = self.handle_operand(operand, state);
380                state.assign(target.as_ref(), pointer, &self.map);
381
382                if let Some(target_len) = self.map.find_len(target.as_ref())
383                    && let operand_ty = operand.ty(self.local_decls, self.tcx)
384                    && let Some(operand_ty) = operand_ty.builtin_deref(true)
385                    && let ty::Array(_, len) = operand_ty.kind()
386                    && let Some(len) = Const::Ty(self.tcx.types.usize, *len)
387                        .try_eval_scalar_int(self.tcx, self.typing_env)
388                {
389                    state.insert_value_idx(target_len, FlatSet::Elem(len.into()), &self.map);
390                }
391            }
392            _ => {
393                let result = self.handle_rvalue(rvalue, state);
394                state.assign(target.as_ref(), result, &self.map);
395            }
396        }
397    }
398
399    fn handle_rvalue(
400        &self,
401        rvalue: &Rvalue<'tcx>,
402        state: &mut State<FlatSet<Scalar>>,
403    ) -> ValueOrPlace<FlatSet<Scalar>> {
404        let val = match rvalue {
405            Rvalue::Cast(CastKind::IntToInt | CastKind::IntToFloat, operand, ty) => {
406                let Ok(layout) = self.tcx.layout_of(self.typing_env.as_query_input(*ty)) else {
407                    return ValueOrPlace::Value(FlatSet::Top);
408                };
409                match self.eval_operand(operand, state) {
410                    FlatSet::Elem(op) => self
411                        .ecx
412                        .int_to_int_or_float(&op, layout)
413                        .discard_err()
414                        .map_or(FlatSet::Top, |result| self.wrap_immediate(*result)),
415                    FlatSet::Bottom => FlatSet::Bottom,
416                    FlatSet::Top => FlatSet::Top,
417                }
418            }
419            Rvalue::Cast(CastKind::FloatToInt | CastKind::FloatToFloat, operand, ty) => {
420                let Ok(layout) = self.tcx.layout_of(self.typing_env.as_query_input(*ty)) else {
421                    return ValueOrPlace::Value(FlatSet::Top);
422                };
423                match self.eval_operand(operand, state) {
424                    FlatSet::Elem(op) => self
425                        .ecx
426                        .float_to_float_or_int(&op, layout)
427                        .discard_err()
428                        .map_or(FlatSet::Top, |result| self.wrap_immediate(*result)),
429                    FlatSet::Bottom => FlatSet::Bottom,
430                    FlatSet::Top => FlatSet::Top,
431                }
432            }
433            Rvalue::Cast(CastKind::Transmute | CastKind::Subtype, operand, _) => {
434                match self.eval_operand(operand, state) {
435                    FlatSet::Elem(op) => self.wrap_immediate(*op),
436                    FlatSet::Bottom => FlatSet::Bottom,
437                    FlatSet::Top => FlatSet::Top,
438                }
439            }
440            Rvalue::BinaryOp(op, (left, right)) if !op.is_overflowing() => {
441                // Overflows must be ignored here.
442                // The overflowing operators are handled in `handle_assign`.
443                let (val, _overflow) = self.binary_op(state, *op, left, right);
444                val
445            }
446            Rvalue::UnaryOp(op, operand) => {
447                if let UnOp::PtrMetadata = op
448                    && let Some(place) = operand.place()
449                    && let Some(len) = self.map.find_len(place.as_ref())
450                {
451                    return ValueOrPlace::Place(len);
452                }
453                match self.eval_operand(operand, state) {
454                    FlatSet::Elem(value) => self
455                        .ecx
456                        .unary_op(*op, &value)
457                        .discard_err()
458                        .map_or(FlatSet::Top, |val| self.wrap_immediate(*val)),
459                    FlatSet::Bottom => FlatSet::Bottom,
460                    FlatSet::Top => FlatSet::Top,
461                }
462            }
463            Rvalue::Discriminant(place) => state.get_discr(place.as_ref(), &self.map),
464            Rvalue::Use(operand, _) => return self.handle_operand(operand, state),
465            Rvalue::CopyForDeref(_) => bug!("`CopyForDeref` in runtime MIR"),
466            Rvalue::Ref(..) | Rvalue::Reborrow(..) | Rvalue::RawPtr(..) => {
467                // We don't track such places.
468                return ValueOrPlace::TOP;
469            }
470            Rvalue::Repeat(..)
471            | Rvalue::ThreadLocalRef(..)
472            | Rvalue::Cast(..)
473            | Rvalue::BinaryOp(..)
474            | Rvalue::Aggregate(..)
475            | Rvalue::WrapUnsafeBinder(..) => {
476                // No modification is possible through these r-values.
477                return ValueOrPlace::TOP;
478            }
479        };
480        ValueOrPlace::Value(val)
481    }
482
483    fn handle_constant(
484        &self,
485        constant: &ConstOperand<'tcx>,
486        _state: &mut State<FlatSet<Scalar>>,
487    ) -> FlatSet<Scalar> {
488        constant
489            .const_
490            .try_eval_scalar(self.tcx, self.typing_env)
491            .map_or(FlatSet::Top, FlatSet::Elem)
492    }
493
494    fn handle_switch_int<'mir>(
495        &self,
496        discr: &'mir Operand<'tcx>,
497        targets: &'mir SwitchTargets,
498        state: &mut State<FlatSet<Scalar>>,
499    ) -> TerminatorEdges<'mir, 'tcx> {
500        let value = match self.handle_operand(discr, state) {
501            ValueOrPlace::Value(value) => value,
502            ValueOrPlace::Place(place) => state.get_idx(place, &self.map),
503        };
504        match value {
505            // We are branching on uninitialized data, this is UB, treat it as unreachable.
506            // This allows the set of visited edges to grow monotonically with the lattice.
507            FlatSet::Bottom => TerminatorEdges::None,
508            FlatSet::Elem(scalar) => {
509                if let Ok(scalar_int) = scalar.try_to_scalar_int() {
510                    TerminatorEdges::Single(
511                        targets.target_for_value(scalar_int.to_bits_unchecked()),
512                    )
513                } else {
514                    TerminatorEdges::SwitchInt { discr, targets }
515                }
516            }
517            FlatSet::Top => TerminatorEdges::SwitchInt { discr, targets },
518        }
519    }
520
521    /// The caller must have flooded `place`.
522    fn assign_operand(
523        &self,
524        state: &mut State<FlatSet<Scalar>>,
525        place: PlaceIndex,
526        operand: &Operand<'tcx>,
527    ) {
528        match operand {
529            Operand::RuntimeChecks(_) => {}
530            Operand::Copy(rhs) | Operand::Move(rhs) => {
531                if let Some(rhs) = self.map.find(rhs.as_ref()) {
532                    state.insert_place_idx(place, rhs, &self.map);
533                } else if rhs.projection.first() == Some(&PlaceElem::Deref)
534                    && let FlatSet::Elem(pointer) = state.get(rhs.local.into(), &self.map)
535                    && let rhs_ty = self.local_decls[rhs.local].ty
536                    && let Ok(rhs_layout) =
537                        self.tcx.layout_of(self.typing_env.as_query_input(rhs_ty))
538                {
539                    let op = ImmTy::from_scalar(pointer, rhs_layout).into();
540                    self.assign_constant(state, place, op, rhs.projection);
541                }
542            }
543            Operand::Constant(constant) => {
544                if let Some(constant) =
545                    self.ecx.eval_mir_constant(&constant.const_, constant.span, None).discard_err()
546                {
547                    self.assign_constant(state, place, constant, &[]);
548                }
549            }
550        }
551    }
552
553    /// The caller must have flooded `place`.
554    ///
555    /// Perform: `place = operand.projection`.
556    #[instrument(level = "trace", skip(self, state))]
557    fn assign_constant(
558        &self,
559        state: &mut State<FlatSet<Scalar>>,
560        place: PlaceIndex,
561        mut operand: OpTy<'tcx>,
562        projection: &[PlaceElem<'tcx>],
563    ) {
564        for &(mut proj_elem) in projection {
565            if let PlaceElem::Index(index) = proj_elem {
566                if let FlatSet::Elem(index) = state.get(index.into(), &self.map)
567                    && let Some(offset) = index.to_target_usize(&self.tcx).discard_err()
568                    && let Some(min_length) = offset.checked_add(1)
569                {
570                    proj_elem = PlaceElem::ConstantIndex { offset, min_length, from_end: false };
571                } else {
572                    return;
573                }
574            }
575            operand = if let Some(operand) = self.ecx.project(&operand, proj_elem).discard_err() {
576                operand
577            } else {
578                return;
579            }
580        }
581
582        self.map.for_each_projection_value(
583            place,
584            operand,
585            &mut |elem, op| match elem {
586                TrackElem::Field(idx) => self.ecx.project_field(op, idx).discard_err(),
587                TrackElem::Variant(idx) => self.ecx.project_downcast(op, idx).discard_err(),
588                TrackElem::Discriminant => {
589                    let variant = self.ecx.read_discriminant(op).discard_err()?;
590                    let discr_value =
591                        self.ecx.discriminant_for_variant(op.layout.ty, variant).discard_err()?;
592                    Some(discr_value.into())
593                }
594                TrackElem::DerefLen => {
595                    let op: OpTy<'_> = self.ecx.deref_pointer(op).discard_err()?.into();
596                    let len_usize = op.len(&self.ecx).discard_err()?;
597                    let layout = self
598                        .tcx
599                        .layout_of(self.typing_env.as_query_input(self.tcx.types.usize))
600                        .unwrap();
601                    Some(ImmTy::from_uint(len_usize, layout).into())
602                }
603            },
604            &mut |place, op| {
605                if let Some(imm) = self.ecx.read_immediate_raw(op).discard_err()
606                    && let Some(imm) = imm.right()
607                {
608                    let elem = self.wrap_immediate(*imm);
609                    state.insert_value_idx(place, elem, &self.map);
610                }
611            },
612        );
613    }
614
615    fn binary_op(
616        &self,
617        state: &mut State<FlatSet<Scalar>>,
618        op: BinOp,
619        left: &Operand<'tcx>,
620        right: &Operand<'tcx>,
621    ) -> (FlatSet<Scalar>, FlatSet<Scalar>) {
622        let left = self.eval_operand(left, state);
623        let right = self.eval_operand(right, state);
624
625        match (left, right) {
626            (FlatSet::Bottom, _) | (_, FlatSet::Bottom) => (FlatSet::Bottom, FlatSet::Bottom),
627            // Both sides are known, do the actual computation.
628            (FlatSet::Elem(left), FlatSet::Elem(right)) => {
629                match self.ecx.binary_op(op, &left, &right).discard_err() {
630                    // Ideally this would return an Immediate, since it's sometimes
631                    // a pair and sometimes not. But as a hack we always return a pair
632                    // and just make the 2nd component `Bottom` when it does not exist.
633                    Some(val) => {
634                        if matches!(val.layout.backend_repr, BackendRepr::ScalarPair { .. }) {
635                            let (val, overflow) = val.to_scalar_pair();
636                            (FlatSet::Elem(val), FlatSet::Elem(overflow))
637                        } else {
638                            (FlatSet::Elem(val.to_scalar()), FlatSet::Bottom)
639                        }
640                    }
641                    _ => (FlatSet::Top, FlatSet::Top),
642                }
643            }
644            // Exactly one side is known, attempt some algebraic simplifications.
645            (FlatSet::Elem(const_arg), _) | (_, FlatSet::Elem(const_arg)) => {
646                let layout = const_arg.layout;
647                if !matches!(layout.backend_repr, rustc_abi::BackendRepr::Scalar(..)) {
648                    return (FlatSet::Top, FlatSet::Top);
649                }
650
651                let arg_scalar = const_arg.to_scalar();
652                let Some(arg_value) = arg_scalar.to_bits(layout.size).discard_err() else {
653                    return (FlatSet::Top, FlatSet::Top);
654                };
655
656                match op {
657                    BinOp::BitAnd if arg_value == 0 => (FlatSet::Elem(arg_scalar), FlatSet::Bottom),
658                    BinOp::BitOr
659                        if arg_value == layout.size.truncate(u128::MAX)
660                            || (layout.ty.is_bool() && arg_value == 1) =>
661                    {
662                        (FlatSet::Elem(arg_scalar), FlatSet::Bottom)
663                    }
664                    BinOp::Mul if layout.ty.is_integral() && arg_value == 0 => {
665                        (FlatSet::Elem(arg_scalar), FlatSet::Elem(Scalar::from_bool(false)))
666                    }
667                    _ => (FlatSet::Top, FlatSet::Top),
668                }
669            }
670            (FlatSet::Top, FlatSet::Top) => (FlatSet::Top, FlatSet::Top),
671        }
672    }
673
674    fn eval_operand(
675        &self,
676        op: &Operand<'tcx>,
677        state: &mut State<FlatSet<Scalar>>,
678    ) -> FlatSet<ImmTy<'tcx>> {
679        let value = match self.handle_operand(op, state) {
680            ValueOrPlace::Value(value) => value,
681            ValueOrPlace::Place(place) => state.get_idx(place, &self.map),
682        };
683        match value {
684            FlatSet::Top => FlatSet::Top,
685            FlatSet::Elem(scalar) => {
686                let ty = op.ty(self.local_decls, self.tcx);
687                self.tcx
688                    .layout_of(self.typing_env.as_query_input(ty))
689                    .map_or(FlatSet::Top, |layout| {
690                        FlatSet::Elem(ImmTy::from_scalar(scalar, layout))
691                    })
692            }
693            FlatSet::Bottom => FlatSet::Bottom,
694        }
695    }
696
697    fn eval_discriminant(&self, enum_ty: Ty<'tcx>, variant_index: VariantIdx) -> Option<Scalar> {
698        if !enum_ty.is_enum() {
699            return None;
700        }
701        let enum_ty_layout = self.tcx.layout_of(self.typing_env.as_query_input(enum_ty)).ok()?;
702        let discr_value =
703            self.ecx.discriminant_for_variant(enum_ty_layout.ty, variant_index).discard_err()?;
704        Some(discr_value.to_scalar())
705    }
706
707    fn wrap_immediate(&self, imm: Immediate) -> FlatSet<Scalar> {
708        match imm {
709            Immediate::Scalar(scalar) => FlatSet::Elem(scalar),
710            Immediate::Uninit => FlatSet::Bottom,
711            _ => FlatSet::Top,
712        }
713    }
714}
715
716/// This is used to visualize the dataflow analysis.
717impl<'tcx> DebugWithContext<ConstAnalysis<'_, 'tcx>> for State<FlatSet<Scalar>> {
718    fn fmt_with(&self, ctxt: &ConstAnalysis<'_, 'tcx>, f: &mut Formatter<'_>) -> std::fmt::Result {
719        match self {
720            State::Reachable(values) => debug_with_context(values, None, &ctxt.map, f),
721            State::Unreachable => write!(f, "unreachable"),
722        }
723    }
724
725    fn fmt_diff_with(
726        &self,
727        old: &Self,
728        ctxt: &ConstAnalysis<'_, 'tcx>,
729        f: &mut Formatter<'_>,
730    ) -> std::fmt::Result {
731        match (self, old) {
732            (State::Reachable(this), State::Reachable(old)) => {
733                debug_with_context(this, Some(old), &ctxt.map, f)
734            }
735            _ => Ok(()), // Consider printing something here.
736        }
737    }
738}
739
740struct Patch<'tcx> {
741    tcx: TyCtxt<'tcx>,
742
743    /// For a given MIR location, this stores the values of the operands used by that location. In
744    /// particular, this is before the effect, such that the operands of `_1 = _1 + _2` are
745    /// properly captured. (This may become UB soon, but it is currently emitted even by safe code.)
746    before_effect: FxHashMap<(Location, Place<'tcx>), Const<'tcx>>,
747
748    /// Stores the assigned values for assignments where the Rvalue is constant.
749    assignments: FxHashMap<Location, Const<'tcx>>,
750}
751
752impl<'tcx> Patch<'tcx> {
753    pub(crate) fn new(tcx: TyCtxt<'tcx>) -> Self {
754        Self { tcx, before_effect: FxHashMap::default(), assignments: FxHashMap::default() }
755    }
756
757    fn make_operand(&self, const_: Const<'tcx>) -> Operand<'tcx> {
758        Operand::Constant(Box::new(ConstOperand { span: DUMMY_SP, user_ty: None, const_ }))
759    }
760}
761
762struct Collector<'a, 'tcx> {
763    patch: Patch<'tcx>,
764    local_decls: &'a LocalDecls<'tcx>,
765    ecx: InterpCx<'tcx, DummyMachine>,
766}
767
768impl<'a, 'tcx> Collector<'a, 'tcx> {
769    pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> Self {
770        Self {
771            patch: Patch::new(tcx),
772            local_decls: &body.local_decls,
773            ecx: InterpCx::new(tcx, DUMMY_SP, body.typing_env(tcx), DummyMachine),
774        }
775    }
776
777    #[instrument(level = "trace", skip(self, map), ret)]
778    fn try_make_constant(
779        &mut self,
780        place: Place<'tcx>,
781        state: &State<FlatSet<Scalar>>,
782        map: &Map<'tcx>,
783    ) -> Option<Const<'tcx>> {
784        let ty = place.ty(self.local_decls, self.patch.tcx).ty;
785        let layout = self.ecx.layout_of(ty).ok()?;
786
787        if layout.is_zst() {
788            return Some(Const::zero_sized(ty));
789        }
790
791        if layout.is_unsized() {
792            return None;
793        }
794
795        let place = map.find(place.as_ref())?;
796        if layout.backend_repr.is_scalar()
797            && let Some(value) = propagatable_scalar(place, state, map)
798        {
799            return Some(Const::Val(ConstValue::Scalar(value), ty));
800        }
801
802        if matches!(layout.backend_repr, BackendRepr::Scalar(..) | BackendRepr::ScalarPair { .. }) {
803            let alloc_id = self
804                .ecx
805                .intern_with_temp_alloc(layout, |ecx, dest| {
806                    try_write_constant(ecx, dest, place, ty, state, map)
807                })
808                .discard_err()?;
809            return Some(Const::Val(ConstValue::Indirect { alloc_id, offset: Size::ZERO }, ty));
810        }
811
812        None
813    }
814}
815
816#[instrument(level = "trace", skip(map), ret)]
817fn propagatable_scalar(
818    place: PlaceIndex,
819    state: &State<FlatSet<Scalar>>,
820    map: &Map<'_>,
821) -> Option<Scalar> {
822    if let FlatSet::Elem(value) = state.get_idx(place, map)
823        && value.try_to_scalar_int().is_ok()
824    {
825        // Do not attempt to propagate pointers, as we may fail to preserve their identity.
826        Some(value)
827    } else {
828        None
829    }
830}
831
832#[instrument(level = "trace", skip(ecx, state, map), ret)]
833fn try_write_constant<'tcx>(
834    ecx: &mut InterpCx<'tcx, DummyMachine>,
835    dest: &PlaceTy<'tcx>,
836    place: PlaceIndex,
837    ty: Ty<'tcx>,
838    state: &State<FlatSet<Scalar>>,
839    map: &Map<'tcx>,
840) -> InterpResult<'tcx> {
841    let layout = ecx.layout_of(ty)?;
842
843    // Fast path for ZSTs.
844    if layout.is_zst() {
845        return interp_ok(());
846    }
847
848    // Fast path for scalars.
849    if layout.backend_repr.is_scalar()
850        && let Some(value) = propagatable_scalar(place, state, map)
851    {
852        return ecx.write_immediate(Immediate::Scalar(value), dest);
853    }
854
855    match ty.kind() {
856        // ZSTs. Nothing to do.
857        ty::FnDef(..) => {}
858
859        // Those are scalars, must be handled above.
860        ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => {
861            throw_machine_stop_str!("primitive type with provenance")
862        }
863
864        ty::Tuple(elem_tys) => {
865            for (i, elem) in elem_tys.iter().enumerate() {
866                let i = FieldIdx::from_usize(i);
867                let Some(field) = map.apply(place, TrackElem::Field(i)) else {
868                    throw_machine_stop_str!("missing field in tuple")
869                };
870                let field_dest = ecx.project_field(dest, i)?;
871                try_write_constant(ecx, &field_dest, field, elem, state, map)?;
872            }
873        }
874
875        ty::Adt(def, args) => {
876            if def.is_union() {
877                throw_machine_stop_str!("cannot propagate unions")
878            }
879
880            let (variant_idx, variant_def, variant_place, variant_dest) = if def.is_enum() {
881                let Some(discr) = map.apply(place, TrackElem::Discriminant) else {
882                    throw_machine_stop_str!("missing discriminant for enum")
883                };
884                let FlatSet::Elem(Scalar::Int(discr)) = state.get_idx(discr, map) else {
885                    throw_machine_stop_str!("discriminant with provenance")
886                };
887                let discr_bits = discr.to_bits(discr.size());
888                let Some((variant, _)) =
889                    def.discriminants(*ecx.tcx).find(|(_, var)| discr_bits == var.val)
890                else {
891                    throw_machine_stop_str!("illegal discriminant for enum")
892                };
893                let Some(variant_place) = map.apply(place, TrackElem::Variant(variant)) else {
894                    throw_machine_stop_str!("missing variant for enum")
895                };
896                let variant_dest = ecx.project_downcast(dest, variant)?;
897                (variant, def.variant(variant), variant_place, variant_dest)
898            } else {
899                (FIRST_VARIANT, def.non_enum_variant(), place, dest.clone())
900            };
901
902            for (i, field) in variant_def.fields.iter_enumerated() {
903                let ty = field.ty(*ecx.tcx, args).skip_norm_wip();
904                let Some(field) = map.apply(variant_place, TrackElem::Field(i)) else {
905                    throw_machine_stop_str!("missing field in ADT")
906                };
907                let field_dest = ecx.project_field(&variant_dest, i)?;
908                try_write_constant(ecx, &field_dest, field, ty, state, map)?;
909            }
910            ecx.write_discriminant(variant_idx, dest)?;
911        }
912
913        // Unsupported for now.
914        ty::Array(_, _)
915        | ty::Pat(_, _)
916
917        // Do not attempt to support indirection in constants.
918        | ty::Ref(..) | ty::RawPtr(..) | ty::FnPtr(..) | ty::Str | ty::Slice(_)
919
920        | ty::Never
921        | ty::Foreign(..)
922        | ty::Alias(..)
923        | ty::Param(_)
924        | ty::Bound(..)
925        | ty::Placeholder(..)
926        | ty::Closure(..)
927        | ty::CoroutineClosure(..)
928        | ty::Coroutine(..)
929        | ty::Dynamic(..)
930        | ty::UnsafeBinder(_) => throw_machine_stop_str!("unsupported type"),
931
932        ty::Error(_) | ty::Infer(..) | ty::CoroutineWitness(..) => bug!(),
933    }
934
935    interp_ok(())
936}
937
938impl<'tcx> ResultsVisitor<'tcx, ConstAnalysis<'_, 'tcx>> for Collector<'_, 'tcx> {
939    #[instrument(level = "trace", skip(self, analysis, statement))]
940    fn visit_after_early_statement_effect(
941        &mut self,
942        analysis: &ConstAnalysis<'_, 'tcx>,
943        state: &State<FlatSet<Scalar>>,
944        statement: &Statement<'tcx>,
945        location: Location,
946    ) {
947        match &statement.kind {
948            StatementKind::Assign((_, rvalue)) => {
949                OperandCollector { state, visitor: self, map: &analysis.map }
950                    .visit_rvalue(rvalue, location);
951            }
952            _ => (),
953        }
954    }
955
956    #[instrument(level = "trace", skip(self, analysis, statement))]
957    fn visit_after_primary_statement_effect(
958        &mut self,
959        analysis: &ConstAnalysis<'_, 'tcx>,
960        state: &State<FlatSet<Scalar>>,
961        statement: &Statement<'tcx>,
962        location: Location,
963    ) {
964        match statement.kind {
965            StatementKind::Assign((_, Rvalue::Use(Operand::Constant(_), _))) => {
966                // Don't overwrite the assignment if it already uses a constant (to keep the span).
967            }
968            StatementKind::Assign((place, _)) => {
969                if let Some(value) = self.try_make_constant(place, state, &analysis.map) {
970                    self.patch.assignments.insert(location, value);
971                }
972            }
973            _ => (),
974        }
975    }
976
977    fn visit_after_early_terminator_effect(
978        &mut self,
979        analysis: &ConstAnalysis<'_, 'tcx>,
980        state: &State<FlatSet<Scalar>>,
981        terminator: &Terminator<'tcx>,
982        location: Location,
983    ) {
984        OperandCollector { state, visitor: self, map: &analysis.map }
985            .visit_terminator(terminator, location);
986    }
987}
988
989impl<'tcx> MutVisitor<'tcx> for Patch<'tcx> {
990    fn tcx(&self) -> TyCtxt<'tcx> {
991        self.tcx
992    }
993
994    fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
995        if let Some(value) = self.assignments.get(&location) {
996            match &mut statement.kind {
997                StatementKind::Assign((_, rvalue)) => {
998                    let old_retag = match rvalue {
999                        Rvalue::Use(_, retag) => *retag,
1000                        _ => WithRetag::Yes,
1001                    };
1002                    *rvalue = Rvalue::Use(self.make_operand(*value), old_retag);
1003                }
1004                _ => bug!("found assignment info for non-assign statement"),
1005            }
1006        } else {
1007            self.super_statement(statement, location);
1008        }
1009    }
1010
1011    fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) {
1012        match operand {
1013            Operand::Copy(place) | Operand::Move(place) => {
1014                if let Some(value) = self.before_effect.get(&(location, *place)) {
1015                    *operand = self.make_operand(*value);
1016                } else if !place.projection.is_empty() {
1017                    self.super_operand(operand, location)
1018                }
1019            }
1020            Operand::Constant(_) | Operand::RuntimeChecks(_) => {}
1021        }
1022    }
1023
1024    fn process_projection_elem(
1025        &mut self,
1026        elem: PlaceElem<'tcx>,
1027        location: Location,
1028    ) -> Option<PlaceElem<'tcx>> {
1029        if let PlaceElem::Index(local) = elem {
1030            let offset = self.before_effect.get(&(location, local.into()))?;
1031            let offset = offset.try_to_scalar()?;
1032            let offset = offset.to_target_usize(&self.tcx).discard_err()?;
1033            let min_length = offset.checked_add(1)?;
1034            Some(PlaceElem::ConstantIndex { offset, min_length, from_end: false })
1035        } else {
1036            None
1037        }
1038    }
1039}
1040
1041struct OperandCollector<'a, 'b, 'tcx> {
1042    state: &'a State<FlatSet<Scalar>>,
1043    visitor: &'a mut Collector<'b, 'tcx>,
1044    map: &'a Map<'tcx>,
1045}
1046
1047impl<'tcx> Visitor<'tcx> for OperandCollector<'_, '_, 'tcx> {
1048    fn visit_projection_elem(
1049        &mut self,
1050        _: PlaceRef<'tcx>,
1051        elem: PlaceElem<'tcx>,
1052        _: PlaceContext,
1053        location: Location,
1054    ) {
1055        if let PlaceElem::Index(local) = elem
1056            && let Some(value) = self.visitor.try_make_constant(local.into(), self.state, self.map)
1057        {
1058            self.visitor.patch.before_effect.insert((location, local.into()), value);
1059        }
1060    }
1061
1062    fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
1063        if let Some(place) = operand.place() {
1064            if let Some(value) = self.visitor.try_make_constant(place, self.state, self.map) {
1065                self.visitor.patch.before_effect.insert((location, place), value);
1066            } else if !place.projection.is_empty() {
1067                // Try to propagate into `Index` projections.
1068                self.super_operand(operand, location)
1069            }
1070        }
1071    }
1072}