11//! Inlining pass for MIR functions
22use crate :: deref_separator:: deref_finder;
33use rustc_attr:: InlineAttr ;
4+ use rustc_hir:: def_id:: DefId ;
45use rustc_index:: bit_set:: BitSet ;
56use rustc_index:: vec:: Idx ;
67use rustc_middle:: middle:: codegen_fn_attrs:: { CodegenFnAttrFlags , CodegenFnAttrs } ;
@@ -27,6 +28,8 @@ const RESUME_PENALTY: usize = 45;
2728
2829const UNKNOWN_SIZE_COST : usize = 10 ;
2930
31+ const TOP_DOWN_DEPTH_LIMIT : usize = 5 ;
32+
3033pub struct Inline ;
3134
3235#[ derive( Copy , Clone , Debug ) ]
@@ -86,8 +89,13 @@ fn inline<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool {
8689
8790 let param_env = tcx. param_env_reveal_all_normalized ( def_id) ;
8891
89- let mut this =
90- Inliner { tcx, param_env, codegen_fn_attrs : tcx. codegen_fn_attrs ( def_id) , changed : false } ;
92+ let mut this = Inliner {
93+ tcx,
94+ param_env,
95+ codegen_fn_attrs : tcx. codegen_fn_attrs ( def_id) ,
96+ history : Vec :: new ( ) ,
97+ changed : false ,
98+ } ;
9199 let blocks = BasicBlock :: new ( 0 ) ..body. basic_blocks . next_index ( ) ;
92100 this. process_blocks ( body, blocks) ;
93101 this. changed
@@ -98,12 +106,26 @@ struct Inliner<'tcx> {
98106 param_env : ParamEnv < ' tcx > ,
99107 /// Caller codegen attributes.
100108 codegen_fn_attrs : & ' tcx CodegenFnAttrs ,
109+ /// Stack of inlined instances.
110+ /// We only check the `DefId` and not the substs because we want to
111+ /// avoid inlining cases of polymorphic recursion.
112+ /// The number of `DefId`s is finite, so checking history is enough
113+ /// to ensure that we do not loop endlessly while inlining.
114+ history : Vec < DefId > ,
101115 /// Indicates that the caller body has been modified.
102116 changed : bool ,
103117}
104118
105119impl < ' tcx > Inliner < ' tcx > {
106120 fn process_blocks ( & mut self , caller_body : & mut Body < ' tcx > , blocks : Range < BasicBlock > ) {
121+ // How many callsites in this body are we allowed to inline? We need to limit this in order
122+ // to prevent super-linear growth in MIR size
123+ let inline_limit = match self . history . len ( ) {
124+ 0 => usize:: MAX ,
125+ 1 ..=TOP_DOWN_DEPTH_LIMIT => 1 ,
126+ _ => return ,
127+ } ;
128+ let mut inlined_count = 0 ;
107129 for bb in blocks {
108130 let bb_data = & caller_body[ bb] ;
109131 if bb_data. is_cleanup {
@@ -122,12 +144,16 @@ impl<'tcx> Inliner<'tcx> {
122144 debug ! ( "not-inlined {} [{}]" , callsite. callee, reason) ;
123145 continue ;
124146 }
125- Ok ( _ ) => {
147+ Ok ( new_blocks ) => {
126148 debug ! ( "inlined {}" , callsite. callee) ;
127149 self . changed = true ;
128- // We could process the blocks returned by `try_inlining` here. However, that
129- // leads to exponential compile times due to the top-down nature of this kind
130- // of inlining.
150+ inlined_count += 1 ;
151+ if inlined_count == inline_limit {
152+ return ;
153+ }
154+ self . history . push ( callsite. callee . def_id ( ) ) ;
155+ self . process_blocks ( caller_body, new_blocks) ;
156+ self . history . pop ( ) ;
131157 }
132158 }
133159 }
@@ -301,6 +327,10 @@ impl<'tcx> Inliner<'tcx> {
301327 return None ;
302328 }
303329
330+ if self . history . contains ( & callee. def_id ( ) ) {
331+ return None ;
332+ }
333+
304334 let fn_sig = self . tcx . bound_fn_sig ( def_id) . subst ( self . tcx , substs) ;
305335 let source_info = SourceInfo { span : fn_span, ..terminator. source_info } ;
306336
0 commit comments