Path: blob/main/cranelift/codegen/src/inline.rs
3054 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, DebugTag, 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 { func_ref, .. }142if cursor.func.dfg.ext_funcs[func_ref].patchable =>143{144// Can't inline patchable calls; they need to145// remain patchable and inlining the whole body is146// decidedly *not* patchable!147}148149ir::InstructionData::Call {150opcode: opcode @ ir::Opcode::Call | opcode @ ir::Opcode::ReturnCall,151args: _,152func_ref,153} => {154trace!(155"considering call site for inlining: {inst}: {}",156cursor.func.dfg.display_inst(inst),157);158let args = cursor.func.dfg.inst_args(inst);159match inliner.inline(&cursor.func, inst, opcode, func_ref, args) {160InlineCommand::KeepCall => {161trace!(" --> keeping call");162}163InlineCommand::Inline {164callee,165visit_callee,166} => {167let last_inlined_block = inline_one(168&mut allocs,169cursor.func,170func_ref,171block,172inst,173opcode,174&callee,175None,176);177inlined_any = true;178if visit_callee {179cursor.set_position(prev_pos);180} else {181// Arrange it so that the `next_block()` loop182// will continue to the next block that is not183// associated with the just-inlined callee.184cursor.goto_bottom(last_inlined_block);185continue 'block_loop;186}187}188}189}190ir::InstructionData::TryCall {191opcode: opcode @ ir::Opcode::TryCall,192args: _,193func_ref,194exception,195} => {196trace!(197"considering call site for inlining: {inst}: {}",198cursor.func.dfg.display_inst(inst),199);200let args = cursor.func.dfg.inst_args(inst);201match inliner.inline(&cursor.func, inst, opcode, func_ref, args) {202InlineCommand::KeepCall => {203trace!(" --> keeping call");204}205InlineCommand::Inline {206callee,207visit_callee,208} => {209let last_inlined_block = inline_one(210&mut allocs,211cursor.func,212func_ref,213block,214inst,215opcode,216&callee,217Some(exception),218);219inlined_any = true;220if visit_callee {221cursor.set_position(prev_pos);222} else {223// Arrange it so that the `next_block()` loop224// will continue to the next block that is not225// associated with the just-inlined callee.226cursor.goto_bottom(last_inlined_block);227continue 'block_loop;228}229}230}231}232ir::InstructionData::CallIndirect { .. }233| ir::InstructionData::TryCallIndirect { .. } => {234// Can't inline indirect calls; need to have some earlier235// pass rewrite them into direct calls first, when possible.236}237_ => {238debug_assert!(239!cursor.func.dfg.insts[inst].opcode().is_call(),240"should have matched all call instructions, but found: {inst}: {}",241cursor.func.dfg.display_inst(inst),242);243}244}245}246}247248if inlined_any {249trace!("function {} after inlining: {}", func.name, func);250} else {251trace!("function {} did not have any callees inlined", func.name);252}253254Ok(inlined_any)255}256257#[derive(Default)]258struct InliningAllocs {259/// Map from callee value to inlined caller value.260values: SecondaryMap<ir::Value, PackedOption<ir::Value>>,261262/// Map from callee constant to inlined caller constant.263///264/// Not in `EntityMap` because these are hash-consed inside the265/// `ir::Function`.266constants: SecondaryMap<ir::Constant, PackedOption<ir::Constant>>,267268/// Map from callee to inlined caller external name refs.269///270/// Not in `EntityMap` because these are hash-consed inside the271/// `ir::Function`.272user_external_name_refs:273SecondaryMap<ir::UserExternalNameRef, PackedOption<ir::UserExternalNameRef>>,274275/// The set of _caller_ inlined call instructions that need exception table276/// fixups at the end of inlining.277///278/// This includes all kinds of non-returning calls, not just the literal279/// `call` instruction: `call_indirect`, `try_call`, `try_call_indirect`,280/// etc... However, it does not include `return_call` and281/// `return_call_indirect` instructions because the caller cannot catch282/// exceptions that those calls throw because the caller is no longer on the283/// stack as soon as they are executed.284///285/// Note: this is a simple `Vec`, and not an `EntitySet`, because it is very286/// sparse: most of the caller's instructions are not inlined call287/// instructions. Additionally, we require deterministic iteration order and288/// do not require set-membership testing, so a hash set is not a good289/// choice either.290calls_needing_exception_table_fixup: Vec<ir::Inst>,291}292293impl InliningAllocs {294fn reset(&mut self, callee: &ir::Function) {295let InliningAllocs {296values,297constants,298user_external_name_refs,299calls_needing_exception_table_fixup,300} = self;301302values.clear();303values.resize(callee.dfg.len_values());304305constants.clear();306constants.resize(callee.dfg.constants.len());307308user_external_name_refs.clear();309user_external_name_refs.resize(callee.params.user_named_funcs().len());310311// Note: We do not reserve capacity for312// `calls_needing_exception_table_fixup` because it is a sparse set and313// we don't know how large it needs to be ahead of time.314calls_needing_exception_table_fixup.clear();315}316317fn set_inlined_value(318&mut self,319callee: &ir::Function,320callee_val: ir::Value,321inlined_val: ir::Value,322) {323trace!(" --> callee {callee_val:?} = inlined {inlined_val:?}");324debug_assert!(self.values[callee_val].is_none());325let resolved_callee_val = callee.dfg.resolve_aliases(callee_val);326debug_assert!(self.values[resolved_callee_val].is_none());327self.values[resolved_callee_val] = Some(inlined_val).into();328}329330fn get_inlined_value(&self, callee: &ir::Function, callee_val: ir::Value) -> Option<ir::Value> {331let resolved_callee_val = callee.dfg.resolve_aliases(callee_val);332self.values[resolved_callee_val].expand()333}334}335336/// Inline one particular function call.337///338/// Returns the last inlined block in the layout.339fn inline_one(340allocs: &mut InliningAllocs,341func: &mut ir::Function,342callee_func_ref: ir::FuncRef,343call_block: ir::Block,344call_inst: ir::Inst,345call_opcode: ir::Opcode,346callee: &ir::Function,347call_exception_table: Option<ir::ExceptionTable>,348) -> ir::Block {349trace!(350"Inlining call {call_inst:?}: {}\n\351with callee = {callee:?}",352func.dfg.display_inst(call_inst)353);354355// Type check callee signature.356let expected_callee_sig = func.dfg.ext_funcs[callee_func_ref].signature;357let expected_callee_sig = &func.dfg.signatures[expected_callee_sig];358assert_eq!(expected_callee_sig, &callee.signature);359360allocs.reset(callee);361362// First, append various callee entity arenas to the end of the caller's363// entity arenas.364let entity_map = create_entities(allocs, func, callee);365366// Inlined prologue: split the call instruction's block at the point of the367// call and replace the call with a jump.368let return_block = split_off_return_block(func, call_inst, call_opcode, callee);369let call_stack_map = replace_call_with_jump(allocs, func, call_inst, callee, &entity_map);370371// Prepare for translating the actual instructions by inserting the inlined372// blocks into the caller's layout in the same order that they appear in the373// callee.374let mut last_inlined_block = inline_block_layout(func, call_block, callee, &entity_map);375376// Get a copy of debug tags on the call instruction; these are377// prepended to debug tags on inlined instructions. Remove them378// from the call itself as it will be rewritten to a jump (which379// cannot have tags).380let call_debug_tags = func.debug_tags.get(call_inst).to_vec();381func.debug_tags.set(call_inst, []);382383// Translate each instruction from the callee into the caller,384// appending them to their associated block in the caller.385//386// Note that we iterate over the callee with a pre-order traversal so that387// we see value defs before uses.388for callee_block in Dfs::new().pre_order_iter(callee) {389let inlined_block = entity_map.inlined_block(callee_block);390trace!(391"Processing instructions in callee block {callee_block:?} (inlined block {inlined_block:?}"392);393394let mut next_callee_inst = callee.layout.first_inst(callee_block);395while let Some(callee_inst) = next_callee_inst {396trace!(397"Processing callee instruction {callee_inst:?}: {}",398callee.dfg.display_inst(callee_inst)399);400401assert_ne!(402callee.dfg.insts[callee_inst].opcode(),403ir::Opcode::GlobalValue,404"callee must already be legalized, we shouldn't see any `global_value` \405instructions when inlining; found {callee_inst:?}: {}",406callee.dfg.display_inst(callee_inst)407);408409// Remap the callee instruction's entities and insert it into the410// caller's DFG.411let inlined_inst_data = callee.dfg.insts[callee_inst].map(InliningInstRemapper {412allocs: &allocs,413func,414callee,415entity_map: &entity_map,416});417let inlined_inst = func.dfg.make_inst(inlined_inst_data);418func.layout.append_inst(inlined_inst, inlined_block);419420// Copy over debug tags, translating referenced entities421// as appropriate.422let debug_tags = callee.debug_tags.get(callee_inst);423// If there are tags on the inlined instruction, we always424// add tags, and we prepend any tags from the call425// instruction; but we don't add tags if only the callsite426// had them (this would otherwise mean that every single427// instruction in an inlined function body would get428// tags).429if !debug_tags.is_empty() {430let tags = call_debug_tags431.iter()432.cloned()433.chain(debug_tags.iter().map(|tag| match *tag {434DebugTag::User(value) => DebugTag::User(value),435DebugTag::StackSlot(slot) => {436DebugTag::StackSlot(entity_map.inlined_stack_slot(slot))437}438}))439.collect::<SmallVec<[_; 4]>>();440func.debug_tags.set(inlined_inst, tags);441}442443let opcode = callee.dfg.insts[callee_inst].opcode();444if opcode.is_return() {445// Instructions that return do not define any values, so we446// don't need to worry about that, but we do need to fix them up447// so that they return by jumping to our control-flow join448// block, rather than returning from the caller.449if let Some(return_block) = return_block {450fixup_inst_that_returns(451allocs,452func,453callee,454&entity_map,455call_opcode,456inlined_inst,457callee_inst,458return_block,459call_stack_map.as_ref().map(|es| &**es),460);461} else {462// If we are inlining a callee that was invoked via463// `return_call`, we leave inlined return instructions464// as-is: there is no logical caller frame on the stack to465// continue to.466debug_assert_eq!(call_opcode, ir::Opcode::ReturnCall);467}468} else {469// Make the instruction's result values.470let ctrl_typevar = callee.dfg.ctrl_typevar(callee_inst);471func.dfg.make_inst_results(inlined_inst, ctrl_typevar);472473// Update the value map for this instruction's defs.474let callee_results = callee.dfg.inst_results(callee_inst);475let inlined_results = func.dfg.inst_results(inlined_inst);476debug_assert_eq!(callee_results.len(), inlined_results.len());477for (callee_val, inlined_val) in callee_results.iter().zip(inlined_results) {478allocs.set_inlined_value(callee, *callee_val, *inlined_val);479}480481if opcode.is_call() {482append_stack_map_entries(483func,484callee,485&entity_map,486call_stack_map.as_deref(),487inlined_inst,488callee_inst,489);490491// When we are inlining a `try_call` call site, we need to merge492// the call site's exception table into the inlined calls'493// exception tables. This can involve rewriting regular `call`s494// into `try_call`s, which requires mutating the CFG because495// `try_call` is a block terminator. However, we can't mutate496// the CFG in the middle of this traversal because we rely on497// the existence of a one-to-one mapping between the callee498// layout and the inlined layout. Instead, we record the set of499// inlined call instructions that will need fixing up, and500// perform that possibly-CFG-mutating exception table merging in501// a follow up pass, when we no longer rely on that one-to-one502// layout mapping.503debug_assert_eq!(504call_opcode == ir::Opcode::TryCall,505call_exception_table.is_some()506);507if call_opcode == ir::Opcode::TryCall {508allocs509.calls_needing_exception_table_fixup510.push(inlined_inst);511}512}513}514515trace!(516" --> inserted inlined instruction {inlined_inst:?}: {}",517func.dfg.display_inst(inlined_inst)518);519520next_callee_inst = callee.layout.next_inst(callee_inst);521}522}523524// We copied *all* callee blocks into the caller's layout, but only copied525// the callee instructions in *reachable* callee blocks into the caller's526// associated blocks. Therefore, any *unreachable* blocks are empty in the527// caller, which is invalid CLIF because all blocks must end in a528// terminator, so do a quick pass over the inlined blocks and remove any529// empty blocks from the caller's layout.530for block in entity_map.iter_inlined_blocks(func) {531if func.layout.is_block_inserted(block) && func.layout.first_inst(block).is_none() {532log::trace!("removing unreachable inlined block from layout: {block}");533534// If the block being removed is our last-inlined block, then back535// it up to the previous block in the layout, which will be the new536// last-inlined block after this one's removal.537if block == last_inlined_block {538last_inlined_block = func.layout.prev_block(last_inlined_block).expect(539"there will always at least be the block that contained the call we are \540inlining",541);542}543544func.layout.remove_block(block);545}546}547548// Final step: fixup the exception tables of any inlined calls when we are549// inlining a `try_call` site.550//551// Subtly, this requires rewriting non-catching `call[_indirect]`552// instructions into `try_call[_indirect]` instructions so that exceptions553// that unwound through the original callee frame and were caught by the554// caller's `try_call` do not unwind past this inlined frame. And turning a555// `call` into a `try_call` mutates the CFG, breaking our one-to-one mapping556// between callee blocks and inlined blocks, so we delay these fixups to557// this final step, when we no longer rely on that mapping.558debug_assert!(559allocs.calls_needing_exception_table_fixup.is_empty() || call_exception_table.is_some()560);561debug_assert_eq!(562call_opcode == ir::Opcode::TryCall,563call_exception_table.is_some()564);565if let Some(call_exception_table) = call_exception_table {566fixup_inlined_call_exception_tables(allocs, func, call_exception_table);567}568569debug_assert!(570func.layout.is_block_inserted(last_inlined_block),571"last_inlined_block={last_inlined_block} should be inserted in the layout"572);573last_inlined_block574}575576/// Append stack map entries from the caller and callee to the given inlined577/// instruction.578fn append_stack_map_entries(579func: &mut ir::Function,580callee: &ir::Function,581entity_map: &EntityMap,582call_stack_map: Option<&[ir::UserStackMapEntry]>,583inlined_inst: ir::Inst,584callee_inst: ir::Inst,585) {586// Add the caller's stack map to this call. These entries587// already refer to caller entities and do not need further588// translation.589func.dfg.append_user_stack_map_entries(590inlined_inst,591call_stack_map592.iter()593.flat_map(|entries| entries.iter().cloned()),594);595596// Append the callee's stack map to this call. These entries597// refer to callee entities and therefore do require598// translation into the caller's index space.599func.dfg.append_user_stack_map_entries(600inlined_inst,601callee602.dfg603.user_stack_map_entries(callee_inst)604.iter()605.flat_map(|entries| entries.iter())606.map(|entry| ir::UserStackMapEntry {607ty: entry.ty,608slot: entity_map.inlined_stack_slot(entry.slot),609offset: entry.offset,610}),611);612}613614/// Create or update the exception tables for any inlined call instructions:615/// when inlining at a `try_call` site, we must forward our exceptional edges616/// into each inlined call instruction.617fn fixup_inlined_call_exception_tables(618allocs: &mut InliningAllocs,619func: &mut ir::Function,620call_exception_table: ir::ExceptionTable,621) {622// Split a block at a `call[_indirect]` instruction, detach the623// instruction's results, and alias them to the new block's parameters.624let split_block_for_new_try_call = |func: &mut ir::Function, inst: ir::Inst| -> ir::Block {625debug_assert!(func.dfg.insts[inst].opcode().is_call());626debug_assert!(!func.dfg.insts[inst].opcode().is_terminator());627628// Split the block.629let next_inst = func630.layout631.next_inst(inst)632.expect("inst is not a terminator, should have a successor");633let new_block = func.dfg.blocks.add();634func.layout.split_block(new_block, next_inst);635636// `try_call[_indirect]` instructions do not define values themselves;637// the normal-return block has parameters for the results. So remove638// this instruction's results, create an associated block parameter for639// each of them, and alias them to the new block parameter.640let old_results = SmallValueVec::from_iter(func.dfg.inst_results(inst).iter().copied());641func.dfg.detach_inst_results(inst);642for old_result in old_results {643let ty = func.dfg.value_type(old_result);644let new_block_param = func.dfg.append_block_param(new_block, ty);645func.dfg.change_to_alias(old_result, new_block_param);646}647648new_block649};650651// Clone the caller's exception table, updating it for use in the current652// `call[_indirect]` instruction as it becomes a `try_call[_indirect]`.653let clone_exception_table_for_this_call = |func: &mut ir::Function,654signature: ir::SigRef,655new_block: ir::Block|656-> ir::ExceptionTable {657let mut exception = func.stencil.dfg.exception_tables[call_exception_table]658.deep_clone(&mut func.stencil.dfg.value_lists);659660*exception.signature_mut() = signature;661662let returns_len = func.dfg.signatures[signature].returns.len();663let returns_len = u32::try_from(returns_len).unwrap();664665*exception.normal_return_mut() = ir::BlockCall::new(666new_block,667(0..returns_len).map(|i| ir::BlockArg::TryCallRet(i)),668&mut func.dfg.value_lists,669);670671func.dfg.exception_tables.push(exception)672};673674for inst in allocs.calls_needing_exception_table_fixup.drain(..) {675debug_assert!(func.dfg.insts[inst].opcode().is_call());676debug_assert!(!func.dfg.insts[inst].opcode().is_return());677match func.dfg.insts[inst] {678// current_block:679// preds...680// rets... = call f(args...)681// succs...682//683// becomes684//685// current_block:686// preds...687// try_call f(args...), new_block(rets...), [call_exception_table...]688// new_block(rets...):689// succs...690ir::InstructionData::Call {691opcode: ir::Opcode::Call,692args,693func_ref,694} => {695let new_block = split_block_for_new_try_call(func, inst);696let signature = func.dfg.ext_funcs[func_ref].signature;697let exception = clone_exception_table_for_this_call(func, signature, new_block);698func.dfg.insts[inst] = ir::InstructionData::TryCall {699opcode: ir::Opcode::TryCall,700args,701func_ref,702exception,703};704}705706// current_block:707// preds...708// rets... = call_indirect sig, val(args...)709// succs...710//711// becomes712//713// current_block:714// preds...715// try_call_indirect sig, val(args...), new_block(rets...), [call_exception_table...]716// new_block(rets...):717// succs...718ir::InstructionData::CallIndirect {719opcode: ir::Opcode::CallIndirect,720args,721sig_ref,722} => {723let new_block = split_block_for_new_try_call(func, inst);724let exception = clone_exception_table_for_this_call(func, sig_ref, new_block);725func.dfg.insts[inst] = ir::InstructionData::TryCallIndirect {726opcode: ir::Opcode::TryCallIndirect,727args,728exception,729};730}731732// For `try_call[_indirect]` instructions, we just need to merge the733// exception tables.734ir::InstructionData::TryCall {735opcode: ir::Opcode::TryCall,736exception,737..738}739| ir::InstructionData::TryCallIndirect {740opcode: ir::Opcode::TryCallIndirect,741exception,742..743} => {744// Construct a new exception table that consists of745// the inlined instruction's exception table match746// sequence, with the inlining site's exception table747// appended. This will ensure that the first-match748// semantics emulates the original behavior of749// matching in the inner frame first.750let sig = func.dfg.exception_tables[exception].signature();751let normal_return = *func.dfg.exception_tables[exception].normal_return();752let exception_data = ExceptionTableData::new(753sig,754normal_return,755func.dfg.exception_tables[exception]756.items()757.chain(func.dfg.exception_tables[call_exception_table].items()),758)759.deep_clone(&mut func.dfg.value_lists);760761func.dfg.exception_tables[exception] = exception_data;762}763764otherwise => unreachable!("unknown non-return call instruction: {otherwise:?}"),765}766}767}768769/// After having created an inlined version of a callee instruction that returns770/// in the caller, we need to fix it up so that it doesn't actually return771/// (since we are already in the caller's frame) and instead just jumps to the772/// control-flow join point.773fn fixup_inst_that_returns(774allocs: &mut InliningAllocs,775func: &mut ir::Function,776callee: &ir::Function,777entity_map: &EntityMap,778call_opcode: ir::Opcode,779inlined_inst: ir::Inst,780callee_inst: ir::Inst,781return_block: ir::Block,782call_stack_map: Option<&[ir::UserStackMapEntry]>,783) {784debug_assert!(func.dfg.insts[inlined_inst].opcode().is_return());785match func.dfg.insts[inlined_inst] {786// return rets...787//788// becomes789//790// jump return_block(rets...)791ir::InstructionData::MultiAry {792opcode: ir::Opcode::Return,793args,794} => {795let rets = SmallBlockArgVec::from_iter(796args.as_slice(&func.dfg.value_lists)797.iter()798.copied()799.map(|v| v.into()),800);801func.dfg.replace(inlined_inst).jump(return_block, &rets);802}803804// return_call f(args...)805//806// becomes807//808// rets... = call f(args...)809// jump return_block(rets...)810ir::InstructionData::Call {811opcode: ir::Opcode::ReturnCall,812args,813func_ref,814} => {815func.dfg.insts[inlined_inst] = ir::InstructionData::Call {816opcode: ir::Opcode::Call,817args,818func_ref,819};820func.dfg.make_inst_results(inlined_inst, ir::types::INVALID);821822append_stack_map_entries(823func,824callee,825&entity_map,826call_stack_map,827inlined_inst,828callee_inst,829);830831let rets = SmallBlockArgVec::from_iter(832func.dfg833.inst_results(inlined_inst)834.iter()835.copied()836.map(|v| v.into()),837);838let mut cursor = FuncCursor::new(func);839cursor.goto_after_inst(inlined_inst);840cursor.ins().jump(return_block, &rets);841842if call_opcode == ir::Opcode::TryCall {843allocs844.calls_needing_exception_table_fixup845.push(inlined_inst);846}847}848849// return_call_indirect val(args...)850//851// becomes852//853// rets... = call_indirect val(args...)854// jump return_block(rets...)855ir::InstructionData::CallIndirect {856opcode: ir::Opcode::ReturnCallIndirect,857args,858sig_ref,859} => {860func.dfg.insts[inlined_inst] = ir::InstructionData::CallIndirect {861opcode: ir::Opcode::CallIndirect,862args,863sig_ref,864};865func.dfg.make_inst_results(inlined_inst, ir::types::INVALID);866867append_stack_map_entries(868func,869callee,870&entity_map,871call_stack_map,872inlined_inst,873callee_inst,874);875876let rets = SmallBlockArgVec::from_iter(877func.dfg878.inst_results(inlined_inst)879.iter()880.copied()881.map(|v| v.into()),882);883let mut cursor = FuncCursor::new(func);884cursor.goto_after_inst(inlined_inst);885cursor.ins().jump(return_block, &rets);886887if call_opcode == ir::Opcode::TryCall {888allocs889.calls_needing_exception_table_fixup890.push(inlined_inst);891}892}893894inst_data => unreachable!(895"should have handled all `is_return() == true` instructions above; \896got {inst_data:?}"897),898}899}900901/// An `InstructionMapper` implementation that remaps a callee instruction's902/// entity references to their new indices in the caller function.903struct InliningInstRemapper<'a> {904allocs: &'a InliningAllocs,905func: &'a mut ir::Function,906callee: &'a ir::Function,907entity_map: &'a EntityMap,908}909910impl<'a> ir::instructions::InstructionMapper for InliningInstRemapper<'a> {911fn map_value(&mut self, value: ir::Value) -> ir::Value {912self.allocs.get_inlined_value(self.callee, value).expect(913"defs come before uses; we should have already inlined all values \914used by an instruction",915)916}917918fn map_value_list(&mut self, value_list: ir::ValueList) -> ir::ValueList {919let mut inlined_list = ir::ValueList::new();920for callee_val in value_list.as_slice(&self.callee.dfg.value_lists) {921let inlined_val = self.map_value(*callee_val);922inlined_list.push(inlined_val, &mut self.func.dfg.value_lists);923}924inlined_list925}926927fn map_global_value(&mut self, global_value: ir::GlobalValue) -> ir::GlobalValue {928self.entity_map.inlined_global_value(global_value)929}930931fn map_jump_table(&mut self, jump_table: ir::JumpTable) -> ir::JumpTable {932let inlined_default =933self.map_block_call(self.callee.dfg.jump_tables[jump_table].default_block());934let inlined_table = self.callee.dfg.jump_tables[jump_table]935.as_slice()936.iter()937.map(|callee_block_call| self.map_block_call(*callee_block_call))938.collect::<SmallBlockCallVec>();939self.func940.dfg941.jump_tables942.push(ir::JumpTableData::new(inlined_default, &inlined_table))943}944945fn map_exception_table(&mut self, exception_table: ir::ExceptionTable) -> ir::ExceptionTable {946let exception_table = &self.callee.dfg.exception_tables[exception_table];947let inlined_sig_ref = self.map_sig_ref(exception_table.signature());948let inlined_normal_return = self.map_block_call(*exception_table.normal_return());949let inlined_table = exception_table950.items()951.map(|item| match item {952ExceptionTableItem::Tag(tag, block_call) => {953ExceptionTableItem::Tag(tag, self.map_block_call(block_call))954}955ExceptionTableItem::Default(block_call) => {956ExceptionTableItem::Default(self.map_block_call(block_call))957}958ExceptionTableItem::Context(value) => {959ExceptionTableItem::Context(self.map_value(value))960}961})962.collect::<SmallVec<[_; 8]>>();963self.func964.dfg965.exception_tables966.push(ir::ExceptionTableData::new(967inlined_sig_ref,968inlined_normal_return,969inlined_table,970))971}972973fn map_block_call(&mut self, block_call: ir::BlockCall) -> ir::BlockCall {974let callee_block = block_call.block(&self.callee.dfg.value_lists);975let inlined_block = self.entity_map.inlined_block(callee_block);976let args = block_call977.args(&self.callee.dfg.value_lists)978.map(|arg| match arg {979ir::BlockArg::Value(value) => self.map_value(value).into(),980ir::BlockArg::TryCallRet(_) | ir::BlockArg::TryCallExn(_) => arg,981})982.collect::<SmallBlockArgVec>();983ir::BlockCall::new(inlined_block, args, &mut self.func.dfg.value_lists)984}985986fn map_block(&mut self, block: ir::Block) -> ir::Block {987self.entity_map.inlined_block(block)988}989990fn map_func_ref(&mut self, func_ref: ir::FuncRef) -> ir::FuncRef {991self.entity_map.inlined_func_ref(func_ref)992}993994fn map_sig_ref(&mut self, sig_ref: ir::SigRef) -> ir::SigRef {995self.entity_map.inlined_sig_ref(sig_ref)996}997998fn map_stack_slot(&mut self, stack_slot: ir::StackSlot) -> ir::StackSlot {999self.entity_map.inlined_stack_slot(stack_slot)1000}10011002fn map_dynamic_stack_slot(1003&mut self,1004dynamic_stack_slot: ir::DynamicStackSlot,1005) -> ir::DynamicStackSlot {1006self.entity_map1007.inlined_dynamic_stack_slot(dynamic_stack_slot)1008}10091010fn map_constant(&mut self, constant: ir::Constant) -> ir::Constant {1011self.allocs1012.constants1013.get(constant)1014.and_then(|o| o.expand())1015.expect("should have inlined all callee constants")1016}10171018fn map_immediate(&mut self, immediate: ir::Immediate) -> ir::Immediate {1019self.entity_map.inlined_immediate(immediate)1020}1021}10221023/// Inline the callee's layout into the caller's layout.1024///1025/// Returns the last inlined block in the layout.1026fn inline_block_layout(1027func: &mut ir::Function,1028call_block: ir::Block,1029callee: &ir::Function,1030entity_map: &EntityMap,1031) -> ir::Block {1032debug_assert!(func.layout.is_block_inserted(call_block));10331034// Iterate over callee blocks in layout order, inserting their associated1035// inlined block into the caller's layout.1036let mut prev_inlined_block = call_block;1037let mut next_callee_block = callee.layout.entry_block();1038while let Some(callee_block) = next_callee_block {1039debug_assert!(func.layout.is_block_inserted(prev_inlined_block));10401041let inlined_block = entity_map.inlined_block(callee_block);1042func.layout1043.insert_block_after(inlined_block, prev_inlined_block);10441045prev_inlined_block = inlined_block;1046next_callee_block = callee.layout.next_block(callee_block);1047}10481049debug_assert!(func.layout.is_block_inserted(prev_inlined_block));1050prev_inlined_block1051}10521053/// Split the call instruction's block just after the call instruction to create1054/// the point where control-flow joins after the inlined callee "returns".1055///1056/// Note that tail calls do not return to the caller and therefore do not have a1057/// control-flow join point.1058fn split_off_return_block(1059func: &mut ir::Function,1060call_inst: ir::Inst,1061opcode: ir::Opcode,1062callee: &ir::Function,1063) -> Option<ir::Block> {1064// When the `call_inst` is not a block terminator, we need to split the1065// block.1066let return_block = func.layout.next_inst(call_inst).map(|next_inst| {1067let return_block = func.dfg.blocks.add();1068func.layout.split_block(return_block, next_inst);10691070// Add block parameters for each return value and alias the call1071// instruction's results to them.1072let old_results =1073SmallValueVec::from_iter(func.dfg.inst_results(call_inst).iter().copied());1074debug_assert_eq!(old_results.len(), callee.signature.returns.len());1075func.dfg.detach_inst_results(call_inst);1076for (abi, old_val) in callee.signature.returns.iter().zip(old_results) {1077debug_assert_eq!(abi.value_type, func.dfg.value_type(old_val));1078let ret_param = func.dfg.append_block_param(return_block, abi.value_type);1079func.dfg.change_to_alias(old_val, ret_param);1080}10811082return_block1083});10841085// When the `call_inst` is a block terminator, then it is either a1086// `return_call` or a `try_call`:1087//1088// * For `return_call`s, we don't have a control-flow join point, because1089// the caller permanently transfers control to the callee.1090//1091// * For `try_call`s, we probably already have a block for the control-flow1092// join point, but it isn't guaranteed: the `try_call` might ignore the1093// call's returns and not forward them to the normal-return block or it1094// might also pass additional arguments. We can only reuse the existing1095// normal-return block when the `try_call` forwards exactly our callee's1096// returns to that block (and therefore that block's parameter types also1097// exactly match the callee's return types). Otherwise, we must create a new1098// return block that forwards to the existing normal-return1099// block. (Elsewhere, at the end of inlining, we will also update any inlined1100// calls to forward any raised exceptions to the caller's exception table,1101// as necessary.)1102//1103// Finally, note that reusing the normal-return's target block is just an1104// optimization to emit a simpler CFG when we can, and is not1105// fundamentally required for correctness. We could always insert a1106// temporary block as our control-flow join point that then forwards to1107// the normal-return's target block. However, at the time of writing,1108// Cranelift doesn't currently do any jump-threading or branch1109// simplification in the mid-end, and removing unnecessary blocks in this1110// way can help some subsequent mid-end optimizations. If, in the future,1111// we gain support for jump-threading optimizations in the mid-end, we can1112// come back and simplify the below code a bit to always generate the1113// temporary block, and then rely on the subsequent optimizations to clean1114// everything up.1115debug_assert_eq!(1116return_block.is_none(),1117opcode == ir::Opcode::ReturnCall || opcode == ir::Opcode::TryCall,1118);1119return_block.or_else(|| match func.dfg.insts[call_inst] {1120ir::InstructionData::TryCall {1121opcode: ir::Opcode::TryCall,1122args: _,1123func_ref: _,1124exception,1125} => {1126let normal_return = func.dfg.exception_tables[exception].normal_return();1127let normal_return_block = normal_return.block(&func.dfg.value_lists);11281129// Check to see if we can reuse the existing normal-return block.1130{1131let normal_return_args = normal_return.args(&func.dfg.value_lists);1132if normal_return_args.len() == callee.signature.returns.len()1133&& normal_return_args.enumerate().all(|(i, arg)| {1134let i = u32::try_from(i).unwrap();1135arg == ir::BlockArg::TryCallRet(i)1136})1137{1138return Some(normal_return_block);1139}1140}11411142// Okay, we cannot reuse the normal-return block. Create a new block1143// that has the expected block parameter types and have it jump to1144// the normal-return block.1145let return_block = func.dfg.blocks.add();1146func.layout.insert_block(return_block, normal_return_block);11471148let return_block_params = callee1149.signature1150.returns1151.iter()1152.map(|abi| func.dfg.append_block_param(return_block, abi.value_type))1153.collect::<SmallValueVec>();11541155let normal_return_args = func.dfg.exception_tables[exception]1156.normal_return()1157.args(&func.dfg.value_lists)1158.collect::<SmallBlockArgVec>();1159let jump_args = normal_return_args1160.into_iter()1161.map(|arg| match arg {1162ir::BlockArg::Value(value) => ir::BlockArg::Value(value),1163ir::BlockArg::TryCallRet(i) => {1164let i = usize::try_from(i).unwrap();1165ir::BlockArg::Value(return_block_params[i])1166}1167ir::BlockArg::TryCallExn(_) => {1168unreachable!("normal-return edges cannot use exceptional results")1169}1170})1171.collect::<SmallBlockArgVec>();11721173let mut cursor = FuncCursor::new(func);1174cursor.goto_first_insertion_point(return_block);1175cursor.ins().jump(normal_return_block, &jump_args);11761177Some(return_block)1178}1179_ => None,1180})1181}11821183/// Replace the caller's call instruction with a jump to the caller's inlined1184/// copy of the callee's entry block.1185///1186/// Also associates the callee's parameters with the caller's arguments in our1187/// value map.1188///1189/// Returns the caller's stack map entries, if any.1190fn replace_call_with_jump(1191allocs: &mut InliningAllocs,1192func: &mut ir::Function,1193call_inst: ir::Inst,1194callee: &ir::Function,1195entity_map: &EntityMap,1196) -> Option<ir::UserStackMapEntryVec> {1197trace!("Replacing `call` with `jump`");1198trace!(1199" --> call instruction: {call_inst:?}: {}",1200func.dfg.display_inst(call_inst)1201);12021203let callee_entry_block = callee1204.layout1205.entry_block()1206.expect("callee function should have an entry block");1207let callee_param_values = callee.dfg.block_params(callee_entry_block);1208let caller_arg_values = SmallValueVec::from_iter(func.dfg.inst_args(call_inst).iter().copied());1209debug_assert_eq!(callee_param_values.len(), caller_arg_values.len());1210debug_assert_eq!(callee_param_values.len(), callee.signature.params.len());1211for (abi, (callee_param_value, caller_arg_value)) in callee1212.signature1213.params1214.iter()1215.zip(callee_param_values.into_iter().zip(caller_arg_values))1216{1217debug_assert_eq!(abi.value_type, callee.dfg.value_type(*callee_param_value));1218debug_assert_eq!(abi.value_type, func.dfg.value_type(caller_arg_value));1219allocs.set_inlined_value(callee, *callee_param_value, caller_arg_value);1220}12211222// Replace the caller's call instruction with a jump to the caller's inlined1223// copy of the callee's entry block.1224//1225// Note that the call block dominates the inlined entry block (and also all1226// other inlined blocks) so we can reference the arguments directly, and do1227// not need to add block parameters to the inlined entry block.1228let inlined_entry_block = entity_map.inlined_block(callee_entry_block);1229func.dfg.replace(call_inst).jump(inlined_entry_block, &[]);1230trace!(1231" --> replaced with jump instruction: {call_inst:?}: {}",1232func.dfg.display_inst(call_inst)1233);12341235let stack_map_entries = func.dfg.take_user_stack_map_entries(call_inst);1236stack_map_entries1237}12381239/// Keeps track of mapping callee entities to their associated inlined caller1240/// entities.1241#[derive(Default)]1242struct EntityMap {1243// Rather than doing an implicit, demand-based, DCE'ing translation of1244// entities, which would require maps from each callee entity to its1245// associated caller entity, we copy all entities into the caller, remember1246// each entity's initial offset, and then mapping from the callee to the1247// inlined caller entity is just adding that initial offset to the callee's1248// index. This should be both faster and simpler than the alternative. Most1249// of these sets are relatively small, and they rarely have too much dead1250// code in practice, so this is a good trade off.1251//1252// Note that there are a few kinds of entities that are excluded from the1253// `EntityMap`, and for which we do actually take the demand-based approach:1254// values and value lists being the notable ones.1255block_offset: Option<u32>,1256global_value_offset: Option<u32>,1257sig_ref_offset: Option<u32>,1258func_ref_offset: Option<u32>,1259stack_slot_offset: Option<u32>,1260dynamic_type_offset: Option<u32>,1261dynamic_stack_slot_offset: Option<u32>,1262immediate_offset: Option<u32>,1263}12641265impl EntityMap {1266fn inlined_block(&self, callee_block: ir::Block) -> ir::Block {1267let offset = self1268.block_offset1269.expect("must create inlined `ir::Block`s before calling `EntityMap::inlined_block`");1270ir::Block::from_u32(offset + callee_block.as_u32())1271}12721273fn iter_inlined_blocks(&self, func: &ir::Function) -> impl Iterator<Item = ir::Block> + use<> {1274let start = self.block_offset.expect(1275"must create inlined `ir::Block`s before calling `EntityMap::iter_inlined_blocks`",1276);12771278let end = func.dfg.blocks.len();1279let end = u32::try_from(end).unwrap();12801281(start..end).map(|i| ir::Block::from_u32(i))1282}12831284fn inlined_global_value(&self, callee_global_value: ir::GlobalValue) -> ir::GlobalValue {1285let offset = self1286.global_value_offset1287.expect("must create inlined `ir::GlobalValue`s before calling `EntityMap::inlined_global_value`");1288ir::GlobalValue::from_u32(offset + callee_global_value.as_u32())1289}12901291fn inlined_sig_ref(&self, callee_sig_ref: ir::SigRef) -> ir::SigRef {1292let offset = self.sig_ref_offset.expect(1293"must create inlined `ir::SigRef`s before calling `EntityMap::inlined_sig_ref`",1294);1295ir::SigRef::from_u32(offset + callee_sig_ref.as_u32())1296}12971298fn inlined_func_ref(&self, callee_func_ref: ir::FuncRef) -> ir::FuncRef {1299let offset = self.func_ref_offset.expect(1300"must create inlined `ir::FuncRef`s before calling `EntityMap::inlined_func_ref`",1301);1302ir::FuncRef::from_u32(offset + callee_func_ref.as_u32())1303}13041305fn inlined_stack_slot(&self, callee_stack_slot: ir::StackSlot) -> ir::StackSlot {1306let offset = self.stack_slot_offset.expect(1307"must create inlined `ir::StackSlot`s before calling `EntityMap::inlined_stack_slot`",1308);1309ir::StackSlot::from_u32(offset + callee_stack_slot.as_u32())1310}13111312fn inlined_dynamic_type(&self, callee_dynamic_type: ir::DynamicType) -> ir::DynamicType {1313let offset = self.dynamic_type_offset.expect(1314"must create inlined `ir::DynamicType`s before calling `EntityMap::inlined_dynamic_type`",1315);1316ir::DynamicType::from_u32(offset + callee_dynamic_type.as_u32())1317}13181319fn inlined_dynamic_stack_slot(1320&self,1321callee_dynamic_stack_slot: ir::DynamicStackSlot,1322) -> ir::DynamicStackSlot {1323let offset = self.dynamic_stack_slot_offset.expect(1324"must create inlined `ir::DynamicStackSlot`s before calling `EntityMap::inlined_dynamic_stack_slot`",1325);1326ir::DynamicStackSlot::from_u32(offset + callee_dynamic_stack_slot.as_u32())1327}13281329fn inlined_immediate(&self, callee_immediate: ir::Immediate) -> ir::Immediate {1330let offset = self.immediate_offset.expect(1331"must create inlined `ir::Immediate`s before calling `EntityMap::inlined_immediate`",1332);1333ir::Immediate::from_u32(offset + callee_immediate.as_u32())1334}1335}13361337/// Translate all of the callee's various entities into the caller, producing an1338/// `EntityMap` that can be used to translate callee entity references into1339/// inlined caller entity references.1340fn create_entities(1341allocs: &mut InliningAllocs,1342func: &mut ir::Function,1343callee: &ir::Function,1344) -> EntityMap {1345let mut entity_map = EntityMap::default();13461347entity_map.block_offset = Some(create_blocks(allocs, func, callee));1348entity_map.global_value_offset = Some(create_global_values(func, callee));1349entity_map.sig_ref_offset = Some(create_sig_refs(func, callee));1350create_user_external_name_refs(allocs, func, callee);1351entity_map.func_ref_offset = Some(create_func_refs(allocs, func, callee, &entity_map));1352entity_map.stack_slot_offset = Some(create_stack_slots(func, callee));1353entity_map.dynamic_type_offset = Some(create_dynamic_types(func, callee, &entity_map));1354entity_map.dynamic_stack_slot_offset =1355Some(create_dynamic_stack_slots(func, callee, &entity_map));1356entity_map.immediate_offset = Some(create_immediates(func, callee));13571358// `ir::ConstantData` is deduplicated, so we cannot use our offset scheme1359// for `ir::Constant`s. Nonetheless, we still insert them into the caller1360// now, at the same time as the rest of our entities.1361create_constants(allocs, func, callee);13621363entity_map1364}13651366/// Create inlined blocks in the caller for every block in the callee.1367fn create_blocks(1368allocs: &mut InliningAllocs,1369func: &mut ir::Function,1370callee: &ir::Function,1371) -> u32 {1372let offset = func.dfg.blocks.len();1373let offset = u32::try_from(offset).unwrap();13741375func.dfg.blocks.reserve(callee.dfg.blocks.len());1376for callee_block in callee.dfg.blocks.iter() {1377let caller_block = func.dfg.blocks.add();1378trace!("Callee {callee_block:?} = inlined {caller_block:?}");13791380if callee.layout.is_cold(callee_block) {1381func.layout.set_cold(caller_block);1382}13831384// Note: the entry block does not need parameters because the only1385// predecessor is the call block and we associate the callee's1386// parameters with the caller's arguments directly.1387if callee.layout.entry_block() != Some(callee_block) {1388for callee_param in callee.dfg.blocks[callee_block].params(&callee.dfg.value_lists) {1389let ty = callee.dfg.value_type(*callee_param);1390let caller_param = func.dfg.append_block_param(caller_block, ty);13911392allocs.set_inlined_value(callee, *callee_param, caller_param);1393}1394}1395}13961397offset1398}13991400/// Copy and translate global values from the callee into the caller.1401fn create_global_values(func: &mut ir::Function, callee: &ir::Function) -> u32 {1402let gv_offset = func.global_values.len();1403let gv_offset = u32::try_from(gv_offset).unwrap();14041405func.global_values.reserve(callee.global_values.len());1406for gv in callee.global_values.values() {1407func.global_values.push(match gv {1408// These kinds of global values reference other global values, so we1409// need to fixup that reference.1410ir::GlobalValueData::Load {1411base,1412offset,1413global_type,1414flags,1415} => ir::GlobalValueData::Load {1416base: ir::GlobalValue::from_u32(base.as_u32() + gv_offset),1417offset: *offset,1418global_type: *global_type,1419flags: *flags,1420},1421ir::GlobalValueData::IAddImm {1422base,1423offset,1424global_type,1425} => ir::GlobalValueData::IAddImm {1426base: ir::GlobalValue::from_u32(base.as_u32() + gv_offset),1427offset: *offset,1428global_type: *global_type,1429},14301431// These kinds of global values do not reference other global1432// values, so we can just clone them.1433ir::GlobalValueData::VMContext1434| ir::GlobalValueData::Symbol { .. }1435| ir::GlobalValueData::DynScaleTargetConst { .. } => gv.clone(),1436});1437}14381439gv_offset1440}14411442/// Copy `ir::SigRef`s from the callee into the caller.1443fn create_sig_refs(func: &mut ir::Function, callee: &ir::Function) -> u32 {1444let offset = func.dfg.signatures.len();1445let offset = u32::try_from(offset).unwrap();14461447func.dfg.signatures.reserve(callee.dfg.signatures.len());1448for sig in callee.dfg.signatures.values() {1449func.dfg.signatures.push(sig.clone());1450}14511452offset1453}14541455fn create_user_external_name_refs(1456allocs: &mut InliningAllocs,1457func: &mut ir::Function,1458callee: &ir::Function,1459) {1460for (callee_named_func_ref, name) in callee.params.user_named_funcs().iter() {1461let caller_named_func_ref = func.declare_imported_user_function(name.clone());1462allocs.user_external_name_refs[callee_named_func_ref] = Some(caller_named_func_ref).into();1463}1464}14651466/// Translate `ir::FuncRef`s from the callee into the caller.1467fn create_func_refs(1468allocs: &InliningAllocs,1469func: &mut ir::Function,1470callee: &ir::Function,1471entity_map: &EntityMap,1472) -> u32 {1473let offset = func.dfg.ext_funcs.len();1474let offset = u32::try_from(offset).unwrap();14751476func.dfg.ext_funcs.reserve(callee.dfg.ext_funcs.len());1477for ir::ExtFuncData {1478name,1479signature,1480colocated,1481patchable,1482} in callee.dfg.ext_funcs.values()1483{1484func.dfg.ext_funcs.push(ir::ExtFuncData {1485name: match name {1486ir::ExternalName::User(name_ref) => {1487ir::ExternalName::User(allocs.user_external_name_refs[*name_ref].expect(1488"should have translated all `ir::UserExternalNameRef`s before translating \1489`ir::FuncRef`s",1490))1491}1492ir::ExternalName::TestCase(_)1493| ir::ExternalName::LibCall(_)1494| ir::ExternalName::KnownSymbol(_) => name.clone(),1495},1496signature: entity_map.inlined_sig_ref(*signature),1497colocated: *colocated,1498patchable: *patchable,1499});1500}15011502offset1503}15041505/// Copy stack slots from the callee into the caller.1506fn create_stack_slots(func: &mut ir::Function, callee: &ir::Function) -> u32 {1507let offset = func.sized_stack_slots.len();1508let offset = u32::try_from(offset).unwrap();15091510func.sized_stack_slots1511.reserve(callee.sized_stack_slots.len());1512for slot in callee.sized_stack_slots.values() {1513func.sized_stack_slots.push(slot.clone());1514}15151516offset1517}15181519/// Copy dynamic types from the callee into the caller.1520fn create_dynamic_types(1521func: &mut ir::Function,1522callee: &ir::Function,1523entity_map: &EntityMap,1524) -> u32 {1525let offset = func.dynamic_stack_slots.len();1526let offset = u32::try_from(offset).unwrap();15271528func.dfg1529.dynamic_types1530.reserve(callee.dfg.dynamic_types.len());1531for ir::DynamicTypeData {1532base_vector_ty,1533dynamic_scale,1534} in callee.dfg.dynamic_types.values()1535{1536func.dfg.dynamic_types.push(ir::DynamicTypeData {1537base_vector_ty: *base_vector_ty,1538dynamic_scale: entity_map.inlined_global_value(*dynamic_scale),1539});1540}15411542offset1543}15441545/// Copy dynamic stack slots from the callee into the caller.1546fn create_dynamic_stack_slots(1547func: &mut ir::Function,1548callee: &ir::Function,1549entity_map: &EntityMap,1550) -> u32 {1551let offset = func.dynamic_stack_slots.len();1552let offset = u32::try_from(offset).unwrap();15531554func.dynamic_stack_slots1555.reserve(callee.dynamic_stack_slots.len());1556for ir::DynamicStackSlotData { kind, dyn_ty } in callee.dynamic_stack_slots.values() {1557func.dynamic_stack_slots.push(ir::DynamicStackSlotData {1558kind: *kind,1559dyn_ty: entity_map.inlined_dynamic_type(*dyn_ty),1560});1561}15621563offset1564}15651566/// Copy immediates from the callee into the caller.1567fn create_immediates(func: &mut ir::Function, callee: &ir::Function) -> u32 {1568let offset = func.dfg.immediates.len();1569let offset = u32::try_from(offset).unwrap();15701571func.dfg.immediates.reserve(callee.dfg.immediates.len());1572for imm in callee.dfg.immediates.values() {1573func.dfg.immediates.push(imm.clone());1574}15751576offset1577}15781579/// Copy constants from the callee into the caller.1580fn create_constants(allocs: &mut InliningAllocs, func: &mut ir::Function, callee: &ir::Function) {1581for (callee_constant, data) in callee.dfg.constants.iter() {1582let inlined_constant = func.dfg.constants.insert(data.clone());1583allocs.constants[*callee_constant] = Some(inlined_constant).into();1584}1585}158615871588