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 as hir;
13use rustc_hir::def_id::LocalDefId;
14use rustc_index::IndexVec;
15use rustc_middle::middle::deduced_param_attrs::{DeducedParamAttrs, UsageSummary};
16use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
17use rustc_middle::mir::*;
18use rustc_middle::ty::{self, Ty, TyCtxt};
19use rustc_session::config::OptLevel;
20
21/// A visitor that determines how a return place and arguments are used inside MIR body.
22/// To determine whether a local is mutated we can't use the mutability field on LocalDecl
23/// because it has no meaning post-optimization.
24struct DeduceParamAttrs {
25 /// Summarizes how a return place and arguments are used inside MIR body.
26 usage: IndexVec<Local, UsageSummary>,
27}
28
29impl DeduceParamAttrs {
30 /// Returns a new DeduceParamAttrs instance.
31 fn new(body: &Body<'_>) -> Self {
32 let mut this =
33 Self { usage: IndexVec::from_elem_n(UsageSummary::empty(), body.arg_count + 1) };
34 // Code generation indicates that a return place is writable. To avoid setting both
35 // `readonly` and `writable` attributes, when return place is never written to, mark it as
36 // mutated.
37 this.usage[RETURN_PLACE] |= UsageSummary::MUTATE;
38 this
39 }
40
41 /// Returns whether a local is the return place or an argument and returns its index.
42 fn as_param(&self, local: Local) -> Option<Local> {
43 if local.index() < self.usage.len() { Some(local) } else { None }
44 }
45}
46
47impl<'tcx> Visitor<'tcx> for DeduceParamAttrs {
48 fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
49 // We're only interested in the return place or an argument.
50 let Some(i) = self.as_param(place.local) else { return };
51
52 match context {
53 // Not actually using the local.
54 PlaceContext::NonUse(..) => {}
55 // Neither mutated nor captured.
56 _ if place.is_indirect_first_projection() => {}
57 // This is a `Drop`. It could disappear at monomorphization, so mark it specially.
58 PlaceContext::MutatingUse(MutatingUseContext::Drop)
59 // Projection changes the place's type, so `needs_drop(local.ty)` is not
60 // `needs_drop(place.ty)`.
61 if place.projection.is_empty() => {
62 self.usage[i] |= UsageSummary::DROP;
63 }
64 PlaceContext::MutatingUse(
65 MutatingUseContext::Call
66 | MutatingUseContext::Yield
67 | MutatingUseContext::Drop
68 | MutatingUseContext::Borrow
69 | MutatingUseContext::RawBorrow) => {
70 self.usage[i] |= UsageSummary::MUTATE;
71 self.usage[i] |= UsageSummary::CAPTURE;
72 }
73 PlaceContext::MutatingUse(
74 MutatingUseContext::Store
75 | MutatingUseContext::SetDiscriminant
76 | MutatingUseContext::AsmOutput
77 | MutatingUseContext::Projection
78 | MutatingUseContext::Retag) => {
79 self.usage[i] |= UsageSummary::MUTATE;
80 }
81 | PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) => {
82 // Whether mutating though a `&raw const` is allowed is still undecided, so we
83 // disable any sketchy `readonly` optimizations for now.
84 self.usage[i] |= UsageSummary::MUTATE;
85 self.usage[i] |= UsageSummary::CAPTURE;
86 }
87 PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) => {
88 // Not mutating if the parameter is `Freeze`.
89 self.usage[i] |= UsageSummary::SHARED_BORROW;
90 self.usage[i] |= UsageSummary::CAPTURE;
91 }
92 // Not mutating, so it's fine.
93 PlaceContext::NonMutatingUse(
94 NonMutatingUseContext::Inspect
95 | NonMutatingUseContext::Copy
96 | NonMutatingUseContext::Move
97 | NonMutatingUseContext::FakeBorrow
98 | NonMutatingUseContext::PlaceMention
99 | NonMutatingUseContext::Projection) => {}
100 }
101 }
102
103 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
104 // OK, this is subtle. Suppose that we're trying to deduce whether `x` in `f` is read-only
105 // and we have the following:
106 //
107 // fn f(x: BigStruct) { g(x) }
108 // fn g(mut y: BigStruct) { y.foo = 1 }
109 //
110 // If, at the generated MIR level, `f` turned into something like:
111 //
112 // fn f(_1: BigStruct) -> () {
113 // let mut _0: ();
114 // bb0: {
115 // _0 = g(move _1) -> bb1;
116 // }
117 // ...
118 // }
119 //
120 // then it would be incorrect to mark `x` (i.e. `_1`) as `readonly`, because `g`'s write to
121 // its copy of the indirect parameter would actually be a write directly to the pointer that
122 // `f` passes. Note that function arguments are the only situation in which this problem can
123 // arise: every other use of `move` in MIR doesn't actually write to the value it moves
124 // from.
125 match terminator.kind {
126 TerminatorKind::Call { ref args, .. } => {
127 for arg in args {
128 if let Operand::Move(place) = arg.node
129 && !place.is_indirect_first_projection()
130 && let Some(i) = self.as_param(place.local)
131 {
132 self.usage[i] |= UsageSummary::MUTATE;
133 self.usage[i] |= UsageSummary::CAPTURE;
134 }
135 }
136 }
137
138 // Like a call, but more conservative because the backend may introduce writes to an
139 // argument if the argument is passed as `PassMode::Indirect { on_stack: false, ... }`.
140 TerminatorKind::TailCall { .. } => {
141 for usage in self.usage.iter_mut() {
142 *usage |= UsageSummary::MUTATE;
143 *usage |= UsageSummary::CAPTURE;
144 }
145 }
146 _ => {}
147 }
148
149 self.super_terminator(terminator, location);
150 }
151}
152
153/// Returns true if values of a given type will never be passed indirectly, regardless of ABI.
154fn type_will_always_be_passed_directly(ty: Ty<'_>) -> bool {
155 matches!(
156 ty.kind(),
157 ty::Bool
158 | ty::Char
159 | ty::Float(..)
160 | ty::Int(..)
161 | ty::RawPtr(..)
162 | ty::Ref(..)
163 | ty::Slice(..)
164 | ty::Uint(..)
165 )
166}
167
168/// Returns the deduced parameter attributes for a function.
169///
170/// Deduced parameter attributes are those that can only be soundly determined by examining the
171/// body of the function instead of just the signature. These can be useful for optimization
172/// purposes on a best-effort basis. We compute them here and store them into the crate metadata so
173/// dependent crates can use them.
174#[tracing::instrument(level = "trace", skip(tcx), ret)]
175pub(super) fn deduced_param_attrs<'tcx>(
176 tcx: TyCtxt<'tcx>,
177 def_id: LocalDefId,
178) -> &'tcx [DeducedParamAttrs] {
179 // This computation is unfortunately rather expensive, so don't do it unless we're optimizing.
180 // Also skip it in incremental mode.
181 if tcx.sess.opts.optimize == OptLevel::No || tcx.sess.opts.incremental.is_some() {
182 return &[];
183 }
184
185 // If the Freeze lang item isn't present, then don't bother.
186 if tcx.lang_items().freeze_trait().is_none() {
187 return &[];
188 }
189
190 // Codegen won't use this information for anything if all the function parameters are passed
191 // directly. Detect that and bail, for compilation speed.
192 let fn_ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
193 if matches!(fn_ty.kind(), ty::FnDef(..))
194 && fn_ty
195 .fn_sig(tcx)
196 .inputs_and_output()
197 .skip_binder()
198 .iter()
199 .all(type_will_always_be_passed_directly)
200 {
201 return &[];
202 }
203
204 // Don't deduce any attributes for functions that have no MIR.
205 if !tcx.is_mir_available(def_id) {
206 return &[];
207 }
208
209 if let hir::Constness::Const { always: true } = tcx.constness(def_id) {
210 // Comptime functions only exist during const eval and can never be passed
211 // to codegen.
212 return &[];
213 }
214
215 // Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
216 let body: &Body<'tcx> = tcx.optimized_mir(def_id);
217 // Arguments spread at ABI level are currently unsupported.
218 if body.spread_arg.is_some() {
219 return &[];
220 }
221
222 let mut deduce = DeduceParamAttrs::new(body);
223 deduce.visit_body(body);
224 tracing::trace!(?deduce.usage);
225
226 let mut deduced_param_attrs: &[_] = tcx
227 .arena
228 .alloc_from_iter(deduce.usage.into_iter().map(|usage| DeducedParamAttrs { usage }));
229
230 // Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
231 // default set of attributes, so we don't have to store them explicitly. Pop them off to save a
232 // few bytes in metadata.
233 while let Some((last, rest)) = deduced_param_attrs.split_last()
234 && last.is_default()
235 {
236 deduced_param_attrs = rest;
237 }
238
239 deduced_param_attrs
240}