@@ -26,6 +26,7 @@ use crate::{check_inline, util};
2626
2727pub ( crate ) mod cycle;
2828
29+ const HISTORY_DEPTH_LIMIT : usize = 20 ;
2930const TOP_DOWN_DEPTH_LIMIT : usize = 5 ;
3031
3132#[ derive( Clone , Debug ) ]
@@ -117,6 +118,13 @@ trait Inliner<'tcx> {
117118 /// Should inlining happen for a given callee?
118119 fn should_inline_for_callee ( & self , def_id : DefId ) -> bool ;
119120
121+ fn check_codegen_attributes_extra (
122+ & self ,
123+ _callee_attrs : & CodegenFnAttrs ,
124+ ) -> Result < ( ) , & ' static str > {
125+ Ok ( ( ) )
126+ }
127+
120128 fn check_caller_mir_body ( & self , body : & Body < ' tcx > ) -> bool ;
121129
122130 /// Returns inlining decision that is based on the examination of callee MIR body.
@@ -128,10 +136,6 @@ trait Inliner<'tcx> {
128136 callee_attrs : & CodegenFnAttrs ,
129137 ) -> Result < ( ) , & ' static str > ;
130138
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-
135139 /// Called when inlining succeeds.
136140 fn on_inline_success (
137141 & mut self ,
@@ -142,9 +146,6 @@ trait Inliner<'tcx> {
142146
143147 /// Called when inlining failed or was not performed.
144148 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 ;
148149}
149150
150151struct ForceInliner < ' tcx > {
@@ -224,10 +225,6 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
224225 }
225226 }
226227
227- fn inline_limit_for_block ( & self ) -> Option < usize > {
228- Some ( usize:: MAX )
229- }
230-
231228 fn on_inline_success (
232229 & mut self ,
233230 callsite : & CallSite < ' tcx > ,
@@ -261,10 +258,6 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
261258 justification : justification. map ( |sym| crate :: errors:: ForceInlineJustification { sym } ) ,
262259 } ) ;
263260 }
264-
265- fn on_inline_limit_reached ( & self ) -> bool {
266- false
267- }
268261}
269262
270263struct NormalInliner < ' tcx > {
@@ -278,13 +271,23 @@ struct NormalInliner<'tcx> {
278271 /// The number of `DefId`s is finite, so checking history is enough
279272 /// to ensure that we do not loop endlessly while inlining.
280273 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 ,
281278 /// Indicates that the caller body has been modified.
282279 changed : bool ,
283280 /// Indicates that the caller is #[inline] and just calls another function,
284281 /// and thus we can inline less into it as it'll be inlined itself.
285282 caller_is_inline_forwarder : bool ,
286283}
287284
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+
288291impl < ' tcx > Inliner < ' tcx > for NormalInliner < ' tcx > {
289292 fn new ( tcx : TyCtxt < ' tcx > , def_id : DefId , body : & Body < ' tcx > ) -> Self {
290293 let typing_env = body. typing_env ( tcx) ;
@@ -295,6 +298,7 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
295298 typing_env,
296299 def_id,
297300 history : Vec :: new ( ) ,
301+ top_down_counter : 0 ,
298302 changed : false ,
299303 caller_is_inline_forwarder : matches ! (
300304 codegen_fn_attrs. inline,
@@ -327,6 +331,17 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
327331 true
328332 }
329333
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+
330345 fn check_caller_mir_body ( & self , body : & Body < ' tcx > ) -> bool {
331346 // Avoid inlining into coroutines, since their `optimized_mir` is used for layout computation,
332347 // 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> {
351366 return Err ( "body has errors" ) ;
352367 }
353368
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+
354373 let mut threshold = if self . caller_is_inline_forwarder {
355374 tcx. sess . opts . unstable_opts . inline_mir_forwarder_threshold . unwrap_or ( 30 )
356375 } else if tcx. cross_crate_inlinable ( callsite. callee . def_id ( ) ) {
@@ -431,14 +450,6 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
431450 }
432451 }
433452
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-
442453 fn on_inline_success (
443454 & mut self ,
444455 callsite : & CallSite < ' tcx > ,
@@ -447,13 +458,26 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
447458 ) {
448459 self . changed = true ;
449460
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+
450474 self . history . push ( callsite. callee . def_id ( ) ) ;
451475 process_blocks ( self , caller_body, new_blocks) ;
452476 self . history . pop ( ) ;
453- }
454477
455- fn on_inline_limit_reached ( & self ) -> bool {
456- true
478+ if self . history . is_empty ( ) {
479+ self . top_down_counter = 0 ;
480+ }
457481 }
458482
459483 fn on_inline_failure ( & self , _: & CallSite < ' tcx > , _: & ' static str ) { }
@@ -482,8 +506,6 @@ fn process_blocks<'tcx, I: Inliner<'tcx>>(
482506 caller_body : & mut Body < ' tcx > ,
483507 blocks : Range < BasicBlock > ,
484508) {
485- let Some ( inline_limit) = inliner. inline_limit_for_block ( ) else { return } ;
486- let mut inlined_count = 0 ;
487509 for bb in blocks {
488510 let bb_data = & caller_body[ bb] ;
489511 if bb_data. is_cleanup {
@@ -505,13 +527,6 @@ fn process_blocks<'tcx, I: Inliner<'tcx>>(
505527 Ok ( new_blocks) => {
506528 debug ! ( "inlined {}" , callsite. callee) ;
507529 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- }
515530 }
516531 }
517532 }
@@ -584,6 +599,7 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
584599 let callee_attrs = tcx. codegen_fn_attrs ( callsite. callee . def_id ( ) ) ;
585600 check_inline:: is_inline_valid_on_fn ( tcx, callsite. callee . def_id ( ) ) ?;
586601 check_codegen_attributes ( inliner, callsite, callee_attrs) ?;
602+ inliner. check_codegen_attributes_extra ( callee_attrs) ?;
587603
588604 let terminator = caller_body[ callsite. block ] . terminator . as_ref ( ) . unwrap ( ) ;
589605 let TerminatorKind :: Call { args, destination, .. } = & terminator. kind else { bug ! ( ) } ;
@@ -770,6 +786,8 @@ fn check_codegen_attributes<'tcx, I: Inliner<'tcx>>(
770786 return Err ( "has DoNotOptimize attribute" ) ;
771787 }
772788
789+ inliner. check_codegen_attributes_extra ( callee_attrs) ?;
790+
773791 // Reachability pass defines which functions are eligible for inlining. Generally inlining
774792 // other functions is incorrect because they could reference symbols that aren't exported.
775793 let is_generic = callsite. callee . args . non_erasable_generics ( ) . next ( ) . is_some ( ) ;
0 commit comments