@@ -26,6 +26,7 @@ use crate::{check_inline, util};
26
26
27
27
pub ( crate ) mod cycle;
28
28
29
+ const HISTORY_DEPTH_LIMIT : usize = 20 ;
29
30
const TOP_DOWN_DEPTH_LIMIT : usize = 5 ;
30
31
31
32
#[ derive( Clone , Debug ) ]
@@ -117,6 +118,13 @@ trait Inliner<'tcx> {
117
118
/// Should inlining happen for a given callee?
118
119
fn should_inline_for_callee ( & self , def_id : DefId ) -> bool ;
119
120
121
+ fn check_codegen_attributes_extra (
122
+ & self ,
123
+ _callee_attrs : & CodegenFnAttrs ,
124
+ ) -> Result < ( ) , & ' static str > {
125
+ Ok ( ( ) )
126
+ }
127
+
120
128
fn check_caller_mir_body ( & self , body : & Body < ' tcx > ) -> bool ;
121
129
122
130
/// Returns inlining decision that is based on the examination of callee MIR body.
@@ -128,10 +136,6 @@ trait Inliner<'tcx> {
128
136
callee_attrs : & CodegenFnAttrs ,
129
137
) -> Result < ( ) , & ' static str > ;
130
138
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
139
/// Called when inlining succeeds.
136
140
fn on_inline_success (
137
141
& mut self ,
@@ -142,9 +146,6 @@ trait Inliner<'tcx> {
142
146
143
147
/// Called when inlining failed or was not performed.
144
148
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
149
}
149
150
150
151
struct ForceInliner < ' tcx > {
@@ -224,10 +225,6 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
224
225
}
225
226
}
226
227
227
- fn inline_limit_for_block ( & self ) -> Option < usize > {
228
- Some ( usize:: MAX )
229
- }
230
-
231
228
fn on_inline_success (
232
229
& mut self ,
233
230
callsite : & CallSite < ' tcx > ,
@@ -261,10 +258,6 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
261
258
justification : justification. map ( |sym| crate :: errors:: ForceInlineJustification { sym } ) ,
262
259
} ) ;
263
260
}
264
-
265
- fn on_inline_limit_reached ( & self ) -> bool {
266
- false
267
- }
268
261
}
269
262
270
263
struct NormalInliner < ' tcx > {
@@ -278,13 +271,23 @@ struct NormalInliner<'tcx> {
278
271
/// The number of `DefId`s is finite, so checking history is enough
279
272
/// to ensure that we do not loop endlessly while inlining.
280
273
history : Vec < DefId > ,
274
+ /// How many (multi-call) callsites have we inlined for the top-level call?
275
+ ///
276
+ /// We need to limit this in order to prevent super-linear growth in MIR size.
277
+ top_down_counter : usize ,
281
278
/// Indicates that the caller body has been modified.
282
279
changed : bool ,
283
280
/// Indicates that the caller is #[inline] and just calls another function,
284
281
/// and thus we can inline less into it as it'll be inlined itself.
285
282
caller_is_inline_forwarder : bool ,
286
283
}
287
284
285
+ impl < ' tcx > NormalInliner < ' tcx > {
286
+ fn past_depth_limit ( & self ) -> bool {
287
+ self . history . len ( ) > HISTORY_DEPTH_LIMIT || self . top_down_counter > TOP_DOWN_DEPTH_LIMIT
288
+ }
289
+ }
290
+
288
291
impl < ' tcx > Inliner < ' tcx > for NormalInliner < ' tcx > {
289
292
fn new ( tcx : TyCtxt < ' tcx > , def_id : DefId , body : & Body < ' tcx > ) -> Self {
290
293
let typing_env = body. typing_env ( tcx) ;
@@ -295,6 +298,7 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
295
298
typing_env,
296
299
def_id,
297
300
history : Vec :: new ( ) ,
301
+ top_down_counter : 0 ,
298
302
changed : false ,
299
303
caller_is_inline_forwarder : matches ! (
300
304
codegen_fn_attrs. inline,
@@ -327,6 +331,17 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
327
331
true
328
332
}
329
333
334
+ fn check_codegen_attributes_extra (
335
+ & self ,
336
+ callee_attrs : & CodegenFnAttrs ,
337
+ ) -> Result < ( ) , & ' static str > {
338
+ if self . past_depth_limit ( ) && matches ! ( callee_attrs. inline, InlineAttr :: None ) {
339
+ Err ( "Past depth limit so not inspecting unmarked callee" )
340
+ } else {
341
+ Ok ( ( ) )
342
+ }
343
+ }
344
+
330
345
fn check_caller_mir_body ( & self , body : & Body < ' tcx > ) -> bool {
331
346
// Avoid inlining into coroutines, since their `optimized_mir` is used for layout computation,
332
347
// which can create a cycle, even when no attempt is made to inline the function in the other
@@ -351,6 +366,10 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
351
366
return Err ( "body has errors" ) ;
352
367
}
353
368
369
+ if self . past_depth_limit ( ) && callee_body. basic_blocks . len ( ) > 1 {
370
+ return Err ( "Not inlining multi-block body as we're past a depth limit" ) ;
371
+ }
372
+
354
373
let mut threshold = if self . caller_is_inline_forwarder {
355
374
tcx. sess . opts . unstable_opts . inline_mir_forwarder_threshold . unwrap_or ( 30 )
356
375
} else if tcx. cross_crate_inlinable ( callsite. callee . def_id ( ) ) {
@@ -431,14 +450,6 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
431
450
}
432
451
}
433
452
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
453
fn on_inline_success (
443
454
& mut self ,
444
455
callsite : & CallSite < ' tcx > ,
@@ -447,13 +458,26 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
447
458
) {
448
459
self . changed = true ;
449
460
461
+ let new_calls_count = new_blocks
462
+ . clone ( )
463
+ . filter ( |& bb| {
464
+ matches ! (
465
+ caller_body. basic_blocks[ bb] . terminator( ) . kind,
466
+ TerminatorKind :: Call { .. } ,
467
+ )
468
+ } )
469
+ . count ( ) ;
470
+ if new_calls_count > 1 {
471
+ self . top_down_counter += 1 ;
472
+ }
473
+
450
474
self . history . push ( callsite. callee . def_id ( ) ) ;
451
475
process_blocks ( self , caller_body, new_blocks) ;
452
476
self . history . pop ( ) ;
453
- }
454
477
455
- fn on_inline_limit_reached ( & self ) -> bool {
456
- true
478
+ if self . history . is_empty ( ) {
479
+ self . top_down_counter = 0 ;
480
+ }
457
481
}
458
482
459
483
fn on_inline_failure ( & self , _: & CallSite < ' tcx > , _: & ' static str ) { }
@@ -482,8 +506,6 @@ fn process_blocks<'tcx, I: Inliner<'tcx>>(
482
506
caller_body : & mut Body < ' tcx > ,
483
507
blocks : Range < BasicBlock > ,
484
508
) {
485
- let Some ( inline_limit) = inliner. inline_limit_for_block ( ) else { return } ;
486
- let mut inlined_count = 0 ;
487
509
for bb in blocks {
488
510
let bb_data = & caller_body[ bb] ;
489
511
if bb_data. is_cleanup {
@@ -505,13 +527,6 @@ fn process_blocks<'tcx, I: Inliner<'tcx>>(
505
527
Ok ( new_blocks) => {
506
528
debug ! ( "inlined {}" , callsite. callee) ;
507
529
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
530
}
516
531
}
517
532
}
@@ -584,6 +599,7 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
584
599
let callee_attrs = tcx. codegen_fn_attrs ( callsite. callee . def_id ( ) ) ;
585
600
check_inline:: is_inline_valid_on_fn ( tcx, callsite. callee . def_id ( ) ) ?;
586
601
check_codegen_attributes ( inliner, callsite, callee_attrs) ?;
602
+ inliner. check_codegen_attributes_extra ( callee_attrs) ?;
587
603
588
604
let terminator = caller_body[ callsite. block ] . terminator . as_ref ( ) . unwrap ( ) ;
589
605
let TerminatorKind :: Call { args, destination, .. } = & terminator. kind else { bug ! ( ) } ;
@@ -770,6 +786,8 @@ fn check_codegen_attributes<'tcx, I: Inliner<'tcx>>(
770
786
return Err ( "has DoNotOptimize attribute" ) ;
771
787
}
772
788
789
+ inliner. check_codegen_attributes_extra ( callee_attrs) ?;
790
+
773
791
// Reachability pass defines which functions are eligible for inlining. Generally inlining
774
792
// other functions is incorrect because they could reference symbols that aren't exported.
775
793
let is_generic = callsite. callee . args . non_erasable_generics ( ) . next ( ) . is_some ( ) ;
0 commit comments