1 //! Function inlining infrastructure.
2 //!
3 //! This module provides "inlining as a library" to Cranelift users; it does
4 //! _not_ provide a complete, off-the-shelf inlining solution. Cranelift's
5 //! compilation context is per-function and does not encompass the full call
6 //! graph. It does not know which functions are hot and which are cold, which
7 //! have been marked the equivalent of `#[inline(never)]`, etc... Only the
8 //! Cranelift user can understand these aspects of the full compilation
9 //! pipeline, and these things can be very different between (say) Wasmtime and
10 //! `cg_clif`. Therefore, this module does not attempt to define hueristics for
11 //! when inlining a particular call is likely beneficial. This module only
12 //! provides hooks for the Cranelift user to define whether a given call should
13 //! be inlined or not, and the mechanics to inline a callee into a particular
14 //! call site when directed to do so by the Cranelift user.
15 //!
16 //! The top-level inlining entry point during Cranelift compilation is
17 //! [`Context::inline`][crate::Context::inline]. It takes an [`Inline`] trait
18 //! implementation, which is authored by the Cranelift user and directs
19 //! Cranelift whether to inline a particular call, and, when inlining, gives
20 //! Cranelift the body of the callee that is to be inlined.
21 
22 use crate::cursor::{Cursor as _, FuncCursor};
23 use crate::ir::{self, InstBuilder as _};
24 use crate::result::CodegenResult;
25 use crate::trace;
26 use crate::traversals::Dfs;
27 use alloc::borrow::Cow;
28 use alloc::vec::Vec;
29 use cranelift_entity::{SecondaryMap, packed_option::PackedOption};
30 use smallvec::SmallVec;
31 
32 type SmallValueVec = SmallVec<[ir::Value; 8]>;
33 type SmallBlockArgVec = SmallVec<[ir::BlockArg; 8]>;
34 type SmallBlockCallVec = SmallVec<[ir::BlockCall; 8]>;
35 
36 /// A command directing Cranelift whether or not to inline a particular call.
37 pub enum InlineCommand<'a> {
38     /// Keep the call as-is, out-of-line, and do not inline the callee.
39     KeepCall,
40     /// Inline the call, using this function as the body of the callee.
41     ///
42     /// It is the `Inline` implementor's responsibility to ensure that this
43     /// function is the correct callee. Providing the wrong function may result
44     /// in panics during compilation or incorrect runtime behavior.
45     Inline(Cow<'a, ir::Function>),
46 }
47 
48 /// A trait for directing Cranelift whether to inline a particular call or not.
49 ///
50 /// Used in combination with the [`Context::inline`][crate::Context::inline]
51 /// method.
52 pub trait Inline {
53     /// A hook invoked for each direct call instruction in a function, whose
54     /// result determines whether Cranelift should inline a given call.
55     ///
56     /// The Cranelift user is responsible for defining their own hueristics and
57     /// deciding whether inlining the call is beneficial.
58     ///
59     /// When returning a function and directing Cranelift to inline its body
60     /// into the call site, the `Inline` implementer must ensure the following:
61     ///
62     /// * The returned function's signature exactly matches the `callee`
63     ///   `FuncRef`'s signature.
64     ///
65     /// * The returned function must be legalized.
66     ///
67     /// * The returned function must be valid (i.e. it must pass the CLIF
68     ///   verifier).
69     ///
70     /// * The returned function is a correct and valid implementation of the
71     ///   `callee` according to your language's semantics.
72     ///
73     /// Failure to uphold these invariants may result in panics during
74     /// compilation or incorrect runtime behavior in the generated code.
75     fn inline(
76         &self,
77         caller: &ir::Function,
78         call_inst: ir::Inst,
79         call_opcode: ir::Opcode,
80         callee: ir::FuncRef,
81         call_args: &[ir::Value],
82     ) -> InlineCommand<'_>;
83 }
84 
85 impl<'a, T> Inline for &'a T
86 where
87     T: Inline,
88 {
89     fn inline(
90         &self,
91         caller: &ir::Function,
92         inst: ir::Inst,
93         opcode: ir::Opcode,
94         callee: ir::FuncRef,
95         args: &[ir::Value],
96     ) -> InlineCommand<'_> {
97         (*self).inline(caller, inst, opcode, callee, args)
98     }
99 }
100 
101 /// Walk the given function, invoke the `Inline` implementation for each call
102 /// instruction, and inline the callee when directed to do so.
103 ///
104 /// Returns whether any call was inlined.
105 pub(crate) fn do_inlining(func: &mut ir::Function, inliner: impl Inline) -> CodegenResult<bool> {
106     let mut inlined_any = false;
107     let mut allocs = InliningAllocs::default();
108 
109     let mut cursor = FuncCursor::new(func);
110     while let Some(block) = cursor.next_block() {
111         // Always keep track of our previous cursor position. After we inline a
112         // call, replacing the current position with an arbitrary sub-CFG, we
113         // back up to this previous position. This makes sure our cursor is
114         // always at a position that is inserted in the layout and also enables
115         // multi-level inlining, if desired by the user, where we consider any
116         // newly-inlined call instructions for further inlining.
117         let mut prev_pos;
118 
119         while let Some(inst) = {
120             prev_pos = cursor.position();
121             cursor.next_inst()
122         } {
123             match cursor.func.dfg.insts[inst] {
124                 ir::InstructionData::Call {
125                     opcode: opcode @ ir::Opcode::Call | opcode @ ir::Opcode::ReturnCall,
126                     args: _,
127                     func_ref,
128                 } => {
129                     let args = cursor.func.dfg.inst_args(inst);
130                     match inliner.inline(&cursor.func, inst, opcode, func_ref, args) {
131                         InlineCommand::KeepCall => continue,
132                         InlineCommand::Inline(callee) => {
133                             inline_one(
134                                 &mut allocs,
135                                 cursor.func,
136                                 func_ref,
137                                 block,
138                                 inst,
139                                 opcode,
140                                 &callee,
141                                 None,
142                             );
143                             inlined_any = true;
144                             cursor.set_position(prev_pos);
145                         }
146                     }
147                 }
148                 ir::InstructionData::TryCall {
149                     opcode: opcode @ ir::Opcode::TryCall,
150                     args: _,
151                     func_ref,
152                     exception,
153                 } => {
154                     let args = cursor.func.dfg.inst_args(inst);
155                     match inliner.inline(&cursor.func, inst, opcode, func_ref, args) {
156                         InlineCommand::KeepCall => continue,
157                         InlineCommand::Inline(callee) => {
158                             inline_one(
159                                 &mut allocs,
160                                 cursor.func,
161                                 func_ref,
162                                 block,
163                                 inst,
164                                 opcode,
165                                 &callee,
166                                 Some(exception),
167                             );
168                             inlined_any = true;
169                             cursor.set_position(prev_pos);
170                         }
171                     }
172                 }
173                 _ => continue,
174             }
175         }
176     }
177 
178     Ok(inlined_any)
179 }
180 
181 #[derive(Default)]
182 struct InliningAllocs {
183     /// Map from callee value to inlined caller value.
184     values: SecondaryMap<ir::Value, PackedOption<ir::Value>>,
185 
186     /// Map from callee constant to inlined caller constant.
187     constants: SecondaryMap<ir::Constant, PackedOption<ir::Constant>>,
188 
189     /// The set of _caller_ inlined call instructions that need exception table
190     /// fixups at the end of inlining.
191     ///
192     /// This includes all kinds of non-returning calls, not just the literal
193     /// `call` instruction: `call_indirect`, `try_call`, `try_call_indirect`,
194     /// etc... However, it does not include `return_call` and
195     /// `return_call_indirect` instructions because the caller cannot catch
196     /// exceptions that those calls throw because the caller is no longer on the
197     /// stack as soon as they are executed.
198     ///
199     /// Note: this is a simple `Vec`, and not an `EntitySet`, because it is very
200     /// sparse: most of the caller's instructions are not inlined call
201     /// instructions. Additionally, we require deterministic iteration order and
202     /// do not require set-membership testing, so a hash set is not a good
203     /// choice either.
204     calls_needing_exception_table_fixup: Vec<ir::Inst>,
205 
206     /// The set of existing tags already caught by an inlined `try_call`
207     /// instruction's exception table, for filtering out duplicate tags when
208     /// merging exception tables.
209     ///
210     /// Note: this is a `HashSet`, and not an `EntitySet`, because tags indices
211     /// are completely arbitrary and there is no guarantee that they start from
212     /// zero or are contiguous.
213     existing_exception_tags: crate::HashSet<Option<ir::ExceptionTag>>,
214 }
215 
216 impl InliningAllocs {
217     fn reset(&mut self, callee: &ir::Function) {
218         let InliningAllocs {
219             values,
220             constants,
221             calls_needing_exception_table_fixup,
222             existing_exception_tags,
223         } = self;
224 
225         values.clear();
226         values.resize(callee.dfg.len_values());
227 
228         constants.clear();
229         constants.resize(callee.dfg.constants.len());
230 
231         // Note: We do not reserve capacity for
232         // `calls_needing_exception_table_fixup` because it is a sparse set and
233         // we don't know how large it needs to be ahead of time.
234         calls_needing_exception_table_fixup.clear();
235 
236         // Note: We do not reserve capacity for `existing_exception_tags`
237         // because it is a sparse set and we don't know how large it needs to be
238         // ahead of time.
239         existing_exception_tags.clear();
240     }
241 
242     fn set_inlined_value(
243         &mut self,
244         callee: &ir::Function,
245         callee_val: ir::Value,
246         inlined_val: ir::Value,
247     ) {
248         trace!("  --> callee {callee_val:?} = inlined {inlined_val:?}");
249         debug_assert!(self.values[callee_val].is_none());
250         let resolved_callee_val = callee.dfg.resolve_aliases(callee_val);
251         debug_assert!(self.values[resolved_callee_val].is_none());
252         self.values[resolved_callee_val] = Some(inlined_val).into();
253     }
254 
255     fn get_inlined_value(&self, callee: &ir::Function, callee_val: ir::Value) -> Option<ir::Value> {
256         let resolved_callee_val = callee.dfg.resolve_aliases(callee_val);
257         self.values[resolved_callee_val].expand()
258     }
259 }
260 
261 /// Inline one particular function call.
262 fn inline_one(
263     allocs: &mut InliningAllocs,
264     func: &mut ir::Function,
265     callee_func_ref: ir::FuncRef,
266     call_block: ir::Block,
267     call_inst: ir::Inst,
268     call_opcode: ir::Opcode,
269     callee: &ir::Function,
270     call_exception_table: Option<ir::ExceptionTable>,
271 ) {
272     trace!(
273         "Inlining call {call_inst:?}: {}\n\
274          with callee = {callee:?}",
275         func.dfg.display_inst(call_inst)
276     );
277 
278     // Type check callee signature.
279     let expected_callee_sig = func.dfg.ext_funcs[callee_func_ref].signature;
280     let expected_callee_sig = &func.dfg.signatures[expected_callee_sig];
281     assert_eq!(expected_callee_sig, &callee.signature);
282 
283     allocs.reset(callee);
284 
285     // First, append various callee entity arenas to the end of the caller's
286     // entity arenas.
287     let entity_map = create_entities(allocs, func, callee);
288 
289     // Inlined prologue: split the call instruction's block at the point of the
290     // call and replace the call with a jump.
291     let return_block = split_off_return_block(func, call_inst, call_opcode, callee);
292     let call_stack_map = replace_call_with_jump(allocs, func, call_inst, callee, &entity_map);
293 
294     // Prepare for translating the actual instructions by inserting the inlined
295     // blocks into the caller's layout in the same order that they appear in the
296     // callee.
297     inline_block_layout(func, call_block, callee, &entity_map);
298 
299     // Translate each instruction from the callee into the caller,
300     // appending them to their associated block in the caller.
301     //
302     // Note that we iterate over the callee with a pre-order traversal so that
303     // we see value defs before uses.
304     for callee_block in Dfs::new().pre_order_iter(callee) {
305         let inlined_block = entity_map.inlined_block(callee_block);
306         trace!(
307             "Processing instructions in callee block {callee_block:?} (inlined block {inlined_block:?}"
308         );
309 
310         let mut next_callee_inst = callee.layout.first_inst(callee_block);
311         while let Some(callee_inst) = next_callee_inst {
312             trace!(
313                 "Processing callee instruction {callee_inst:?}: {}",
314                 callee.dfg.display_inst(callee_inst)
315             );
316 
317             assert_ne!(
318                 callee.dfg.insts[callee_inst].opcode(),
319                 ir::Opcode::GlobalValue,
320                 "callee must already be legalized, we shouldn't see any `global_value` \
321                  instructions when inlining; found {callee_inst:?}: {}",
322                 callee.dfg.display_inst(callee_inst)
323             );
324 
325             // Remap the callee instruction's entities and insert it into the
326             // caller's DFG.
327             let inlined_inst_data = callee.dfg.insts[callee_inst].map(InliningInstRemapper {
328                 allocs: &allocs,
329                 func,
330                 callee,
331                 entity_map: &entity_map,
332             });
333             let inlined_inst = func.dfg.make_inst(inlined_inst_data);
334             func.layout.append_inst(inlined_inst, inlined_block);
335 
336             let opcode = callee.dfg.insts[callee_inst].opcode();
337             if opcode.is_return() {
338                 // Instructions that return do not define any values, so we
339                 // don't need to worry about that, but we do need to fix them up
340                 // so that they return by jumping to our control-flow join
341                 // block, rather than returning from the caller.
342                 if let Some(return_block) = return_block {
343                     fixup_inst_that_returns(
344                         allocs,
345                         func,
346                         callee,
347                         &entity_map,
348                         call_opcode,
349                         inlined_inst,
350                         callee_inst,
351                         return_block,
352                         call_stack_map.as_ref().map(|es| &**es),
353                     );
354                 } else {
355                     // If we are inlining a callee that was invoked via
356                     // `return_call`, we leave inlined return instructions
357                     // as-is: there is no logical caller frame on the stack to
358                     // continue to.
359                     debug_assert_eq!(call_opcode, ir::Opcode::ReturnCall);
360                 }
361             } else {
362                 // Make the instruction's result values.
363                 let ctrl_typevar = callee.dfg.ctrl_typevar(callee_inst);
364                 func.dfg.make_inst_results(inlined_inst, ctrl_typevar);
365 
366                 // Update the value map for this instruction's defs.
367                 let callee_results = callee.dfg.inst_results(callee_inst);
368                 let inlined_results = func.dfg.inst_results(inlined_inst);
369                 debug_assert_eq!(callee_results.len(), inlined_results.len());
370                 for (callee_val, inlined_val) in callee_results.iter().zip(inlined_results) {
371                     allocs.set_inlined_value(callee, *callee_val, *inlined_val);
372                 }
373 
374                 if opcode.is_call() {
375                     append_stack_map_entries(
376                         func,
377                         callee,
378                         &entity_map,
379                         call_stack_map.as_deref(),
380                         inlined_inst,
381                         callee_inst,
382                     );
383 
384                     // When we are inlining a `try_call` call site, we need to merge
385                     // the call site's exception table into the inlined calls'
386                     // exception tables. This can involve rewriting regular `call`s
387                     // into `try_call`s, which requires mutating the CFG because
388                     // `try_call` is a block terminator. However, we can't mutate
389                     // the CFG in the middle of this traversal because we rely on
390                     // the existence of a one-to-one mapping between the callee
391                     // layout and the inlined layout. Instead, we record the set of
392                     // inlined call instructions that will need fixing up, and
393                     // perform that possibly-CFG-mutating exception table merging in
394                     // a follow up pass, when we no longer rely on that one-to-one
395                     // layout mapping.
396                     debug_assert_eq!(
397                         call_opcode == ir::Opcode::TryCall,
398                         call_exception_table.is_some()
399                     );
400                     if call_opcode == ir::Opcode::TryCall {
401                         allocs
402                             .calls_needing_exception_table_fixup
403                             .push(inlined_inst);
404                     }
405                 }
406             }
407 
408             trace!(
409                 "  --> inserted inlined instruction {inlined_inst:?}: {}",
410                 func.dfg.display_inst(inlined_inst)
411             );
412 
413             next_callee_inst = callee.layout.next_inst(callee_inst);
414         }
415     }
416 
417     // Final step: fixup the exception tables of any inlined calls when we are
418     // inlining a `try_call` site.
419     //
420     // Subtly, this requires rewriting non-catching `call[_indirect]`
421     // instructions into `try_call[_indirect]` instructions so that exceptions
422     // that unwound through the original callee frame and were caught by the
423     // caller's `try_call` do not unwind past this inlined frame. And turning a
424     // `call` into a `try_call` mutates the CFG, breaking our one-to-one mapping
425     // between callee blocks and inlined blocks, so we delay these fixups to
426     // this final step, when we no longer rely on that mapping.
427     debug_assert!(
428         allocs.calls_needing_exception_table_fixup.is_empty() || call_exception_table.is_some()
429     );
430     debug_assert_eq!(
431         call_opcode == ir::Opcode::TryCall,
432         call_exception_table.is_some()
433     );
434     if let Some(call_exception_table) = call_exception_table {
435         fixup_inlined_call_exception_tables(allocs, func, call_exception_table);
436     }
437 }
438 
439 /// Append stack map entries from the caller and callee to the given inlined
440 /// instruction.
441 fn append_stack_map_entries(
442     func: &mut ir::Function,
443     callee: &ir::Function,
444     entity_map: &EntityMap,
445     call_stack_map: Option<&[ir::UserStackMapEntry]>,
446     inlined_inst: ir::Inst,
447     callee_inst: ir::Inst,
448 ) {
449     // Add the caller's stack map to this call. These entries
450     // already refer to caller entities and do not need further
451     // translation.
452     func.dfg.append_user_stack_map_entries(
453         inlined_inst,
454         call_stack_map
455             .iter()
456             .flat_map(|entries| entries.iter().cloned()),
457     );
458 
459     // Append the callee's stack map to this call. These entries
460     // refer to callee entities and therefore do require
461     // translation into the caller's index space.
462     func.dfg.append_user_stack_map_entries(
463         inlined_inst,
464         callee
465             .dfg
466             .user_stack_map_entries(callee_inst)
467             .iter()
468             .flat_map(|entries| entries.iter())
469             .map(|entry| ir::UserStackMapEntry {
470                 ty: entry.ty,
471                 slot: entity_map.inlined_stack_slot(entry.slot),
472                 offset: entry.offset,
473             }),
474     );
475 }
476 
477 /// Create or update the exception tables for any inlined call instructions:
478 /// when inlining at a `try_call` site, we must forward our exceptional edges
479 /// into each inlined call instruction.
480 fn fixup_inlined_call_exception_tables(
481     allocs: &mut InliningAllocs,
482     func: &mut ir::Function,
483     call_exception_table: ir::ExceptionTable,
484 ) {
485     // Split a block at a `call[_indirect]` instruction, detach the
486     // instruction's results, and alias them to the new block's parameters.
487     let split_block_for_new_try_call = |func: &mut ir::Function, inst: ir::Inst| -> ir::Block {
488         debug_assert!(func.dfg.insts[inst].opcode().is_call());
489         debug_assert!(!func.dfg.insts[inst].opcode().is_terminator());
490 
491         // Split the block.
492         let next_inst = func
493             .layout
494             .next_inst(inst)
495             .expect("inst is not a terminator, should have a successor");
496         let new_block = func.dfg.blocks.add();
497         func.layout.split_block(new_block, next_inst);
498 
499         // `try_call[_indirect]` instructions do not define values themselves;
500         // the normal-return block has parameters for the results. So remove
501         // this instruction's results, create an associated block parameter for
502         // each of them, and alias them to the new block parameter.
503         let old_results = SmallValueVec::from_iter(func.dfg.inst_results(inst).iter().copied());
504         func.dfg.detach_inst_results(inst);
505         for old_result in old_results {
506             let ty = func.dfg.value_type(old_result);
507             let new_block_param = func.dfg.append_block_param(new_block, ty);
508             func.dfg.change_to_alias(old_result, new_block_param);
509         }
510 
511         new_block
512     };
513 
514     // Clone the caller's exception table, updating it for use in the current
515     // `call[_indirect]` instruction as it becomes a `try_call[_indirect]`.
516     let clone_exception_table_for_this_call = |func: &mut ir::Function,
517                                                signature: ir::SigRef,
518                                                new_block: ir::Block|
519      -> ir::ExceptionTable {
520         let mut exception = func.stencil.dfg.exception_tables[call_exception_table]
521             .deep_clone(&mut func.stencil.dfg.value_lists);
522 
523         *exception.signature_mut() = signature;
524 
525         let returns_len = func.dfg.signatures[signature].returns.len();
526         let returns_len = u32::try_from(returns_len).unwrap();
527 
528         *exception.normal_return_mut() = ir::BlockCall::new(
529             new_block,
530             (0..returns_len).map(|i| ir::BlockArg::TryCallRet(i)),
531             &mut func.dfg.value_lists,
532         );
533 
534         func.dfg.exception_tables.push(exception)
535     };
536 
537     for inst in allocs.calls_needing_exception_table_fixup.drain(..) {
538         debug_assert!(func.dfg.insts[inst].opcode().is_call());
539         debug_assert!(!func.dfg.insts[inst].opcode().is_return());
540         match func.dfg.insts[inst] {
541             //     current_block:
542             //         preds...
543             //         rets... = call f(args...)
544             //         succs...
545             //
546             // becomes
547             //
548             //     current_block:
549             //         preds...
550             //         try_call f(args...), new_block(rets...), [call_exception_table...]
551             //     new_block(rets...):
552             //         succs...
553             ir::InstructionData::Call {
554                 opcode: ir::Opcode::Call,
555                 args,
556                 func_ref,
557             } => {
558                 let new_block = split_block_for_new_try_call(func, inst);
559                 let signature = func.dfg.ext_funcs[func_ref].signature;
560                 let exception = clone_exception_table_for_this_call(func, signature, new_block);
561                 func.dfg.insts[inst] = ir::InstructionData::TryCall {
562                     opcode: ir::Opcode::TryCall,
563                     args,
564                     func_ref,
565                     exception,
566                 };
567             }
568 
569             //     current_block:
570             //         preds...
571             //         rets... = call_indirect sig, val(args...)
572             //         succs...
573             //
574             // becomes
575             //
576             //     current_block:
577             //         preds...
578             //         try_call_indirect sig, val(args...), new_block(rets...), [call_exception_table...]
579             //     new_block(rets...):
580             //         succs...
581             ir::InstructionData::CallIndirect {
582                 opcode: ir::Opcode::CallIndirect,
583                 args,
584                 sig_ref,
585             } => {
586                 let new_block = split_block_for_new_try_call(func, inst);
587                 let exception = clone_exception_table_for_this_call(func, sig_ref, new_block);
588                 func.dfg.insts[inst] = ir::InstructionData::TryCallIndirect {
589                     opcode: ir::Opcode::TryCallIndirect,
590                     args,
591                     exception,
592                 };
593             }
594 
595             // For `try_call[_indirect]` instructions, we just need to merge the
596             // exception tables.
597             ir::InstructionData::TryCall {
598                 opcode: ir::Opcode::TryCall,
599                 exception,
600                 ..
601             }
602             | ir::InstructionData::TryCallIndirect {
603                 opcode: ir::Opcode::TryCallIndirect,
604                 exception,
605                 ..
606             } => {
607                 // Gather the set of tags that this instruction's exception
608                 // table already has entries for.
609                 allocs.existing_exception_tags.clear();
610                 allocs.existing_exception_tags.extend(
611                     func.dfg.exception_tables[exception]
612                         .catches()
613                         .map(|(c, _)| c),
614                 );
615 
616                 // Add only the catch edges from our original `try_call`'s
617                 // exception table that are not already handled by this
618                 // instruction.
619                 for i in 0..func.dfg.exception_tables[call_exception_table].len_catches() {
620                     let exception_tables = &mut func.stencil.dfg.exception_tables;
621                     let value_lists = &mut func.stencil.dfg.value_lists;
622 
623                     let (tag, block_call) =
624                         exception_tables[call_exception_table].get_catch(i).unwrap();
625                     if allocs.existing_exception_tags.contains(&tag) {
626                         continue;
627                     }
628 
629                     let block_call = block_call.deep_clone(value_lists);
630                     exception_tables[exception].push_catch(tag, block_call);
631                 }
632             }
633 
634             otherwise => unreachable!("unknown non-return call instruction: {otherwise:?}"),
635         }
636     }
637 }
638 
639 /// After having created an inlined version of a callee instruction that returns
640 /// in the caller, we need to fix it up so that it doesn't actually return
641 /// (since we are already in the caller's frame) and instead just jumps to the
642 /// control-flow join point.
643 fn fixup_inst_that_returns(
644     allocs: &mut InliningAllocs,
645     func: &mut ir::Function,
646     callee: &ir::Function,
647     entity_map: &EntityMap,
648     call_opcode: ir::Opcode,
649     inlined_inst: ir::Inst,
650     callee_inst: ir::Inst,
651     return_block: ir::Block,
652     call_stack_map: Option<&[ir::UserStackMapEntry]>,
653 ) {
654     debug_assert!(func.dfg.insts[inlined_inst].opcode().is_return());
655     match func.dfg.insts[inlined_inst] {
656         //     return rets...
657         //
658         // becomes
659         //
660         //     jump return_block(rets...)
661         ir::InstructionData::MultiAry {
662             opcode: ir::Opcode::Return,
663             args,
664         } => {
665             let rets = SmallBlockArgVec::from_iter(
666                 args.as_slice(&func.dfg.value_lists)
667                     .iter()
668                     .copied()
669                     .map(|v| v.into()),
670             );
671             func.dfg.replace(inlined_inst).jump(return_block, &rets);
672         }
673 
674         //     return_call f(args...)
675         //
676         // becomes
677         //
678         //     rets... = call f(args...)
679         //     jump return_block(rets...)
680         ir::InstructionData::Call {
681             opcode: ir::Opcode::ReturnCall,
682             args,
683             func_ref,
684         } => {
685             func.dfg.insts[inlined_inst] = ir::InstructionData::Call {
686                 opcode: ir::Opcode::Call,
687                 args,
688                 func_ref,
689             };
690             func.dfg.make_inst_results(inlined_inst, ir::types::INVALID);
691 
692             append_stack_map_entries(
693                 func,
694                 callee,
695                 &entity_map,
696                 call_stack_map,
697                 inlined_inst,
698                 callee_inst,
699             );
700 
701             let rets = SmallBlockArgVec::from_iter(
702                 func.dfg
703                     .inst_results(inlined_inst)
704                     .iter()
705                     .copied()
706                     .map(|v| v.into()),
707             );
708             let mut cursor = FuncCursor::new(func);
709             cursor.goto_after_inst(inlined_inst);
710             cursor.ins().jump(return_block, &rets);
711 
712             if call_opcode == ir::Opcode::TryCall {
713                 allocs
714                     .calls_needing_exception_table_fixup
715                     .push(inlined_inst);
716             }
717         }
718 
719         //     return_call_indirect val(args...)
720         //
721         // becomes
722         //
723         //     rets... = call_indirect val(args...)
724         //     jump return_block(rets...)
725         ir::InstructionData::CallIndirect {
726             opcode: ir::Opcode::ReturnCallIndirect,
727             args,
728             sig_ref,
729         } => {
730             func.dfg.insts[inlined_inst] = ir::InstructionData::CallIndirect {
731                 opcode: ir::Opcode::CallIndirect,
732                 args,
733                 sig_ref,
734             };
735             func.dfg.make_inst_results(inlined_inst, ir::types::INVALID);
736 
737             append_stack_map_entries(
738                 func,
739                 callee,
740                 &entity_map,
741                 call_stack_map,
742                 inlined_inst,
743                 callee_inst,
744             );
745 
746             let rets = SmallBlockArgVec::from_iter(
747                 func.dfg
748                     .inst_results(inlined_inst)
749                     .iter()
750                     .copied()
751                     .map(|v| v.into()),
752             );
753             let mut cursor = FuncCursor::new(func);
754             cursor.goto_after_inst(inlined_inst);
755             cursor.ins().jump(return_block, &rets);
756 
757             if call_opcode == ir::Opcode::TryCall {
758                 allocs
759                     .calls_needing_exception_table_fixup
760                     .push(inlined_inst);
761             }
762         }
763 
764         inst_data => unreachable!(
765             "should have handled all `is_return() == true` instructions above; \
766              got {inst_data:?}"
767         ),
768     }
769 }
770 
771 /// An `InstructionMapper` implementation that remaps a callee instruction's
772 /// entity references to their new indices in the caller function.
773 struct InliningInstRemapper<'a> {
774     allocs: &'a InliningAllocs,
775     func: &'a mut ir::Function,
776     callee: &'a ir::Function,
777     entity_map: &'a EntityMap,
778 }
779 
780 impl<'a> ir::instructions::InstructionMapper for InliningInstRemapper<'a> {
781     fn map_value(&mut self, value: ir::Value) -> ir::Value {
782         self.allocs.get_inlined_value(self.callee, value).expect(
783             "defs come before uses; we should have already inlined all values \
784              used by an instruction",
785         )
786     }
787 
788     fn map_value_list(&mut self, value_list: ir::ValueList) -> ir::ValueList {
789         let mut inlined_list = ir::ValueList::new();
790         for callee_val in value_list.as_slice(&self.callee.dfg.value_lists) {
791             let inlined_val = self.map_value(*callee_val);
792             inlined_list.push(inlined_val, &mut self.func.dfg.value_lists);
793         }
794         inlined_list
795     }
796 
797     fn map_global_value(&mut self, global_value: ir::GlobalValue) -> ir::GlobalValue {
798         self.entity_map.inlined_global_value(global_value)
799     }
800 
801     fn map_jump_table(&mut self, jump_table: ir::JumpTable) -> ir::JumpTable {
802         let inlined_default =
803             self.map_block_call(self.callee.dfg.jump_tables[jump_table].default_block());
804         let inlined_table = self.callee.dfg.jump_tables[jump_table]
805             .as_slice()
806             .iter()
807             .map(|callee_block_call| self.map_block_call(*callee_block_call))
808             .collect::<SmallBlockCallVec>();
809         self.func
810             .dfg
811             .jump_tables
812             .push(ir::JumpTableData::new(inlined_default, &inlined_table))
813     }
814 
815     fn map_exception_table(&mut self, exception_table: ir::ExceptionTable) -> ir::ExceptionTable {
816         let exception_table = &self.callee.dfg.exception_tables[exception_table];
817         let inlined_sig_ref = self.map_sig_ref(exception_table.signature());
818         let inlined_normal_return = self.map_block_call(*exception_table.normal_return());
819         let inlined_table = exception_table
820             .catches()
821             .map(|(tag, callee_block_call)| (tag, self.map_block_call(*callee_block_call)))
822             .collect::<SmallVec<[_; 8]>>();
823         self.func
824             .dfg
825             .exception_tables
826             .push(ir::ExceptionTableData::new(
827                 inlined_sig_ref,
828                 inlined_normal_return,
829                 inlined_table,
830             ))
831     }
832 
833     fn map_block_call(&mut self, block_call: ir::BlockCall) -> ir::BlockCall {
834         let callee_block = block_call.block(&self.callee.dfg.value_lists);
835         let inlined_block = self.entity_map.inlined_block(callee_block);
836         let args = block_call
837             .args(&self.callee.dfg.value_lists)
838             .map(|arg| match arg {
839                 ir::BlockArg::Value(value) => self.map_value(value).into(),
840                 ir::BlockArg::TryCallRet(_) | ir::BlockArg::TryCallExn(_) => arg,
841             })
842             .collect::<SmallBlockArgVec>();
843         ir::BlockCall::new(inlined_block, args, &mut self.func.dfg.value_lists)
844     }
845 
846     fn map_func_ref(&mut self, func_ref: ir::FuncRef) -> ir::FuncRef {
847         self.entity_map.inlined_func_ref(func_ref)
848     }
849 
850     fn map_sig_ref(&mut self, sig_ref: ir::SigRef) -> ir::SigRef {
851         self.entity_map.inlined_sig_ref(sig_ref)
852     }
853 
854     fn map_stack_slot(&mut self, stack_slot: ir::StackSlot) -> ir::StackSlot {
855         self.entity_map.inlined_stack_slot(stack_slot)
856     }
857 
858     fn map_dynamic_stack_slot(
859         &mut self,
860         dynamic_stack_slot: ir::DynamicStackSlot,
861     ) -> ir::DynamicStackSlot {
862         self.entity_map
863             .inlined_dynamic_stack_slot(dynamic_stack_slot)
864     }
865 
866     fn map_constant(&mut self, constant: ir::Constant) -> ir::Constant {
867         self.allocs
868             .constants
869             .get(constant)
870             .and_then(|o| o.expand())
871             .expect("should have inlined all callee constants")
872     }
873 
874     fn map_immediate(&mut self, immediate: ir::Immediate) -> ir::Immediate {
875         self.entity_map.inlined_immediate(immediate)
876     }
877 }
878 
879 /// Inline the callee's layout into the caller's layout.
880 fn inline_block_layout(
881     func: &mut ir::Function,
882     call_block: ir::Block,
883     callee: &ir::Function,
884     entity_map: &EntityMap,
885 ) {
886     // Iterate over callee blocks in layout order, inserting their associated
887     // inlined block into the caller's layout.
888     let mut prev_inlined_block = call_block;
889     let mut next_callee_block = callee.layout.entry_block();
890     while let Some(callee_block) = next_callee_block {
891         let inlined_block = entity_map.inlined_block(callee_block);
892         func.layout
893             .insert_block_after(inlined_block, prev_inlined_block);
894 
895         prev_inlined_block = inlined_block;
896         next_callee_block = callee.layout.next_block(callee_block);
897     }
898 }
899 
900 /// Split the call instruction's block just after the call instruction to create
901 /// the point where control-flow joins after the inlined callee "returns".
902 ///
903 /// Note that tail calls do not return to the caller and therefore do not have a
904 /// control-flow join point.
905 fn split_off_return_block(
906     func: &mut ir::Function,
907     call_inst: ir::Inst,
908     opcode: ir::Opcode,
909     callee: &ir::Function,
910 ) -> Option<ir::Block> {
911     // When the `call_inst` is not a block terminator, we need to split the
912     // block.
913     let return_block = func.layout.next_inst(call_inst).map(|next_inst| {
914         let return_block = func.dfg.blocks.add();
915         func.layout.split_block(return_block, next_inst);
916 
917         // Add block parameters for each return value and alias the call
918         // instruction's results to them.
919         let old_results =
920             SmallValueVec::from_iter(func.dfg.inst_results(call_inst).iter().copied());
921         debug_assert_eq!(old_results.len(), callee.signature.returns.len());
922         func.dfg.detach_inst_results(call_inst);
923         for (abi, old_val) in callee.signature.returns.iter().zip(old_results) {
924             debug_assert_eq!(abi.value_type, func.dfg.value_type(old_val));
925             let ret_param = func.dfg.append_block_param(return_block, abi.value_type);
926             func.dfg.change_to_alias(old_val, ret_param);
927         }
928 
929         return_block
930     });
931 
932     // When the `call_inst` is a block terminator, then it is either a
933     // `return_call` or a `try_call`:
934     //
935     // * For `return_call`s, we don't have a control-flow join point, because
936     //   the caller permanently transfers control to the callee.
937     //
938     // * For `try_call`s, we probably already have a block for the control-flow
939     //   join point, but it isn't guaranteed: the `try_call` might ignore the
940     //   call's returns and not forward them to the normal-return block or it
941     //   might also pass additional arguments. We can only reuse the existing
942     //   normal-return block when the `try_call` forwards exactly our callee's
943     //   returns to that block (and therefore that block's parameter types also
944     //   exactly match the callee's return types). Otherwise, we must create a new
945     //   return block that forwards to the existing normal-return
946     //   block. (Elsewhere, at the end of inlining, we will also update any inlined
947     //   calls to forward any raised exceptions to the caller's exception table,
948     //   as necessary.)
949     //
950     //   Finally, note that reusing the normal-return's target block is just an
951     //   optimization to emit a simpler CFG when we can, and is not
952     //   fundamentally required for correctness. We could always insert a
953     //   temporary block as our control-flow join point that then forwards to
954     //   the normal-return's target block. However, at the time of writing,
955     //   Cranelift doesn't currently do any jump-threading or branch
956     //   simplification in the mid-end, and removing unnecessary blocks in this
957     //   way can help some subsequent mid-end optimizations. If, in the future,
958     //   we gain support for jump-threading optimizations in the mid-end, we can
959     //   come back and simplify the below code a bit to always generate the
960     //   temporary block, and then rely on the subsequent optimizations to clean
961     //   everything up.
962     debug_assert_eq!(
963         return_block.is_none(),
964         opcode == ir::Opcode::ReturnCall || opcode == ir::Opcode::TryCall,
965     );
966     return_block.or_else(|| match func.dfg.insts[call_inst] {
967         ir::InstructionData::TryCall {
968             opcode: ir::Opcode::TryCall,
969             args: _,
970             func_ref: _,
971             exception,
972         } => {
973             let normal_return = func.dfg.exception_tables[exception].normal_return();
974             let normal_return_block = normal_return.block(&func.dfg.value_lists);
975 
976             // Check to see if we can reuse the existing normal-return block.
977             {
978                 let normal_return_args = normal_return.args(&func.dfg.value_lists);
979                 if normal_return_args.len() == callee.signature.returns.len()
980                     && normal_return_args.enumerate().all(|(i, arg)| {
981                         let i = u32::try_from(i).unwrap();
982                         arg == ir::BlockArg::TryCallRet(i)
983                     })
984                 {
985                     return Some(normal_return_block);
986                 }
987             }
988 
989             // Okay, we cannot reuse the normal-return block. Create a new block
990             // that has the expected block parameter types and have it jump to
991             // the normal-return block.
992             let return_block = func.dfg.blocks.add();
993             func.layout.insert_block(return_block, normal_return_block);
994 
995             let return_block_params = callee
996                 .signature
997                 .returns
998                 .iter()
999                 .map(|abi| func.dfg.append_block_param(return_block, abi.value_type))
1000                 .collect::<SmallValueVec>();
1001 
1002             let normal_return_args = func.dfg.exception_tables[exception]
1003                 .normal_return()
1004                 .args(&func.dfg.value_lists)
1005                 .collect::<SmallBlockArgVec>();
1006             let jump_args = normal_return_args
1007                 .into_iter()
1008                 .map(|arg| match arg {
1009                     ir::BlockArg::Value(value) => ir::BlockArg::Value(value),
1010                     ir::BlockArg::TryCallRet(i) => {
1011                         let i = usize::try_from(i).unwrap();
1012                         ir::BlockArg::Value(return_block_params[i])
1013                     }
1014                     ir::BlockArg::TryCallExn(_) => {
1015                         unreachable!("normal-return edges cannot use exceptional results")
1016                     }
1017                 })
1018                 .collect::<SmallBlockArgVec>();
1019 
1020             let mut cursor = FuncCursor::new(func);
1021             cursor.goto_first_insertion_point(return_block);
1022             cursor.ins().jump(normal_return_block, &jump_args);
1023 
1024             Some(return_block)
1025         }
1026         _ => None,
1027     })
1028 }
1029 
1030 /// Replace the caller's call instruction with a jump to the caller's inlined
1031 /// copy of the callee's entry block.
1032 ///
1033 /// Also associates the callee's parameters with the caller's arguments in our
1034 /// value map.
1035 ///
1036 /// Returns the caller's stack map entries, if any.
1037 fn replace_call_with_jump(
1038     allocs: &mut InliningAllocs,
1039     func: &mut ir::Function,
1040     call_inst: ir::Inst,
1041     callee: &ir::Function,
1042     entity_map: &EntityMap,
1043 ) -> Option<ir::UserStackMapEntryVec> {
1044     trace!("Replacing `call` with `jump`");
1045     trace!(
1046         "  --> call instruction: {call_inst:?}: {}",
1047         func.dfg.display_inst(call_inst)
1048     );
1049 
1050     let callee_entry_block = callee
1051         .layout
1052         .entry_block()
1053         .expect("callee function should have an entry block");
1054     let callee_param_values = callee.dfg.block_params(callee_entry_block);
1055     let caller_arg_values = SmallValueVec::from_iter(func.dfg.inst_args(call_inst).iter().copied());
1056     debug_assert_eq!(callee_param_values.len(), caller_arg_values.len());
1057     debug_assert_eq!(callee_param_values.len(), callee.signature.params.len());
1058     for (abi, (callee_param_value, caller_arg_value)) in callee
1059         .signature
1060         .params
1061         .iter()
1062         .zip(callee_param_values.into_iter().zip(caller_arg_values))
1063     {
1064         debug_assert_eq!(abi.value_type, callee.dfg.value_type(*callee_param_value));
1065         debug_assert_eq!(abi.value_type, func.dfg.value_type(caller_arg_value));
1066         allocs.set_inlined_value(callee, *callee_param_value, caller_arg_value);
1067     }
1068 
1069     // Replace the caller's call instruction with a jump to the caller's inlined
1070     // copy of the callee's entry block.
1071     //
1072     // Note that the call block dominates the inlined entry block (and also all
1073     // other inlined blocks) so we can reference the arguments directly, and do
1074     // not need to add block parameters to the inlined entry block.
1075     let inlined_entry_block = entity_map.inlined_block(callee_entry_block);
1076     func.dfg.replace(call_inst).jump(inlined_entry_block, &[]);
1077     trace!(
1078         "  --> replaced with jump instruction: {call_inst:?}: {}",
1079         func.dfg.display_inst(call_inst)
1080     );
1081 
1082     let stack_map_entries = func.dfg.take_user_stack_map_entries(call_inst);
1083     stack_map_entries
1084 }
1085 
1086 /// Keeps track of mapping callee entities to their associated inlined caller
1087 /// entities.
1088 #[derive(Default)]
1089 struct EntityMap {
1090     // Rather than doing an implicit, demand-based, DCE'ing translation of
1091     // entities, which would require maps from each callee entity to its
1092     // associated caller entity, we copy all entities into the caller, remember
1093     // each entity's initial offset, and then mapping from the callee to the
1094     // inlined caller entity is just adding that initial offset to the callee's
1095     // index. This should be both faster and simpler than the alternative. Most
1096     // of these sets are relatively small, and they rarely have too much dead
1097     // code in practice, so this is a good trade off.
1098     //
1099     // Note that there are a few kinds of entities that are excluded from the
1100     // `EntityMap`, and for which we do actually take the demand-based approach:
1101     // values and value lists being the notable ones.
1102     block_offset: Option<u32>,
1103     global_value_offset: Option<u32>,
1104     sig_ref_offset: Option<u32>,
1105     func_ref_offset: Option<u32>,
1106     stack_slot_offset: Option<u32>,
1107     dynamic_type_offset: Option<u32>,
1108     dynamic_stack_slot_offset: Option<u32>,
1109     immediate_offset: Option<u32>,
1110 }
1111 
1112 impl EntityMap {
1113     fn inlined_block(&self, callee_block: ir::Block) -> ir::Block {
1114         let offset = self
1115             .block_offset
1116             .expect("must create inlined `ir::Block`s before calling `EntityMap::inlined_block`");
1117         ir::Block::from_u32(offset + callee_block.as_u32())
1118     }
1119 
1120     fn inlined_global_value(&self, callee_global_value: ir::GlobalValue) -> ir::GlobalValue {
1121         let offset = self
1122             .global_value_offset
1123             .expect("must create inlined `ir::GlobalValue`s before calling `EntityMap::inlined_global_value`");
1124         ir::GlobalValue::from_u32(offset + callee_global_value.as_u32())
1125     }
1126 
1127     fn inlined_sig_ref(&self, callee_sig_ref: ir::SigRef) -> ir::SigRef {
1128         let offset = self.sig_ref_offset.expect(
1129             "must create inlined `ir::SigRef`s before calling `EntityMap::inlined_sig_ref`",
1130         );
1131         ir::SigRef::from_u32(offset + callee_sig_ref.as_u32())
1132     }
1133 
1134     fn inlined_func_ref(&self, callee_func_ref: ir::FuncRef) -> ir::FuncRef {
1135         let offset = self.func_ref_offset.expect(
1136             "must create inlined `ir::FuncRef`s before calling `EntityMap::inlined_func_ref`",
1137         );
1138         ir::FuncRef::from_u32(offset + callee_func_ref.as_u32())
1139     }
1140 
1141     fn inlined_stack_slot(&self, callee_stack_slot: ir::StackSlot) -> ir::StackSlot {
1142         let offset = self.stack_slot_offset.expect(
1143             "must create inlined `ir::StackSlot`s before calling `EntityMap::inlined_stack_slot`",
1144         );
1145         ir::StackSlot::from_u32(offset + callee_stack_slot.as_u32())
1146     }
1147 
1148     fn inlined_dynamic_type(&self, callee_dynamic_type: ir::DynamicType) -> ir::DynamicType {
1149         let offset = self.dynamic_type_offset.expect(
1150             "must create inlined `ir::DynamicType`s before calling `EntityMap::inlined_dynamic_type`",
1151         );
1152         ir::DynamicType::from_u32(offset + callee_dynamic_type.as_u32())
1153     }
1154 
1155     fn inlined_dynamic_stack_slot(
1156         &self,
1157         callee_dynamic_stack_slot: ir::DynamicStackSlot,
1158     ) -> ir::DynamicStackSlot {
1159         let offset = self.dynamic_stack_slot_offset.expect(
1160             "must create inlined `ir::DynamicStackSlot`s before calling `EntityMap::inlined_dynamic_stack_slot`",
1161         );
1162         ir::DynamicStackSlot::from_u32(offset + callee_dynamic_stack_slot.as_u32())
1163     }
1164 
1165     fn inlined_immediate(&self, callee_immediate: ir::Immediate) -> ir::Immediate {
1166         let offset = self.immediate_offset.expect(
1167             "must create inlined `ir::Immediate`s before calling `EntityMap::inlined_immediate`",
1168         );
1169         ir::Immediate::from_u32(offset + callee_immediate.as_u32())
1170     }
1171 }
1172 
1173 /// Translate all of the callee's various entities into the caller, producing an
1174 /// `EntityMap` that can be used to translate callee entity references into
1175 /// inlined caller entity references.
1176 fn create_entities(
1177     allocs: &mut InliningAllocs,
1178     func: &mut ir::Function,
1179     callee: &ir::Function,
1180 ) -> EntityMap {
1181     let mut entity_map = EntityMap::default();
1182 
1183     entity_map.block_offset = Some(create_blocks(allocs, func, callee));
1184     entity_map.global_value_offset = Some(create_global_values(func, callee));
1185     entity_map.sig_ref_offset = Some(create_sig_refs(func, callee));
1186     entity_map.func_ref_offset = Some(create_func_refs(func, callee, &entity_map));
1187     entity_map.stack_slot_offset = Some(create_stack_slots(func, callee));
1188     entity_map.dynamic_type_offset = Some(create_dynamic_types(func, callee, &entity_map));
1189     entity_map.dynamic_stack_slot_offset =
1190         Some(create_dynamic_stack_slots(func, callee, &entity_map));
1191     entity_map.immediate_offset = Some(create_immediates(func, callee));
1192 
1193     // `ir::ConstantData` is deduplicated, so we cannot use our offset scheme
1194     // for `ir::Constant`s. Nonetheless, we still insert them into the caller
1195     // now, at the same time as the rest of our entities.
1196     create_constants(allocs, func, callee);
1197 
1198     entity_map
1199 }
1200 
1201 /// Create inlined blocks in the caller for every block in the callee.
1202 fn create_blocks(
1203     allocs: &mut InliningAllocs,
1204     func: &mut ir::Function,
1205     callee: &ir::Function,
1206 ) -> u32 {
1207     let offset = func.dfg.blocks.len();
1208     let offset = u32::try_from(offset).unwrap();
1209 
1210     func.dfg.blocks.reserve(callee.dfg.blocks.len());
1211     for callee_block in callee.dfg.blocks.iter() {
1212         let caller_block = func.dfg.blocks.add();
1213         trace!("Callee {callee_block:?} = inlined {caller_block:?}");
1214 
1215         if callee.layout.is_cold(callee_block) {
1216             func.layout.set_cold(caller_block);
1217         }
1218 
1219         // Note: the entry block does not need parameters because the only
1220         // predecessor is the call block and we associate the callee's
1221         // parameters with the caller's arguments directly.
1222         if callee.layout.entry_block() != Some(callee_block) {
1223             for callee_param in callee.dfg.blocks[callee_block].params(&callee.dfg.value_lists) {
1224                 let ty = callee.dfg.value_type(*callee_param);
1225                 let caller_param = func.dfg.append_block_param(caller_block, ty);
1226 
1227                 allocs.set_inlined_value(callee, *callee_param, caller_param);
1228             }
1229         }
1230     }
1231 
1232     offset
1233 }
1234 
1235 /// Copy and translate global values from the callee into the caller.
1236 fn create_global_values(func: &mut ir::Function, callee: &ir::Function) -> u32 {
1237     let gv_offset = func.global_values.len();
1238     let gv_offset = u32::try_from(gv_offset).unwrap();
1239 
1240     func.global_values.reserve(callee.global_values.len());
1241     for gv in callee.global_values.values() {
1242         func.global_values.push(match gv {
1243             // These kinds of global values reference other global values, so we
1244             // need to fixup that reference.
1245             ir::GlobalValueData::Load {
1246                 base,
1247                 offset,
1248                 global_type,
1249                 flags,
1250             } => ir::GlobalValueData::Load {
1251                 base: ir::GlobalValue::from_u32(base.as_u32() + gv_offset),
1252                 offset: *offset,
1253                 global_type: *global_type,
1254                 flags: *flags,
1255             },
1256             ir::GlobalValueData::IAddImm {
1257                 base,
1258                 offset,
1259                 global_type,
1260             } => ir::GlobalValueData::IAddImm {
1261                 base: ir::GlobalValue::from_u32(base.as_u32() + gv_offset),
1262                 offset: *offset,
1263                 global_type: *global_type,
1264             },
1265 
1266             // These kinds of global values do not reference other global
1267             // values, so we can just clone them.
1268             ir::GlobalValueData::VMContext
1269             | ir::GlobalValueData::Symbol { .. }
1270             | ir::GlobalValueData::DynScaleTargetConst { .. } => gv.clone(),
1271         });
1272     }
1273 
1274     gv_offset
1275 }
1276 
1277 /// Copy `ir::SigRef`s from the callee into the caller.
1278 fn create_sig_refs(func: &mut ir::Function, callee: &ir::Function) -> u32 {
1279     let offset = func.dfg.signatures.len();
1280     let offset = u32::try_from(offset).unwrap();
1281 
1282     func.dfg.signatures.reserve(callee.dfg.signatures.len());
1283     for sig in callee.dfg.signatures.values() {
1284         func.dfg.signatures.push(sig.clone());
1285     }
1286 
1287     offset
1288 }
1289 
1290 /// Translate `ir::FuncRef`s from the callee into the caller.
1291 fn create_func_refs(func: &mut ir::Function, callee: &ir::Function, entity_map: &EntityMap) -> u32 {
1292     let offset = func.dfg.ext_funcs.len();
1293     let offset = u32::try_from(offset).unwrap();
1294 
1295     func.dfg.ext_funcs.reserve(callee.dfg.ext_funcs.len());
1296     for ir::ExtFuncData {
1297         name,
1298         signature,
1299         colocated,
1300     } in callee.dfg.ext_funcs.values()
1301     {
1302         func.dfg.ext_funcs.push(ir::ExtFuncData {
1303             name: name.clone(),
1304             signature: entity_map.inlined_sig_ref(*signature),
1305             colocated: *colocated,
1306         });
1307     }
1308 
1309     offset
1310 }
1311 
1312 /// Copy stack slots from the callee into the caller.
1313 fn create_stack_slots(func: &mut ir::Function, callee: &ir::Function) -> u32 {
1314     let offset = func.sized_stack_slots.len();
1315     let offset = u32::try_from(offset).unwrap();
1316 
1317     func.sized_stack_slots
1318         .reserve(callee.sized_stack_slots.len());
1319     for slot in callee.sized_stack_slots.values() {
1320         func.sized_stack_slots.push(slot.clone());
1321     }
1322 
1323     offset
1324 }
1325 
1326 /// Copy dynamic types from the callee into the caller.
1327 fn create_dynamic_types(
1328     func: &mut ir::Function,
1329     callee: &ir::Function,
1330     entity_map: &EntityMap,
1331 ) -> u32 {
1332     let offset = func.dynamic_stack_slots.len();
1333     let offset = u32::try_from(offset).unwrap();
1334 
1335     func.dfg
1336         .dynamic_types
1337         .reserve(callee.dfg.dynamic_types.len());
1338     for ir::DynamicTypeData {
1339         base_vector_ty,
1340         dynamic_scale,
1341     } in callee.dfg.dynamic_types.values()
1342     {
1343         func.dfg.dynamic_types.push(ir::DynamicTypeData {
1344             base_vector_ty: *base_vector_ty,
1345             dynamic_scale: entity_map.inlined_global_value(*dynamic_scale),
1346         });
1347     }
1348 
1349     offset
1350 }
1351 
1352 /// Copy dynamic stack slots from the callee into the caller.
1353 fn create_dynamic_stack_slots(
1354     func: &mut ir::Function,
1355     callee: &ir::Function,
1356     entity_map: &EntityMap,
1357 ) -> u32 {
1358     let offset = func.dynamic_stack_slots.len();
1359     let offset = u32::try_from(offset).unwrap();
1360 
1361     func.dynamic_stack_slots
1362         .reserve(callee.dynamic_stack_slots.len());
1363     for ir::DynamicStackSlotData { kind, dyn_ty } in callee.dynamic_stack_slots.values() {
1364         func.dynamic_stack_slots.push(ir::DynamicStackSlotData {
1365             kind: *kind,
1366             dyn_ty: entity_map.inlined_dynamic_type(*dyn_ty),
1367         });
1368     }
1369 
1370     offset
1371 }
1372 
1373 /// Copy immediates from the callee into the caller.
1374 fn create_immediates(func: &mut ir::Function, callee: &ir::Function) -> u32 {
1375     let offset = func.dfg.immediates.len();
1376     let offset = u32::try_from(offset).unwrap();
1377 
1378     func.dfg.immediates.reserve(callee.dfg.immediates.len());
1379     for imm in callee.dfg.immediates.values() {
1380         func.dfg.immediates.push(imm.clone());
1381     }
1382 
1383     offset
1384 }
1385 
1386 /// Copy constants from the callee into the caller.
1387 fn create_constants(allocs: &mut InliningAllocs, func: &mut ir::Function, callee: &ir::Function) {
1388     for (callee_constant, data) in callee.dfg.constants.iter() {
1389         let inlined_constant = func.dfg.constants.insert(data.clone());
1390         allocs.constants[*callee_constant] = Some(inlined_constant).into();
1391     }
1392 }
1393