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