11//! Inlining pass for MIR functions.
22
3+ use std:: assert_matches:: debug_assert_matches;
34use std:: iter;
45use std:: ops:: { Range , RangeFrom } ;
56
@@ -18,14 +19,15 @@ use rustc_session::config::{DebugInfo, OptLevel};
1819use rustc_span:: source_map:: Spanned ;
1920use tracing:: { debug, instrument, trace, trace_span} ;
2021
21- use crate :: cost_checker:: CostChecker ;
22+ use crate :: cost_checker:: { CostChecker , is_call_like } ;
2223use crate :: deref_separator:: deref_finder;
2324use crate :: simplify:: simplify_cfg;
2425use crate :: validate:: validate_types;
2526use crate :: { check_inline, util} ;
2627
2728pub ( crate ) mod cycle;
2829
30+ const HISTORY_DEPTH_LIMIT : usize = 20 ;
2931const TOP_DOWN_DEPTH_LIMIT : usize = 5 ;
3032
3133#[ derive( Clone , Debug ) ]
@@ -117,6 +119,11 @@ trait Inliner<'tcx> {
117119 /// Should inlining happen for a given callee?
118120 fn should_inline_for_callee ( & self , def_id : DefId ) -> bool ;
119121
122+ fn check_codegen_attributes_extra (
123+ & self ,
124+ callee_attrs : & CodegenFnAttrs ,
125+ ) -> Result < ( ) , & ' static str > ;
126+
120127 fn check_caller_mir_body ( & self , body : & Body < ' tcx > ) -> bool ;
121128
122129 /// Returns inlining decision that is based on the examination of callee MIR body.
@@ -128,10 +135,6 @@ trait Inliner<'tcx> {
128135 callee_attrs : & CodegenFnAttrs ,
129136 ) -> Result < ( ) , & ' static str > ;
130137
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-
135138 /// Called when inlining succeeds.
136139 fn on_inline_success (
137140 & mut self ,
@@ -142,9 +145,6 @@ trait Inliner<'tcx> {
142145
143146 /// Called when inlining failed or was not performed.
144147 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 ;
148148}
149149
150150struct ForceInliner < ' tcx > {
@@ -191,6 +191,14 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
191191 ForceInline :: should_run_pass_for_callee ( self . tcx ( ) , def_id)
192192 }
193193
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+
194202 fn check_caller_mir_body ( & self , _: & Body < ' tcx > ) -> bool {
195203 true
196204 }
@@ -224,10 +232,6 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
224232 }
225233 }
226234
227- fn inline_limit_for_block ( & self ) -> Option < usize > {
228- Some ( usize:: MAX )
229- }
230-
231235 fn on_inline_success (
232236 & mut self ,
233237 callsite : & CallSite < ' tcx > ,
@@ -261,10 +265,6 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
261265 justification : justification. map ( |sym| crate :: errors:: ForceInlineJustification { sym } ) ,
262266 } ) ;
263267 }
264-
265- fn on_inline_limit_reached ( & self ) -> bool {
266- false
267- }
268268}
269269
270270struct NormalInliner < ' tcx > {
@@ -278,13 +278,23 @@ struct NormalInliner<'tcx> {
278278 /// The number of `DefId`s is finite, so checking history is enough
279279 /// to ensure that we do not loop endlessly while inlining.
280280 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 ,
281285 /// Indicates that the caller body has been modified.
282286 changed : bool ,
283287 /// Indicates that the caller is #[inline] and just calls another function,
284288 /// and thus we can inline less into it as it'll be inlined itself.
285289 caller_is_inline_forwarder : bool ,
286290}
287291
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+
288298impl < ' tcx > Inliner < ' tcx > for NormalInliner < ' tcx > {
289299 fn new ( tcx : TyCtxt < ' tcx > , def_id : DefId , body : & Body < ' tcx > ) -> Self {
290300 let typing_env = body. typing_env ( tcx) ;
@@ -295,6 +305,7 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
295305 typing_env,
296306 def_id,
297307 history : Vec :: new ( ) ,
308+ top_down_counter : 0 ,
298309 changed : false ,
299310 caller_is_inline_forwarder : matches ! (
300311 codegen_fn_attrs. inline,
@@ -327,6 +338,17 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
327338 true
328339 }
329340
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+
330352 fn check_caller_mir_body ( & self , body : & Body < ' tcx > ) -> bool {
331353 // Avoid inlining into coroutines, since their `optimized_mir` is used for layout computation,
332354 // 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> {
351373 return Err ( "body has errors" ) ;
352374 }
353375
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 ( ) {
355381 tcx. sess . opts . unstable_opts . inline_mir_forwarder_threshold . unwrap_or ( 30 )
356382 } else if tcx. cross_crate_inlinable ( callsite. callee . def_id ( ) ) {
357383 tcx. sess . opts . unstable_opts . inline_mir_hint_threshold . unwrap_or ( 100 )
@@ -431,14 +457,6 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
431457 }
432458 }
433459
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-
442460 fn on_inline_success (
443461 & mut self ,
444462 callsite : & CallSite < ' tcx > ,
@@ -447,13 +465,21 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
447465 ) {
448466 self . changed = true ;
449467
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+
450476 self . history . push ( callsite. callee . def_id ( ) ) ;
451477 process_blocks ( self , caller_body, new_blocks) ;
452478 self . history . pop ( ) ;
453- }
454479
455- fn on_inline_limit_reached ( & self ) -> bool {
456- true
480+ if self . history . is_empty ( ) {
481+ self . top_down_counter = 0 ;
482+ }
457483 }
458484
459485 fn on_inline_failure ( & self , _: & CallSite < ' tcx > , _: & ' static str ) { }
@@ -482,8 +508,6 @@ fn process_blocks<'tcx, I: Inliner<'tcx>>(
482508 caller_body : & mut Body < ' tcx > ,
483509 blocks : Range < BasicBlock > ,
484510) {
485- let Some ( inline_limit) = inliner. inline_limit_for_block ( ) else { return } ;
486- let mut inlined_count = 0 ;
487511 for bb in blocks {
488512 let bb_data = & caller_body[ bb] ;
489513 if bb_data. is_cleanup {
@@ -505,13 +529,6 @@ fn process_blocks<'tcx, I: Inliner<'tcx>>(
505529 Ok ( new_blocks) => {
506530 debug ! ( "inlined {}" , callsite. callee) ;
507531 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- }
515532 }
516533 }
517534 }
@@ -584,6 +601,7 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
584601 let callee_attrs = tcx. codegen_fn_attrs ( callsite. callee . def_id ( ) ) ;
585602 check_inline:: is_inline_valid_on_fn ( tcx, callsite. callee . def_id ( ) ) ?;
586603 check_codegen_attributes ( inliner, callsite, callee_attrs) ?;
604+ inliner. check_codegen_attributes_extra ( callee_attrs) ?;
587605
588606 let terminator = caller_body[ callsite. block ] . terminator . as_ref ( ) . unwrap ( ) ;
589607 let TerminatorKind :: Call { args, destination, .. } = & terminator. kind else { bug ! ( ) } ;
@@ -770,6 +788,8 @@ fn check_codegen_attributes<'tcx, I: Inliner<'tcx>>(
770788 return Err ( "has DoNotOptimize attribute" ) ;
771789 }
772790
791+ inliner. check_codegen_attributes_extra ( callee_attrs) ?;
792+
773793 // Reachability pass defines which functions are eligible for inlining. Generally inlining
774794 // other functions is incorrect because they could reference symbols that aren't exported.
775795 let is_generic = callsite. callee . args . non_erasable_generics ( ) . next ( ) . is_some ( ) ;
0 commit comments