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