1 //! Deduces supplementary parameter attributes from MIR.
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.
8 use rustc_hir::def_id::DefId;
9 use rustc_index::bit_set::BitSet;
10 use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
11 use rustc_middle::mir::{Body, Local, Location, Operand, Terminator, TerminatorKind, RETURN_PLACE};
12 use rustc_middle::ty::{self, DeducedParamAttrs, ParamEnv, Ty, TyCtxt};
13 use rustc_session::config::OptLevel;
15 /// A visitor that determines which arguments have been mutated. We can't use the mutability field
16 /// on LocalDecl for this because it has no meaning post-optimization.
17 struct DeduceReadOnly {
18 /// Each bit is indexed by argument number, starting at zero (so 0 corresponds to local decl
19 /// 1). The bit is true if the argument may have been mutated or false if we know it hasn't
20 /// been up to the point we're at.
21 mutable_args: BitSet<usize>,
25 /// Returns a new DeduceReadOnly instance.
26 fn new(arg_count: usize) -> Self {
27 Self { mutable_args: BitSet::new_empty(arg_count) }
31 impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
32 fn visit_local(&mut self, local: Local, mut context: PlaceContext, _: Location) {
33 // We're only interested in arguments.
34 if local == RETURN_PLACE || local.index() > self.mutable_args.domain_size() {
38 // Replace place contexts that are moves with copies. This is safe in all cases except
39 // function argument position, which we already handled in `visit_terminator()` by using the
40 // ArgumentChecker. See the comment in that method for more details.
42 // In the future, we might want to move this out into a separate pass, but for now let's
43 // just do it on the fly because that's faster.
44 if matches!(context, PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)) {
45 context = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy);
49 PlaceContext::MutatingUse(..)
50 | PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => {
51 // This is a mutation, so mark it as such.
52 self.mutable_args.insert(local.index() - 1);
54 PlaceContext::NonMutatingUse(..) | PlaceContext::NonUse(..) => {
55 // Not mutating, so it's fine.
60 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
61 // OK, this is subtle. Suppose that we're trying to deduce whether `x` in `f` is read-only
62 // and we have the following:
64 // fn f(x: BigStruct) { g(x) }
65 // fn g(mut y: BigStruct) { y.foo = 1 }
67 // If, at the generated MIR level, `f` turned into something like:
69 // fn f(_1: BigStruct) -> () {
72 // _0 = g(move _1) -> bb1;
77 // then it would be incorrect to mark `x` (i.e. `_1`) as `readonly`, because `g`'s write to
78 // its copy of the indirect parameter would actually be a write directly to the pointer that
79 // `f` passes. Note that function arguments are the only situation in which this problem can
80 // arise: every other use of `move` in MIR doesn't actually write to the value it moves
83 // Anyway, right now this situation doesn't actually arise in practice. Instead, the MIR for
84 // that function looks like this:
86 // fn f(_1: BigStruct) -> () {
88 // let mut _2: BigStruct;
91 // _0 = g(move _2) -> bb1;
96 // Because of that extra move that MIR construction inserts, `x` (i.e. `_1`) can *in
97 // practice* safely be marked `readonly`.
99 // To handle the possibility that other optimizations (for example, destination propagation)
100 // might someday generate MIR like the first example above, we panic upon seeing an argument
101 // to *our* function that is directly moved into *another* function as an argument. Having
102 // eliminated that problematic case, we can safely treat moves as copies in this analysis.
104 // In the future, if MIR optimizations cause arguments of a caller to be directly moved into
105 // the argument of a callee, we can just add that argument to `mutated_args` instead of
108 // Note that, because the problematic MIR is never actually generated, we can't add a test
111 if let TerminatorKind::Call { ref args, .. } = terminator.kind {
113 if let Operand::Move(place) = *arg {
114 let local = place.local;
115 if place.is_indirect()
116 || local == RETURN_PLACE
117 || local.index() > self.mutable_args.domain_size()
122 self.mutable_args.insert(local.index() - 1);
127 self.super_terminator(terminator, location);
131 /// Returns true if values of a given type will never be passed indirectly, regardless of ABI.
132 fn type_will_always_be_passed_directly(ty: Ty<'_>) -> bool {
146 /// Returns the deduced parameter attributes for a function.
148 /// Deduced parameter attributes are those that can only be soundly determined by examining the
149 /// body of the function instead of just the signature. These can be useful for optimization
150 /// purposes on a best-effort basis. We compute them here and store them into the crate metadata so
151 /// dependent crates can use them.
152 pub fn deduced_param_attrs<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'tcx [DeducedParamAttrs] {
153 // This computation is unfortunately rather expensive, so don't do it unless we're optimizing.
154 // Also skip it in incremental mode.
155 if tcx.sess.opts.optimize == OptLevel::No || tcx.sess.opts.incremental.is_some() {
159 // If the Freeze language item isn't present, then don't bother.
160 if tcx.lang_items().freeze_trait().is_none() {
164 // Codegen won't use this information for anything if all the function parameters are passed
165 // directly. Detect that and bail, for compilation speed.
166 let fn_ty = tcx.type_of(def_id);
167 if matches!(fn_ty.kind(), ty::FnDef(..)) {
174 .all(type_will_always_be_passed_directly)
180 // Don't deduce any attributes for functions that have no MIR.
181 if !tcx.is_mir_available(def_id) {
185 // Deduced attributes for other crates should be read from the metadata instead of via this
187 debug_assert!(def_id.is_local());
189 // Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
190 let body: &Body<'tcx> = tcx.optimized_mir(def_id);
191 let mut deduce_read_only = DeduceReadOnly::new(body.arg_count);
192 deduce_read_only.visit_body(body);
194 // Set the `readonly` attribute for every argument that we concluded is immutable and that
195 // contains no UnsafeCells.
197 // FIXME: This is overly conservative around generic parameters: `is_freeze()` will always
198 // return false for them. For a description of alternatives that could do a better job here,
201 // [1]: https://github.com/rust-lang/rust/pull/103172#discussion_r999139997
202 let mut deduced_param_attrs = tcx.arena.alloc_from_iter(
203 body.local_decls.iter().skip(1).take(body.arg_count).enumerate().map(
204 |(arg_index, local_decl)| DeducedParamAttrs {
205 read_only: !deduce_read_only.mutable_args.contains(arg_index)
206 && local_decl.ty.is_freeze(tcx, ParamEnv::reveal_all()),
211 // Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
212 // default set of attributes, so we don't have to store them explicitly. Pop them off to save a
213 // few bytes in metadata.
214 while deduced_param_attrs.last() == Some(&DeducedParamAttrs::default()) {
215 let last_index = deduced_param_attrs.len() - 1;
216 deduced_param_attrs = &mut deduced_param_attrs[0..last_index];