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