Path: blob/main/cranelift/codegen/src/inline.rs
1693 views
//! Function inlining infrastructure.1//!2//! This module provides "inlining as a library" to Cranelift users; it does3//! _not_ provide a complete, off-the-shelf inlining solution. Cranelift's4//! compilation context is per-function and does not encompass the full call5//! graph. It does not know which functions are hot and which are cold, which6//! have been marked the equivalent of `#[inline(never)]`, etc... Only the7//! Cranelift user can understand these aspects of the full compilation8//! pipeline, and these things can be very different between (say) Wasmtime and9//! `cg_clif`. Therefore, this module does not attempt to define hueristics for10//! when inlining a particular call is likely beneficial. This module only11//! provides hooks for the Cranelift user to define whether a given call should12//! be inlined or not, and the mechanics to inline a callee into a particular13//! call site when directed to do so by the Cranelift user.14//!15//! The top-level inlining entry point during Cranelift compilation is16//! [`Context::inline`][crate::Context::inline]. It takes an [`Inline`] trait17//! implementation, which is authored by the Cranelift user and directs18//! Cranelift whether to inline a particular call, and, when inlining, gives19//! Cranelift the body of the callee that is to be inlined.2021use crate::cursor::{Cursor as _, FuncCursor};22use crate::ir::{self, ExceptionTableData, ExceptionTableItem, InstBuilder as _};23use crate::result::CodegenResult;24use crate::trace;25use crate::traversals::Dfs;26use alloc::borrow::Cow;27use alloc::vec::Vec;28use cranelift_entity::{SecondaryMap, packed_option::PackedOption};29use smallvec::SmallVec;3031type SmallValueVec = SmallVec<[ir::Value; 8]>;32type SmallBlockArgVec = SmallVec<[ir::BlockArg; 8]>;33type SmallBlockCallVec = SmallVec<[ir::BlockCall; 8]>;3435/// A command directing Cranelift whether or not to inline a particular call.36pub enum InlineCommand<'a> {37/// Keep the call as-is, out-of-line, and do not inline the callee.38KeepCall,3940/// Inline the call, using this function as the body of the callee.41///42/// It is the `Inline` implementor's responsibility to ensure that this43/// function is the correct callee. Providing the wrong function may result44/// in panics during compilation or incorrect runtime behavior.45Inline {46/// The callee function's body.47callee: Cow<'a, ir::Function>,48/// Whether to visit any function calls within the callee body after49/// inlining and consider them for further inlining.50visit_callee: bool,51},52}5354/// A trait for directing Cranelift whether to inline a particular call or not.55///56/// Used in combination with the [`Context::inline`][crate::Context::inline]57/// method.58pub trait Inline {59/// A hook invoked for each direct call instruction in a function, whose60/// result determines whether Cranelift should inline a given call.61///62/// The Cranelift user is responsible for defining their own hueristics and63/// deciding whether inlining the call is beneficial.64///65/// When returning a function and directing Cranelift to inline its body66/// into the call site, the `Inline` implementer must ensure the following:67///68/// * The returned function's signature exactly matches the `callee`69/// `FuncRef`'s signature.70///71/// * The returned function must be legalized.72///73/// * The returned function must be valid (i.e. it must pass the CLIF74/// verifier).75///76/// * The returned function is a correct and valid implementation of the77/// `callee` according to your language's semantics.78///79/// Failure to uphold these invariants may result in panics during80/// compilation or incorrect runtime behavior in the generated code.81fn inline(82&mut self,83caller: &ir::Function,84call_inst: ir::Inst,85call_opcode: ir::Opcode,86callee: ir::FuncRef,87call_args: &[ir::Value],88) -> InlineCommand<'_>;89}9091impl<'a, T> Inline for &'a mut T92where93T: Inline,94{95fn inline(96&mut self,97caller: &ir::Function,98inst: ir::Inst,99opcode: ir::Opcode,100callee: ir::FuncRef,101args: &[ir::Value],102) -> InlineCommand<'_> {103(*self).inline(caller, inst, opcode, callee, args)104}105}106107/// Walk the given function, invoke the `Inline` implementation for each call108/// instruction, and inline the callee when directed to do so.109///110/// Returns whether any call was inlined.111pub(crate) fn do_inlining(112func: &mut ir::Function,113mut inliner: impl Inline,114) -> CodegenResult<bool> {115trace!("function {} before inlining: {}", func.name, func);116117let mut inlined_any = false;118let mut allocs = InliningAllocs::default();119120let mut cursor = FuncCursor::new(func);121'block_loop: while let Some(block) = cursor.next_block() {122// Always keep track of our previous cursor position. Assuming that the123// current position is a function call that we will inline, then the124// previous position is just before the inlined callee function. After125// inlining a call, the Cranelift user can decide whether to consider126// any function calls in the inlined callee for further inlining or127// not. When they do, then we back up to this previous cursor position128// so that our traversal will then continue over the inlined body.129let mut prev_pos;130131while let Some(inst) = {132prev_pos = cursor.position();133cursor.next_inst()134} {135// Make sure that `block` is always `inst`'s block, even with all of136// our cursor-position-updating and block-splitting-during-inlining137// shenanigans below.138debug_assert_eq!(Some(block), cursor.func.layout.inst_block(inst));139140match cursor.func.dfg.insts[inst] {141ir::InstructionData::Call {142opcode: opcode @ ir::Opcode::Call | opcode @ ir::Opcode::ReturnCall,143args: _,144func_ref,145} => {146trace!(147"considering call site for inlining: {inst}: {}",148cursor.func.dfg.display_inst(inst),149);150let args = cursor.func.dfg.inst_args(inst);151match inliner.inline(&cursor.func, inst, opcode, func_ref, args) {152InlineCommand::KeepCall => {153trace!(" --> keeping call");154}155InlineCommand::Inline {156callee,157visit_callee,158} => {159let last_inlined_block = inline_one(160&mut allocs,161cursor.func,162func_ref,163block,164inst,165opcode,166&callee,167None,168);169inlined_any = true;170if visit_callee {171cursor.set_position(prev_pos);172} else {173// Arrange it so that the `next_block()` loop174// will continue to the next block that is not175// associated with the just-inlined callee.176cursor.goto_bottom(last_inlined_block);177continue 'block_loop;178}179}180}181}182ir::InstructionData::TryCall {183opcode: opcode @ ir::Opcode::TryCall,184args: _,185func_ref,186exception,187} => {188trace!(189"considering call site for inlining: {inst}: {}",190cursor.func.dfg.display_inst(inst),191);192let args = cursor.func.dfg.inst_args(inst);193match inliner.inline(&cursor.func, inst, opcode, func_ref, args) {194InlineCommand::KeepCall => {195trace!(" --> keeping call");196}197InlineCommand::Inline {198callee,199visit_callee,200} => {201let last_inlined_block = inline_one(202&mut allocs,203cursor.func,204func_ref,205block,206inst,207opcode,208&callee,209Some(exception),210);211inlined_any = true;212if visit_callee {213cursor.set_position(prev_pos);214} else {215// Arrange it so that the `next_block()` loop216// will continue to the next block that is not217// associated with the just-inlined callee.218cursor.goto_bottom(last_inlined_block);219continue 'block_loop;220}221}222}223}224ir::InstructionData::CallIndirect { .. }225| ir::InstructionData::TryCallIndirect { .. } => {226// Can't inline indirect calls; need to have some earlier227// pass rewrite them into direct calls first, when possible.228}229_ => {230debug_assert!(231!cursor.func.dfg.insts[inst].opcode().is_call(),232"should have matched all call instructions, but found: {inst}: {}",233cursor.func.dfg.display_inst(inst),234);235}236}237}238}239240if inlined_any {241trace!("function {} after inlining: {}", func.name, func);242} else {243trace!("function {} did not have any callees inlined", func.name);244}245246Ok(inlined_any)247}248249#[derive(Default)]250struct InliningAllocs {251/// Map from callee value to inlined caller value.252values: SecondaryMap<ir::Value, PackedOption<ir::Value>>,253254/// Map from callee constant to inlined caller constant.255///256/// Not in `EntityMap` because these are hash-consed inside the257/// `ir::Function`.258constants: SecondaryMap<ir::Constant, PackedOption<ir::Constant>>,259260/// Map from callee to inlined caller external name refs.261///262/// Not in `EntityMap` because these are hash-consed inside the263/// `ir::Function`.264user_external_name_refs:265SecondaryMap<ir::UserExternalNameRef, PackedOption<ir::UserExternalNameRef>>,266267/// The set of _caller_ inlined call instructions that need exception table268/// fixups at the end of inlining.269///270/// This includes all kinds of non-returning calls, not just the literal271/// `call` instruction: `call_indirect`, `try_call`, `try_call_indirect`,272/// etc... However, it does not include `return_call` and273/// `return_call_indirect` instructions because the caller cannot catch274/// exceptions that those calls throw because the caller is no longer on the275/// stack as soon as they are executed.276///277/// Note: this is a simple `Vec`, and not an `EntitySet`, because it is very278/// sparse: most of the caller's instructions are not inlined call279/// instructions. Additionally, we require deterministic iteration order and280/// do not require set-membership testing, so a hash set is not a good281/// choice either.282calls_needing_exception_table_fixup: Vec<ir::Inst>,283}284285impl InliningAllocs {286fn reset(&mut self, callee: &ir::Function) {287let InliningAllocs {288values,289constants,290user_external_name_refs,291calls_needing_exception_table_fixup,292} = self;293294values.clear();295values.resize(callee.dfg.len_values());296297constants.clear();298constants.resize(callee.dfg.constants.len());299300user_external_name_refs.clear();301user_external_name_refs.resize(callee.params.user_named_funcs().len());302303// Note: We do not reserve capacity for304// `calls_needing_exception_table_fixup` because it is a sparse set and305// we don't know how large it needs to be ahead of time.306calls_needing_exception_table_fixup.clear();307}308309fn set_inlined_value(310&mut self,311callee: &ir::Function,312callee_val: ir::Value,313inlined_val: ir::Value,314) {315trace!(" --> callee {callee_val:?} = inlined {inlined_val:?}");316debug_assert!(self.values[callee_val].is_none());317let resolved_callee_val = callee.dfg.resolve_aliases(callee_val);318debug_assert!(self.values[resolved_callee_val].is_none());319self.values[resolved_callee_val] = Some(inlined_val).into();320}321322fn get_inlined_value(&self, callee: &ir::Function, callee_val: ir::Value) -> Option<ir::Value> {323let resolved_callee_val = callee.dfg.resolve_aliases(callee_val);324self.values[resolved_callee_val].expand()325}326}327328/// Inline one particular function call.329///330/// Returns the last inlined block in the layout.331fn inline_one(332allocs: &mut InliningAllocs,333func: &mut ir::Function,334callee_func_ref: ir::FuncRef,335call_block: ir::Block,336call_inst: ir::Inst,337call_opcode: ir::Opcode,338callee: &ir::Function,339call_exception_table: Option<ir::ExceptionTable>,340) -> ir::Block {341trace!(342"Inlining call {call_inst:?}: {}\n\343with callee = {callee:?}",344func.dfg.display_inst(call_inst)345);346347// Type check callee signature.348let expected_callee_sig = func.dfg.ext_funcs[callee_func_ref].signature;349let expected_callee_sig = &func.dfg.signatures[expected_callee_sig];350assert_eq!(expected_callee_sig, &callee.signature);351352allocs.reset(callee);353354// First, append various callee entity arenas to the end of the caller's355// entity arenas.356let entity_map = create_entities(allocs, func, callee);357358// Inlined prologue: split the call instruction's block at the point of the359// call and replace the call with a jump.360let return_block = split_off_return_block(func, call_inst, call_opcode, callee);361let call_stack_map = replace_call_with_jump(allocs, func, call_inst, callee, &entity_map);362363// Prepare for translating the actual instructions by inserting the inlined364// blocks into the caller's layout in the same order that they appear in the365// callee.366let mut last_inlined_block = inline_block_layout(func, call_block, callee, &entity_map);367368// Translate each instruction from the callee into the caller,369// appending them to their associated block in the caller.370//371// Note that we iterate over the callee with a pre-order traversal so that372// we see value defs before uses.373for callee_block in Dfs::new().pre_order_iter(callee) {374let inlined_block = entity_map.inlined_block(callee_block);375trace!(376"Processing instructions in callee block {callee_block:?} (inlined block {inlined_block:?}"377);378379let mut next_callee_inst = callee.layout.first_inst(callee_block);380while let Some(callee_inst) = next_callee_inst {381trace!(382"Processing callee instruction {callee_inst:?}: {}",383callee.dfg.display_inst(callee_inst)384);385386assert_ne!(387callee.dfg.insts[callee_inst].opcode(),388ir::Opcode::GlobalValue,389"callee must already be legalized, we shouldn't see any `global_value` \390instructions when inlining; found {callee_inst:?}: {}",391callee.dfg.display_inst(callee_inst)392);393394// Remap the callee instruction's entities and insert it into the395// caller's DFG.396let inlined_inst_data = callee.dfg.insts[callee_inst].map(InliningInstRemapper {397allocs: &allocs,398func,399callee,400entity_map: &entity_map,401});402let inlined_inst = func.dfg.make_inst(inlined_inst_data);403func.layout.append_inst(inlined_inst, inlined_block);404405let opcode = callee.dfg.insts[callee_inst].opcode();406if opcode.is_return() {407// Instructions that return do not define any values, so we408// don't need to worry about that, but we do need to fix them up409// so that they return by jumping to our control-flow join410// block, rather than returning from the caller.411if let Some(return_block) = return_block {412fixup_inst_that_returns(413allocs,414func,415callee,416&entity_map,417call_opcode,418inlined_inst,419callee_inst,420return_block,421call_stack_map.as_ref().map(|es| &**es),422);423} else {424// If we are inlining a callee that was invoked via425// `return_call`, we leave inlined return instructions426// as-is: there is no logical caller frame on the stack to427// continue to.428debug_assert_eq!(call_opcode, ir::Opcode::ReturnCall);429}430} else {431// Make the instruction's result values.432let ctrl_typevar = callee.dfg.ctrl_typevar(callee_inst);433func.dfg.make_inst_results(inlined_inst, ctrl_typevar);434435// Update the value map for this instruction's defs.436let callee_results = callee.dfg.inst_results(callee_inst);437let inlined_results = func.dfg.inst_results(inlined_inst);438debug_assert_eq!(callee_results.len(), inlined_results.len());439for (callee_val, inlined_val) in callee_results.iter().zip(inlined_results) {440allocs.set_inlined_value(callee, *callee_val, *inlined_val);441}442443if opcode.is_call() {444append_stack_map_entries(445func,446callee,447&entity_map,448call_stack_map.as_deref(),449inlined_inst,450callee_inst,451);452453// When we are inlining a `try_call` call site, we need to merge454// the call site's exception table into the inlined calls'455// exception tables. This can involve rewriting regular `call`s456// into `try_call`s, which requires mutating the CFG because457// `try_call` is a block terminator. However, we can't mutate458// the CFG in the middle of this traversal because we rely on459// the existence of a one-to-one mapping between the callee460// layout and the inlined layout. Instead, we record the set of461// inlined call instructions that will need fixing up, and462// perform that possibly-CFG-mutating exception table merging in463// a follow up pass, when we no longer rely on that one-to-one464// layout mapping.465debug_assert_eq!(466call_opcode == ir::Opcode::TryCall,467call_exception_table.is_some()468);469if call_opcode == ir::Opcode::TryCall {470allocs471.calls_needing_exception_table_fixup472.push(inlined_inst);473}474}475}476477trace!(478" --> inserted inlined instruction {inlined_inst:?}: {}",479func.dfg.display_inst(inlined_inst)480);481482next_callee_inst = callee.layout.next_inst(callee_inst);483}484}485486// We copied *all* callee blocks into the caller's layout, but only copied487// the callee instructions in *reachable* callee blocks into the caller's488// associated blocks. Therefore, any *unreachable* blocks are empty in the489// caller, which is invalid CLIF because all blocks must end in a490// terminator, so do a quick pass over the inlined blocks and remove any491// empty blocks from the caller's layout.492for block in entity_map.iter_inlined_blocks(func) {493if func.layout.is_block_inserted(block) && func.layout.first_inst(block).is_none() {494log::trace!("removing unreachable inlined block from layout: {block}");495496// If the block being removed is our last-inlined block, then back497// it up to the previous block in the layout, which will be the new498// last-inlined block after this one's removal.499if block == last_inlined_block {500last_inlined_block = func.layout.prev_block(last_inlined_block).expect(501"there will always at least be the block that contained the call we are \502inlining",503);504}505506func.layout.remove_block(block);507}508}509510// Final step: fixup the exception tables of any inlined calls when we are511// inlining a `try_call` site.512//513// Subtly, this requires rewriting non-catching `call[_indirect]`514// instructions into `try_call[_indirect]` instructions so that exceptions515// that unwound through the original callee frame and were caught by the516// caller's `try_call` do not unwind past this inlined frame. And turning a517// `call` into a `try_call` mutates the CFG, breaking our one-to-one mapping518// between callee blocks and inlined blocks, so we delay these fixups to519// this final step, when we no longer rely on that mapping.520debug_assert!(521allocs.calls_needing_exception_table_fixup.is_empty() || call_exception_table.is_some()522);523debug_assert_eq!(524call_opcode == ir::Opcode::TryCall,525call_exception_table.is_some()526);527if let Some(call_exception_table) = call_exception_table {528fixup_inlined_call_exception_tables(allocs, func, call_exception_table);529}530531debug_assert!(532func.layout.is_block_inserted(last_inlined_block),533"last_inlined_block={last_inlined_block} should be inserted in the layout"534);535last_inlined_block536}537538/// Append stack map entries from the caller and callee to the given inlined539/// instruction.540fn append_stack_map_entries(541func: &mut ir::Function,542callee: &ir::Function,543entity_map: &EntityMap,544call_stack_map: Option<&[ir::UserStackMapEntry]>,545inlined_inst: ir::Inst,546callee_inst: ir::Inst,547) {548// Add the caller's stack map to this call. These entries549// already refer to caller entities and do not need further550// translation.551func.dfg.append_user_stack_map_entries(552inlined_inst,553call_stack_map554.iter()555.flat_map(|entries| entries.iter().cloned()),556);557558// Append the callee's stack map to this call. These entries559// refer to callee entities and therefore do require560// translation into the caller's index space.561func.dfg.append_user_stack_map_entries(562inlined_inst,563callee564.dfg565.user_stack_map_entries(callee_inst)566.iter()567.flat_map(|entries| entries.iter())568.map(|entry| ir::UserStackMapEntry {569ty: entry.ty,570slot: entity_map.inlined_stack_slot(entry.slot),571offset: entry.offset,572}),573);574}575576/// Create or update the exception tables for any inlined call instructions:577/// when inlining at a `try_call` site, we must forward our exceptional edges578/// into each inlined call instruction.579fn fixup_inlined_call_exception_tables(580allocs: &mut InliningAllocs,581func: &mut ir::Function,582call_exception_table: ir::ExceptionTable,583) {584// Split a block at a `call[_indirect]` instruction, detach the585// instruction's results, and alias them to the new block's parameters.586let split_block_for_new_try_call = |func: &mut ir::Function, inst: ir::Inst| -> ir::Block {587debug_assert!(func.dfg.insts[inst].opcode().is_call());588debug_assert!(!func.dfg.insts[inst].opcode().is_terminator());589590// Split the block.591let next_inst = func592.layout593.next_inst(inst)594.expect("inst is not a terminator, should have a successor");595let new_block = func.dfg.blocks.add();596func.layout.split_block(new_block, next_inst);597598// `try_call[_indirect]` instructions do not define values themselves;599// the normal-return block has parameters for the results. So remove600// this instruction's results, create an associated block parameter for601// each of them, and alias them to the new block parameter.602let old_results = SmallValueVec::from_iter(func.dfg.inst_results(inst).iter().copied());603func.dfg.detach_inst_results(inst);604for old_result in old_results {605let ty = func.dfg.value_type(old_result);606let new_block_param = func.dfg.append_block_param(new_block, ty);607func.dfg.change_to_alias(old_result, new_block_param);608}609610new_block611};612613// Clone the caller's exception table, updating it for use in the current614// `call[_indirect]` instruction as it becomes a `try_call[_indirect]`.615let clone_exception_table_for_this_call = |func: &mut ir::Function,616signature: ir::SigRef,617new_block: ir::Block|618-> ir::ExceptionTable {619let mut exception = func.stencil.dfg.exception_tables[call_exception_table]620.deep_clone(&mut func.stencil.dfg.value_lists);621622*exception.signature_mut() = signature;623624let returns_len = func.dfg.signatures[signature].returns.len();625let returns_len = u32::try_from(returns_len).unwrap();626627*exception.normal_return_mut() = ir::BlockCall::new(628new_block,629(0..returns_len).map(|i| ir::BlockArg::TryCallRet(i)),630&mut func.dfg.value_lists,631);632633func.dfg.exception_tables.push(exception)634};635636for inst in allocs.calls_needing_exception_table_fixup.drain(..) {637debug_assert!(func.dfg.insts[inst].opcode().is_call());638debug_assert!(!func.dfg.insts[inst].opcode().is_return());639match func.dfg.insts[inst] {640// current_block:641// preds...642// rets... = call f(args...)643// succs...644//645// becomes646//647// current_block:648// preds...649// try_call f(args...), new_block(rets...), [call_exception_table...]650// new_block(rets...):651// succs...652ir::InstructionData::Call {653opcode: ir::Opcode::Call,654args,655func_ref,656} => {657let new_block = split_block_for_new_try_call(func, inst);658let signature = func.dfg.ext_funcs[func_ref].signature;659let exception = clone_exception_table_for_this_call(func, signature, new_block);660func.dfg.insts[inst] = ir::InstructionData::TryCall {661opcode: ir::Opcode::TryCall,662args,663func_ref,664exception,665};666}667668// current_block:669// preds...670// rets... = call_indirect sig, val(args...)671// succs...672//673// becomes674//675// current_block:676// preds...677// try_call_indirect sig, val(args...), new_block(rets...), [call_exception_table...]678// new_block(rets...):679// succs...680ir::InstructionData::CallIndirect {681opcode: ir::Opcode::CallIndirect,682args,683sig_ref,684} => {685let new_block = split_block_for_new_try_call(func, inst);686let exception = clone_exception_table_for_this_call(func, sig_ref, new_block);687func.dfg.insts[inst] = ir::InstructionData::TryCallIndirect {688opcode: ir::Opcode::TryCallIndirect,689args,690exception,691};692}693694// For `try_call[_indirect]` instructions, we just need to merge the695// exception tables.696ir::InstructionData::TryCall {697opcode: ir::Opcode::TryCall,698exception,699..700}701| ir::InstructionData::TryCallIndirect {702opcode: ir::Opcode::TryCallIndirect,703exception,704..705} => {706// Construct a new exception table that consists of707// the inlined instruction's exception table match708// sequence, with the inlining site's exception table709// appended. This will ensure that the first-match710// semantics emulates the original behavior of711// matching in the inner frame first.712let sig = func.dfg.exception_tables[exception].signature();713let normal_return = *func.dfg.exception_tables[exception].normal_return();714let exception_data = ExceptionTableData::new(715sig,716normal_return,717func.dfg.exception_tables[exception]718.items()719.chain(func.dfg.exception_tables[call_exception_table].items()),720)721.deep_clone(&mut func.dfg.value_lists);722723func.dfg.exception_tables[exception] = exception_data;724}725726otherwise => unreachable!("unknown non-return call instruction: {otherwise:?}"),727}728}729}730731/// After having created an inlined version of a callee instruction that returns732/// in the caller, we need to fix it up so that it doesn't actually return733/// (since we are already in the caller's frame) and instead just jumps to the734/// control-flow join point.735fn fixup_inst_that_returns(736allocs: &mut InliningAllocs,737func: &mut ir::Function,738callee: &ir::Function,739entity_map: &EntityMap,740call_opcode: ir::Opcode,741inlined_inst: ir::Inst,742callee_inst: ir::Inst,743return_block: ir::Block,744call_stack_map: Option<&[ir::UserStackMapEntry]>,745) {746debug_assert!(func.dfg.insts[inlined_inst].opcode().is_return());747match func.dfg.insts[inlined_inst] {748// return rets...749//750// becomes751//752// jump return_block(rets...)753ir::InstructionData::MultiAry {754opcode: ir::Opcode::Return,755args,756} => {757let rets = SmallBlockArgVec::from_iter(758args.as_slice(&func.dfg.value_lists)759.iter()760.copied()761.map(|v| v.into()),762);763func.dfg.replace(inlined_inst).jump(return_block, &rets);764}765766// return_call f(args...)767//768// becomes769//770// rets... = call f(args...)771// jump return_block(rets...)772ir::InstructionData::Call {773opcode: ir::Opcode::ReturnCall,774args,775func_ref,776} => {777func.dfg.insts[inlined_inst] = ir::InstructionData::Call {778opcode: ir::Opcode::Call,779args,780func_ref,781};782func.dfg.make_inst_results(inlined_inst, ir::types::INVALID);783784append_stack_map_entries(785func,786callee,787&entity_map,788call_stack_map,789inlined_inst,790callee_inst,791);792793let rets = SmallBlockArgVec::from_iter(794func.dfg795.inst_results(inlined_inst)796.iter()797.copied()798.map(|v| v.into()),799);800let mut cursor = FuncCursor::new(func);801cursor.goto_after_inst(inlined_inst);802cursor.ins().jump(return_block, &rets);803804if call_opcode == ir::Opcode::TryCall {805allocs806.calls_needing_exception_table_fixup807.push(inlined_inst);808}809}810811// return_call_indirect val(args...)812//813// becomes814//815// rets... = call_indirect val(args...)816// jump return_block(rets...)817ir::InstructionData::CallIndirect {818opcode: ir::Opcode::ReturnCallIndirect,819args,820sig_ref,821} => {822func.dfg.insts[inlined_inst] = ir::InstructionData::CallIndirect {823opcode: ir::Opcode::CallIndirect,824args,825sig_ref,826};827func.dfg.make_inst_results(inlined_inst, ir::types::INVALID);828829append_stack_map_entries(830func,831callee,832&entity_map,833call_stack_map,834inlined_inst,835callee_inst,836);837838let rets = SmallBlockArgVec::from_iter(839func.dfg840.inst_results(inlined_inst)841.iter()842.copied()843.map(|v| v.into()),844);845let mut cursor = FuncCursor::new(func);846cursor.goto_after_inst(inlined_inst);847cursor.ins().jump(return_block, &rets);848849if call_opcode == ir::Opcode::TryCall {850allocs851.calls_needing_exception_table_fixup852.push(inlined_inst);853}854}855856inst_data => unreachable!(857"should have handled all `is_return() == true` instructions above; \858got {inst_data:?}"859),860}861}862863/// An `InstructionMapper` implementation that remaps a callee instruction's864/// entity references to their new indices in the caller function.865struct InliningInstRemapper<'a> {866allocs: &'a InliningAllocs,867func: &'a mut ir::Function,868callee: &'a ir::Function,869entity_map: &'a EntityMap,870}871872impl<'a> ir::instructions::InstructionMapper for InliningInstRemapper<'a> {873fn map_value(&mut self, value: ir::Value) -> ir::Value {874self.allocs.get_inlined_value(self.callee, value).expect(875"defs come before uses; we should have already inlined all values \876used by an instruction",877)878}879880fn map_value_list(&mut self, value_list: ir::ValueList) -> ir::ValueList {881let mut inlined_list = ir::ValueList::new();882for callee_val in value_list.as_slice(&self.callee.dfg.value_lists) {883let inlined_val = self.map_value(*callee_val);884inlined_list.push(inlined_val, &mut self.func.dfg.value_lists);885}886inlined_list887}888889fn map_global_value(&mut self, global_value: ir::GlobalValue) -> ir::GlobalValue {890self.entity_map.inlined_global_value(global_value)891}892893fn map_jump_table(&mut self, jump_table: ir::JumpTable) -> ir::JumpTable {894let inlined_default =895self.map_block_call(self.callee.dfg.jump_tables[jump_table].default_block());896let inlined_table = self.callee.dfg.jump_tables[jump_table]897.as_slice()898.iter()899.map(|callee_block_call| self.map_block_call(*callee_block_call))900.collect::<SmallBlockCallVec>();901self.func902.dfg903.jump_tables904.push(ir::JumpTableData::new(inlined_default, &inlined_table))905}906907fn map_exception_table(&mut self, exception_table: ir::ExceptionTable) -> ir::ExceptionTable {908let exception_table = &self.callee.dfg.exception_tables[exception_table];909let inlined_sig_ref = self.map_sig_ref(exception_table.signature());910let inlined_normal_return = self.map_block_call(*exception_table.normal_return());911let inlined_table = exception_table912.items()913.map(|item| match item {914ExceptionTableItem::Tag(tag, block_call) => {915ExceptionTableItem::Tag(tag, self.map_block_call(block_call))916}917ExceptionTableItem::Default(block_call) => {918ExceptionTableItem::Default(self.map_block_call(block_call))919}920ExceptionTableItem::Context(value) => {921ExceptionTableItem::Context(self.map_value(value))922}923})924.collect::<SmallVec<[_; 8]>>();925self.func926.dfg927.exception_tables928.push(ir::ExceptionTableData::new(929inlined_sig_ref,930inlined_normal_return,931inlined_table,932))933}934935fn map_block_call(&mut self, block_call: ir::BlockCall) -> ir::BlockCall {936let callee_block = block_call.block(&self.callee.dfg.value_lists);937let inlined_block = self.entity_map.inlined_block(callee_block);938let args = block_call939.args(&self.callee.dfg.value_lists)940.map(|arg| match arg {941ir::BlockArg::Value(value) => self.map_value(value).into(),942ir::BlockArg::TryCallRet(_) | ir::BlockArg::TryCallExn(_) => arg,943})944.collect::<SmallBlockArgVec>();945ir::BlockCall::new(inlined_block, args, &mut self.func.dfg.value_lists)946}947948fn map_block(&mut self, block: ir::Block) -> ir::Block {949self.entity_map.inlined_block(block)950}951952fn map_func_ref(&mut self, func_ref: ir::FuncRef) -> ir::FuncRef {953self.entity_map.inlined_func_ref(func_ref)954}955956fn map_sig_ref(&mut self, sig_ref: ir::SigRef) -> ir::SigRef {957self.entity_map.inlined_sig_ref(sig_ref)958}959960fn map_stack_slot(&mut self, stack_slot: ir::StackSlot) -> ir::StackSlot {961self.entity_map.inlined_stack_slot(stack_slot)962}963964fn map_dynamic_stack_slot(965&mut self,966dynamic_stack_slot: ir::DynamicStackSlot,967) -> ir::DynamicStackSlot {968self.entity_map969.inlined_dynamic_stack_slot(dynamic_stack_slot)970}971972fn map_constant(&mut self, constant: ir::Constant) -> ir::Constant {973self.allocs974.constants975.get(constant)976.and_then(|o| o.expand())977.expect("should have inlined all callee constants")978}979980fn map_immediate(&mut self, immediate: ir::Immediate) -> ir::Immediate {981self.entity_map.inlined_immediate(immediate)982}983}984985/// Inline the callee's layout into the caller's layout.986///987/// Returns the last inlined block in the layout.988fn inline_block_layout(989func: &mut ir::Function,990call_block: ir::Block,991callee: &ir::Function,992entity_map: &EntityMap,993) -> ir::Block {994debug_assert!(func.layout.is_block_inserted(call_block));995996// Iterate over callee blocks in layout order, inserting their associated997// inlined block into the caller's layout.998let mut prev_inlined_block = call_block;999let mut next_callee_block = callee.layout.entry_block();1000while let Some(callee_block) = next_callee_block {1001debug_assert!(func.layout.is_block_inserted(prev_inlined_block));10021003let inlined_block = entity_map.inlined_block(callee_block);1004func.layout1005.insert_block_after(inlined_block, prev_inlined_block);10061007prev_inlined_block = inlined_block;1008next_callee_block = callee.layout.next_block(callee_block);1009}10101011debug_assert!(func.layout.is_block_inserted(prev_inlined_block));1012prev_inlined_block1013}10141015/// Split the call instruction's block just after the call instruction to create1016/// the point where control-flow joins after the inlined callee "returns".1017///1018/// Note that tail calls do not return to the caller and therefore do not have a1019/// control-flow join point.1020fn split_off_return_block(1021func: &mut ir::Function,1022call_inst: ir::Inst,1023opcode: ir::Opcode,1024callee: &ir::Function,1025) -> Option<ir::Block> {1026// When the `call_inst` is not a block terminator, we need to split the1027// block.1028let return_block = func.layout.next_inst(call_inst).map(|next_inst| {1029let return_block = func.dfg.blocks.add();1030func.layout.split_block(return_block, next_inst);10311032// Add block parameters for each return value and alias the call1033// instruction's results to them.1034let old_results =1035SmallValueVec::from_iter(func.dfg.inst_results(call_inst).iter().copied());1036debug_assert_eq!(old_results.len(), callee.signature.returns.len());1037func.dfg.detach_inst_results(call_inst);1038for (abi, old_val) in callee.signature.returns.iter().zip(old_results) {1039debug_assert_eq!(abi.value_type, func.dfg.value_type(old_val));1040let ret_param = func.dfg.append_block_param(return_block, abi.value_type);1041func.dfg.change_to_alias(old_val, ret_param);1042}10431044return_block1045});10461047// When the `call_inst` is a block terminator, then it is either a1048// `return_call` or a `try_call`:1049//1050// * For `return_call`s, we don't have a control-flow join point, because1051// the caller permanently transfers control to the callee.1052//1053// * For `try_call`s, we probably already have a block for the control-flow1054// join point, but it isn't guaranteed: the `try_call` might ignore the1055// call's returns and not forward them to the normal-return block or it1056// might also pass additional arguments. We can only reuse the existing1057// normal-return block when the `try_call` forwards exactly our callee's1058// returns to that block (and therefore that block's parameter types also1059// exactly match the callee's return types). Otherwise, we must create a new1060// return block that forwards to the existing normal-return1061// block. (Elsewhere, at the end of inlining, we will also update any inlined1062// calls to forward any raised exceptions to the caller's exception table,1063// as necessary.)1064//1065// Finally, note that reusing the normal-return's target block is just an1066// optimization to emit a simpler CFG when we can, and is not1067// fundamentally required for correctness. We could always insert a1068// temporary block as our control-flow join point that then forwards to1069// the normal-return's target block. However, at the time of writing,1070// Cranelift doesn't currently do any jump-threading or branch1071// simplification in the mid-end, and removing unnecessary blocks in this1072// way can help some subsequent mid-end optimizations. If, in the future,1073// we gain support for jump-threading optimizations in the mid-end, we can1074// come back and simplify the below code a bit to always generate the1075// temporary block, and then rely on the subsequent optimizations to clean1076// everything up.1077debug_assert_eq!(1078return_block.is_none(),1079opcode == ir::Opcode::ReturnCall || opcode == ir::Opcode::TryCall,1080);1081return_block.or_else(|| match func.dfg.insts[call_inst] {1082ir::InstructionData::TryCall {1083opcode: ir::Opcode::TryCall,1084args: _,1085func_ref: _,1086exception,1087} => {1088let normal_return = func.dfg.exception_tables[exception].normal_return();1089let normal_return_block = normal_return.block(&func.dfg.value_lists);10901091// Check to see if we can reuse the existing normal-return block.1092{1093let normal_return_args = normal_return.args(&func.dfg.value_lists);1094if normal_return_args.len() == callee.signature.returns.len()1095&& normal_return_args.enumerate().all(|(i, arg)| {1096let i = u32::try_from(i).unwrap();1097arg == ir::BlockArg::TryCallRet(i)1098})1099{1100return Some(normal_return_block);1101}1102}11031104// Okay, we cannot reuse the normal-return block. Create a new block1105// that has the expected block parameter types and have it jump to1106// the normal-return block.1107let return_block = func.dfg.blocks.add();1108func.layout.insert_block(return_block, normal_return_block);11091110let return_block_params = callee1111.signature1112.returns1113.iter()1114.map(|abi| func.dfg.append_block_param(return_block, abi.value_type))1115.collect::<SmallValueVec>();11161117let normal_return_args = func.dfg.exception_tables[exception]1118.normal_return()1119.args(&func.dfg.value_lists)1120.collect::<SmallBlockArgVec>();1121let jump_args = normal_return_args1122.into_iter()1123.map(|arg| match arg {1124ir::BlockArg::Value(value) => ir::BlockArg::Value(value),1125ir::BlockArg::TryCallRet(i) => {1126let i = usize::try_from(i).unwrap();1127ir::BlockArg::Value(return_block_params[i])1128}1129ir::BlockArg::TryCallExn(_) => {1130unreachable!("normal-return edges cannot use exceptional results")1131}1132})1133.collect::<SmallBlockArgVec>();11341135let mut cursor = FuncCursor::new(func);1136cursor.goto_first_insertion_point(return_block);1137cursor.ins().jump(normal_return_block, &jump_args);11381139Some(return_block)1140}1141_ => None,1142})1143}11441145/// Replace the caller's call instruction with a jump to the caller's inlined1146/// copy of the callee's entry block.1147///1148/// Also associates the callee's parameters with the caller's arguments in our1149/// value map.1150///1151/// Returns the caller's stack map entries, if any.1152fn replace_call_with_jump(1153allocs: &mut InliningAllocs,1154func: &mut ir::Function,1155call_inst: ir::Inst,1156callee: &ir::Function,1157entity_map: &EntityMap,1158) -> Option<ir::UserStackMapEntryVec> {1159trace!("Replacing `call` with `jump`");1160trace!(1161" --> call instruction: {call_inst:?}: {}",1162func.dfg.display_inst(call_inst)1163);11641165let callee_entry_block = callee1166.layout1167.entry_block()1168.expect("callee function should have an entry block");1169let callee_param_values = callee.dfg.block_params(callee_entry_block);1170let caller_arg_values = SmallValueVec::from_iter(func.dfg.inst_args(call_inst).iter().copied());1171debug_assert_eq!(callee_param_values.len(), caller_arg_values.len());1172debug_assert_eq!(callee_param_values.len(), callee.signature.params.len());1173for (abi, (callee_param_value, caller_arg_value)) in callee1174.signature1175.params1176.iter()1177.zip(callee_param_values.into_iter().zip(caller_arg_values))1178{1179debug_assert_eq!(abi.value_type, callee.dfg.value_type(*callee_param_value));1180debug_assert_eq!(abi.value_type, func.dfg.value_type(caller_arg_value));1181allocs.set_inlined_value(callee, *callee_param_value, caller_arg_value);1182}11831184// Replace the caller's call instruction with a jump to the caller's inlined1185// copy of the callee's entry block.1186//1187// Note that the call block dominates the inlined entry block (and also all1188// other inlined blocks) so we can reference the arguments directly, and do1189// not need to add block parameters to the inlined entry block.1190let inlined_entry_block = entity_map.inlined_block(callee_entry_block);1191func.dfg.replace(call_inst).jump(inlined_entry_block, &[]);1192trace!(1193" --> replaced with jump instruction: {call_inst:?}: {}",1194func.dfg.display_inst(call_inst)1195);11961197let stack_map_entries = func.dfg.take_user_stack_map_entries(call_inst);1198stack_map_entries1199}12001201/// Keeps track of mapping callee entities to their associated inlined caller1202/// entities.1203#[derive(Default)]1204struct EntityMap {1205// Rather than doing an implicit, demand-based, DCE'ing translation of1206// entities, which would require maps from each callee entity to its1207// associated caller entity, we copy all entities into the caller, remember1208// each entity's initial offset, and then mapping from the callee to the1209// inlined caller entity is just adding that initial offset to the callee's1210// index. This should be both faster and simpler than the alternative. Most1211// of these sets are relatively small, and they rarely have too much dead1212// code in practice, so this is a good trade off.1213//1214// Note that there are a few kinds of entities that are excluded from the1215// `EntityMap`, and for which we do actually take the demand-based approach:1216// values and value lists being the notable ones.1217block_offset: Option<u32>,1218global_value_offset: Option<u32>,1219sig_ref_offset: Option<u32>,1220func_ref_offset: Option<u32>,1221stack_slot_offset: Option<u32>,1222dynamic_type_offset: Option<u32>,1223dynamic_stack_slot_offset: Option<u32>,1224immediate_offset: Option<u32>,1225}12261227impl EntityMap {1228fn inlined_block(&self, callee_block: ir::Block) -> ir::Block {1229let offset = self1230.block_offset1231.expect("must create inlined `ir::Block`s before calling `EntityMap::inlined_block`");1232ir::Block::from_u32(offset + callee_block.as_u32())1233}12341235fn iter_inlined_blocks(&self, func: &ir::Function) -> impl Iterator<Item = ir::Block> + use<> {1236let start = self.block_offset.expect(1237"must create inlined `ir::Block`s before calling `EntityMap::iter_inlined_blocks`",1238);12391240let end = func.dfg.blocks.len();1241let end = u32::try_from(end).unwrap();12421243(start..end).map(|i| ir::Block::from_u32(i))1244}12451246fn inlined_global_value(&self, callee_global_value: ir::GlobalValue) -> ir::GlobalValue {1247let offset = self1248.global_value_offset1249.expect("must create inlined `ir::GlobalValue`s before calling `EntityMap::inlined_global_value`");1250ir::GlobalValue::from_u32(offset + callee_global_value.as_u32())1251}12521253fn inlined_sig_ref(&self, callee_sig_ref: ir::SigRef) -> ir::SigRef {1254let offset = self.sig_ref_offset.expect(1255"must create inlined `ir::SigRef`s before calling `EntityMap::inlined_sig_ref`",1256);1257ir::SigRef::from_u32(offset + callee_sig_ref.as_u32())1258}12591260fn inlined_func_ref(&self, callee_func_ref: ir::FuncRef) -> ir::FuncRef {1261let offset = self.func_ref_offset.expect(1262"must create inlined `ir::FuncRef`s before calling `EntityMap::inlined_func_ref`",1263);1264ir::FuncRef::from_u32(offset + callee_func_ref.as_u32())1265}12661267fn inlined_stack_slot(&self, callee_stack_slot: ir::StackSlot) -> ir::StackSlot {1268let offset = self.stack_slot_offset.expect(1269"must create inlined `ir::StackSlot`s before calling `EntityMap::inlined_stack_slot`",1270);1271ir::StackSlot::from_u32(offset + callee_stack_slot.as_u32())1272}12731274fn inlined_dynamic_type(&self, callee_dynamic_type: ir::DynamicType) -> ir::DynamicType {1275let offset = self.dynamic_type_offset.expect(1276"must create inlined `ir::DynamicType`s before calling `EntityMap::inlined_dynamic_type`",1277);1278ir::DynamicType::from_u32(offset + callee_dynamic_type.as_u32())1279}12801281fn inlined_dynamic_stack_slot(1282&self,1283callee_dynamic_stack_slot: ir::DynamicStackSlot,1284) -> ir::DynamicStackSlot {1285let offset = self.dynamic_stack_slot_offset.expect(1286"must create inlined `ir::DynamicStackSlot`s before calling `EntityMap::inlined_dynamic_stack_slot`",1287);1288ir::DynamicStackSlot::from_u32(offset + callee_dynamic_stack_slot.as_u32())1289}12901291fn inlined_immediate(&self, callee_immediate: ir::Immediate) -> ir::Immediate {1292let offset = self.immediate_offset.expect(1293"must create inlined `ir::Immediate`s before calling `EntityMap::inlined_immediate`",1294);1295ir::Immediate::from_u32(offset + callee_immediate.as_u32())1296}1297}12981299/// Translate all of the callee's various entities into the caller, producing an1300/// `EntityMap` that can be used to translate callee entity references into1301/// inlined caller entity references.1302fn create_entities(1303allocs: &mut InliningAllocs,1304func: &mut ir::Function,1305callee: &ir::Function,1306) -> EntityMap {1307let mut entity_map = EntityMap::default();13081309entity_map.block_offset = Some(create_blocks(allocs, func, callee));1310entity_map.global_value_offset = Some(create_global_values(func, callee));1311entity_map.sig_ref_offset = Some(create_sig_refs(func, callee));1312create_user_external_name_refs(allocs, func, callee);1313entity_map.func_ref_offset = Some(create_func_refs(allocs, func, callee, &entity_map));1314entity_map.stack_slot_offset = Some(create_stack_slots(func, callee));1315entity_map.dynamic_type_offset = Some(create_dynamic_types(func, callee, &entity_map));1316entity_map.dynamic_stack_slot_offset =1317Some(create_dynamic_stack_slots(func, callee, &entity_map));1318entity_map.immediate_offset = Some(create_immediates(func, callee));13191320// `ir::ConstantData` is deduplicated, so we cannot use our offset scheme1321// for `ir::Constant`s. Nonetheless, we still insert them into the caller1322// now, at the same time as the rest of our entities.1323create_constants(allocs, func, callee);13241325entity_map1326}13271328/// Create inlined blocks in the caller for every block in the callee.1329fn create_blocks(1330allocs: &mut InliningAllocs,1331func: &mut ir::Function,1332callee: &ir::Function,1333) -> u32 {1334let offset = func.dfg.blocks.len();1335let offset = u32::try_from(offset).unwrap();13361337func.dfg.blocks.reserve(callee.dfg.blocks.len());1338for callee_block in callee.dfg.blocks.iter() {1339let caller_block = func.dfg.blocks.add();1340trace!("Callee {callee_block:?} = inlined {caller_block:?}");13411342if callee.layout.is_cold(callee_block) {1343func.layout.set_cold(caller_block);1344}13451346// Note: the entry block does not need parameters because the only1347// predecessor is the call block and we associate the callee's1348// parameters with the caller's arguments directly.1349if callee.layout.entry_block() != Some(callee_block) {1350for callee_param in callee.dfg.blocks[callee_block].params(&callee.dfg.value_lists) {1351let ty = callee.dfg.value_type(*callee_param);1352let caller_param = func.dfg.append_block_param(caller_block, ty);13531354allocs.set_inlined_value(callee, *callee_param, caller_param);1355}1356}1357}13581359offset1360}13611362/// Copy and translate global values from the callee into the caller.1363fn create_global_values(func: &mut ir::Function, callee: &ir::Function) -> u32 {1364let gv_offset = func.global_values.len();1365let gv_offset = u32::try_from(gv_offset).unwrap();13661367func.global_values.reserve(callee.global_values.len());1368for gv in callee.global_values.values() {1369func.global_values.push(match gv {1370// These kinds of global values reference other global values, so we1371// need to fixup that reference.1372ir::GlobalValueData::Load {1373base,1374offset,1375global_type,1376flags,1377} => ir::GlobalValueData::Load {1378base: ir::GlobalValue::from_u32(base.as_u32() + gv_offset),1379offset: *offset,1380global_type: *global_type,1381flags: *flags,1382},1383ir::GlobalValueData::IAddImm {1384base,1385offset,1386global_type,1387} => ir::GlobalValueData::IAddImm {1388base: ir::GlobalValue::from_u32(base.as_u32() + gv_offset),1389offset: *offset,1390global_type: *global_type,1391},13921393// These kinds of global values do not reference other global1394// values, so we can just clone them.1395ir::GlobalValueData::VMContext1396| ir::GlobalValueData::Symbol { .. }1397| ir::GlobalValueData::DynScaleTargetConst { .. } => gv.clone(),1398});1399}14001401gv_offset1402}14031404/// Copy `ir::SigRef`s from the callee into the caller.1405fn create_sig_refs(func: &mut ir::Function, callee: &ir::Function) -> u32 {1406let offset = func.dfg.signatures.len();1407let offset = u32::try_from(offset).unwrap();14081409func.dfg.signatures.reserve(callee.dfg.signatures.len());1410for sig in callee.dfg.signatures.values() {1411func.dfg.signatures.push(sig.clone());1412}14131414offset1415}14161417fn create_user_external_name_refs(1418allocs: &mut InliningAllocs,1419func: &mut ir::Function,1420callee: &ir::Function,1421) {1422for (callee_named_func_ref, name) in callee.params.user_named_funcs().iter() {1423let caller_named_func_ref = func.declare_imported_user_function(name.clone());1424allocs.user_external_name_refs[callee_named_func_ref] = Some(caller_named_func_ref).into();1425}1426}14271428/// Translate `ir::FuncRef`s from the callee into the caller.1429fn create_func_refs(1430allocs: &InliningAllocs,1431func: &mut ir::Function,1432callee: &ir::Function,1433entity_map: &EntityMap,1434) -> u32 {1435let offset = func.dfg.ext_funcs.len();1436let offset = u32::try_from(offset).unwrap();14371438func.dfg.ext_funcs.reserve(callee.dfg.ext_funcs.len());1439for ir::ExtFuncData {1440name,1441signature,1442colocated,1443} in callee.dfg.ext_funcs.values()1444{1445func.dfg.ext_funcs.push(ir::ExtFuncData {1446name: match name {1447ir::ExternalName::User(name_ref) => {1448ir::ExternalName::User(allocs.user_external_name_refs[*name_ref].expect(1449"should have translated all `ir::UserExternalNameRef`s before translating \1450`ir::FuncRef`s",1451))1452}1453ir::ExternalName::TestCase(_)1454| ir::ExternalName::LibCall(_)1455| ir::ExternalName::KnownSymbol(_) => name.clone(),1456},1457signature: entity_map.inlined_sig_ref(*signature),1458colocated: *colocated,1459});1460}14611462offset1463}14641465/// Copy stack slots from the callee into the caller.1466fn create_stack_slots(func: &mut ir::Function, callee: &ir::Function) -> u32 {1467let offset = func.sized_stack_slots.len();1468let offset = u32::try_from(offset).unwrap();14691470func.sized_stack_slots1471.reserve(callee.sized_stack_slots.len());1472for slot in callee.sized_stack_slots.values() {1473func.sized_stack_slots.push(slot.clone());1474}14751476offset1477}14781479/// Copy dynamic types from the callee into the caller.1480fn create_dynamic_types(1481func: &mut ir::Function,1482callee: &ir::Function,1483entity_map: &EntityMap,1484) -> u32 {1485let offset = func.dynamic_stack_slots.len();1486let offset = u32::try_from(offset).unwrap();14871488func.dfg1489.dynamic_types1490.reserve(callee.dfg.dynamic_types.len());1491for ir::DynamicTypeData {1492base_vector_ty,1493dynamic_scale,1494} in callee.dfg.dynamic_types.values()1495{1496func.dfg.dynamic_types.push(ir::DynamicTypeData {1497base_vector_ty: *base_vector_ty,1498dynamic_scale: entity_map.inlined_global_value(*dynamic_scale),1499});1500}15011502offset1503}15041505/// Copy dynamic stack slots from the callee into the caller.1506fn create_dynamic_stack_slots(1507func: &mut ir::Function,1508callee: &ir::Function,1509entity_map: &EntityMap,1510) -> u32 {1511let offset = func.dynamic_stack_slots.len();1512let offset = u32::try_from(offset).unwrap();15131514func.dynamic_stack_slots1515.reserve(callee.dynamic_stack_slots.len());1516for ir::DynamicStackSlotData { kind, dyn_ty } in callee.dynamic_stack_slots.values() {1517func.dynamic_stack_slots.push(ir::DynamicStackSlotData {1518kind: *kind,1519dyn_ty: entity_map.inlined_dynamic_type(*dyn_ty),1520});1521}15221523offset1524}15251526/// Copy immediates from the callee into the caller.1527fn create_immediates(func: &mut ir::Function, callee: &ir::Function) -> u32 {1528let offset = func.dfg.immediates.len();1529let offset = u32::try_from(offset).unwrap();15301531func.dfg.immediates.reserve(callee.dfg.immediates.len());1532for imm in callee.dfg.immediates.values() {1533func.dfg.immediates.push(imm.clone());1534}15351536offset1537}15381539/// Copy constants from the callee into the caller.1540fn create_constants(allocs: &mut InliningAllocs, func: &mut ir::Function, callee: &ir::Function) {1541for (callee_constant, data) in callee.dfg.constants.iter() {1542let inlined_constant = func.dfg.constants.insert(data.clone());1543allocs.constants[*callee_constant] = Some(inlined_constant).into();1544}1545}154615471548