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