11//! Inlining pass for MIR functions
22
3- use rustc_attr as attr ;
3+ use rustc_attr:: InlineAttr ;
44use rustc_hir as hir;
55use rustc_index:: bit_set:: BitSet ;
66use rustc_index:: vec:: Idx ;
@@ -106,72 +106,90 @@ struct Inliner<'tcx> {
106106impl Inliner < ' tcx > {
107107 fn process_blocks ( & mut self , caller_body : & mut Body < ' tcx > , blocks : Range < BasicBlock > ) {
108108 for bb in blocks {
109- let callsite = match self . get_valid_function_call ( bb, & caller_body[ bb] , caller_body) {
109+ let bb_data = & caller_body[ bb] ;
110+ if bb_data. is_cleanup {
111+ continue ;
112+ }
113+
114+ let callsite = match self . resolve_callsite ( caller_body, bb, bb_data) {
110115 None => continue ,
111116 Some ( it) => it,
112117 } ;
118+
113119 let span = trace_span ! ( "process_blocks" , %callsite. callee, ?bb) ;
114120 let _guard = span. enter ( ) ;
115121
116- trace ! (
117- "checking for self recursion ({:?} vs body_source: {:?})" ,
118- callsite. callee. def_id( ) ,
119- caller_body. source. def_id( )
120- ) ;
121- if callsite. callee . def_id ( ) == caller_body. source . def_id ( ) {
122- debug ! ( "Not inlining a function into itself" ) ;
123- continue ;
124- }
125-
126- if !self . is_mir_available ( callsite. callee , caller_body) {
127- debug ! ( "MIR unavailable {}" , callsite. callee) ;
128- continue ;
122+ match self . try_inlining ( caller_body, & callsite) {
123+ Err ( reason) => {
124+ debug ! ( "not-inlined {} [{}]" , callsite. callee, reason) ;
125+ continue ;
126+ }
127+ Ok ( new_blocks) => {
128+ debug ! ( "inlined {}" , callsite. callee) ;
129+ self . changed = true ;
130+ self . history . push ( callsite. callee ) ;
131+ self . process_blocks ( caller_body, new_blocks) ;
132+ self . history . pop ( ) ;
133+ }
129134 }
135+ }
136+ }
130137
131- let span = trace_span ! ( "instance_mir" , %callsite. callee) ;
132- let instance_mir_guard = span. enter ( ) ;
133- let callee_body = self . tcx . instance_mir ( callsite. callee . def ) ;
134- drop ( instance_mir_guard) ;
135- if !self . should_inline ( callsite, callee_body) {
136- continue ;
137- }
138+ /// Attempts to inline a callsite into the caller body. When successful returns basic blocks
139+ /// containing the inlined body. Otherwise returns an error describing why inlining didn't take
140+ /// place.
141+ fn try_inlining (
142+ & self ,
143+ caller_body : & mut Body < ' tcx > ,
144+ callsite : & CallSite < ' tcx > ,
145+ ) -> Result < std:: ops:: Range < BasicBlock > , & ' static str > {
146+ let callee_attrs = self . tcx . codegen_fn_attrs ( callsite. callee . def_id ( ) ) ;
147+ self . check_codegen_attributes ( callsite, callee_attrs) ?;
148+ self . check_mir_is_available ( caller_body, & callsite. callee ) ?;
149+ let callee_body = self . tcx . instance_mir ( callsite. callee . def ) ;
150+ self . check_mir_body ( callsite, callee_body, callee_attrs) ?;
151+
152+ if !self . tcx . consider_optimizing ( || {
153+ format ! ( "Inline {:?} into {}" , callee_body. span, callsite. callee)
154+ } ) {
155+ return Err ( "optimization fuel exhausted" ) ;
156+ }
138157
139- if ! self . tcx . consider_optimizing ( || {
140- format ! ( "Inline {:?} into {}" , callee_body . span , callsite . callee )
141- } ) {
142- return ;
143- }
158+ let callee_body = callsite . callee . subst_mir_and_normalize_erasing_regions (
159+ self . tcx ,
160+ self . param_env ,
161+ callee_body . clone ( ) ,
162+ ) ;
144163
145- let callee_body = callsite. callee . subst_mir_and_normalize_erasing_regions (
146- self . tcx ,
147- self . param_env ,
148- callee_body. clone ( ) ,
149- ) ;
164+ let old_blocks = caller_body. basic_blocks ( ) . next_index ( ) ;
165+ self . inline_call ( caller_body, & callsite, callee_body) ;
166+ let new_blocks = old_blocks..caller_body. basic_blocks ( ) . next_index ( ) ;
150167
151- let old_blocks = caller_body. basic_blocks ( ) . next_index ( ) ;
152- self . inline_call ( callsite, caller_body, callee_body) ;
153- let new_blocks = old_blocks..caller_body. basic_blocks ( ) . next_index ( ) ;
154- self . changed = true ;
168+ Ok ( new_blocks)
169+ }
155170
156- self . history . push ( callsite. callee ) ;
157- self . process_blocks ( caller_body, new_blocks) ;
158- self . history . pop ( ) ;
171+ fn check_mir_is_available (
172+ & self ,
173+ caller_body : & Body < ' tcx > ,
174+ callee : & Instance < ' tcx > ,
175+ ) -> Result < ( ) , & ' static str > {
176+ if callee. def_id ( ) == caller_body. source . def_id ( ) {
177+ return Err ( "self-recursion" ) ;
159178 }
160- }
161179
162- #[ instrument( level = "debug" , skip( self , caller_body) ) ]
163- fn is_mir_available ( & self , callee : Instance < ' tcx > , caller_body : & Body < ' tcx > ) -> bool {
164180 match callee. def {
165181 InstanceDef :: Item ( _) => {
166182 // If there is no MIR available (either because it was not in metadata or
167183 // because it has no MIR because it's an extern function), then the inliner
168184 // won't cause cycles on this.
169185 if !self . tcx . is_mir_available ( callee. def_id ( ) ) {
170- return false ;
186+ return Err ( "item MIR unavailable" ) ;
171187 }
172188 }
173189 // These have no own callable MIR.
174- InstanceDef :: Intrinsic ( _) | InstanceDef :: Virtual ( ..) => return false ,
190+ InstanceDef :: Intrinsic ( _) | InstanceDef :: Virtual ( ..) => {
191+ return Err ( "instance without MIR (intrinsic / virtual)" ) ;
192+ }
175193 // This cannot result in an immediate cycle since the callee MIR is a shim, which does
176194 // not get any optimizations run on it. Any subsequent inlining may cause cycles, but we
177195 // do not need to catch this here, we can wait until the inliner decides to continue
@@ -181,13 +199,13 @@ impl Inliner<'tcx> {
181199 | InstanceDef :: FnPtrShim ( ..)
182200 | InstanceDef :: ClosureOnceShim { .. }
183201 | InstanceDef :: DropGlue ( ..)
184- | InstanceDef :: CloneShim ( ..) => return true ,
202+ | InstanceDef :: CloneShim ( ..) => return Ok ( ( ) ) ,
185203 }
186204
187205 if self . tcx . is_constructor ( callee. def_id ( ) ) {
188206 trace ! ( "constructors always have MIR" ) ;
189207 // Constructor functions cannot cause a query cycle.
190- return true ;
208+ return Ok ( ( ) ) ;
191209 }
192210
193211 if let Some ( callee_def_id) = callee. def_id ( ) . as_local ( ) {
@@ -196,39 +214,44 @@ impl Inliner<'tcx> {
196214 // since their `optimized_mir` is used for layout computation, which can
197215 // create a cycle, even when no attempt is made to inline the function
198216 // in the other direction.
199- caller_body. generator_kind . is_none ( )
200- && (
201- // Avoid a cycle here by only using `instance_mir` only if we have
202- // a lower `HirId` than the callee. This ensures that the callee will
203- // not inline us. This trick only works without incremental compilation.
204- // So don't do it if that is enabled.
205- !self . tcx . dep_graph . is_fully_enabled ( )
206- && self . hir_id < callee_hir_id
207- // If we know for sure that the function we're calling will itself try to
208- // call us, then we avoid inlining that function.
209- || !self . tcx . mir_callgraph_reachable ( ( callee, caller_body. source . def_id ( ) . expect_local ( ) ) )
210- )
217+ if caller_body. generator_kind . is_some ( ) {
218+ return Err ( "local generator (query cycle avoidance)" ) ;
219+ }
220+
221+ // Avoid a cycle here by only using `instance_mir` only if we have
222+ // a lower `HirId` than the callee. This ensures that the callee will
223+ // not inline us. This trick only works without incremental compilation.
224+ // So don't do it if that is enabled.
225+ if !self . tcx . dep_graph . is_fully_enabled ( ) && self . hir_id < callee_hir_id {
226+ return Ok ( ( ) ) ;
227+ }
228+
229+ // If we know for sure that the function we're calling will itself try to
230+ // call us, then we avoid inlining that function.
231+ if self
232+ . tcx
233+ . mir_callgraph_reachable ( ( * callee, caller_body. source . def_id ( ) . expect_local ( ) ) )
234+ {
235+ return Err ( "caller might be reachable from callee (query cycle avoidance)" ) ;
236+ }
237+
238+ Ok ( ( ) )
211239 } else {
212240 // This cannot result in an immediate cycle since the callee MIR is from another crate
213241 // and is already optimized. Any subsequent inlining may cause cycles, but we do
214242 // not need to catch this here, we can wait until the inliner decides to continue
215243 // inlining a second time.
216244 trace ! ( "functions from other crates always have MIR" ) ;
217- true
245+ Ok ( ( ) )
218246 }
219247 }
220248
221- fn get_valid_function_call (
249+ fn resolve_callsite (
222250 & self ,
251+ caller_body : & Body < ' tcx > ,
223252 bb : BasicBlock ,
224253 bb_data : & BasicBlockData < ' tcx > ,
225- caller_body : & Body < ' tcx > ,
226254 ) -> Option < CallSite < ' tcx > > {
227- // Don't inline calls that are in cleanup blocks.
228- if bb_data. is_cleanup {
229- return None ;
230- }
231-
232255 // Only consider direct calls to functions
233256 let terminator = bb_data. terminator ( ) ;
234257 if let TerminatorKind :: Call { ref func, ref destination, .. } = terminator. kind {
@@ -258,73 +281,73 @@ impl Inliner<'tcx> {
258281 None
259282 }
260283
261- #[ instrument( level = "debug" , skip( self , callee_body) ) ]
262- fn should_inline ( & self , callsite : CallSite < ' tcx > , callee_body : & Body < ' tcx > ) -> bool {
263- let tcx = self . tcx ;
284+ /// Returns an error if inlining is not possible based on codegen attributes alone. A success
285+ /// indicates that inlining decision should be based on other criteria.
286+ fn check_codegen_attributes (
287+ & self ,
288+ callsite : & CallSite < ' tcx > ,
289+ callee_attrs : & CodegenFnAttrs ,
290+ ) -> Result < ( ) , & ' satic str > {
291+ if let InlineAttr :: Never = callee_attrs. inline {
292+ return Err ( "never inline hint" ) ;
293+ }
294+
295+ // Only inline local functions if they would be eligible for cross-crate
296+ // inlining. This is to ensure that the final crate doesn't have MIR that
297+ // reference unexported symbols
298+ if callsite. callee . def_id ( ) . is_local ( ) {
299+ let is_generic = callsite. callee . substs . non_erasable_generics ( ) . next ( ) . is_some ( ) ;
300+ if !is_generic && !callee_attrs. requests_inline ( ) {
301+ return Err ( "not exported" ) ;
302+ }
303+ }
264304
265305 if callsite. fn_sig . c_variadic ( ) {
266- debug ! ( "callee is variadic - not inlining" ) ;
267- return false ;
306+ return Err ( "C variadic" ) ;
268307 }
269308
270- let codegen_fn_attrs = tcx. codegen_fn_attrs ( callsite. callee . def_id ( ) ) ;
309+ if callee_attrs. flags . contains ( CodegenFnAttrFlags :: NAKED ) {
310+ return Err ( "naked" ) ;
311+ }
271312
272- let self_features = & self . codegen_fn_attrs . target_features ;
273- let callee_features = & codegen_fn_attrs. target_features ;
274- if callee_features. iter ( ) . any ( |feature| !self_features. contains ( feature) ) {
275- debug ! ( "`callee has extra target features - not inlining" ) ;
276- return false ;
313+ if callee_attrs. flags . contains ( CodegenFnAttrFlags :: COLD ) {
314+ return Err ( "cold" ) ;
277315 }
278316
279- if self . codegen_fn_attrs . no_sanitize != codegen_fn_attrs. no_sanitize {
280- debug ! ( "`callee has incompatible no_sanitize attribute - not inlining" ) ;
281- return false ;
317+ if callee_attrs. no_sanitize != self . codegen_fn_attrs . no_sanitize {
318+ return Err ( "incompatible sanitizer set" ) ;
282319 }
283320
284- if self . codegen_fn_attrs . instruction_set != codegen_fn_attrs. instruction_set {
285- debug ! ( "`callee has incompatible instruction set - not inlining" ) ;
286- return false ;
321+ if callee_attrs. instruction_set != self . codegen_fn_attrs . instruction_set {
322+ return Err ( "incompatible instruction set" ) ;
287323 }
288324
289- let hinted = match codegen_fn_attrs. inline {
290- // Just treat inline(always) as a hint for now,
291- // there are cases that prevent inlining that we
292- // need to check for first.
293- attr:: InlineAttr :: Always => true ,
294- attr:: InlineAttr :: Never => {
295- debug ! ( "`#[inline(never)]` present - not inlining" ) ;
296- return false ;
297- }
298- attr:: InlineAttr :: Hint => true ,
299- attr:: InlineAttr :: None => false ,
300- } ;
301-
302- // Only inline local functions if they would be eligible for cross-crate
303- // inlining. This is to ensure that the final crate doesn't have MIR that
304- // reference unexported symbols
305- if callsite. callee . def_id ( ) . is_local ( ) {
306- if callsite. callee . substs . non_erasable_generics ( ) . count ( ) == 0 && !hinted {
307- debug ! ( " callee is an exported function - not inlining" ) ;
308- return false ;
325+ for feature in & callee_attrs. target_features {
326+ if !self . codegen_fn_attrs . target_features . contains ( feature) {
327+ return Err ( "incompatible target feature" ) ;
309328 }
310329 }
311330
312- let mut threshold = if hinted {
331+ Ok ( ( ) )
332+ }
333+
334+ /// Returns inlining decision that is based on the examination of callee MIR body.
335+ /// Assumes that codegen attributes have been checked for compatibility already.
336+ #[ instrument( level = "debug" , skip( self , callee_body) ) ]
337+ fn check_mir_body (
338+ & self ,
339+ callsite : & CallSite < ' tcx > ,
340+ callee_body : & Body < ' tcx > ,
341+ callee_attrs : & CodegenFnAttrs ,
342+ ) -> Result < ( ) , & ' static str > {
343+ let tcx = self . tcx ;
344+
345+ let mut threshold = if callee_attrs. requests_inline ( ) {
313346 self . tcx . sess . opts . debugging_opts . inline_mir_hint_threshold
314347 } else {
315348 self . tcx . sess . opts . debugging_opts . inline_mir_threshold
316349 } ;
317350
318- if codegen_fn_attrs. flags . contains ( CodegenFnAttrFlags :: NAKED ) {
319- debug ! ( "#[naked] present - not inlining" ) ;
320- return false ;
321- }
322-
323- if codegen_fn_attrs. flags . contains ( CodegenFnAttrFlags :: COLD ) {
324- debug ! ( "#[cold] present - not inlining" ) ;
325- return false ;
326- }
327-
328351 // Give a bonus functions with a small number of blocks,
329352 // We normally have two or three blocks for even
330353 // very small functions.
@@ -393,11 +416,10 @@ impl Inliner<'tcx> {
393416 if let Ok ( Some ( instance) ) =
394417 Instance :: resolve ( self . tcx , self . param_env , def_id, substs)
395418 {
396- if callsite. callee . def_id ( ) == instance. def_id ( )
397- || self . history . contains ( & instance)
398- {
399- debug ! ( "`callee is recursive - not inlining" ) ;
400- return false ;
419+ if callsite. callee . def_id ( ) == instance. def_id ( ) {
420+ return Err ( "self-recursion" ) ;
421+ } else if self . history . contains ( & instance) {
422+ return Err ( "already inlined" ) ;
401423 }
402424 }
403425 // Don't give intrinsics the extra penalty for calls
@@ -450,24 +472,24 @@ impl Inliner<'tcx> {
450472 }
451473 }
452474
453- if let attr :: InlineAttr :: Always = codegen_fn_attrs . inline {
475+ if let InlineAttr :: Always = callee_attrs . inline {
454476 debug ! ( "INLINING {:?} because inline(always) [cost={}]" , callsite, cost) ;
455- true
477+ Ok ( ( ) )
456478 } else {
457479 if cost <= threshold {
458480 debug ! ( "INLINING {:?} [cost={} <= threshold={}]" , callsite, cost, threshold) ;
459- true
481+ Ok ( ( ) )
460482 } else {
461483 debug ! ( "NOT inlining {:?} [cost={} > threshold={}]" , callsite, cost, threshold) ;
462- false
484+ Err ( "cost above threshold" )
463485 }
464486 }
465487 }
466488
467489 fn inline_call (
468490 & self ,
469- callsite : CallSite < ' tcx > ,
470491 caller_body : & mut Body < ' tcx > ,
492+ callsite : & CallSite < ' tcx > ,
471493 mut callee_body : Body < ' tcx > ,
472494 ) {
473495 let terminator = caller_body[ callsite. block ] . terminator . take ( ) . unwrap ( ) ;
0 commit comments