1
1
//! Inlining pass for MIR functions
2
2
3
- use rustc_attr as attr ;
3
+ use rustc_attr:: InlineAttr ;
4
4
use rustc_hir as hir;
5
5
use rustc_index:: bit_set:: BitSet ;
6
6
use rustc_index:: vec:: Idx ;
@@ -106,72 +106,90 @@ struct Inliner<'tcx> {
106
106
impl Inliner < ' tcx > {
107
107
fn process_blocks ( & mut self , caller_body : & mut Body < ' tcx > , blocks : Range < BasicBlock > ) {
108
108
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) {
110
115
None => continue ,
111
116
Some ( it) => it,
112
117
} ;
118
+
113
119
let span = trace_span ! ( "process_blocks" , %callsite. callee, ?bb) ;
114
120
let _guard = span. enter ( ) ;
115
121
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
+ }
129
134
}
135
+ }
136
+ }
130
137
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
+ }
138
157
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
+ ) ;
144
163
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 ( ) ;
150
167
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
+ }
155
170
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" ) ;
159
178
}
160
- }
161
179
162
- #[ instrument( level = "debug" , skip( self , caller_body) ) ]
163
- fn is_mir_available ( & self , callee : Instance < ' tcx > , caller_body : & Body < ' tcx > ) -> bool {
164
180
match callee. def {
165
181
InstanceDef :: Item ( _) => {
166
182
// If there is no MIR available (either because it was not in metadata or
167
183
// because it has no MIR because it's an extern function), then the inliner
168
184
// won't cause cycles on this.
169
185
if !self . tcx . is_mir_available ( callee. def_id ( ) ) {
170
- return false ;
186
+ return Err ( "item MIR unavailable" ) ;
171
187
}
172
188
}
173
189
// 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
+ }
175
193
// This cannot result in an immediate cycle since the callee MIR is a shim, which does
176
194
// not get any optimizations run on it. Any subsequent inlining may cause cycles, but we
177
195
// do not need to catch this here, we can wait until the inliner decides to continue
@@ -181,13 +199,13 @@ impl Inliner<'tcx> {
181
199
| InstanceDef :: FnPtrShim ( ..)
182
200
| InstanceDef :: ClosureOnceShim { .. }
183
201
| InstanceDef :: DropGlue ( ..)
184
- | InstanceDef :: CloneShim ( ..) => return true ,
202
+ | InstanceDef :: CloneShim ( ..) => return Ok ( ( ) ) ,
185
203
}
186
204
187
205
if self . tcx . is_constructor ( callee. def_id ( ) ) {
188
206
trace ! ( "constructors always have MIR" ) ;
189
207
// Constructor functions cannot cause a query cycle.
190
- return true ;
208
+ return Ok ( ( ) ) ;
191
209
}
192
210
193
211
if let Some ( callee_def_id) = callee. def_id ( ) . as_local ( ) {
@@ -196,39 +214,44 @@ impl Inliner<'tcx> {
196
214
// since their `optimized_mir` is used for layout computation, which can
197
215
// create a cycle, even when no attempt is made to inline the function
198
216
// 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 ( ( ) )
211
239
} else {
212
240
// This cannot result in an immediate cycle since the callee MIR is from another crate
213
241
// and is already optimized. Any subsequent inlining may cause cycles, but we do
214
242
// not need to catch this here, we can wait until the inliner decides to continue
215
243
// inlining a second time.
216
244
trace ! ( "functions from other crates always have MIR" ) ;
217
- true
245
+ Ok ( ( ) )
218
246
}
219
247
}
220
248
221
- fn get_valid_function_call (
249
+ fn resolve_callsite (
222
250
& self ,
251
+ caller_body : & Body < ' tcx > ,
223
252
bb : BasicBlock ,
224
253
bb_data : & BasicBlockData < ' tcx > ,
225
- caller_body : & Body < ' tcx > ,
226
254
) -> Option < CallSite < ' tcx > > {
227
- // Don't inline calls that are in cleanup blocks.
228
- if bb_data. is_cleanup {
229
- return None ;
230
- }
231
-
232
255
// Only consider direct calls to functions
233
256
let terminator = bb_data. terminator ( ) ;
234
257
if let TerminatorKind :: Call { ref func, ref destination, .. } = terminator. kind {
@@ -258,73 +281,73 @@ impl Inliner<'tcx> {
258
281
None
259
282
}
260
283
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
+ }
264
304
265
305
if callsite. fn_sig . c_variadic ( ) {
266
- debug ! ( "callee is variadic - not inlining" ) ;
267
- return false ;
306
+ return Err ( "C variadic" ) ;
268
307
}
269
308
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
+ }
271
312
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" ) ;
277
315
}
278
316
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" ) ;
282
319
}
283
320
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" ) ;
287
323
}
288
324
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" ) ;
309
328
}
310
329
}
311
330
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 ( ) {
313
346
self . tcx . sess . opts . debugging_opts . inline_mir_hint_threshold
314
347
} else {
315
348
self . tcx . sess . opts . debugging_opts . inline_mir_threshold
316
349
} ;
317
350
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
-
328
351
// Give a bonus functions with a small number of blocks,
329
352
// We normally have two or three blocks for even
330
353
// very small functions.
@@ -393,11 +416,10 @@ impl Inliner<'tcx> {
393
416
if let Ok ( Some ( instance) ) =
394
417
Instance :: resolve ( self . tcx , self . param_env , def_id, substs)
395
418
{
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" ) ;
401
423
}
402
424
}
403
425
// Don't give intrinsics the extra penalty for calls
@@ -450,24 +472,24 @@ impl Inliner<'tcx> {
450
472
}
451
473
}
452
474
453
- if let attr :: InlineAttr :: Always = codegen_fn_attrs . inline {
475
+ if let InlineAttr :: Always = callee_attrs . inline {
454
476
debug ! ( "INLINING {:?} because inline(always) [cost={}]" , callsite, cost) ;
455
- true
477
+ Ok ( ( ) )
456
478
} else {
457
479
if cost <= threshold {
458
480
debug ! ( "INLINING {:?} [cost={} <= threshold={}]" , callsite, cost, threshold) ;
459
- true
481
+ Ok ( ( ) )
460
482
} else {
461
483
debug ! ( "NOT inlining {:?} [cost={} > threshold={}]" , callsite, cost, threshold) ;
462
- false
484
+ Err ( "cost above threshold" )
463
485
}
464
486
}
465
487
}
466
488
467
489
fn inline_call (
468
490
& self ,
469
- callsite : CallSite < ' tcx > ,
470
491
caller_body : & mut Body < ' tcx > ,
492
+ callsite : & CallSite < ' tcx > ,
471
493
mut callee_body : Body < ' tcx > ,
472
494
) {
473
495
let terminator = caller_body[ callsite. block ] . terminator . take ( ) . unwrap ( ) ;
0 commit comments