1
1
//! Inlining pass for MIR functions.
2
2
3
+ use std:: cmp:: Ordering ;
4
+ use std:: collections:: BinaryHeap ;
3
5
use std:: iter;
4
6
use std:: ops:: { Range , RangeFrom } ;
5
7
6
8
use rustc_attr:: InlineAttr ;
7
9
use rustc_hir:: def:: DefKind ;
8
- use rustc_hir:: def_id:: DefId ;
9
10
use rustc_index:: bit_set:: BitSet ;
10
11
use rustc_index:: Idx ;
11
12
use rustc_middle:: bug;
@@ -30,7 +31,7 @@ use crate::validate::validate_types;
30
31
31
32
pub ( crate ) mod cycle;
32
33
33
- const TOP_DOWN_DEPTH_LIMIT : usize = 5 ;
34
+ const MAX_INLINED_CALLEES_PER_BODY : usize = 9 ;
34
35
35
36
// Made public so that `mir_drops_elaborated_and_const_checked` can be overridden
36
37
// by custom rustc drivers, running all the steps by themselves. See #114628.
@@ -102,46 +103,80 @@ fn inline<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool {
102
103
tcx,
103
104
param_env,
104
105
codegen_fn_attrs,
105
- history : Vec :: new ( ) ,
106
- changed : false ,
106
+ queue : BinaryHeap :: new ( ) ,
107
107
caller_is_inline_forwarder : matches ! (
108
108
codegen_fn_attrs. inline,
109
109
InlineAttr :: Hint | InlineAttr :: Always
110
110
) && body_is_forwarder ( body) ,
111
111
} ;
112
- let blocks = START_BLOCK ..body. basic_blocks . next_index ( ) ;
113
- this. process_blocks ( body, blocks) ;
114
- this. changed
112
+ let initial_blocks = START_BLOCK ..body. basic_blocks . next_index ( ) ;
113
+ this. enqueue_new_candidates ( body, initial_blocks) ;
114
+
115
+ let mut changed = false ;
116
+ let mut remaining_cost = tcx. sess . opts . unstable_opts . inline_mir_total_threshold . unwrap_or ( 300 ) ;
117
+ let mut remaining_count = MAX_INLINED_CALLEES_PER_BODY ;
118
+ while remaining_count > 0
119
+ && let Some ( candidate) = this. queue . pop ( )
120
+ {
121
+ let Some ( c) = remaining_cost. checked_sub ( candidate. cost ) else {
122
+ debug ! (
123
+ "next-cheapest candidate has cost {}, but only {} left" ,
124
+ candidate. cost, remaining_cost,
125
+ ) ;
126
+ break ;
127
+ } ;
128
+ remaining_cost = c;
129
+ remaining_count -= 1 ;
130
+
131
+ if let Ok ( new_blocks) = this. try_inline_candidate ( body, candidate) {
132
+ changed = true ;
133
+ this. enqueue_new_candidates ( body, new_blocks) ;
134
+ }
135
+ }
136
+
137
+ changed
115
138
}
116
139
117
140
struct Inliner < ' tcx > {
118
141
tcx : TyCtxt < ' tcx > ,
119
142
param_env : ParamEnv < ' tcx > ,
120
143
/// Caller codegen attributes.
121
144
codegen_fn_attrs : & ' tcx CodegenFnAttrs ,
122
- /// Stack of inlined instances.
123
- /// We only check the `DefId` and not the args because we want to
124
- /// avoid inlining cases of polymorphic recursion.
125
- /// The number of `DefId`s is finite, so checking history is enough
126
- /// to ensure that we do not loop endlessly while inlining.
127
- history : Vec < DefId > ,
128
- /// Indicates that the caller body has been modified.
129
- changed : bool ,
145
+ /// The currently-known inlining candidates.
146
+ queue : BinaryHeap < InlineCandidate < ' tcx > > ,
130
147
/// Indicates that the caller is #[inline] and just calls another function,
131
148
/// and thus we can inline less into it as it'll be inlined itself.
132
149
caller_is_inline_forwarder : bool ,
133
150
}
134
151
152
+ struct InlineCandidate < ' tcx > {
153
+ cost : usize ,
154
+ callsite : CallSite < ' tcx > ,
155
+ callee_body : & ' tcx Body < ' tcx > ,
156
+ destination_ty : Ty < ' tcx > ,
157
+ }
158
+
159
+ /// Smallest-cost is "max" for use in `BinaryHeap`, with ties broken
160
+ /// by preferring the one in the smaller-numbered `BasicBlock`.
161
+ impl Ord for InlineCandidate < ' _ > {
162
+ fn cmp ( & self , other : & Self ) -> Ordering {
163
+ ( other. cost , other. callsite . block ) . cmp ( & ( self . cost , self . callsite . block ) )
164
+ }
165
+ }
166
+ impl PartialOrd for InlineCandidate < ' _ > {
167
+ fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
168
+ Some ( self . cmp ( other) )
169
+ }
170
+ }
171
+ impl Eq for InlineCandidate < ' _ > { }
172
+ impl PartialEq for InlineCandidate < ' _ > {
173
+ fn eq ( & self , other : & Self ) -> bool {
174
+ self . cmp ( other) == Ordering :: Equal
175
+ }
176
+ }
177
+
135
178
impl < ' tcx > Inliner < ' tcx > {
136
- fn process_blocks ( & mut self , caller_body : & mut Body < ' tcx > , blocks : Range < BasicBlock > ) {
137
- // How many callsites in this body are we allowed to inline? We need to limit this in order
138
- // to prevent super-linear growth in MIR size
139
- let inline_limit = match self . history . len ( ) {
140
- 0 => usize:: MAX ,
141
- 1 ..=TOP_DOWN_DEPTH_LIMIT => 1 ,
142
- _ => return ,
143
- } ;
144
- let mut inlined_count = 0 ;
179
+ fn enqueue_new_candidates ( & mut self , caller_body : & Body < ' tcx > , blocks : Range < BasicBlock > ) {
145
180
for bb in blocks {
146
181
let bb_data = & caller_body[ bb] ;
147
182
if bb_data. is_cleanup {
@@ -152,44 +187,23 @@ impl<'tcx> Inliner<'tcx> {
152
187
continue ;
153
188
} ;
154
189
155
- let span = trace_span ! ( "process_blocks" , %callsite. callee, ?bb) ;
156
- let _guard = span. enter ( ) ;
157
-
158
- match self . try_inlining ( caller_body, & callsite) {
159
- Err ( reason) => {
160
- debug ! ( "not-inlined {} [{}]" , callsite. callee, reason) ;
161
- }
162
- Ok ( new_blocks) => {
163
- debug ! ( "inlined {}" , callsite. callee) ;
164
- self . changed = true ;
165
-
166
- self . history . push ( callsite. callee . def_id ( ) ) ;
167
- self . process_blocks ( caller_body, new_blocks) ;
168
- self . history . pop ( ) ;
169
-
170
- inlined_count += 1 ;
171
- if inlined_count == inline_limit {
172
- debug ! ( "inline count reached" ) ;
173
- return ;
174
- }
175
- }
176
- }
190
+ let Ok ( candidate) = self . validate_inlining_candidate ( caller_body, callsite) else {
191
+ continue ;
192
+ } ;
193
+ self . queue . push ( candidate) ;
177
194
}
178
195
}
179
196
180
- /// Attempts to inline a callsite into the caller body. When successful returns basic blocks
181
- /// containing the inlined body. Otherwise returns an error describing why inlining didn't take
182
- /// place.
183
- fn try_inlining (
197
+ fn validate_inlining_candidate (
184
198
& self ,
185
- caller_body : & mut Body < ' tcx > ,
186
- callsite : & CallSite < ' tcx > ,
187
- ) -> Result < std :: ops :: Range < BasicBlock > , & ' static str > {
199
+ caller_body : & Body < ' tcx > ,
200
+ callsite : CallSite < ' tcx > ,
201
+ ) -> Result < InlineCandidate < ' tcx > , & ' static str > {
188
202
self . check_mir_is_available ( caller_body, callsite. callee ) ?;
189
203
190
204
let callee_attrs = self . tcx . codegen_fn_attrs ( callsite. callee . def_id ( ) ) ;
191
205
let cross_crate_inlinable = self . tcx . cross_crate_inlinable ( callsite. callee . def_id ( ) ) ;
192
- self . check_codegen_attributes ( callsite, callee_attrs, cross_crate_inlinable) ?;
206
+ self . check_codegen_attributes ( & callsite, callee_attrs, cross_crate_inlinable) ?;
193
207
194
208
// Intrinsic fallback bodies are automatically made cross-crate inlineable,
195
209
// but at this stage we don't know whether codegen knows the intrinsic,
@@ -210,7 +224,20 @@ impl<'tcx> Inliner<'tcx> {
210
224
}
211
225
212
226
let callee_body = try_instance_mir ( self . tcx , callsite. callee . def ) ?;
213
- self . check_mir_body ( callsite, callee_body, callee_attrs, cross_crate_inlinable) ?;
227
+ let cost =
228
+ self . check_mir_body ( & callsite, callee_body, callee_attrs, cross_crate_inlinable) ?;
229
+ Ok ( InlineCandidate { cost, callsite, callee_body, destination_ty } )
230
+ }
231
+
232
+ /// Attempts to inline a callsite into the caller body. When successful returns basic blocks
233
+ /// containing the inlined body. Otherwise returns an error describing why inlining didn't take
234
+ /// place.
235
+ fn try_inline_candidate (
236
+ & self ,
237
+ caller_body : & mut Body < ' tcx > ,
238
+ candidate : InlineCandidate < ' tcx > ,
239
+ ) -> Result < std:: ops:: Range < BasicBlock > , & ' static str > {
240
+ let InlineCandidate { callsite, callee_body, destination_ty, .. } = candidate;
214
241
215
242
if !self . tcx . consider_optimizing ( || {
216
243
format ! ( "Inline {:?} into {:?}" , callsite. callee, caller_body. source)
@@ -249,6 +276,10 @@ impl<'tcx> Inliner<'tcx> {
249
276
trace ! ( ?output_type, ?destination_ty) ;
250
277
return Err ( "failed to normalize return type" ) ;
251
278
}
279
+
280
+ let terminator = caller_body[ callsite. block ] . terminator . as_ref ( ) . unwrap ( ) ;
281
+ let TerminatorKind :: Call { args, .. } = & terminator. kind else { bug ! ( ) } ;
282
+
252
283
if callsite. fn_sig . abi ( ) == Abi :: RustCall {
253
284
// FIXME: Don't inline user-written `extern "rust-call"` functions,
254
285
// since this is generally perf-negative on rustc, and we hope that
@@ -294,7 +325,7 @@ impl<'tcx> Inliner<'tcx> {
294
325
}
295
326
296
327
let old_blocks = caller_body. basic_blocks . next_index ( ) ;
297
- self . inline_call ( caller_body, callsite, callee_body) ;
328
+ self . inline_call ( caller_body, & callsite, callee_body) ;
298
329
let new_blocks = old_blocks..caller_body. basic_blocks . next_index ( ) ;
299
330
300
331
Ok ( new_blocks)
@@ -396,10 +427,6 @@ impl<'tcx> Inliner<'tcx> {
396
427
return None ;
397
428
}
398
429
399
- if self . history . contains ( & callee. def_id ( ) ) {
400
- return None ;
401
- }
402
-
403
430
let fn_sig = self . tcx . fn_sig ( def_id) . instantiate ( self . tcx , args) ;
404
431
405
432
// Additionally, check that the body that we're inlining actually agrees
@@ -493,7 +520,7 @@ impl<'tcx> Inliner<'tcx> {
493
520
callee_body : & Body < ' tcx > ,
494
521
callee_attrs : & CodegenFnAttrs ,
495
522
cross_crate_inlinable : bool ,
496
- ) -> Result < ( ) , & ' static str > {
523
+ ) -> Result < usize , & ' static str > {
497
524
let tcx = self . tcx ;
498
525
499
526
if let Some ( _) = callee_body. tainted_by_errors {
@@ -573,7 +600,7 @@ impl<'tcx> Inliner<'tcx> {
573
600
let cost = checker. cost ( ) ;
574
601
if cost <= threshold {
575
602
debug ! ( "INLINING {:?} [cost={} <= threshold={}]" , callsite, cost, threshold) ;
576
- Ok ( ( ) )
603
+ Ok ( cost )
577
604
} else {
578
605
debug ! ( "NOT inlining {:?} [cost={} > threshold={}]" , callsite, cost, threshold) ;
579
606
Err ( "cost above threshold" )
0 commit comments