1
1
//! Inlining pass for MIR functions.
2
2
3
+ use std:: assert_matches:: debug_assert_matches;
3
4
use std:: iter;
4
5
use std:: ops:: { Range , RangeFrom } ;
5
6
@@ -18,14 +19,15 @@ use rustc_session::config::{DebugInfo, OptLevel};
18
19
use rustc_span:: source_map:: Spanned ;
19
20
use tracing:: { debug, instrument, trace, trace_span} ;
20
21
21
- use crate :: cost_checker:: CostChecker ;
22
+ use crate :: cost_checker:: { CostChecker , is_call_like } ;
22
23
use crate :: deref_separator:: deref_finder;
23
24
use crate :: simplify:: simplify_cfg;
24
25
use crate :: validate:: validate_types;
25
26
use crate :: { check_inline, util} ;
26
27
27
28
pub ( crate ) mod cycle;
28
29
30
+ const HISTORY_DEPTH_LIMIT : usize = 20 ;
29
31
const TOP_DOWN_DEPTH_LIMIT : usize = 5 ;
30
32
31
33
#[ derive( Clone , Debug ) ]
@@ -117,6 +119,11 @@ trait Inliner<'tcx> {
117
119
/// Should inlining happen for a given callee?
118
120
fn should_inline_for_callee ( & self , def_id : DefId ) -> bool ;
119
121
122
+ fn check_codegen_attributes_extra (
123
+ & self ,
124
+ callee_attrs : & CodegenFnAttrs ,
125
+ ) -> Result < ( ) , & ' static str > ;
126
+
120
127
fn check_caller_mir_body ( & self , body : & Body < ' tcx > ) -> bool ;
121
128
122
129
/// Returns inlining decision that is based on the examination of callee MIR body.
@@ -128,10 +135,6 @@ trait Inliner<'tcx> {
128
135
callee_attrs : & CodegenFnAttrs ,
129
136
) -> Result < ( ) , & ' static str > ;
130
137
131
- // How many callsites in a body are we allowed to inline? We need to limit this in order
132
- // to prevent super-linear growth in MIR size.
133
- fn inline_limit_for_block ( & self ) -> Option < usize > ;
134
-
135
138
/// Called when inlining succeeds.
136
139
fn on_inline_success (
137
140
& mut self ,
@@ -142,9 +145,6 @@ trait Inliner<'tcx> {
142
145
143
146
/// Called when inlining failed or was not performed.
144
147
fn on_inline_failure ( & self , callsite : & CallSite < ' tcx > , reason : & ' static str ) ;
145
-
146
- /// Called when the inline limit for a body is reached.
147
- fn on_inline_limit_reached ( & self ) -> bool ;
148
148
}
149
149
150
150
struct ForceInliner < ' tcx > {
@@ -191,6 +191,14 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
191
191
ForceInline :: should_run_pass_for_callee ( self . tcx ( ) , def_id)
192
192
}
193
193
194
+ fn check_codegen_attributes_extra (
195
+ & self ,
196
+ callee_attrs : & CodegenFnAttrs ,
197
+ ) -> Result < ( ) , & ' static str > {
198
+ debug_assert_matches ! ( callee_attrs. inline, InlineAttr :: Force { .. } ) ;
199
+ Ok ( ( ) )
200
+ }
201
+
194
202
fn check_caller_mir_body ( & self , _: & Body < ' tcx > ) -> bool {
195
203
true
196
204
}
@@ -224,10 +232,6 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
224
232
}
225
233
}
226
234
227
- fn inline_limit_for_block ( & self ) -> Option < usize > {
228
- Some ( usize:: MAX )
229
- }
230
-
231
235
fn on_inline_success (
232
236
& mut self ,
233
237
callsite : & CallSite < ' tcx > ,
@@ -261,10 +265,6 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
261
265
justification : justification. map ( |sym| crate :: errors:: ForceInlineJustification { sym } ) ,
262
266
} ) ;
263
267
}
264
-
265
- fn on_inline_limit_reached ( & self ) -> bool {
266
- false
267
- }
268
268
}
269
269
270
270
struct NormalInliner < ' tcx > {
@@ -278,13 +278,23 @@ struct NormalInliner<'tcx> {
278
278
/// The number of `DefId`s is finite, so checking history is enough
279
279
/// to ensure that we do not loop endlessly while inlining.
280
280
history : Vec < DefId > ,
281
+ /// How many (multi-call) callsites have we inlined for the top-level call?
282
+ ///
283
+ /// We need to limit this in order to prevent super-linear growth in MIR size.
284
+ top_down_counter : usize ,
281
285
/// Indicates that the caller body has been modified.
282
286
changed : bool ,
283
287
/// Indicates that the caller is #[inline] and just calls another function,
284
288
/// and thus we can inline less into it as it'll be inlined itself.
285
289
caller_is_inline_forwarder : bool ,
286
290
}
287
291
292
+ impl < ' tcx > NormalInliner < ' tcx > {
293
+ fn past_depth_limit ( & self ) -> bool {
294
+ self . history . len ( ) > HISTORY_DEPTH_LIMIT || self . top_down_counter > TOP_DOWN_DEPTH_LIMIT
295
+ }
296
+ }
297
+
288
298
impl < ' tcx > Inliner < ' tcx > for NormalInliner < ' tcx > {
289
299
fn new ( tcx : TyCtxt < ' tcx > , def_id : DefId , body : & Body < ' tcx > ) -> Self {
290
300
let typing_env = body. typing_env ( tcx) ;
@@ -295,6 +305,7 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
295
305
typing_env,
296
306
def_id,
297
307
history : Vec :: new ( ) ,
308
+ top_down_counter : 0 ,
298
309
changed : false ,
299
310
caller_is_inline_forwarder : matches ! (
300
311
codegen_fn_attrs. inline,
@@ -327,6 +338,17 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
327
338
true
328
339
}
329
340
341
+ fn check_codegen_attributes_extra (
342
+ & self ,
343
+ callee_attrs : & CodegenFnAttrs ,
344
+ ) -> Result < ( ) , & ' static str > {
345
+ if self . past_depth_limit ( ) && matches ! ( callee_attrs. inline, InlineAttr :: None ) {
346
+ Err ( "Past depth limit so not inspecting unmarked callee" )
347
+ } else {
348
+ Ok ( ( ) )
349
+ }
350
+ }
351
+
330
352
fn check_caller_mir_body ( & self , body : & Body < ' tcx > ) -> bool {
331
353
// Avoid inlining into coroutines, since their `optimized_mir` is used for layout computation,
332
354
// which can create a cycle, even when no attempt is made to inline the function in the other
@@ -351,7 +373,11 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
351
373
return Err ( "body has errors" ) ;
352
374
}
353
375
354
- let mut threshold = if self . caller_is_inline_forwarder {
376
+ if self . past_depth_limit ( ) && callee_body. basic_blocks . len ( ) > 1 {
377
+ return Err ( "Not inlining multi-block body as we're past a depth limit" ) ;
378
+ }
379
+
380
+ let mut threshold = if self . caller_is_inline_forwarder || self . past_depth_limit ( ) {
355
381
tcx. sess . opts . unstable_opts . inline_mir_forwarder_threshold . unwrap_or ( 30 )
356
382
} else if tcx. cross_crate_inlinable ( callsite. callee . def_id ( ) ) {
357
383
tcx. sess . opts . unstable_opts . inline_mir_hint_threshold . unwrap_or ( 100 )
@@ -431,14 +457,6 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
431
457
}
432
458
}
433
459
434
- fn inline_limit_for_block ( & self ) -> Option < usize > {
435
- match self . history . len ( ) {
436
- 0 => Some ( usize:: MAX ) ,
437
- 1 ..=TOP_DOWN_DEPTH_LIMIT => Some ( 1 ) ,
438
- _ => None ,
439
- }
440
- }
441
-
442
460
fn on_inline_success (
443
461
& mut self ,
444
462
callsite : & CallSite < ' tcx > ,
@@ -447,13 +465,21 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
447
465
) {
448
466
self . changed = true ;
449
467
468
+ let new_calls_count = new_blocks
469
+ . clone ( )
470
+ . filter ( |& bb| is_call_like ( caller_body. basic_blocks [ bb] . terminator ( ) ) )
471
+ . count ( ) ;
472
+ if new_calls_count > 1 {
473
+ self . top_down_counter += 1 ;
474
+ }
475
+
450
476
self . history . push ( callsite. callee . def_id ( ) ) ;
451
477
process_blocks ( self , caller_body, new_blocks) ;
452
478
self . history . pop ( ) ;
453
- }
454
479
455
- fn on_inline_limit_reached ( & self ) -> bool {
456
- true
480
+ if self . history . is_empty ( ) {
481
+ self . top_down_counter = 0 ;
482
+ }
457
483
}
458
484
459
485
fn on_inline_failure ( & self , _: & CallSite < ' tcx > , _: & ' static str ) { }
@@ -482,8 +508,6 @@ fn process_blocks<'tcx, I: Inliner<'tcx>>(
482
508
caller_body : & mut Body < ' tcx > ,
483
509
blocks : Range < BasicBlock > ,
484
510
) {
485
- let Some ( inline_limit) = inliner. inline_limit_for_block ( ) else { return } ;
486
- let mut inlined_count = 0 ;
487
511
for bb in blocks {
488
512
let bb_data = & caller_body[ bb] ;
489
513
if bb_data. is_cleanup {
@@ -505,13 +529,6 @@ fn process_blocks<'tcx, I: Inliner<'tcx>>(
505
529
Ok ( new_blocks) => {
506
530
debug ! ( "inlined {}" , callsite. callee) ;
507
531
inliner. on_inline_success ( & callsite, caller_body, new_blocks) ;
508
-
509
- inlined_count += 1 ;
510
- if inlined_count == inline_limit {
511
- if inliner. on_inline_limit_reached ( ) {
512
- return ;
513
- }
514
- }
515
532
}
516
533
}
517
534
}
@@ -584,6 +601,7 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
584
601
let callee_attrs = tcx. codegen_fn_attrs ( callsite. callee . def_id ( ) ) ;
585
602
check_inline:: is_inline_valid_on_fn ( tcx, callsite. callee . def_id ( ) ) ?;
586
603
check_codegen_attributes ( inliner, callsite, callee_attrs) ?;
604
+ inliner. check_codegen_attributes_extra ( callee_attrs) ?;
587
605
588
606
let terminator = caller_body[ callsite. block ] . terminator . as_ref ( ) . unwrap ( ) ;
589
607
let TerminatorKind :: Call { args, destination, .. } = & terminator. kind else { bug ! ( ) } ;
@@ -770,6 +788,8 @@ fn check_codegen_attributes<'tcx, I: Inliner<'tcx>>(
770
788
return Err ( "has DoNotOptimize attribute" ) ;
771
789
}
772
790
791
+ inliner. check_codegen_attributes_extra ( callee_attrs) ?;
792
+
773
793
// Reachability pass defines which functions are eligible for inlining. Generally inlining
774
794
// other functions is incorrect because they could reference symbols that aren't exported.
775
795
let is_generic = callsite. callee . args . non_erasable_generics ( ) . next ( ) . is_some ( ) ;
0 commit comments