88use rustc_hir:: def_id:: LocalDefId ;
99use rustc_index:: bit_set:: DenseBitSet ;
1010use 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+ } ;
1214use rustc_middle:: ty:: { self , DeducedParamAttrs , Ty , TyCtxt } ;
1315use rustc_session:: config:: OptLevel ;
16+ use tracing:: instrument;
1417
1518/// A visitor that determines which arguments have been mutated. We can't use the mutability field
1619/// on LocalDecl for this because it has no meaning post-optimization.
1720struct DeduceReadOnly {
1821 /// Each bit is indexed by argument number, starting at zero (so 0 corresponds to local decl
1922 /// 1). The bit is true if the argument may have been mutated or false if we know it hasn't
2023 /// 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 > ,
2227}
2328
2429impl DeduceReadOnly {
2530 /// Returns a new DeduceReadOnly instance.
2631 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+ }
2845 }
2946}
3047
3148impl < ' tcx > Visitor < ' tcx > for DeduceReadOnly {
3249 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 ( ) {
3551 return ;
3652 }
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 {
3956 PlaceContext :: MutatingUse ( ..) => {
4057 // This is a mutation, so mark it as such.
41- true
58+ self . read_only . remove ( arg_index ) ;
4259 }
4360 PlaceContext :: NonMutatingUse ( NonMutatingUseContext :: RawBorrow ) => {
4461 // 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) ;
4964 }
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) ;
5367 }
68+ PlaceContext :: NonMutatingUse ( ..) | PlaceContext :: NonUse ( ..) => { }
5469 } ;
55-
56- if mark_as_mutable {
57- self . mutable_args . insert ( place. local . index ( ) - 1 ) ;
58- }
5970 }
6071
6172 fn visit_terminator ( & mut self , terminator : & Terminator < ' tcx > , location : Location ) {
@@ -83,18 +94,23 @@ impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
8394 if let TerminatorKind :: Call { ref args, .. } = terminator. kind {
8495 for arg in args {
8596 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 ( ) {
9198 continue ;
9299 }
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+ }
95103 }
96104 }
97105 } ;
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+ }
98114
99115 self . super_terminator ( terminator, location) ;
100116 }
@@ -121,6 +137,7 @@ fn type_will_always_be_passed_directly(ty: Ty<'_>) -> bool {
121137/// body of the function instead of just the signature. These can be useful for optimization
122138/// purposes on a best-effort basis. We compute them here and store them into the crate metadata so
123139/// dependent crates can use them.
140+ #[ instrument( level = "debug" , ret, skip( tcx) ) ]
124141pub ( super ) fn deduced_param_attrs < ' tcx > (
125142 tcx : TyCtxt < ' tcx > ,
126143 def_id : LocalDefId ,
@@ -158,31 +175,19 @@ pub(super) fn deduced_param_attrs<'tcx>(
158175
159176 // Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
160177 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) ;
163180
164181 // Set the `readonly` attribute for every argument that we concluded is immutable and that
165182 // 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+ } ) ) ;
186191
187192 // Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
188193 // default set of attributes, so we don't have to store them explicitly. Pop them off to save a
0 commit comments