rustc_mir_transform/
trivial_const.rs

1use std::ops::Deref;
2
3use rustc_hir::def::DefKind;
4use rustc_hir::def_id::LocalDefId;
5use rustc_middle::mir::{
6    Body, ConstValue, Operand, Place, RETURN_PLACE, Rvalue, START_BLOCK, StatementKind,
7    TerminatorKind,
8};
9use rustc_middle::ty::{Ty, TyCtxt, TypeVisitableExt};
10
11/// If the given def is a trivial const, returns the value and type the const evaluates to.
12///
13/// A "trivial const" is a const which can be easily proven to evaluate successfully, and the value
14/// that it evaluates to can be easily found without going through the usual MIR phases for a const.
15///
16/// Currently the only form of trivial const that is supported is this:
17/// ```
18/// const A: usize = 0;
19/// ```
20/// which has this MIR:
21/// ```text
22/// const A: usize = {
23///     let mut _0: usize;
24///
25///     bb0: {
26///         _0 = const 0_usize;
27///         return;
28///     }
29/// }
30/// ```
31/// Which we recognize by looking for a Body which has a single basic block with a return
32/// terminator and a single statement which assigns an `Operand::Constant(Const::Val)` to the
33/// return place.
34/// This scenario meets the required criteria because:
35/// * Control flow cannot panic, we don't have any calls or assert terminators
36/// * The value of the const is already computed, so it cannot fail
37pub(crate) fn trivial_const<'a, 'tcx: 'a, F, B>(
38    tcx: TyCtxt<'tcx>,
39    def: LocalDefId,
40    body_provider: F,
41) -> Option<(ConstValue, Ty<'tcx>)>
42where
43    F: FnOnce() -> B,
44    B: Deref<Target = Body<'tcx>>,
45{
46    if !matches!(tcx.def_kind(def), DefKind::AssocConst | DefKind::Const | DefKind::AnonConst) {
47        return None;
48    }
49
50    let body = body_provider();
51
52    if body.has_opaque_types() {
53        return None;
54    }
55
56    if body.basic_blocks.len() != 1 {
57        return None;
58    }
59
60    let block = &body.basic_blocks[START_BLOCK];
61    if block.statements.len() != 1 {
62        return None;
63    }
64
65    if block.terminator().kind != TerminatorKind::Return {
66        return None;
67    }
68
69    let StatementKind::Assign(box (place, rvalue)) = &block.statements[0].kind else {
70        return None;
71    };
72
73    if *place != Place::from(RETURN_PLACE) {
74        return None;
75    }
76
77    if let Rvalue::Use(Operand::Constant(c)) = rvalue {
78        if let rustc_middle::mir::Const::Val(v, ty) = c.const_ {
79            return Some((v, ty));
80        }
81    }
82
83    return None;
84}
85
86// The query provider is based on calling the free function trivial_const, which calls mir_built,
87// which internally has a fast-path for trivial consts so it too calls trivial_const. This isn't
88// recursive, but we are checking if the const is trivial twice. A better design might detect
89// trivial consts before getting to MIR, which would hopefully straighten this out.
90pub(crate) fn trivial_const_provider<'tcx>(
91    tcx: TyCtxt<'tcx>,
92    def: LocalDefId,
93) -> Option<(ConstValue, Ty<'tcx>)> {
94    trivial_const(tcx, def, || tcx.mir_built(def).borrow())
95}