]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_transform/src/deduce_param_attrs.rs
Rollup merge of #105655 - RedDocMD:bug-105645, r=oli-obk
[rust.git] / compiler / rustc_mir_transform / src / 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 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;
14
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>,
22 }
23
24 impl DeduceReadOnly {
25     /// Returns a new DeduceReadOnly instance.
26     fn new(arg_count: usize) -> Self {
27         Self { mutable_args: BitSet::new_empty(arg_count) }
28     }
29 }
30
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() {
35             return;
36         }
37
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.
41         //
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);
46         }
47
48         match context {
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);
53             }
54             PlaceContext::NonMutatingUse(..) | PlaceContext::NonUse(..) => {
55                 // Not mutating, so it's fine.
56             }
57         }
58     }
59
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:
63         //
64         //     fn f(x: BigStruct) { g(x) }
65         //     fn g(mut y: BigStruct) { y.foo = 1 }
66         //
67         // If, at the generated MIR level, `f` turned into something like:
68         //
69         //      fn f(_1: BigStruct) -> () {
70         //          let mut _0: ();
71         //          bb0: {
72         //              _0 = g(move _1) -> bb1;
73         //          }
74         //          ...
75         //      }
76         //
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
81         // from.
82         //
83         // Anyway, right now this situation doesn't actually arise in practice. Instead, the MIR for
84         // that function looks like this:
85         //
86         //      fn f(_1: BigStruct) -> () {
87         //          let mut _0: ();
88         //          let mut _2: BigStruct;
89         //          bb0: {
90         //              _2 = move _1;
91         //              _0 = g(move _2) -> bb1;
92         //          }
93         //          ...
94         //      }
95         //
96         // Because of that extra move that MIR construction inserts, `x` (i.e. `_1`) can *in
97         // practice* safely be marked `readonly`.
98         //
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.
103         //
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
106         // panicking.
107         //
108         // Note that, because the problematic MIR is never actually generated, we can't add a test
109         // case for this.
110
111         if let TerminatorKind::Call { ref args, .. } = terminator.kind {
112             for arg in args {
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()
118                     {
119                         continue;
120                     }
121
122                     self.mutable_args.insert(local.index() - 1);
123                 }
124             }
125         };
126
127         self.super_terminator(terminator, location);
128     }
129 }
130
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 {
133     matches!(
134         ty.kind(),
135         ty::Bool
136             | ty::Char
137             | ty::Float(..)
138             | ty::Int(..)
139             | ty::RawPtr(..)
140             | ty::Ref(..)
141             | ty::Slice(..)
142             | ty::Uint(..)
143     )
144 }
145
146 /// Returns the deduced parameter attributes for a function.
147 ///
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() {
156         return &[];
157     }
158
159     // If the Freeze language item isn't present, then don't bother.
160     if tcx.lang_items().freeze_trait().is_none() {
161         return &[];
162     }
163
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(..)) {
168         if fn_ty
169             .fn_sig(tcx)
170             .inputs()
171             .skip_binder()
172             .iter()
173             .cloned()
174             .all(type_will_always_be_passed_directly)
175         {
176             return &[];
177         }
178     }
179
180     // Don't deduce any attributes for functions that have no MIR.
181     if !tcx.is_mir_available(def_id) {
182         return &[];
183     }
184
185     // Deduced attributes for other crates should be read from the metadata instead of via this
186     // function.
187     debug_assert!(def_id.is_local());
188
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);
193
194     // Set the `readonly` attribute for every argument that we concluded is immutable and that
195     // contains no UnsafeCells.
196     //
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,
199     // see [1].
200     //
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()),
207             },
208         ),
209     );
210
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];
217     }
218
219     deduced_param_attrs
220 }