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.
1112use rustc_hiras 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;
2021/// 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.
26usage: IndexVec<Local, UsageSummary>,
27}
2829impl DeduceParamAttrs {
30/// Returns a new DeduceParamAttrs instance.
31fn new(body: &Body<'_>) -> Self {
32let mut this =
33Self { 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.
37this.usage[RETURN_PLACE] |= UsageSummary::MUTATE;
38this39 }
4041/// Returns whether a local is the return place or an argument and returns its index.
42fn as_param(&self, local: Local) -> Option<Local> {
43if local.index() < self.usage.len() { Some(local) } else { None }
44 }
45}
4647impl<'tcx> Visitor<'tcx> for DeduceParamAttrs {
48fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
49// We're only interested in the return place or an argument.
50let Some(i) = self.as_param(place.local) else { return };
5152match context {
53// Not actually using the local.
54PlaceContext::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.
58PlaceContext::MutatingUse(MutatingUseContext::Drop)
59// Projection changes the place's type, so `needs_drop(local.ty)` is not
60 // `needs_drop(place.ty)`.
61if place.projection.is_empty() => {
62self.usage[i] |= UsageSummary::DROP;
63 }
64 PlaceContext::MutatingUse(
65 MutatingUseContext::Call66 | MutatingUseContext::Yield67 | MutatingUseContext::Drop68 | MutatingUseContext::Borrow69 | MutatingUseContext::RawBorrow) => {
70self.usage[i] |= UsageSummary::MUTATE;
71self.usage[i] |= UsageSummary::CAPTURE;
72 }
73 PlaceContext::MutatingUse(
74 MutatingUseContext::Store75 | MutatingUseContext::SetDiscriminant76 | MutatingUseContext::AsmOutput77 | MutatingUseContext::Projection) => {
78self.usage[i] |= UsageSummary::MUTATE;
79 }
80 | PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) => {
81// Whether mutating though a `&raw const` is allowed is still undecided, so we
82 // disable any sketchy `readonly` optimizations for now.
83self.usage[i] |= UsageSummary::MUTATE;
84self.usage[i] |= UsageSummary::CAPTURE;
85 }
86 PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) => {
87// Not mutating if the parameter is `Freeze`.
88self.usage[i] |= UsageSummary::SHARED_BORROW;
89self.usage[i] |= UsageSummary::CAPTURE;
90 }
91// Not mutating, so it's fine.
92PlaceContext::NonMutatingUse(
93 NonMutatingUseContext::Inspect94 | NonMutatingUseContext::Copy95 | NonMutatingUseContext::Move96 | NonMutatingUseContext::FakeBorrow97 | NonMutatingUseContext::PlaceMention98 | NonMutatingUseContext::Projection) => {}
99 }
100 }
101102fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
103// OK, this is subtle. Suppose that we're trying to deduce whether `x` in `f` is read-only
104 // and we have the following:
105 //
106 // fn f(x: BigStruct) { g(x) }
107 // fn g(mut y: BigStruct) { y.foo = 1 }
108 //
109 // If, at the generated MIR level, `f` turned into something like:
110 //
111 // fn f(_1: BigStruct) -> () {
112 // let mut _0: ();
113 // bb0: {
114 // _0 = g(move _1) -> bb1;
115 // }
116 // ...
117 // }
118 //
119 // then it would be incorrect to mark `x` (i.e. `_1`) as `readonly`, because `g`'s write to
120 // its copy of the indirect parameter would actually be a write directly to the pointer that
121 // `f` passes. Note that function arguments are the only situation in which this problem can
122 // arise: every other use of `move` in MIR doesn't actually write to the value it moves
123 // from.
124match terminator.kind {
125 TerminatorKind::Call { ref args, .. } => {
126for arg in args {
127if let Operand::Move(place) = arg.node
128 && !place.is_indirect_first_projection()
129 && let Some(i) = self.as_param(place.local)
130 {
131self.usage[i] |= UsageSummary::MUTATE;
132self.usage[i] |= UsageSummary::CAPTURE;
133 }
134 }
135 }
136137// Like a call, but more conservative because the backend may introduce writes to an
138 // argument if the argument is passed as `PassMode::Indirect { mode: IndirectMode::Pointer, ... }`.
139TerminatorKind::TailCall { .. } => {
140for usage in self.usage.iter_mut() {
141*usage |= UsageSummary::MUTATE;
142*usage |= UsageSummary::CAPTURE;
143 }
144 }
145_ => {}
146 }
147148self.super_terminator(terminator, location);
149 }
150}
151152/// Returns true if a type could potentially benefit from deduction when passed at ABI level.
153fn type_could_benefit_from_deduction(ty: Ty<'_>) -> bool {
154match ty.kind() {
155// Passed directly
156ty::Bool157 | ty::Char158 | ty::Float(..)
159 | ty::Int(..)
160 | ty::RawPtr(..)
161 | ty::Ref(..)
162 | ty::Slice(..)
163 | ty::Uint(..) => false,
164// Ignored / ZST.
165ty::Tuple(tys) if tys.is_empty() => false,
166_ => true,
167 }
168}
169170/// Returns the deduced parameter attributes for a function.
171///
172/// Deduced parameter attributes are those that can only be soundly determined by examining the
173/// body of the function instead of just the signature. These can be useful for optimization
174/// purposes on a best-effort basis. We compute them here and store them into the crate metadata so
175/// dependent crates can use them.
176{}
let __tracing_attr_span;
let __tracing_attr_guard;
if ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() || { false }
{
__tracing_attr_span =
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("deduced_param_attrs",
"rustc_mir_transform::deduce_param_attrs",
::tracing::Level::TRACE,
::tracing_core::__macro_support::Option::Some("/rustc-dev/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_mir_transform/src/deduce_param_attrs.rs"),
::tracing_core::__macro_support::Option::Some(176u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::deduce_param_attrs"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("def_id")
}> =
::tracing::__macro_support::FieldName::new("def_id");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::SPAN)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let mut interest = ::tracing::subscriber::Interest::never();
if ::tracing::Level::TRACE <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() &&
{ interest = __CALLSITE.interest(); !interest.is_never() }
&&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest) {
let meta = __CALLSITE.metadata();
::tracing::Span::new(meta,
&{
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
meta.fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&def_id)
as &dyn ::tracing::field::Value))])
})
} else {
let span =
::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
{};
span
}
};
__tracing_attr_guard = __tracing_attr_span.enter();
}
#[allow(clippy :: redundant_closure_call)]
let x =
(move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: &'tcx [DeducedParamAttrs] =
loop {};
return __tracing_attr_fake_return;
}
{
if tcx.sess.opts.optimize == OptLevel::No ||
tcx.sess.opts.incremental.is_some() {
return &[];
}
if tcx.lang_items().freeze_trait().is_none() { return &[]; }
let fn_ty =
tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
if #[allow(non_exhaustive_omitted_patterns)] match fn_ty.kind()
{
ty::FnDef(..) => true,
_ => false,
} &&
!fn_ty.fn_sig(tcx).inputs_and_output().skip_binder().iter().any(type_could_benefit_from_deduction)
{
return &[];
}
if !tcx.is_mir_available(def_id) { return &[]; }
if let hir::Constness::Const { always: true } =
tcx.constness(def_id) {
return &[];
}
let body: &Body<'tcx> = tcx.optimized_mir(def_id);
if body.spread_arg.is_some() { return &[]; }
let mut deduce = DeduceParamAttrs::new(body);
deduce.visit_body(body);
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_mir_transform/src/deduce_param_attrs.rs:226",
"rustc_mir_transform::deduce_param_attrs",
::tracing::Level::TRACE,
::tracing_core::__macro_support::Option::Some("/rustc-dev/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_mir_transform/src/deduce_param_attrs.rs"),
::tracing_core::__macro_support::Option::Some(226u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::deduce_param_attrs"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("deduce.usage")
}> =
::tracing::__macro_support::FieldName::new("deduce.usage");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::TRACE <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&deduce.usage)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
let mut deduced_param_attrs: &[_] =
tcx.arena.alloc_from_iter(deduce.usage.into_iter().map(|usage|
DeducedParamAttrs { usage }));
while let Some((last, rest)) =
deduced_param_attrs.split_last() && last.is_default() {
deduced_param_attrs = rest;
}
deduced_param_attrs
}
})();
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_mir_transform/src/deduce_param_attrs.rs:176",
"rustc_mir_transform::deduce_param_attrs",
::tracing::Level::TRACE,
::tracing_core::__macro_support::Option::Some("/rustc-dev/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_mir_transform/src/deduce_param_attrs.rs"),
::tracing_core::__macro_support::Option::Some(176u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::deduce_param_attrs"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("return")
}> =
::tracing::__macro_support::FieldName::new("return");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::TRACE <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&x)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
x;#[tracing::instrument(level = "trace", skip(tcx), ret)]177pub(super) fn deduced_param_attrs<'tcx>(
178 tcx: TyCtxt<'tcx>,
179 def_id: LocalDefId,
180) -> &'tcx [DeducedParamAttrs] {
181// This computation is unfortunately rather expensive, so don't do it unless we're optimizing.
182 // Also skip it in incremental mode.
183if tcx.sess.opts.optimize == OptLevel::No || tcx.sess.opts.incremental.is_some() {
184return &[];
185 }
186187// If the Freeze lang item isn't present, then don't bother.
188if tcx.lang_items().freeze_trait().is_none() {
189return &[];
190 }
191192// Based on a type of inputs and output, we can sometimes quickly identify that deduction
193 // will offers no benefit. Detect that and bail, for compilation speed.
194let fn_ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
195if matches!(fn_ty.kind(), ty::FnDef(..))
196 && !fn_ty
197 .fn_sig(tcx)
198 .inputs_and_output()
199 .skip_binder()
200 .iter()
201 .any(type_could_benefit_from_deduction)
202 {
203return &[];
204 }
205206// Don't deduce any attributes for functions that have no MIR.
207if !tcx.is_mir_available(def_id) {
208return &[];
209 }
210211if let hir::Constness::Const { always: true } = tcx.constness(def_id) {
212// Comptime functions only exist during const eval and can never be passed
213 // to codegen.
214return &[];
215 }
216217// Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
218let body: &Body<'tcx> = tcx.optimized_mir(def_id);
219// Arguments spread at ABI level are currently unsupported.
220if body.spread_arg.is_some() {
221return &[];
222 }
223224let mut deduce = DeduceParamAttrs::new(body);
225 deduce.visit_body(body);
226tracing::trace!(?deduce.usage);
227228let mut deduced_param_attrs: &[_] = tcx
229 .arena
230 .alloc_from_iter(deduce.usage.into_iter().map(|usage| DeducedParamAttrs { usage }));
231232// Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
233 // default set of attributes, so we don't have to store them explicitly. Pop them off to save a
234 // few bytes in metadata.
235while let Some((last, rest)) = deduced_param_attrs.split_last()
236 && last.is_default()
237 {
238 deduced_param_attrs = rest;
239 }
240241 deduced_param_attrs
242}