8
8
use rustc_hir:: def_id:: LocalDefId ;
9
9
use rustc_index:: bit_set:: DenseBitSet ;
10
10
use rustc_middle:: mir:: visit:: { NonMutatingUseContext , PlaceContext , Visitor } ;
11
- use rustc_middle:: mir:: { Body , Location , Operand , Place , RETURN_PLACE , Terminator , TerminatorKind } ;
11
+ use rustc_middle:: mir:: {
12
+ Body , Local , Location , Operand , Place , RETURN_PLACE , Terminator , TerminatorKind ,
13
+ } ;
12
14
use rustc_middle:: ty:: { self , DeducedParamAttrs , Ty , TyCtxt } ;
13
15
use rustc_session:: config:: OptLevel ;
16
+ use tracing:: instrument;
14
17
15
18
/// A visitor that determines which arguments have been mutated. We can't use the mutability field
16
19
/// on LocalDecl for this because it has no meaning post-optimization.
17
20
struct DeduceReadOnly {
18
21
/// Each bit is indexed by argument number, starting at zero (so 0 corresponds to local decl
19
22
/// 1). The bit is true if the argument may have been mutated or false if we know it hasn't
20
23
/// been up to the point we're at.
21
- mutable_args : DenseBitSet < usize > ,
24
+ read_only : DenseBitSet < usize > ,
25
+ requires_freeze : DenseBitSet < usize > ,
26
+ requires_nop_drop : DenseBitSet < usize > ,
22
27
}
23
28
24
29
impl DeduceReadOnly {
25
30
/// Returns a new DeduceReadOnly instance.
26
31
fn new ( arg_count : usize ) -> Self {
27
- Self { mutable_args : DenseBitSet :: new_empty ( arg_count) }
32
+ Self {
33
+ read_only : DenseBitSet :: new_filled ( arg_count) ,
34
+ requires_freeze : DenseBitSet :: new_empty ( arg_count) ,
35
+ requires_nop_drop : DenseBitSet :: new_empty ( arg_count) ,
36
+ }
37
+ }
38
+
39
+ fn arg_index ( & self , local : Local ) -> Option < usize > {
40
+ if local == RETURN_PLACE || local. index ( ) > self . read_only . domain_size ( ) {
41
+ None
42
+ } else {
43
+ Some ( local. index ( ) - 1 )
44
+ }
28
45
}
29
46
}
30
47
31
48
impl < ' tcx > Visitor < ' tcx > for DeduceReadOnly {
32
49
fn visit_place ( & mut self , place : & Place < ' tcx > , context : PlaceContext , _location : Location ) {
33
- // We're only interested in arguments.
34
- if place. local == RETURN_PLACE || place. local . index ( ) > self . mutable_args . domain_size ( ) {
50
+ if place. is_indirect ( ) {
35
51
return ;
36
52
}
37
-
38
- let mark_as_mutable = match context {
53
+ // We're only interested in arguments.
54
+ let Some ( arg_index) = self . arg_index ( place. local ) else { return } ;
55
+ match context {
39
56
PlaceContext :: MutatingUse ( ..) => {
40
57
// This is a mutation, so mark it as such.
41
- true
58
+ self . read_only . remove ( arg_index ) ;
42
59
}
43
60
PlaceContext :: NonMutatingUse ( NonMutatingUseContext :: RawBorrow ) => {
44
61
// Whether mutating though a `&raw const` is allowed is still undecided, so we
45
- // disable any sketchy `readonly` optimizations for now. But we only need to do
46
- // this if the pointer would point into the argument. IOW: for indirect places,
47
- // like `&raw (*local).field`, this surely cannot mutate `local`.
48
- !place. is_indirect ( )
62
+ // disable any sketchy `readonly` optimizations for now.
63
+ self . read_only . remove ( arg_index) ;
49
64
}
50
- PlaceContext :: NonMutatingUse ( ..) | PlaceContext :: NonUse ( ..) => {
51
- // Not mutating, so it's fine.
52
- false
65
+ PlaceContext :: NonMutatingUse ( NonMutatingUseContext :: SharedBorrow ) => {
66
+ self . requires_freeze . insert ( arg_index) ;
53
67
}
68
+ PlaceContext :: NonMutatingUse ( ..) | PlaceContext :: NonUse ( ..) => { }
54
69
} ;
55
-
56
- if mark_as_mutable {
57
- self . mutable_args . insert ( place. local . index ( ) - 1 ) ;
58
- }
59
70
}
60
71
61
72
fn visit_terminator ( & mut self , terminator : & Terminator < ' tcx > , location : Location ) {
@@ -83,18 +94,23 @@ impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
83
94
if let TerminatorKind :: Call { ref args, .. } = terminator. kind {
84
95
for arg in args {
85
96
if let Operand :: Move ( place) = arg. node {
86
- let local = place. local ;
87
- if place. is_indirect ( )
88
- || local == RETURN_PLACE
89
- || local. index ( ) > self . mutable_args . domain_size ( )
90
- {
97
+ if place. is_indirect ( ) {
91
98
continue ;
92
99
}
93
-
94
- self . mutable_args . insert ( local. index ( ) - 1 ) ;
100
+ if let Some ( arg_index) = self . arg_index ( place. local ) {
101
+ self . read_only . remove ( arg_index) ;
102
+ }
95
103
}
96
104
}
97
105
} ;
106
+ if let TerminatorKind :: Drop { place, .. } = terminator. kind {
107
+ if let Some ( local) = place. as_local ( )
108
+ && let Some ( arg_index) = self . arg_index ( local)
109
+ {
110
+ self . requires_nop_drop . insert ( arg_index) ;
111
+ return ;
112
+ }
113
+ }
98
114
99
115
self . super_terminator ( terminator, location) ;
100
116
}
@@ -121,6 +137,7 @@ fn type_will_always_be_passed_directly(ty: Ty<'_>) -> bool {
121
137
/// body of the function instead of just the signature. These can be useful for optimization
122
138
/// purposes on a best-effort basis. We compute them here and store them into the crate metadata so
123
139
/// dependent crates can use them.
140
+ #[ instrument( level = "debug" , ret, skip( tcx) ) ]
124
141
pub ( super ) fn deduced_param_attrs < ' tcx > (
125
142
tcx : TyCtxt < ' tcx > ,
126
143
def_id : LocalDefId ,
@@ -158,31 +175,19 @@ pub(super) fn deduced_param_attrs<'tcx>(
158
175
159
176
// Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
160
177
let body: & Body < ' tcx > = tcx. optimized_mir ( def_id) ;
161
- let mut deduce_read_only = DeduceReadOnly :: new ( body. arg_count ) ;
162
- deduce_read_only . visit_body ( body) ;
178
+ let mut deduce = DeduceReadOnly :: new ( body. arg_count ) ;
179
+ deduce . visit_body ( body) ;
163
180
164
181
// Set the `readonly` attribute for every argument that we concluded is immutable and that
165
182
// contains no UnsafeCells.
166
- //
167
- // FIXME: This is overly conservative around generic parameters: `is_freeze()` will always
168
- // return false for them. For a description of alternatives that could do a better job here,
169
- // see [1].
170
- //
171
- // [1]: https://github.com/rust-lang/rust/pull/103172#discussion_r999139997
172
- let typing_env = body. typing_env ( tcx) ;
173
- let mut deduced_param_attrs = tcx. arena . alloc_from_iter (
174
- body. local_decls . iter ( ) . skip ( 1 ) . take ( body. arg_count ) . enumerate ( ) . map (
175
- |( arg_index, local_decl) | DeducedParamAttrs {
176
- read_only : !deduce_read_only. mutable_args . contains ( arg_index)
177
- // We must normalize here to reveal opaques and normalize
178
- // their generic parameters, otherwise we'll see exponential
179
- // blow-up in compile times: #113372
180
- && tcx
181
- . normalize_erasing_regions ( typing_env, local_decl. ty )
182
- . is_freeze ( tcx, typing_env) ,
183
- } ,
184
- ) ,
185
- ) ;
183
+ let mut deduced_param_attrs = tcx. arena . alloc_from_iter ( ( 0 ..body. arg_count ) . map ( |arg_index| {
184
+ let read_only = deduce. read_only . contains ( arg_index) ;
185
+ DeducedParamAttrs {
186
+ read_only,
187
+ requires_freeze : read_only && deduce. requires_freeze . contains ( arg_index) ,
188
+ requires_nop_drop : read_only && deduce. requires_nop_drop . contains ( arg_index) ,
189
+ }
190
+ } ) ) ;
186
191
187
192
// Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
188
193
// default set of attributes, so we don't have to store them explicitly. Pop them off to save a
0 commit comments