Skip to main content

cranelift_codegen/
inline.rs

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