]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_transform/src/deduce_param_attrs.rs
Rollup merge of #102977 - lukas-code:is-sorted-hrtb, r=m-ou-se
[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(_) = *arg {
114                     // ArgumentChecker panics if a direct move of an argument from a caller to a
115                     // callee was detected.
116                     //
117                     // If, in the future, MIR optimizations cause arguments to be moved directly
118                     // from callers to callees, change the panic to instead add the argument in
119                     // question to `mutating_uses`.
120                     ArgumentChecker::new(self.mutable_args.domain_size())
121                         .visit_operand(arg, location)
122                 }
123             }
124         };
125
126         self.super_terminator(terminator, location);
127     }
128 }
129
130 /// A visitor that simply panics if a direct move of an argument from a caller to a callee was
131 /// detected.
132 struct ArgumentChecker {
133     /// The number of arguments to the calling function.
134     arg_count: usize,
135 }
136
137 impl ArgumentChecker {
138     /// Creates a new ArgumentChecker.
139     fn new(arg_count: usize) -> Self {
140         Self { arg_count }
141     }
142 }
143
144 impl<'tcx> Visitor<'tcx> for ArgumentChecker {
145     fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
146         // Check to make sure that, if this local is an argument, we didn't move directly from it.
147         if matches!(context, PlaceContext::NonMutatingUse(NonMutatingUseContext::Move))
148             && local != RETURN_PLACE
149             && local.index() <= self.arg_count
150         {
151             // If, in the future, MIR optimizations cause arguments to be moved directly from
152             // callers to callees, change this panic to instead add the argument in question to
153             // `mutating_uses`.
154             panic!("Detected a direct move from a caller's argument to a callee's argument!")
155         }
156     }
157 }
158
159 /// Returns true if values of a given type will never be passed indirectly, regardless of ABI.
160 fn type_will_always_be_passed_directly<'tcx>(ty: Ty<'tcx>) -> bool {
161     matches!(
162         ty.kind(),
163         ty::Bool
164             | ty::Char
165             | ty::Float(..)
166             | ty::Int(..)
167             | ty::RawPtr(..)
168             | ty::Ref(..)
169             | ty::Slice(..)
170             | ty::Uint(..)
171     )
172 }
173
174 /// Returns the deduced parameter attributes for a function.
175 ///
176 /// Deduced parameter attributes are those that can only be soundly determined by examining the
177 /// body of the function instead of just the signature. These can be useful for optimization
178 /// purposes on a best-effort basis. We compute them here and store them into the crate metadata so
179 /// dependent crates can use them.
180 pub fn deduced_param_attrs<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'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.
183     if tcx.sess.opts.optimize == OptLevel::No || tcx.sess.opts.incremental.is_some() {
184         return &[];
185     }
186
187     // If the Freeze language item isn't present, then don't bother.
188     if tcx.lang_items().freeze_trait().is_none() {
189         return &[];
190     }
191
192     // Codegen won't use this information for anything if all the function parameters are passed
193     // directly. Detect that and bail, for compilation speed.
194     let fn_ty = tcx.type_of(def_id);
195     if matches!(fn_ty.kind(), ty::FnDef(..)) {
196         if fn_ty
197             .fn_sig(tcx)
198             .inputs()
199             .skip_binder()
200             .iter()
201             .cloned()
202             .all(type_will_always_be_passed_directly)
203         {
204             return &[];
205         }
206     }
207
208     // Don't deduce any attributes for functions that have no MIR.
209     if !tcx.is_mir_available(def_id) {
210         return &[];
211     }
212
213     // Deduced attributes for other crates should be read from the metadata instead of via this
214     // function.
215     debug_assert!(def_id.is_local());
216
217     // Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
218     let body: &Body<'tcx> = tcx.optimized_mir(def_id);
219     let mut deduce_read_only = DeduceReadOnly::new(body.arg_count);
220     deduce_read_only.visit_body(body);
221
222     // Set the `readonly` attribute for every argument that we concluded is immutable and that
223     // contains no UnsafeCells.
224     //
225     // FIXME: This is overly conservative around generic parameters: `is_freeze()` will always
226     // return false for them. For a description of alternatives that could do a better job here,
227     // see [1].
228     //
229     // [1]: https://github.com/rust-lang/rust/pull/103172#discussion_r999139997
230     let mut deduced_param_attrs = tcx.arena.alloc_from_iter(
231         body.local_decls.iter().skip(1).take(body.arg_count).enumerate().map(
232             |(arg_index, local_decl)| DeducedParamAttrs {
233                 read_only: !deduce_read_only.mutable_args.contains(arg_index)
234                     && local_decl.ty.is_freeze(tcx, ParamEnv::reveal_all()),
235             },
236         ),
237     );
238
239     // Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
240     // default set of attributes, so we don't have to store them explicitly. Pop them off to save a
241     // few bytes in metadata.
242     while deduced_param_attrs.last() == Some(&DeducedParamAttrs::default()) {
243         let last_index = deduced_param_attrs.len() - 1;
244         deduced_param_attrs = &mut deduced_param_attrs[0..last_index];
245     }
246
247     deduced_param_attrs
248 }