1
1
//! Inlining pass for MIR functions
2
2
3
3
use rustc_attr as attr;
4
+ use rustc_hir as hir;
4
5
use rustc_index:: bit_set:: BitSet ;
5
6
use rustc_index:: vec:: Idx ;
6
7
use rustc_middle:: middle:: codegen_fn_attrs:: { CodegenFnAttrFlags , CodegenFnAttrs } ;
@@ -12,9 +13,8 @@ use rustc_target::spec::abi::Abi;
12
13
13
14
use super :: simplify:: { remove_dead_blocks, CfgSimplifier } ;
14
15
use crate :: transform:: MirPass ;
15
- use std:: collections:: VecDeque ;
16
16
use std:: iter;
17
- use std:: ops:: RangeFrom ;
17
+ use std:: ops:: { Range , RangeFrom } ;
18
18
19
19
const DEFAULT_THRESHOLD : usize = 50 ;
20
20
const HINT_THRESHOLD : usize = 100 ;
@@ -37,132 +37,128 @@ struct CallSite<'tcx> {
37
37
38
38
impl < ' tcx > MirPass < ' tcx > for Inline {
39
39
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) ;
54
56
}
55
57
}
56
58
}
57
59
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
+
58
85
struct Inliner < ' tcx > {
59
86
tcx : TyCtxt < ' tcx > ,
60
87
param_env : ParamEnv < ' tcx > ,
88
+ /// Caller codegen attributes.
61
89
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 ,
62
96
}
63
97
64
98
impl 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
+ } ;
99
105
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 ;
105
109
}
106
110
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) {
131
113
continue ;
132
114
}
133
115
116
+ if !self . tcx . consider_optimizing ( || {
117
+ format ! ( "Inline {:?} into {}" , callee_body. span, callsite. callee)
118
+ } ) {
119
+ return ;
120
+ }
121
+
134
122
let callee_body = callsite. callee . subst_mir_and_normalize_erasing_regions (
135
123
self . tcx ,
136
124
self . param_env ,
137
125
callee_body,
138
126
) ;
139
127
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
+ }
157
138
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
+ }
159
144
}
160
145
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
166
162
}
167
163
}
168
164
@@ -179,7 +175,8 @@ impl Inliner<'tcx> {
179
175
180
176
// Only consider direct calls to functions
181
177
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 {
183
180
if let ty:: FnDef ( callee_def_id, substs) = * op. ty ( caller_body, self . tcx ) . kind ( ) {
184
181
// To resolve an instance its substs have to be fully normalized, so
185
182
// we do this here.
@@ -200,14 +197,6 @@ impl Inliner<'tcx> {
200
197
None
201
198
}
202
199
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
-
211
200
fn should_inline ( & self , callsite : CallSite < ' tcx > , callee_body : & Body < ' tcx > ) -> bool {
212
201
debug ! ( "should_inline({:?})" , callsite) ;
213
202
let tcx = self . tcx ;
@@ -327,7 +316,18 @@ impl Inliner<'tcx> {
327
316
}
328
317
329
318
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
+ }
331
331
// Don't give intrinsics the extra penalty for calls
332
332
let f = tcx. fn_sig ( def_id) ;
333
333
if f. abi ( ) == Abi :: RustIntrinsic || f. abi ( ) == Abi :: PlatformIntrinsic {
@@ -397,13 +397,10 @@ impl Inliner<'tcx> {
397
397
callsite : CallSite < ' tcx > ,
398
398
caller_body : & mut Body < ' tcx > ,
399
399
mut callee_body : Body < ' tcx > ,
400
- ) -> bool {
400
+ ) {
401
401
let terminator = caller_body[ callsite. bb ] . terminator . take ( ) . unwrap ( ) ;
402
402
match terminator. kind {
403
- // FIXME: Handle inlining of diverging calls
404
403
TerminatorKind :: Call { args, destination : Some ( destination) , cleanup, .. } => {
405
- debug ! ( "inlined {:?} into {:?}" , callsite. callee, caller_body. source) ;
406
-
407
404
// If the call is something like `a[*i] = f(i)`, where
408
405
// `i : &mut usize`, then just duplicating the `a[*i]`
409
406
// Place could result in two different locations if `f`
@@ -519,14 +516,8 @@ impl Inliner<'tcx> {
519
516
matches ! ( constant. literal. val, ConstKind :: Unevaluated ( _, _, _) )
520
517
} ) ,
521
518
) ;
522
-
523
- true
524
- }
525
- kind => {
526
- caller_body[ callsite. bb ] . terminator =
527
- Some ( Terminator { source_info : terminator. source_info , kind } ) ;
528
- false
529
519
}
520
+ kind => bug ! ( "unexpected terminator kind {:?}" , kind) ,
530
521
}
531
522
}
532
523
0 commit comments