@@ -37,12 +37,19 @@ type SmallBlockCallVec = SmallVec<[ir::BlockCall; 8]>;
3737pub enum InlineCommand < ' a > {
3838 /// Keep the call as-is, out-of-line, and do not inline the callee.
3939 KeepCall ,
40+
4041 /// Inline the call, using this function as the body of the callee.
4142 ///
4243 /// It is the `Inline` implementor's responsibility to ensure that this
4344 /// function is the correct callee. Providing the wrong function may result
4445 /// in panics during compilation or incorrect runtime behavior.
45- Inline ( Cow < ' a , ir:: Function > ) ,
46+ Inline {
47+ /// The callee function's body.
48+ callee : Cow < ' a , ir:: Function > ,
49+ /// Whether to visit any function calls within the callee body after
50+ /// inlining and consider them for further inlining.
51+ visit_callee : bool ,
52+ } ,
4653}
4754
4855/// A trait for directing Cranelift whether to inline a particular call or not.
@@ -112,37 +119,45 @@ pub(crate) fn do_inlining(
112119 let mut allocs = InliningAllocs :: default ( ) ;
113120
114121 let mut cursor = FuncCursor :: new ( func) ;
115- while let Some ( block) = cursor. next_block ( ) {
116- // Always keep track of our previous cursor position. After we inline a
117- // call, replacing the current position with an arbitrary sub-CFG, we
118- // back up to this previous position. This makes sure our cursor is
119- // always at a position that is inserted in the layout and also enables
120- // multi-level inlining, if desired by the user, where we consider any
121- // newly-inlined call instructions for further inlining.
122+ ' block_loop: while let Some ( block) = cursor. next_block ( ) {
123+ // Always keep track of our previous cursor position. Assuming that the
124+ // current position is a function call that we will inline, then the
125+ // previous position is just before the inlined callee function. After
126+ // inlining a call, the Cranelift user can decide whether to consider
127+ // any function calls in the inlined callee for further inlining or
128+ // not. When they do, then we back up to this previous cursor position
129+ // so that our traversal will then continue over the inlined body.
122130 let mut prev_pos;
123131
124132 while let Some ( inst) = {
125133 prev_pos = cursor. position ( ) ;
126134 cursor. next_inst ( )
127135 } {
136+ // Make sure that `block` is always `inst`'s block, even with all of
137+ // our cursor-position-updating and block-splitting-during-inlining
138+ // shenanigans below.
139+ debug_assert_eq ! ( Some ( block) , cursor. func. layout. inst_block( inst) ) ;
140+
128141 match cursor. func . dfg . insts [ inst] {
129142 ir:: InstructionData :: Call {
130143 opcode : opcode @ ir:: Opcode :: Call | opcode @ ir:: Opcode :: ReturnCall ,
131144 args : _,
132145 func_ref,
133146 } => {
134- let args = cursor. func . dfg . inst_args ( inst) ;
135147 trace ! (
136148 "considering call site for inlining: {inst}: {}" ,
137149 cursor. func. dfg. display_inst( inst) ,
138150 ) ;
151+ let args = cursor. func . dfg . inst_args ( inst) ;
139152 match inliner. inline ( & cursor. func , inst, opcode, func_ref, args) {
140153 InlineCommand :: KeepCall => {
141154 trace ! ( " --> keeping call" ) ;
142- continue ;
143155 }
144- InlineCommand :: Inline ( callee) => {
145- inline_one (
156+ InlineCommand :: Inline {
157+ callee,
158+ visit_callee,
159+ } => {
160+ let last_inlined_block = inline_one (
146161 & mut allocs,
147162 cursor. func ,
148163 func_ref,
@@ -153,7 +168,15 @@ pub(crate) fn do_inlining(
153168 None ,
154169 ) ;
155170 inlined_any = true ;
156- cursor. set_position ( prev_pos) ;
171+ if visit_callee {
172+ cursor. set_position ( prev_pos) ;
173+ } else {
174+ // Arrange it so that the `next_block()` loop
175+ // will continue to the next block that is not
176+ // associated with the just-inlined callee.
177+ cursor. goto_bottom ( last_inlined_block) ;
178+ continue ' block_loop;
179+ }
157180 }
158181 }
159182 }
@@ -163,18 +186,20 @@ pub(crate) fn do_inlining(
163186 func_ref,
164187 exception,
165188 } => {
166- let args = cursor. func . dfg . inst_args ( inst) ;
167189 trace ! (
168190 "considering call site for inlining: {inst}: {}" ,
169191 cursor. func. dfg. display_inst( inst) ,
170192 ) ;
193+ let args = cursor. func . dfg . inst_args ( inst) ;
171194 match inliner. inline ( & cursor. func , inst, opcode, func_ref, args) {
172195 InlineCommand :: KeepCall => {
173196 trace ! ( " --> keeping call" ) ;
174- continue ;
175197 }
176- InlineCommand :: Inline ( callee) => {
177- inline_one (
198+ InlineCommand :: Inline {
199+ callee,
200+ visit_callee,
201+ } => {
202+ let last_inlined_block = inline_one (
178203 & mut allocs,
179204 cursor. func ,
180205 func_ref,
@@ -185,11 +210,30 @@ pub(crate) fn do_inlining(
185210 Some ( exception) ,
186211 ) ;
187212 inlined_any = true ;
188- cursor. set_position ( prev_pos) ;
213+ if visit_callee {
214+ cursor. set_position ( prev_pos) ;
215+ } else {
216+ // Arrange it so that the `next_block()` loop
217+ // will continue to the next block that is not
218+ // associated with the just-inlined callee.
219+ cursor. goto_bottom ( last_inlined_block) ;
220+ continue ' block_loop;
221+ }
189222 }
190223 }
191224 }
192- _ => continue ,
225+ ir:: InstructionData :: CallIndirect { .. }
226+ | ir:: InstructionData :: TryCallIndirect { .. } => {
227+ // Can't inline indirect calls; need to have some earlier
228+ // pass rewrite them into direct calls first, when possible.
229+ }
230+ _ => {
231+ debug_assert ! (
232+ !cursor. func. dfg. insts[ inst] . opcode( ) . is_call( ) ,
233+ "should have matched all call instructions, but found: {inst}: {}" ,
234+ cursor. func. dfg. display_inst( inst) ,
235+ ) ;
236+ }
193237 }
194238 }
195239 }
@@ -283,6 +327,8 @@ impl InliningAllocs {
283327}
284328
285329/// Inline one particular function call.
330+ ///
331+ /// Returns the last inlined block in the layout.
286332fn inline_one (
287333 allocs : & mut InliningAllocs ,
288334 func : & mut ir:: Function ,
@@ -292,7 +338,7 @@ fn inline_one(
292338 call_opcode : ir:: Opcode ,
293339 callee : & ir:: Function ,
294340 call_exception_table : Option < ir:: ExceptionTable > ,
295- ) {
341+ ) -> ir :: Block {
296342 trace ! (
297343 "Inlining call {call_inst:?}: {}\n \
298344 with callee = {callee:?}",
@@ -318,7 +364,7 @@ fn inline_one(
318364 // Prepare for translating the actual instructions by inserting the inlined
319365 // blocks into the caller's layout in the same order that they appear in the
320366 // callee.
321- inline_block_layout ( func, call_block, callee, & entity_map) ;
367+ let last_inlined_block = inline_block_layout ( func, call_block, callee, & entity_map) ;
322368
323369 // Translate each instruction from the callee into the caller,
324370 // appending them to their associated block in the caller.
@@ -470,6 +516,8 @@ fn inline_one(
470516 if let Some ( call_exception_table) = call_exception_table {
471517 fixup_inlined_call_exception_tables ( allocs, func, call_exception_table) ;
472518 }
519+
520+ last_inlined_block
473521}
474522
475523/// Append stack map entries from the caller and callee to the given inlined
@@ -916,12 +964,14 @@ impl<'a> ir::instructions::InstructionMapper for InliningInstRemapper<'a> {
916964}
917965
918966/// Inline the callee's layout into the caller's layout.
967+ ///
968+ /// Returns the last inlined block in the layout.
919969fn inline_block_layout (
920970 func : & mut ir:: Function ,
921971 call_block : ir:: Block ,
922972 callee : & ir:: Function ,
923973 entity_map : & EntityMap ,
924- ) {
974+ ) -> ir :: Block {
925975 // Iterate over callee blocks in layout order, inserting their associated
926976 // inlined block into the caller's layout.
927977 let mut prev_inlined_block = call_block;
@@ -934,6 +984,7 @@ fn inline_block_layout(
934984 prev_inlined_block = inlined_block;
935985 next_callee_block = callee. layout . next_block ( callee_block) ;
936986 }
987+ prev_inlined_block
937988}
938989
939990/// Split the call instruction's block just after the call instruction to create
0 commit comments