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 // We copied *all* callee blocks into the caller's layout, but only copied 418 // the callee instructions in *reachable* callee blocks into the caller's 419 // associated blocks. Therefore, any *unreachable* blocks are empty in the 420 // caller, which is invalid CLIF because all blocks must end in a 421 // terminator, so do a quick pass over the inlined blocks and remove any 422 // empty blocks from the caller's layout. 423 for block in entity_map.iter_inlined_blocks(func) { 424 if func.layout.first_inst(block).is_none() { 425 func.layout.remove_block(block); 426 } 427 } 428 429 // Final step: fixup the exception tables of any inlined calls when we are 430 // inlining a `try_call` site. 431 // 432 // Subtly, this requires rewriting non-catching `call[_indirect]` 433 // instructions into `try_call[_indirect]` instructions so that exceptions 434 // that unwound through the original callee frame and were caught by the 435 // caller's `try_call` do not unwind past this inlined frame. And turning a 436 // `call` into a `try_call` mutates the CFG, breaking our one-to-one mapping 437 // between callee blocks and inlined blocks, so we delay these fixups to 438 // this final step, when we no longer rely on that mapping. 439 debug_assert!( 440 allocs.calls_needing_exception_table_fixup.is_empty() || call_exception_table.is_some() 441 ); 442 debug_assert_eq!( 443 call_opcode == ir::Opcode::TryCall, 444 call_exception_table.is_some() 445 ); 446 if let Some(call_exception_table) = call_exception_table { 447 fixup_inlined_call_exception_tables(allocs, func, call_exception_table); 448 } 449 } 450 451 /// Append stack map entries from the caller and callee to the given inlined 452 /// instruction. 453 fn append_stack_map_entries( 454 func: &mut ir::Function, 455 callee: &ir::Function, 456 entity_map: &EntityMap, 457 call_stack_map: Option<&[ir::UserStackMapEntry]>, 458 inlined_inst: ir::Inst, 459 callee_inst: ir::Inst, 460 ) { 461 // Add the caller's stack map to this call. These entries 462 // already refer to caller entities and do not need further 463 // translation. 464 func.dfg.append_user_stack_map_entries( 465 inlined_inst, 466 call_stack_map 467 .iter() 468 .flat_map(|entries| entries.iter().cloned()), 469 ); 470 471 // Append the callee's stack map to this call. These entries 472 // refer to callee entities and therefore do require 473 // translation into the caller's index space. 474 func.dfg.append_user_stack_map_entries( 475 inlined_inst, 476 callee 477 .dfg 478 .user_stack_map_entries(callee_inst) 479 .iter() 480 .flat_map(|entries| entries.iter()) 481 .map(|entry| ir::UserStackMapEntry { 482 ty: entry.ty, 483 slot: entity_map.inlined_stack_slot(entry.slot), 484 offset: entry.offset, 485 }), 486 ); 487 } 488 489 /// Create or update the exception tables for any inlined call instructions: 490 /// when inlining at a `try_call` site, we must forward our exceptional edges 491 /// into each inlined call instruction. 492 fn fixup_inlined_call_exception_tables( 493 allocs: &mut InliningAllocs, 494 func: &mut ir::Function, 495 call_exception_table: ir::ExceptionTable, 496 ) { 497 // Split a block at a `call[_indirect]` instruction, detach the 498 // instruction's results, and alias them to the new block's parameters. 499 let split_block_for_new_try_call = |func: &mut ir::Function, inst: ir::Inst| -> ir::Block { 500 debug_assert!(func.dfg.insts[inst].opcode().is_call()); 501 debug_assert!(!func.dfg.insts[inst].opcode().is_terminator()); 502 503 // Split the block. 504 let next_inst = func 505 .layout 506 .next_inst(inst) 507 .expect("inst is not a terminator, should have a successor"); 508 let new_block = func.dfg.blocks.add(); 509 func.layout.split_block(new_block, next_inst); 510 511 // `try_call[_indirect]` instructions do not define values themselves; 512 // the normal-return block has parameters for the results. So remove 513 // this instruction's results, create an associated block parameter for 514 // each of them, and alias them to the new block parameter. 515 let old_results = SmallValueVec::from_iter(func.dfg.inst_results(inst).iter().copied()); 516 func.dfg.detach_inst_results(inst); 517 for old_result in old_results { 518 let ty = func.dfg.value_type(old_result); 519 let new_block_param = func.dfg.append_block_param(new_block, ty); 520 func.dfg.change_to_alias(old_result, new_block_param); 521 } 522 523 new_block 524 }; 525 526 // Clone the caller's exception table, updating it for use in the current 527 // `call[_indirect]` instruction as it becomes a `try_call[_indirect]`. 528 let clone_exception_table_for_this_call = |func: &mut ir::Function, 529 signature: ir::SigRef, 530 new_block: ir::Block| 531 -> ir::ExceptionTable { 532 let mut exception = func.stencil.dfg.exception_tables[call_exception_table] 533 .deep_clone(&mut func.stencil.dfg.value_lists); 534 535 *exception.signature_mut() = signature; 536 537 let returns_len = func.dfg.signatures[signature].returns.len(); 538 let returns_len = u32::try_from(returns_len).unwrap(); 539 540 *exception.normal_return_mut() = ir::BlockCall::new( 541 new_block, 542 (0..returns_len).map(|i| ir::BlockArg::TryCallRet(i)), 543 &mut func.dfg.value_lists, 544 ); 545 546 func.dfg.exception_tables.push(exception) 547 }; 548 549 for inst in allocs.calls_needing_exception_table_fixup.drain(..) { 550 debug_assert!(func.dfg.insts[inst].opcode().is_call()); 551 debug_assert!(!func.dfg.insts[inst].opcode().is_return()); 552 match func.dfg.insts[inst] { 553 // current_block: 554 // preds... 555 // rets... = call f(args...) 556 // succs... 557 // 558 // becomes 559 // 560 // current_block: 561 // preds... 562 // try_call f(args...), new_block(rets...), [call_exception_table...] 563 // new_block(rets...): 564 // succs... 565 ir::InstructionData::Call { 566 opcode: ir::Opcode::Call, 567 args, 568 func_ref, 569 } => { 570 let new_block = split_block_for_new_try_call(func, inst); 571 let signature = func.dfg.ext_funcs[func_ref].signature; 572 let exception = clone_exception_table_for_this_call(func, signature, new_block); 573 func.dfg.insts[inst] = ir::InstructionData::TryCall { 574 opcode: ir::Opcode::TryCall, 575 args, 576 func_ref, 577 exception, 578 }; 579 } 580 581 // current_block: 582 // preds... 583 // rets... = call_indirect sig, val(args...) 584 // succs... 585 // 586 // becomes 587 // 588 // current_block: 589 // preds... 590 // try_call_indirect sig, val(args...), new_block(rets...), [call_exception_table...] 591 // new_block(rets...): 592 // succs... 593 ir::InstructionData::CallIndirect { 594 opcode: ir::Opcode::CallIndirect, 595 args, 596 sig_ref, 597 } => { 598 let new_block = split_block_for_new_try_call(func, inst); 599 let exception = clone_exception_table_for_this_call(func, sig_ref, new_block); 600 func.dfg.insts[inst] = ir::InstructionData::TryCallIndirect { 601 opcode: ir::Opcode::TryCallIndirect, 602 args, 603 exception, 604 }; 605 } 606 607 // For `try_call[_indirect]` instructions, we just need to merge the 608 // exception tables. 609 ir::InstructionData::TryCall { 610 opcode: ir::Opcode::TryCall, 611 exception, 612 .. 613 } 614 | ir::InstructionData::TryCallIndirect { 615 opcode: ir::Opcode::TryCallIndirect, 616 exception, 617 .. 618 } => { 619 // Gather the set of tags that this instruction's exception 620 // table already has entries for. 621 allocs.existing_exception_tags.clear(); 622 allocs.existing_exception_tags.extend( 623 func.dfg.exception_tables[exception] 624 .catches() 625 .map(|(c, _)| c), 626 ); 627 628 // Add only the catch edges from our original `try_call`'s 629 // exception table that are not already handled by this 630 // instruction. 631 for i in 0..func.dfg.exception_tables[call_exception_table].len_catches() { 632 let exception_tables = &mut func.stencil.dfg.exception_tables; 633 let value_lists = &mut func.stencil.dfg.value_lists; 634 635 let (tag, block_call) = 636 exception_tables[call_exception_table].get_catch(i).unwrap(); 637 if allocs.existing_exception_tags.contains(&tag) { 638 continue; 639 } 640 641 let block_call = block_call.deep_clone(value_lists); 642 exception_tables[exception].push_catch(tag, block_call); 643 } 644 } 645 646 otherwise => unreachable!("unknown non-return call instruction: {otherwise:?}"), 647 } 648 } 649 } 650 651 /// After having created an inlined version of a callee instruction that returns 652 /// in the caller, we need to fix it up so that it doesn't actually return 653 /// (since we are already in the caller's frame) and instead just jumps to the 654 /// control-flow join point. 655 fn fixup_inst_that_returns( 656 allocs: &mut InliningAllocs, 657 func: &mut ir::Function, 658 callee: &ir::Function, 659 entity_map: &EntityMap, 660 call_opcode: ir::Opcode, 661 inlined_inst: ir::Inst, 662 callee_inst: ir::Inst, 663 return_block: ir::Block, 664 call_stack_map: Option<&[ir::UserStackMapEntry]>, 665 ) { 666 debug_assert!(func.dfg.insts[inlined_inst].opcode().is_return()); 667 match func.dfg.insts[inlined_inst] { 668 // return rets... 669 // 670 // becomes 671 // 672 // jump return_block(rets...) 673 ir::InstructionData::MultiAry { 674 opcode: ir::Opcode::Return, 675 args, 676 } => { 677 let rets = SmallBlockArgVec::from_iter( 678 args.as_slice(&func.dfg.value_lists) 679 .iter() 680 .copied() 681 .map(|v| v.into()), 682 ); 683 func.dfg.replace(inlined_inst).jump(return_block, &rets); 684 } 685 686 // return_call f(args...) 687 // 688 // becomes 689 // 690 // rets... = call f(args...) 691 // jump return_block(rets...) 692 ir::InstructionData::Call { 693 opcode: ir::Opcode::ReturnCall, 694 args, 695 func_ref, 696 } => { 697 func.dfg.insts[inlined_inst] = ir::InstructionData::Call { 698 opcode: ir::Opcode::Call, 699 args, 700 func_ref, 701 }; 702 func.dfg.make_inst_results(inlined_inst, ir::types::INVALID); 703 704 append_stack_map_entries( 705 func, 706 callee, 707 &entity_map, 708 call_stack_map, 709 inlined_inst, 710 callee_inst, 711 ); 712 713 let rets = SmallBlockArgVec::from_iter( 714 func.dfg 715 .inst_results(inlined_inst) 716 .iter() 717 .copied() 718 .map(|v| v.into()), 719 ); 720 let mut cursor = FuncCursor::new(func); 721 cursor.goto_after_inst(inlined_inst); 722 cursor.ins().jump(return_block, &rets); 723 724 if call_opcode == ir::Opcode::TryCall { 725 allocs 726 .calls_needing_exception_table_fixup 727 .push(inlined_inst); 728 } 729 } 730 731 // return_call_indirect val(args...) 732 // 733 // becomes 734 // 735 // rets... = call_indirect val(args...) 736 // jump return_block(rets...) 737 ir::InstructionData::CallIndirect { 738 opcode: ir::Opcode::ReturnCallIndirect, 739 args, 740 sig_ref, 741 } => { 742 func.dfg.insts[inlined_inst] = ir::InstructionData::CallIndirect { 743 opcode: ir::Opcode::CallIndirect, 744 args, 745 sig_ref, 746 }; 747 func.dfg.make_inst_results(inlined_inst, ir::types::INVALID); 748 749 append_stack_map_entries( 750 func, 751 callee, 752 &entity_map, 753 call_stack_map, 754 inlined_inst, 755 callee_inst, 756 ); 757 758 let rets = SmallBlockArgVec::from_iter( 759 func.dfg 760 .inst_results(inlined_inst) 761 .iter() 762 .copied() 763 .map(|v| v.into()), 764 ); 765 let mut cursor = FuncCursor::new(func); 766 cursor.goto_after_inst(inlined_inst); 767 cursor.ins().jump(return_block, &rets); 768 769 if call_opcode == ir::Opcode::TryCall { 770 allocs 771 .calls_needing_exception_table_fixup 772 .push(inlined_inst); 773 } 774 } 775 776 inst_data => unreachable!( 777 "should have handled all `is_return() == true` instructions above; \ 778 got {inst_data:?}" 779 ), 780 } 781 } 782 783 /// An `InstructionMapper` implementation that remaps a callee instruction's 784 /// entity references to their new indices in the caller function. 785 struct InliningInstRemapper<'a> { 786 allocs: &'a InliningAllocs, 787 func: &'a mut ir::Function, 788 callee: &'a ir::Function, 789 entity_map: &'a EntityMap, 790 } 791 792 impl<'a> ir::instructions::InstructionMapper for InliningInstRemapper<'a> { 793 fn map_value(&mut self, value: ir::Value) -> ir::Value { 794 self.allocs.get_inlined_value(self.callee, value).expect( 795 "defs come before uses; we should have already inlined all values \ 796 used by an instruction", 797 ) 798 } 799 800 fn map_value_list(&mut self, value_list: ir::ValueList) -> ir::ValueList { 801 let mut inlined_list = ir::ValueList::new(); 802 for callee_val in value_list.as_slice(&self.callee.dfg.value_lists) { 803 let inlined_val = self.map_value(*callee_val); 804 inlined_list.push(inlined_val, &mut self.func.dfg.value_lists); 805 } 806 inlined_list 807 } 808 809 fn map_global_value(&mut self, global_value: ir::GlobalValue) -> ir::GlobalValue { 810 self.entity_map.inlined_global_value(global_value) 811 } 812 813 fn map_jump_table(&mut self, jump_table: ir::JumpTable) -> ir::JumpTable { 814 let inlined_default = 815 self.map_block_call(self.callee.dfg.jump_tables[jump_table].default_block()); 816 let inlined_table = self.callee.dfg.jump_tables[jump_table] 817 .as_slice() 818 .iter() 819 .map(|callee_block_call| self.map_block_call(*callee_block_call)) 820 .collect::<SmallBlockCallVec>(); 821 self.func 822 .dfg 823 .jump_tables 824 .push(ir::JumpTableData::new(inlined_default, &inlined_table)) 825 } 826 827 fn map_exception_table(&mut self, exception_table: ir::ExceptionTable) -> ir::ExceptionTable { 828 let exception_table = &self.callee.dfg.exception_tables[exception_table]; 829 let inlined_sig_ref = self.map_sig_ref(exception_table.signature()); 830 let inlined_normal_return = self.map_block_call(*exception_table.normal_return()); 831 let inlined_table = exception_table 832 .catches() 833 .map(|(tag, callee_block_call)| (tag, self.map_block_call(*callee_block_call))) 834 .collect::<SmallVec<[_; 8]>>(); 835 self.func 836 .dfg 837 .exception_tables 838 .push(ir::ExceptionTableData::new( 839 inlined_sig_ref, 840 inlined_normal_return, 841 inlined_table, 842 )) 843 } 844 845 fn map_block_call(&mut self, block_call: ir::BlockCall) -> ir::BlockCall { 846 let callee_block = block_call.block(&self.callee.dfg.value_lists); 847 let inlined_block = self.entity_map.inlined_block(callee_block); 848 let args = block_call 849 .args(&self.callee.dfg.value_lists) 850 .map(|arg| match arg { 851 ir::BlockArg::Value(value) => self.map_value(value).into(), 852 ir::BlockArg::TryCallRet(_) | ir::BlockArg::TryCallExn(_) => arg, 853 }) 854 .collect::<SmallBlockArgVec>(); 855 ir::BlockCall::new(inlined_block, args, &mut self.func.dfg.value_lists) 856 } 857 858 fn map_func_ref(&mut self, func_ref: ir::FuncRef) -> ir::FuncRef { 859 self.entity_map.inlined_func_ref(func_ref) 860 } 861 862 fn map_sig_ref(&mut self, sig_ref: ir::SigRef) -> ir::SigRef { 863 self.entity_map.inlined_sig_ref(sig_ref) 864 } 865 866 fn map_stack_slot(&mut self, stack_slot: ir::StackSlot) -> ir::StackSlot { 867 self.entity_map.inlined_stack_slot(stack_slot) 868 } 869 870 fn map_dynamic_stack_slot( 871 &mut self, 872 dynamic_stack_slot: ir::DynamicStackSlot, 873 ) -> ir::DynamicStackSlot { 874 self.entity_map 875 .inlined_dynamic_stack_slot(dynamic_stack_slot) 876 } 877 878 fn map_constant(&mut self, constant: ir::Constant) -> ir::Constant { 879 self.allocs 880 .constants 881 .get(constant) 882 .and_then(|o| o.expand()) 883 .expect("should have inlined all callee constants") 884 } 885 886 fn map_immediate(&mut self, immediate: ir::Immediate) -> ir::Immediate { 887 self.entity_map.inlined_immediate(immediate) 888 } 889 } 890 891 /// Inline the callee's layout into the caller's layout. 892 fn inline_block_layout( 893 func: &mut ir::Function, 894 call_block: ir::Block, 895 callee: &ir::Function, 896 entity_map: &EntityMap, 897 ) { 898 // Iterate over callee blocks in layout order, inserting their associated 899 // inlined block into the caller's layout. 900 let mut prev_inlined_block = call_block; 901 let mut next_callee_block = callee.layout.entry_block(); 902 while let Some(callee_block) = next_callee_block { 903 let inlined_block = entity_map.inlined_block(callee_block); 904 func.layout 905 .insert_block_after(inlined_block, prev_inlined_block); 906 907 prev_inlined_block = inlined_block; 908 next_callee_block = callee.layout.next_block(callee_block); 909 } 910 } 911 912 /// Split the call instruction's block just after the call instruction to create 913 /// the point where control-flow joins after the inlined callee "returns". 914 /// 915 /// Note that tail calls do not return to the caller and therefore do not have a 916 /// control-flow join point. 917 fn split_off_return_block( 918 func: &mut ir::Function, 919 call_inst: ir::Inst, 920 opcode: ir::Opcode, 921 callee: &ir::Function, 922 ) -> Option<ir::Block> { 923 // When the `call_inst` is not a block terminator, we need to split the 924 // block. 925 let return_block = func.layout.next_inst(call_inst).map(|next_inst| { 926 let return_block = func.dfg.blocks.add(); 927 func.layout.split_block(return_block, next_inst); 928 929 // Add block parameters for each return value and alias the call 930 // instruction's results to them. 931 let old_results = 932 SmallValueVec::from_iter(func.dfg.inst_results(call_inst).iter().copied()); 933 debug_assert_eq!(old_results.len(), callee.signature.returns.len()); 934 func.dfg.detach_inst_results(call_inst); 935 for (abi, old_val) in callee.signature.returns.iter().zip(old_results) { 936 debug_assert_eq!(abi.value_type, func.dfg.value_type(old_val)); 937 let ret_param = func.dfg.append_block_param(return_block, abi.value_type); 938 func.dfg.change_to_alias(old_val, ret_param); 939 } 940 941 return_block 942 }); 943 944 // When the `call_inst` is a block terminator, then it is either a 945 // `return_call` or a `try_call`: 946 // 947 // * For `return_call`s, we don't have a control-flow join point, because 948 // the caller permanently transfers control to the callee. 949 // 950 // * For `try_call`s, we probably already have a block for the control-flow 951 // join point, but it isn't guaranteed: the `try_call` might ignore the 952 // call's returns and not forward them to the normal-return block or it 953 // might also pass additional arguments. We can only reuse the existing 954 // normal-return block when the `try_call` forwards exactly our callee's 955 // returns to that block (and therefore that block's parameter types also 956 // exactly match the callee's return types). Otherwise, we must create a new 957 // return block that forwards to the existing normal-return 958 // block. (Elsewhere, at the end of inlining, we will also update any inlined 959 // calls to forward any raised exceptions to the caller's exception table, 960 // as necessary.) 961 // 962 // Finally, note that reusing the normal-return's target block is just an 963 // optimization to emit a simpler CFG when we can, and is not 964 // fundamentally required for correctness. We could always insert a 965 // temporary block as our control-flow join point that then forwards to 966 // the normal-return's target block. However, at the time of writing, 967 // Cranelift doesn't currently do any jump-threading or branch 968 // simplification in the mid-end, and removing unnecessary blocks in this 969 // way can help some subsequent mid-end optimizations. If, in the future, 970 // we gain support for jump-threading optimizations in the mid-end, we can 971 // come back and simplify the below code a bit to always generate the 972 // temporary block, and then rely on the subsequent optimizations to clean 973 // everything up. 974 debug_assert_eq!( 975 return_block.is_none(), 976 opcode == ir::Opcode::ReturnCall || opcode == ir::Opcode::TryCall, 977 ); 978 return_block.or_else(|| match func.dfg.insts[call_inst] { 979 ir::InstructionData::TryCall { 980 opcode: ir::Opcode::TryCall, 981 args: _, 982 func_ref: _, 983 exception, 984 } => { 985 let normal_return = func.dfg.exception_tables[exception].normal_return(); 986 let normal_return_block = normal_return.block(&func.dfg.value_lists); 987 988 // Check to see if we can reuse the existing normal-return block. 989 { 990 let normal_return_args = normal_return.args(&func.dfg.value_lists); 991 if normal_return_args.len() == callee.signature.returns.len() 992 && normal_return_args.enumerate().all(|(i, arg)| { 993 let i = u32::try_from(i).unwrap(); 994 arg == ir::BlockArg::TryCallRet(i) 995 }) 996 { 997 return Some(normal_return_block); 998 } 999 } 1000 1001 // Okay, we cannot reuse the normal-return block. Create a new block 1002 // that has the expected block parameter types and have it jump to 1003 // the normal-return block. 1004 let return_block = func.dfg.blocks.add(); 1005 func.layout.insert_block(return_block, normal_return_block); 1006 1007 let return_block_params = callee 1008 .signature 1009 .returns 1010 .iter() 1011 .map(|abi| func.dfg.append_block_param(return_block, abi.value_type)) 1012 .collect::<SmallValueVec>(); 1013 1014 let normal_return_args = func.dfg.exception_tables[exception] 1015 .normal_return() 1016 .args(&func.dfg.value_lists) 1017 .collect::<SmallBlockArgVec>(); 1018 let jump_args = normal_return_args 1019 .into_iter() 1020 .map(|arg| match arg { 1021 ir::BlockArg::Value(value) => ir::BlockArg::Value(value), 1022 ir::BlockArg::TryCallRet(i) => { 1023 let i = usize::try_from(i).unwrap(); 1024 ir::BlockArg::Value(return_block_params[i]) 1025 } 1026 ir::BlockArg::TryCallExn(_) => { 1027 unreachable!("normal-return edges cannot use exceptional results") 1028 } 1029 }) 1030 .collect::<SmallBlockArgVec>(); 1031 1032 let mut cursor = FuncCursor::new(func); 1033 cursor.goto_first_insertion_point(return_block); 1034 cursor.ins().jump(normal_return_block, &jump_args); 1035 1036 Some(return_block) 1037 } 1038 _ => None, 1039 }) 1040 } 1041 1042 /// Replace the caller's call instruction with a jump to the caller's inlined 1043 /// copy of the callee's entry block. 1044 /// 1045 /// Also associates the callee's parameters with the caller's arguments in our 1046 /// value map. 1047 /// 1048 /// Returns the caller's stack map entries, if any. 1049 fn replace_call_with_jump( 1050 allocs: &mut InliningAllocs, 1051 func: &mut ir::Function, 1052 call_inst: ir::Inst, 1053 callee: &ir::Function, 1054 entity_map: &EntityMap, 1055 ) -> Option<ir::UserStackMapEntryVec> { 1056 trace!("Replacing `call` with `jump`"); 1057 trace!( 1058 " --> call instruction: {call_inst:?}: {}", 1059 func.dfg.display_inst(call_inst) 1060 ); 1061 1062 let callee_entry_block = callee 1063 .layout 1064 .entry_block() 1065 .expect("callee function should have an entry block"); 1066 let callee_param_values = callee.dfg.block_params(callee_entry_block); 1067 let caller_arg_values = SmallValueVec::from_iter(func.dfg.inst_args(call_inst).iter().copied()); 1068 debug_assert_eq!(callee_param_values.len(), caller_arg_values.len()); 1069 debug_assert_eq!(callee_param_values.len(), callee.signature.params.len()); 1070 for (abi, (callee_param_value, caller_arg_value)) in callee 1071 .signature 1072 .params 1073 .iter() 1074 .zip(callee_param_values.into_iter().zip(caller_arg_values)) 1075 { 1076 debug_assert_eq!(abi.value_type, callee.dfg.value_type(*callee_param_value)); 1077 debug_assert_eq!(abi.value_type, func.dfg.value_type(caller_arg_value)); 1078 allocs.set_inlined_value(callee, *callee_param_value, caller_arg_value); 1079 } 1080 1081 // Replace the caller's call instruction with a jump to the caller's inlined 1082 // copy of the callee's entry block. 1083 // 1084 // Note that the call block dominates the inlined entry block (and also all 1085 // other inlined blocks) so we can reference the arguments directly, and do 1086 // not need to add block parameters to the inlined entry block. 1087 let inlined_entry_block = entity_map.inlined_block(callee_entry_block); 1088 func.dfg.replace(call_inst).jump(inlined_entry_block, &[]); 1089 trace!( 1090 " --> replaced with jump instruction: {call_inst:?}: {}", 1091 func.dfg.display_inst(call_inst) 1092 ); 1093 1094 let stack_map_entries = func.dfg.take_user_stack_map_entries(call_inst); 1095 stack_map_entries 1096 } 1097 1098 /// Keeps track of mapping callee entities to their associated inlined caller 1099 /// entities. 1100 #[derive(Default)] 1101 struct EntityMap { 1102 // Rather than doing an implicit, demand-based, DCE'ing translation of 1103 // entities, which would require maps from each callee entity to its 1104 // associated caller entity, we copy all entities into the caller, remember 1105 // each entity's initial offset, and then mapping from the callee to the 1106 // inlined caller entity is just adding that initial offset to the callee's 1107 // index. This should be both faster and simpler than the alternative. Most 1108 // of these sets are relatively small, and they rarely have too much dead 1109 // code in practice, so this is a good trade off. 1110 // 1111 // Note that there are a few kinds of entities that are excluded from the 1112 // `EntityMap`, and for which we do actually take the demand-based approach: 1113 // values and value lists being the notable ones. 1114 block_offset: Option<u32>, 1115 global_value_offset: Option<u32>, 1116 sig_ref_offset: Option<u32>, 1117 func_ref_offset: Option<u32>, 1118 stack_slot_offset: Option<u32>, 1119 dynamic_type_offset: Option<u32>, 1120 dynamic_stack_slot_offset: Option<u32>, 1121 immediate_offset: Option<u32>, 1122 } 1123 1124 impl EntityMap { 1125 fn inlined_block(&self, callee_block: ir::Block) -> ir::Block { 1126 let offset = self 1127 .block_offset 1128 .expect("must create inlined `ir::Block`s before calling `EntityMap::inlined_block`"); 1129 ir::Block::from_u32(offset + callee_block.as_u32()) 1130 } 1131 1132 fn iter_inlined_blocks(&self, func: &ir::Function) -> impl Iterator<Item = ir::Block> + use<> { 1133 let start = self.block_offset.expect( 1134 "must create inlined `ir::Block`s before calling `EntityMap::iter_inlined_blocks`", 1135 ); 1136 1137 let end = func.dfg.blocks.len(); 1138 let end = u32::try_from(end).unwrap(); 1139 1140 (start..end).map(|i| ir::Block::from_u32(i)) 1141 } 1142 1143 fn inlined_global_value(&self, callee_global_value: ir::GlobalValue) -> ir::GlobalValue { 1144 let offset = self 1145 .global_value_offset 1146 .expect("must create inlined `ir::GlobalValue`s before calling `EntityMap::inlined_global_value`"); 1147 ir::GlobalValue::from_u32(offset + callee_global_value.as_u32()) 1148 } 1149 1150 fn inlined_sig_ref(&self, callee_sig_ref: ir::SigRef) -> ir::SigRef { 1151 let offset = self.sig_ref_offset.expect( 1152 "must create inlined `ir::SigRef`s before calling `EntityMap::inlined_sig_ref`", 1153 ); 1154 ir::SigRef::from_u32(offset + callee_sig_ref.as_u32()) 1155 } 1156 1157 fn inlined_func_ref(&self, callee_func_ref: ir::FuncRef) -> ir::FuncRef { 1158 let offset = self.func_ref_offset.expect( 1159 "must create inlined `ir::FuncRef`s before calling `EntityMap::inlined_func_ref`", 1160 ); 1161 ir::FuncRef::from_u32(offset + callee_func_ref.as_u32()) 1162 } 1163 1164 fn inlined_stack_slot(&self, callee_stack_slot: ir::StackSlot) -> ir::StackSlot { 1165 let offset = self.stack_slot_offset.expect( 1166 "must create inlined `ir::StackSlot`s before calling `EntityMap::inlined_stack_slot`", 1167 ); 1168 ir::StackSlot::from_u32(offset + callee_stack_slot.as_u32()) 1169 } 1170 1171 fn inlined_dynamic_type(&self, callee_dynamic_type: ir::DynamicType) -> ir::DynamicType { 1172 let offset = self.dynamic_type_offset.expect( 1173 "must create inlined `ir::DynamicType`s before calling `EntityMap::inlined_dynamic_type`", 1174 ); 1175 ir::DynamicType::from_u32(offset + callee_dynamic_type.as_u32()) 1176 } 1177 1178 fn inlined_dynamic_stack_slot( 1179 &self, 1180 callee_dynamic_stack_slot: ir::DynamicStackSlot, 1181 ) -> ir::DynamicStackSlot { 1182 let offset = self.dynamic_stack_slot_offset.expect( 1183 "must create inlined `ir::DynamicStackSlot`s before calling `EntityMap::inlined_dynamic_stack_slot`", 1184 ); 1185 ir::DynamicStackSlot::from_u32(offset + callee_dynamic_stack_slot.as_u32()) 1186 } 1187 1188 fn inlined_immediate(&self, callee_immediate: ir::Immediate) -> ir::Immediate { 1189 let offset = self.immediate_offset.expect( 1190 "must create inlined `ir::Immediate`s before calling `EntityMap::inlined_immediate`", 1191 ); 1192 ir::Immediate::from_u32(offset + callee_immediate.as_u32()) 1193 } 1194 } 1195 1196 /// Translate all of the callee's various entities into the caller, producing an 1197 /// `EntityMap` that can be used to translate callee entity references into 1198 /// inlined caller entity references. 1199 fn create_entities( 1200 allocs: &mut InliningAllocs, 1201 func: &mut ir::Function, 1202 callee: &ir::Function, 1203 ) -> EntityMap { 1204 let mut entity_map = EntityMap::default(); 1205 1206 entity_map.block_offset = Some(create_blocks(allocs, func, callee)); 1207 entity_map.global_value_offset = Some(create_global_values(func, callee)); 1208 entity_map.sig_ref_offset = Some(create_sig_refs(func, callee)); 1209 entity_map.func_ref_offset = Some(create_func_refs(func, callee, &entity_map)); 1210 entity_map.stack_slot_offset = Some(create_stack_slots(func, callee)); 1211 entity_map.dynamic_type_offset = Some(create_dynamic_types(func, callee, &entity_map)); 1212 entity_map.dynamic_stack_slot_offset = 1213 Some(create_dynamic_stack_slots(func, callee, &entity_map)); 1214 entity_map.immediate_offset = Some(create_immediates(func, callee)); 1215 1216 // `ir::ConstantData` is deduplicated, so we cannot use our offset scheme 1217 // for `ir::Constant`s. Nonetheless, we still insert them into the caller 1218 // now, at the same time as the rest of our entities. 1219 create_constants(allocs, func, callee); 1220 1221 entity_map 1222 } 1223 1224 /// Create inlined blocks in the caller for every block in the callee. 1225 fn create_blocks( 1226 allocs: &mut InliningAllocs, 1227 func: &mut ir::Function, 1228 callee: &ir::Function, 1229 ) -> u32 { 1230 let offset = func.dfg.blocks.len(); 1231 let offset = u32::try_from(offset).unwrap(); 1232 1233 func.dfg.blocks.reserve(callee.dfg.blocks.len()); 1234 for callee_block in callee.dfg.blocks.iter() { 1235 let caller_block = func.dfg.blocks.add(); 1236 trace!("Callee {callee_block:?} = inlined {caller_block:?}"); 1237 1238 if callee.layout.is_cold(callee_block) { 1239 func.layout.set_cold(caller_block); 1240 } 1241 1242 // Note: the entry block does not need parameters because the only 1243 // predecessor is the call block and we associate the callee's 1244 // parameters with the caller's arguments directly. 1245 if callee.layout.entry_block() != Some(callee_block) { 1246 for callee_param in callee.dfg.blocks[callee_block].params(&callee.dfg.value_lists) { 1247 let ty = callee.dfg.value_type(*callee_param); 1248 let caller_param = func.dfg.append_block_param(caller_block, ty); 1249 1250 allocs.set_inlined_value(callee, *callee_param, caller_param); 1251 } 1252 } 1253 } 1254 1255 offset 1256 } 1257 1258 /// Copy and translate global values from the callee into the caller. 1259 fn create_global_values(func: &mut ir::Function, callee: &ir::Function) -> u32 { 1260 let gv_offset = func.global_values.len(); 1261 let gv_offset = u32::try_from(gv_offset).unwrap(); 1262 1263 func.global_values.reserve(callee.global_values.len()); 1264 for gv in callee.global_values.values() { 1265 func.global_values.push(match gv { 1266 // These kinds of global values reference other global values, so we 1267 // need to fixup that reference. 1268 ir::GlobalValueData::Load { 1269 base, 1270 offset, 1271 global_type, 1272 flags, 1273 } => ir::GlobalValueData::Load { 1274 base: ir::GlobalValue::from_u32(base.as_u32() + gv_offset), 1275 offset: *offset, 1276 global_type: *global_type, 1277 flags: *flags, 1278 }, 1279 ir::GlobalValueData::IAddImm { 1280 base, 1281 offset, 1282 global_type, 1283 } => ir::GlobalValueData::IAddImm { 1284 base: ir::GlobalValue::from_u32(base.as_u32() + gv_offset), 1285 offset: *offset, 1286 global_type: *global_type, 1287 }, 1288 1289 // These kinds of global values do not reference other global 1290 // values, so we can just clone them. 1291 ir::GlobalValueData::VMContext 1292 | ir::GlobalValueData::Symbol { .. } 1293 | ir::GlobalValueData::DynScaleTargetConst { .. } => gv.clone(), 1294 }); 1295 } 1296 1297 gv_offset 1298 } 1299 1300 /// Copy `ir::SigRef`s from the callee into the caller. 1301 fn create_sig_refs(func: &mut ir::Function, callee: &ir::Function) -> u32 { 1302 let offset = func.dfg.signatures.len(); 1303 let offset = u32::try_from(offset).unwrap(); 1304 1305 func.dfg.signatures.reserve(callee.dfg.signatures.len()); 1306 for sig in callee.dfg.signatures.values() { 1307 func.dfg.signatures.push(sig.clone()); 1308 } 1309 1310 offset 1311 } 1312 1313 /// Translate `ir::FuncRef`s from the callee into the caller. 1314 fn create_func_refs(func: &mut ir::Function, callee: &ir::Function, entity_map: &EntityMap) -> u32 { 1315 let offset = func.dfg.ext_funcs.len(); 1316 let offset = u32::try_from(offset).unwrap(); 1317 1318 func.dfg.ext_funcs.reserve(callee.dfg.ext_funcs.len()); 1319 for ir::ExtFuncData { 1320 name, 1321 signature, 1322 colocated, 1323 } in callee.dfg.ext_funcs.values() 1324 { 1325 func.dfg.ext_funcs.push(ir::ExtFuncData { 1326 name: name.clone(), 1327 signature: entity_map.inlined_sig_ref(*signature), 1328 colocated: *colocated, 1329 }); 1330 } 1331 1332 offset 1333 } 1334 1335 /// Copy stack slots from the callee into the caller. 1336 fn create_stack_slots(func: &mut ir::Function, callee: &ir::Function) -> u32 { 1337 let offset = func.sized_stack_slots.len(); 1338 let offset = u32::try_from(offset).unwrap(); 1339 1340 func.sized_stack_slots 1341 .reserve(callee.sized_stack_slots.len()); 1342 for slot in callee.sized_stack_slots.values() { 1343 func.sized_stack_slots.push(slot.clone()); 1344 } 1345 1346 offset 1347 } 1348 1349 /// Copy dynamic types from the callee into the caller. 1350 fn create_dynamic_types( 1351 func: &mut ir::Function, 1352 callee: &ir::Function, 1353 entity_map: &EntityMap, 1354 ) -> u32 { 1355 let offset = func.dynamic_stack_slots.len(); 1356 let offset = u32::try_from(offset).unwrap(); 1357 1358 func.dfg 1359 .dynamic_types 1360 .reserve(callee.dfg.dynamic_types.len()); 1361 for ir::DynamicTypeData { 1362 base_vector_ty, 1363 dynamic_scale, 1364 } in callee.dfg.dynamic_types.values() 1365 { 1366 func.dfg.dynamic_types.push(ir::DynamicTypeData { 1367 base_vector_ty: *base_vector_ty, 1368 dynamic_scale: entity_map.inlined_global_value(*dynamic_scale), 1369 }); 1370 } 1371 1372 offset 1373 } 1374 1375 /// Copy dynamic stack slots from the callee into the caller. 1376 fn create_dynamic_stack_slots( 1377 func: &mut ir::Function, 1378 callee: &ir::Function, 1379 entity_map: &EntityMap, 1380 ) -> u32 { 1381 let offset = func.dynamic_stack_slots.len(); 1382 let offset = u32::try_from(offset).unwrap(); 1383 1384 func.dynamic_stack_slots 1385 .reserve(callee.dynamic_stack_slots.len()); 1386 for ir::DynamicStackSlotData { kind, dyn_ty } in callee.dynamic_stack_slots.values() { 1387 func.dynamic_stack_slots.push(ir::DynamicStackSlotData { 1388 kind: *kind, 1389 dyn_ty: entity_map.inlined_dynamic_type(*dyn_ty), 1390 }); 1391 } 1392 1393 offset 1394 } 1395 1396 /// Copy immediates from the callee into the caller. 1397 fn create_immediates(func: &mut ir::Function, callee: &ir::Function) -> u32 { 1398 let offset = func.dfg.immediates.len(); 1399 let offset = u32::try_from(offset).unwrap(); 1400 1401 func.dfg.immediates.reserve(callee.dfg.immediates.len()); 1402 for imm in callee.dfg.immediates.values() { 1403 func.dfg.immediates.push(imm.clone()); 1404 } 1405 1406 offset 1407 } 1408 1409 /// Copy constants from the callee into the caller. 1410 fn create_constants(allocs: &mut InliningAllocs, func: &mut ir::Function, callee: &ir::Function) { 1411 for (callee_constant, data) in callee.dfg.constants.iter() { 1412 let inlined_constant = func.dfg.constants.insert(data.clone()); 1413 allocs.constants[*callee_constant] = Some(inlined_constant).into(); 1414 } 1415 } 1416