luaur_bytecode/methods/
call_inliner_inline_target.rs1use crate::enums::bc_block_edge_kind::BcBlockEdgeKind;
2use crate::enums::bc_block_edge_kind::BcBlockEdgeKind::Fallthrough;
3use crate::enums::bc_block_edge_kind::BcBlockEdgeKind::Fallthrough as FallthroughKind;
4use crate::enums::bc_block_edge_kind::BcBlockEdgeKind::{Branch, Loop};
5use crate::enums::bc_block_flag::BcBlockFlag;
6use crate::enums::bc_op_kind::BcOpKind;
7use crate::enums::bc_op_kind::BcOpKind::Block;
8use crate::records::bc_block::BcBlock;
9use crate::records::bc_block_edge::BcBlockEdge;
10use crate::records::bc_function::BcFunction;
11use crate::records::bc_inst::BcInst;
12use crate::records::bc_op::BcOp;
13use crate::records::bc_ref::BcRef;
14use crate::records::call_inliner::CallInliner;
15use luaur_common::enums::luau_opcode::LuauOpcode;
16use luaur_common::macros::luau_assert::LUAU_ASSERT;
17
18const k_max_inliner_combined_stack_size: u8 = 250;
19
20impl<'a> CallInliner<'a> {
21 pub fn inline_target(&mut self, target_proto_id: u32) -> bool {
22 let mut new_max_stack_size: u32 =
23 self.caller.maxstacksize as u32 + self.target.maxstacksize as u32;
24
25 if self.target.is_vararg {
26 new_max_stack_size = new_max_stack_size.wrapping_add(self.call_params.len() as u32);
27 }
28
29 if new_max_stack_size >= k_max_inliner_combined_stack_size as u32 {
30 return false;
31 }
32
33 if self.call.param_count() < 0 || self.call.return_count() < 0 {
34 return false;
35 }
36
37 if self.target.nups > 0 {
39 return false;
40 }
41
42 self.caller.maxstacksize = new_max_stack_size as u8;
43
44 let (mut prev_block, mut next_block) = self.split_block_on_op(self.call.op());
45
46 let mut target_op: BcOp = self.call.target();
47
48 if prev_block.operator_deref().ops.len() > 0 {
49 let last_inst = self
50 .caller
51 .inst(prev_block.operator_deref().ops.back().copied().unwrap());
52 if last_inst.operator_deref().op == LuauOpcode::LOP_NAMECALL {
53 target_op = self.replace_namecall(last_inst.op, &mut prev_block);
54 }
55 }
56
57 self.append_cmp_proto(&mut prev_block, target_op, target_proto_id);
58
59 self.allocate_graph_entities_for_target();
60
61 self.fill_under_call_arguments();
62
63 self.find_target_call_projections();
64
65 if !self.migrate_blocks(&mut next_block) {
66 return false;
67 }
68
69 let caller_inlined_entry_op = self.map_block_op(self.target.entry_block);
70
71 let call_block = self.call.base.operator_deref().block;
73 let mut call_block_ref = self.caller.block(call_block);
74 let insn_preds: &mut crate::type_aliases::bc_edges::BcEdges =
75 &mut call_block_ref.operator_deref_mut().predecessors;
76
77 let prev_block_op = prev_block.op;
78
79 let retained: Vec<_> = insn_preds
80 .iter()
81 .cloned()
82 .filter(|p| !(p.kind == BcBlockEdgeKind::Fallthrough && p.target == prev_block_op))
83 .collect();
84
85 insn_preds.clear();
86 for edge in retained {
87 insn_preds.push_back(edge);
88 }
89
90 {
91 let edges = &mut prev_block.operator_deref_mut().successors;
92 let mut found = false;
93 for edge in edges.iter_mut() {
94 if edge.kind == BcBlockEdgeKind::Fallthrough {
95 edge.target = caller_inlined_entry_op;
96 found = true;
97 break;
98 }
99 }
100 if !found {
101 edges.push_back(BcBlockEdge {
102 kind: BcBlockEdgeKind::Fallthrough,
103 target: caller_inlined_entry_op,
104 });
105 }
106 }
107 {
108 let edges =
109 &mut self.caller.blocks[caller_inlined_entry_op.index as usize].predecessors;
110 let mut found = false;
111 for edge in edges.iter_mut() {
112 if edge.kind == BcBlockEdgeKind::Fallthrough {
113 edge.target = prev_block.op;
114 found = true;
115 break;
116 }
117 }
118 if !found {
119 edges.push_back(BcBlockEdge {
120 kind: BcBlockEdgeKind::Fallthrough,
121 target: prev_block.op,
122 });
123 }
124 }
125
126 self.migrate_instructions();
127
128 self.replace_call_usages_with_return_phis();
129
130 self.drop_prep_var_args_in_inlined_path();
131
132 LUAU_ASSERT!(self.validate_cfg());
133
134 true
135 }
136}