11//! Inlining pass for MIR functions
22
33use rustc_attr as attr;
4+ use rustc_hir as hir;
45use rustc_index:: bit_set:: BitSet ;
56use rustc_index:: vec:: Idx ;
67use rustc_middle:: middle:: codegen_fn_attrs:: { CodegenFnAttrFlags , CodegenFnAttrs } ;
@@ -12,9 +13,8 @@ use rustc_target::spec::abi::Abi;
1213
1314use super :: simplify:: { remove_dead_blocks, CfgSimplifier } ;
1415use crate :: transform:: MirPass ;
15- use std:: collections:: VecDeque ;
1616use std:: iter;
17- use std:: ops:: RangeFrom ;
17+ use std:: ops:: { Range , RangeFrom } ;
1818
1919const DEFAULT_THRESHOLD : usize = 50 ;
2020const HINT_THRESHOLD : usize = 100 ;
@@ -37,132 +37,128 @@ struct CallSite<'tcx> {
3737
3838impl < ' tcx > MirPass < ' tcx > for Inline {
3939 fn run_pass ( & self , tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) {
40- if tcx. sess . opts . debugging_opts . mir_opt_level >= 2 {
41- if tcx. sess . opts . debugging_opts . instrument_coverage {
42- // The current implementation of source code coverage injects code region counters
43- // into the MIR, and assumes a 1-to-1 correspondence between MIR and source-code-
44- // based function.
45- debug ! ( "function inlining is disabled when compiling with `instrument_coverage`" ) ;
46- } else {
47- Inliner {
48- tcx,
49- param_env : tcx. param_env_reveal_all_normalized ( body. source . def_id ( ) ) ,
50- codegen_fn_attrs : tcx. codegen_fn_attrs ( body. source . def_id ( ) ) ,
51- }
52- . run_pass ( body) ;
53- }
40+ if tcx. sess . opts . debugging_opts . mir_opt_level < 2 {
41+ return ;
42+ }
43+
44+ if tcx. sess . opts . debugging_opts . instrument_coverage {
45+ // The current implementation of source code coverage injects code region counters
46+ // into the MIR, and assumes a 1-to-1 correspondence between MIR and source-code-
47+ // based function.
48+ debug ! ( "function inlining is disabled when compiling with `instrument_coverage`" ) ;
49+ return ;
50+ }
51+
52+ if inline ( tcx, body) {
53+ debug ! ( "running simplify cfg on {:?}" , body. source) ;
54+ CfgSimplifier :: new ( body) . simplify ( ) ;
55+ remove_dead_blocks ( body) ;
5456 }
5557 }
5658}
5759
60+ fn inline ( tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) -> bool {
61+ let def_id = body. source . def_id ( ) ;
62+ let hir_id = tcx. hir ( ) . local_def_id_to_hir_id ( def_id. expect_local ( ) ) ;
63+
64+ // Only do inlining into fn bodies.
65+ if !tcx. hir ( ) . body_owner_kind ( hir_id) . is_fn_or_closure ( ) {
66+ return false ;
67+ }
68+ if body. source . promoted . is_some ( ) {
69+ return false ;
70+ }
71+
72+ let mut this = Inliner {
73+ tcx,
74+ param_env : tcx. param_env_reveal_all_normalized ( body. source . def_id ( ) ) ,
75+ codegen_fn_attrs : tcx. codegen_fn_attrs ( body. source . def_id ( ) ) ,
76+ hir_id,
77+ history : Vec :: new ( ) ,
78+ changed : false ,
79+ } ;
80+ let blocks = BasicBlock :: new ( 0 ) ..body. basic_blocks ( ) . next_index ( ) ;
81+ this. process_blocks ( body, blocks) ;
82+ this. changed
83+ }
84+
5885struct Inliner < ' tcx > {
5986 tcx : TyCtxt < ' tcx > ,
6087 param_env : ParamEnv < ' tcx > ,
88+ /// Caller codegen attributes.
6189 codegen_fn_attrs : & ' tcx CodegenFnAttrs ,
90+ /// Caller HirID.
91+ hir_id : hir:: HirId ,
92+ /// Stack of inlined instances.
93+ history : Vec < Instance < ' tcx > > ,
94+ /// Indicates that the caller body has been modified.
95+ changed : bool ,
6296}
6397
6498impl Inliner < ' tcx > {
65- fn run_pass ( & self , caller_body : & mut Body < ' tcx > ) {
66- // Keep a queue of callsites to try inlining on. We take
67- // advantage of the fact that queries detect cycles here to
68- // allow us to try and fetch the fully optimized MIR of a
69- // call; if it succeeds, we can inline it and we know that
70- // they do not call us. Otherwise, we just don't try to
71- // inline.
72- //
73- // We use a queue so that we inline "broadly" before we inline
74- // in depth. It is unclear if this is the best heuristic,
75- // really, but that's true of all the heuristics in this
76- // file. =)
77-
78- let mut callsites = VecDeque :: new ( ) ;
79-
80- let def_id = caller_body. source . def_id ( ) ;
81-
82- // Only do inlining into fn bodies.
83- let self_hir_id = self . tcx . hir ( ) . local_def_id_to_hir_id ( def_id. expect_local ( ) ) ;
84- if self . tcx . hir ( ) . body_owner_kind ( self_hir_id) . is_fn_or_closure ( )
85- && caller_body. source . promoted . is_none ( )
86- {
87- for ( bb, bb_data) in caller_body. basic_blocks ( ) . iter_enumerated ( ) {
88- if let Some ( callsite) = self . get_valid_function_call ( bb, bb_data, caller_body) {
89- callsites. push_back ( callsite) ;
90- }
91- }
92- } else {
93- return ;
94- }
95-
96- let mut changed = false ;
97- while let Some ( callsite) = callsites. pop_front ( ) {
98- debug ! ( "checking whether to inline callsite {:?}" , callsite) ;
99+ fn process_blocks ( & mut self , caller_body : & mut Body < ' tcx > , blocks : Range < BasicBlock > ) {
100+ for bb in blocks {
101+ let callsite = match self . get_valid_function_call ( bb, & caller_body[ bb] , caller_body) {
102+ None => continue ,
103+ Some ( it) => it,
104+ } ;
99105
100- if let InstanceDef :: Item ( _) = callsite. callee . def {
101- if !self . tcx . is_mir_available ( callsite. callee . def_id ( ) ) {
102- debug ! ( "checking whether to inline callsite {:?} - MIR unavailable" , callsite, ) ;
103- continue ;
104- }
106+ if !self . is_mir_available ( & callsite. callee , caller_body) {
107+ debug ! ( "MIR unavailable {}" , callsite. callee) ;
108+ continue ;
105109 }
106110
107- let callee_body = if let Some ( callee_def_id) = callsite. callee . def_id ( ) . as_local ( ) {
108- let callee_hir_id = self . tcx . hir ( ) . local_def_id_to_hir_id ( callee_def_id) ;
109- // Avoid a cycle here by only using `instance_mir` only if we have
110- // a lower `HirId` than the callee. This ensures that the callee will
111- // not inline us. This trick only works without incremental compilation.
112- // So don't do it if that is enabled. Also avoid inlining into generators,
113- // since their `optimized_mir` is used for layout computation, which can
114- // create a cycle, even when no attempt is made to inline the function
115- // in the other direction.
116- if !self . tcx . dep_graph . is_fully_enabled ( )
117- && self_hir_id < callee_hir_id
118- && caller_body. generator_kind . is_none ( )
119- {
120- self . tcx . instance_mir ( callsite. callee . def )
121- } else {
122- continue ;
123- }
124- } else {
125- // This cannot result in a cycle since the callee MIR is from another crate
126- // and is already optimized.
127- self . tcx . instance_mir ( callsite. callee . def )
128- } ;
129-
130- if !self . consider_optimizing ( callsite, & callee_body) {
111+ let callee_body = self . tcx . instance_mir ( callsite. callee . def ) ;
112+ if !self . should_inline ( callsite, callee_body) {
131113 continue ;
132114 }
133115
116+ if !self . tcx . consider_optimizing ( || {
117+ format ! ( "Inline {:?} into {}" , callee_body. span, callsite. callee)
118+ } ) {
119+ return ;
120+ }
121+
134122 let callee_body = callsite. callee . subst_mir_and_normalize_erasing_regions (
135123 self . tcx ,
136124 self . param_env ,
137125 callee_body,
138126 ) ;
139127
140- let start = caller_body. basic_blocks ( ) . len ( ) ;
141- debug ! ( "attempting to inline callsite {:?} - body={:?}" , callsite, callee_body) ;
142- if !self . inline_call ( callsite, caller_body, callee_body) {
143- debug ! ( "attempting to inline callsite {:?} - failure" , callsite) ;
144- continue ;
145- }
146- debug ! ( "attempting to inline callsite {:?} - success" , callsite) ;
147-
148- // Add callsites from inlined function
149- for ( bb, bb_data) in caller_body. basic_blocks ( ) . iter_enumerated ( ) . skip ( start) {
150- if let Some ( new_callsite) = self . get_valid_function_call ( bb, bb_data, caller_body) {
151- // Don't inline the same function multiple times.
152- if callsite. callee != new_callsite. callee {
153- callsites. push_back ( new_callsite) ;
154- }
155- }
156- }
128+ let old_blocks = caller_body. basic_blocks ( ) . next_index ( ) ;
129+ self . inline_call ( callsite, caller_body, callee_body) ;
130+ let new_blocks = old_blocks..caller_body. basic_blocks ( ) . next_index ( ) ;
131+ self . changed = true ;
132+
133+ self . history . push ( callsite. callee ) ;
134+ self . process_blocks ( caller_body, new_blocks) ;
135+ self . history . pop ( ) ;
136+ }
137+ }
157138
158- changed = true ;
139+ fn is_mir_available ( & self , callee : & Instance < ' tcx > , caller_body : & Body < ' tcx > ) -> bool {
140+ if let InstanceDef :: Item ( _) = callee. def {
141+ if !self . tcx . is_mir_available ( callee. def_id ( ) ) {
142+ return false ;
143+ }
159144 }
160145
161- // Simplify if we inlined anything.
162- if changed {
163- debug ! ( "running simplify cfg on {:?}" , caller_body. source) ;
164- CfgSimplifier :: new ( caller_body) . simplify ( ) ;
165- remove_dead_blocks ( caller_body) ;
146+ if let Some ( callee_def_id) = callee. def_id ( ) . as_local ( ) {
147+ let callee_hir_id = self . tcx . hir ( ) . local_def_id_to_hir_id ( callee_def_id) ;
148+ // Avoid a cycle here by only using `instance_mir` only if we have
149+ // a lower `HirId` than the callee. This ensures that the callee will
150+ // not inline us. This trick only works without incremental compilation.
151+ // So don't do it if that is enabled. Also avoid inlining into generators,
152+ // since their `optimized_mir` is used for layout computation, which can
153+ // create a cycle, even when no attempt is made to inline the function
154+ // in the other direction.
155+ !self . tcx . dep_graph . is_fully_enabled ( )
156+ && self . hir_id < callee_hir_id
157+ && caller_body. generator_kind . is_none ( )
158+ } else {
159+ // This cannot result in a cycle since the callee MIR is from another crate
160+ // and is already optimized.
161+ true
166162 }
167163 }
168164
@@ -179,7 +175,8 @@ impl Inliner<'tcx> {
179175
180176 // Only consider direct calls to functions
181177 let terminator = bb_data. terminator ( ) ;
182- if let TerminatorKind :: Call { func : ref op, .. } = terminator. kind {
178+ // FIXME: Handle inlining of diverging calls
179+ if let TerminatorKind :: Call { func : ref op, destination : Some ( _) , .. } = terminator. kind {
183180 if let ty:: FnDef ( callee_def_id, substs) = * op. ty ( caller_body, self . tcx ) . kind ( ) {
184181 // To resolve an instance its substs have to be fully normalized, so
185182 // we do this here.
@@ -200,14 +197,6 @@ impl Inliner<'tcx> {
200197 None
201198 }
202199
203- fn consider_optimizing ( & self , callsite : CallSite < ' tcx > , callee_body : & Body < ' tcx > ) -> bool {
204- debug ! ( "consider_optimizing({:?})" , callsite) ;
205- self . should_inline ( callsite, callee_body)
206- && self . tcx . consider_optimizing ( || {
207- format ! ( "Inline {:?} into {:?}" , callee_body. span, callsite)
208- } )
209- }
210-
211200 fn should_inline ( & self , callsite : CallSite < ' tcx > , callee_body : & Body < ' tcx > ) -> bool {
212201 debug ! ( "should_inline({:?})" , callsite) ;
213202 let tcx = self . tcx ;
@@ -327,7 +316,18 @@ impl Inliner<'tcx> {
327316 }
328317
329318 TerminatorKind :: Call { func : Operand :: Constant ( ref f) , cleanup, .. } => {
330- if let ty:: FnDef ( def_id, _) = * f. literal . ty . kind ( ) {
319+ if let ty:: FnDef ( def_id, substs) =
320+ * callsite. callee . subst_mir ( self . tcx , & f. literal . ty ) . kind ( )
321+ {
322+ let substs = self . tcx . normalize_erasing_regions ( self . param_env , substs) ;
323+ if let Ok ( Some ( instance) ) =
324+ Instance :: resolve ( self . tcx , self . param_env , def_id, substs)
325+ {
326+ if callsite. callee == instance || self . history . contains ( & instance) {
327+ debug ! ( "`callee is recursive - not inlining" ) ;
328+ return false ;
329+ }
330+ }
331331 // Don't give intrinsics the extra penalty for calls
332332 let f = tcx. fn_sig ( def_id) ;
333333 if f. abi ( ) == Abi :: RustIntrinsic || f. abi ( ) == Abi :: PlatformIntrinsic {
@@ -397,13 +397,10 @@ impl Inliner<'tcx> {
397397 callsite : CallSite < ' tcx > ,
398398 caller_body : & mut Body < ' tcx > ,
399399 mut callee_body : Body < ' tcx > ,
400- ) -> bool {
400+ ) {
401401 let terminator = caller_body[ callsite. bb ] . terminator . take ( ) . unwrap ( ) ;
402402 match terminator. kind {
403- // FIXME: Handle inlining of diverging calls
404403 TerminatorKind :: Call { args, destination : Some ( destination) , cleanup, .. } => {
405- debug ! ( "inlined {:?} into {:?}" , callsite. callee, caller_body. source) ;
406-
407404 // If the call is something like `a[*i] = f(i)`, where
408405 // `i : &mut usize`, then just duplicating the `a[*i]`
409406 // Place could result in two different locations if `f`
@@ -519,14 +516,8 @@ impl Inliner<'tcx> {
519516 matches ! ( constant. literal. val, ConstKind :: Unevaluated ( _, _, _) )
520517 } ) ,
521518 ) ;
522-
523- true
524- }
525- kind => {
526- caller_body[ callsite. bb ] . terminator =
527- Some ( Terminator { source_info : terminator. source_info , kind } ) ;
528- false
529519 }
520+ kind => bug ! ( "unexpected terminator kind {:?}" , kind) ,
530521 }
531522 }
532523
0 commit comments