rustc_mir_transform/
deduce_param_attrs.rs

1//! Deduces supplementary parameter attributes from MIR.
2//!
3//! Deduced parameter attributes are those that can only be soundly determined by examining the
4//! body of the function instead of just the signature. These can be useful for optimization
5//! purposes on a best-effort basis. We compute them here and store them into the crate metadata so
6//! dependent crates can use them.
7//!
8//! Note that this *crucially* relies on codegen *not* doing any more MIR-level transformations
9//! after `optimized_mir`! We check for things that are *not* guaranteed to be preserved by MIR
10//! transforms, such as which local variables happen to be mutated.
11
12use rustc_hir::def_id::LocalDefId;
13use rustc_index::IndexVec;
14use rustc_middle::middle::deduced_param_attrs::{DeducedParamAttrs, DeducedReadOnlyParam};
15use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
16use rustc_middle::mir::*;
17use rustc_middle::ty::{self, Ty, TyCtxt};
18use rustc_session::config::OptLevel;
19
20/// A visitor that determines which arguments have been mutated. We can't use the mutability field
21/// on LocalDecl for this because it has no meaning post-optimization.
22struct DeduceReadOnly {
23    /// Each bit is indexed by argument number, starting at zero (so 0 corresponds to local decl
24    /// 1). The bit is false if the argument may have been mutated or true if we know it hasn't
25    /// been up to the point we're at.
26    read_only: IndexVec<usize, DeducedReadOnlyParam>,
27}
28
29impl DeduceReadOnly {
30    /// Returns a new DeduceReadOnly instance.
31    fn new(arg_count: usize) -> Self {
32        Self { read_only: IndexVec::from_elem_n(DeducedReadOnlyParam::empty(), arg_count) }
33    }
34
35    /// Returns whether the given local is a parameter and its index.
36    fn as_param(&self, local: Local) -> Option<usize> {
37        // Locals and parameters are shifted by `RETURN_PLACE`.
38        let param_index = local.as_usize().checked_sub(1)?;
39        if param_index < self.read_only.len() { Some(param_index) } else { None }
40    }
41}
42
43impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
44    fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
45        // We're only interested in arguments.
46        let Some(param_index) = self.as_param(place.local) else { return };
47
48        match context {
49            // Not mutating, so it's fine.
50            PlaceContext::NonUse(..) => {}
51            // Dereference is not a mutation.
52            _ if place.is_indirect_first_projection() => {}
53            // This is a `Drop`. It could disappear at monomorphization, so mark it specially.
54            PlaceContext::MutatingUse(MutatingUseContext::Drop)
55                // Projection changes the place's type, so `needs_drop(local.ty)` is not
56                // `needs_drop(place.ty)`.
57                if place.projection.is_empty() => {
58                self.read_only[param_index] |= DeducedReadOnlyParam::IF_NO_DROP;
59            }
60            // This is a mutation, so mark it as such.
61            PlaceContext::MutatingUse(..)
62            // Whether mutating though a `&raw const` is allowed is still undecided, so we
63            // disable any sketchy `readonly` optimizations for now.
64            | PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) => {
65                self.read_only[param_index] |= DeducedReadOnlyParam::MUTATED;
66            }
67            // Not mutating if the parameter is `Freeze`.
68            PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) => {
69                self.read_only[param_index] |= DeducedReadOnlyParam::IF_FREEZE;
70            }
71            // Not mutating, so it's fine.
72            PlaceContext::NonMutatingUse(..) => {}
73        }
74    }
75
76    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
77        // OK, this is subtle. Suppose that we're trying to deduce whether `x` in `f` is read-only
78        // and we have the following:
79        //
80        //     fn f(x: BigStruct) { g(x) }
81        //     fn g(mut y: BigStruct) { y.foo = 1 }
82        //
83        // If, at the generated MIR level, `f` turned into something like:
84        //
85        //      fn f(_1: BigStruct) -> () {
86        //          let mut _0: ();
87        //          bb0: {
88        //              _0 = g(move _1) -> bb1;
89        //          }
90        //          ...
91        //      }
92        //
93        // then it would be incorrect to mark `x` (i.e. `_1`) as `readonly`, because `g`'s write to
94        // its copy of the indirect parameter would actually be a write directly to the pointer that
95        // `f` passes. Note that function arguments are the only situation in which this problem can
96        // arise: every other use of `move` in MIR doesn't actually write to the value it moves
97        // from.
98        if let TerminatorKind::Call { ref args, .. } = terminator.kind {
99            for arg in args {
100                if let Operand::Move(place) = arg.node
101                    // We're only interested in arguments.
102                    && let Some(param_index) = self.as_param(place.local)
103                    && !place.is_indirect_first_projection()
104                {
105                    self.read_only[param_index] |= DeducedReadOnlyParam::MUTATED;
106                }
107            }
108        };
109
110        self.super_terminator(terminator, location);
111    }
112}
113
114/// Returns true if values of a given type will never be passed indirectly, regardless of ABI.
115fn type_will_always_be_passed_directly(ty: Ty<'_>) -> bool {
116    matches!(
117        ty.kind(),
118        ty::Bool
119            | ty::Char
120            | ty::Float(..)
121            | ty::Int(..)
122            | ty::RawPtr(..)
123            | ty::Ref(..)
124            | ty::Slice(..)
125            | ty::Uint(..)
126    )
127}
128
129/// Returns the deduced parameter attributes for a function.
130///
131/// Deduced parameter attributes are those that can only be soundly determined by examining the
132/// body of the function instead of just the signature. These can be useful for optimization
133/// purposes on a best-effort basis. We compute them here and store them into the crate metadata so
134/// dependent crates can use them.
135#[tracing::instrument(level = "trace", skip(tcx), ret)]
136pub(super) fn deduced_param_attrs<'tcx>(
137    tcx: TyCtxt<'tcx>,
138    def_id: LocalDefId,
139) -> &'tcx [DeducedParamAttrs] {
140    // This computation is unfortunately rather expensive, so don't do it unless we're optimizing.
141    // Also skip it in incremental mode.
142    if tcx.sess.opts.optimize == OptLevel::No || tcx.sess.opts.incremental.is_some() {
143        return &[];
144    }
145
146    // If the Freeze lang item isn't present, then don't bother.
147    if tcx.lang_items().freeze_trait().is_none() {
148        return &[];
149    }
150
151    // Codegen won't use this information for anything if all the function parameters are passed
152    // directly. Detect that and bail, for compilation speed.
153    let fn_ty = tcx.type_of(def_id).instantiate_identity();
154    if matches!(fn_ty.kind(), ty::FnDef(..))
155        && fn_ty
156            .fn_sig(tcx)
157            .inputs()
158            .skip_binder()
159            .iter()
160            .cloned()
161            .all(type_will_always_be_passed_directly)
162    {
163        return &[];
164    }
165
166    // Don't deduce any attributes for functions that have no MIR.
167    if !tcx.is_mir_available(def_id) {
168        return &[];
169    }
170
171    // Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
172    let body: &Body<'tcx> = tcx.optimized_mir(def_id);
173    let mut deduce_read_only = DeduceReadOnly::new(body.arg_count);
174    deduce_read_only.visit_body(body);
175    tracing::trace!(?deduce_read_only.read_only);
176
177    let mut deduced_param_attrs: &[_] = tcx.arena.alloc_from_iter(
178        deduce_read_only.read_only.into_iter().map(|read_only| DeducedParamAttrs { read_only }),
179    );
180
181    // Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
182    // default set of attributes, so we don't have to store them explicitly. Pop them off to save a
183    // few bytes in metadata.
184    while let Some((last, rest)) = deduced_param_attrs.split_last()
185        && last.is_default()
186    {
187        deduced_param_attrs = rest;
188    }
189
190    deduced_param_attrs
191}