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}