Skip to main content

luaur_compiler/methods/
compiler_compile_expr_call.rs

1use crate::functions::get_builtin::get_builtin;
2use crate::functions::get_builtin_info::get_builtin_info;
3use crate::functions::sref_compiler::sref_ast_name;
4use crate::records::builtin_info::BuiltinInfo;
5use crate::records::compiler::Compiler;
6use crate::records::reg_scope::RegScope;
7use luaur_ast::records::ast_expr_call::AstExprCall;
8use luaur_ast::records::ast_expr_index_name::AstExprIndexName;
9use luaur_ast::records::ast_node::AstNode;
10use luaur_bytecode::methods::bytecode_builder_get_string_hash::bytecode_builder_get_string_hash;
11use luaur_common::enums::luau_builtin_function::LuauBuiltinFunction;
12use luaur_common::enums::luau_bytecode_type::LuauBytecodeType;
13use luaur_common::enums::luau_feedback_type::LuauFeedbackType;
14use luaur_common::enums::luau_opcode::LuauOpcode;
15use luaur_common::macros::luau_assert::LUAU_ASSERT;
16
17impl Compiler {
18    pub fn compile_expr_call(
19        &mut self,
20        expr: *mut AstExprCall,
21        target: u8,
22        target_count: u8,
23        target_top: bool,
24        mult_ret: bool,
25    ) {
26        unsafe {
27            let expr_ref = &*expr;
28            LUAU_ASSERT!(target_count < 255);
29            LUAU_ASSERT!(!target_top || (target as u32 + target_count as u32) == self.reg_top);
30
31            self.set_debug_line_ast_node(expr as *mut _);
32
33            if self.options.optimization_level >= 2 && !expr_ref.self_ {
34                let func = self.get_function_expr(expr_ref.func);
35                let fi = if !func.is_null() {
36                    self.functions.find(&func)
37                } else {
38                    None
39                };
40
41                if fi.map_or(false, |f| f.can_inline)
42                    && self.try_compile_inlined_call(
43                        expr,
44                        func,
45                        target,
46                        target_count,
47                        mult_ret,
48                        luaur_common::FInt::LuauCompileInlineThreshold.get(),
49                        luaur_common::FInt::LuauCompileInlineThresholdMaxBoost.get(),
50                        luaur_common::FInt::LuauCompileInlineDepth.get(),
51                    )
52                {
53                    return;
54                }
55            }
56
57            let mut rs = self.reg_scope_compiler();
58            let reg_count =
59                (1 + (expr_ref.self_ as usize) + expr_ref.args.size).max(target_count as usize);
60            let regs = if target_top {
61                self.alloc_reg(expr as *mut _, (reg_count - target_count as usize) as u32)
62                    - target_count
63            } else {
64                self.alloc_reg(expr as *mut _, reg_count as u32)
65            };
66
67            let mut selfreg = 0u8;
68            let mut bfid = -1;
69
70            if self.options.optimization_level >= 1 && !expr_ref.self_ {
71                // C++ `id && *id != LBF_NONE`: a builtins entry of LBF_NONE (0) means "not a
72                // builtin" and must NOT enable FASTCALL. The inline builtin apply/restore
73                // (and operator[] lookups) can leave a real LBF_NONE entry, so testing
74                // `!= -1` wrongly set bfid = 0 and emitted FASTCALL with builtin 0.
75                if let Some(id) = self.builtins.find(&expr) {
76                    if *id
77                        != luaur_common::enums::luau_builtin_function::LuauBuiltinFunction::LBF_NONE
78                            as i32
79                    {
80                        bfid = *id;
81                    }
82                }
83            }
84
85            if bfid >= 0 && (*self.bytecode).needs_debug_remarks() {
86                let builtin = get_builtin(expr_ref.func, &self.globals, &self.variables);
87                let last_mult = expr_ref.args.size > 0
88                    && self.is_expr_mult_ret(*expr_ref.args.data.add(expr_ref.args.size - 1));
89                if !builtin.empty() {
90                    // C++ `addDebugRemark("builtin %s.%s/%d%s", object, method, args.size, lastMult?"+":"")`
91                    // (the object form) or `"builtin %s/%d%s"` when there is no object.
92                    let argc = expr_ref.args.size as i32;
93                    let suffix = if last_mult { "+" } else { "" };
94                    let method = core::ffi::CStr::from_ptr(builtin.method.value)
95                        .to_str()
96                        .unwrap_or("");
97                    if builtin.object.value.is_null() {
98                        (*self.bytecode).add_debug_remark(format_args!(
99                            "builtin {}/{}{}",
100                            method, argc, suffix
101                        ));
102                    } else {
103                        let object = core::ffi::CStr::from_ptr(builtin.object.value)
104                            .to_str()
105                            .unwrap_or("");
106                        (*self.bytecode).add_debug_remark(format_args!(
107                            "builtin {}.{}/{}{}",
108                            object, method, argc, suffix
109                        ));
110                    }
111                }
112            }
113
114            if bfid == LuauBuiltinFunction::LBF_SELECT_VARARG as i32 {
115                // Optimization: compile select(_, ...) as FASTCALL1; only for single-return
116                // expressions. Otherwise fall back to a regular call (bfid = -1).
117                if !mult_ret && target_count == 1 {
118                    return self.compile_expr_select_vararg(
119                        expr,
120                        target,
121                        target_count,
122                        target_top,
123                        mult_ret,
124                        regs,
125                    );
126                } else {
127                    bfid = -1;
128                }
129            }
130
131            if bfid == LuauBuiltinFunction::LBF_BIT32_EXTRACT as i32
132                && expr_ref.args.size == 3
133                && self.is_constant(*expr_ref.args.data.add(1))
134                && self.is_constant(*expr_ref.args.data.add(2))
135            {
136                let fc = self.get_constant(*expr_ref.args.data.add(1));
137                let wc = self.get_constant(*expr_ref.args.data.add(2));
138                let fi = if fc.r#type == crate::enums::type_constant_folding::Type::Type_Number {
139                    fc.data.value_number as i32
140                } else {
141                    -1
142                };
143                let wi = if wc.r#type == crate::enums::type_constant_folding::Type::Type_Number {
144                    wc.data.value_number as i32
145                } else {
146                    -1
147                };
148                if fi >= 0 && wi > 0 && fi + wi <= 32 {
149                    let fwp = fi | ((wi - 1) << 5);
150                    let cid = (*self.bytecode).add_constant_number(fwp as f64);
151                    if cid >= 0 {
152                        return self.compile_expr_fastcall_n(
153                            expr,
154                            target,
155                            target_count,
156                            target_top,
157                            mult_ret,
158                            regs,
159                            LuauBuiltinFunction::LBF_BIT32_EXTRACTK as i32,
160                            cid,
161                        );
162                    }
163                }
164            }
165
166            let mut max_fastcall_args = 2;
167            if bfid >= 0 && expr_ref.args.size == 3 {
168                for i in 0..expr_ref.args.size {
169                    if self.get_expr_local_reg(*expr_ref.args.data.add(i)) >= 0 {
170                        max_fastcall_args = 3;
171                        break;
172                    }
173                }
174            }
175
176            if bfid >= 0 && expr_ref.args.size >= 1 && expr_ref.args.size <= max_fastcall_args {
177                if !self.is_expr_mult_ret(*expr_ref.args.data.add(expr_ref.args.size - 1)) {
178                    return self.compile_expr_fastcall_n(
179                        expr,
180                        target,
181                        target_count,
182                        target_top,
183                        mult_ret,
184                        regs,
185                        bfid,
186                        -1,
187                    );
188                } else if self.options.optimization_level >= 2 {
189                    let info = get_builtin_info(bfid);
190                    if expr_ref.args.size as i32 == info.params
191                        && (info.flags & BuiltinInfo::Flag_NoneSafe) != 0
192                    {
193                        return self.compile_expr_fastcall_n(
194                            expr,
195                            target,
196                            target_count,
197                            target_top,
198                            mult_ret,
199                            regs,
200                            bfid,
201                            -1,
202                        );
203                    }
204                }
205            }
206
207            if expr_ref.self_ {
208                let fi =
209                    luaur_ast::rtti::ast_node_as::<AstExprIndexName>(expr_ref.func as *mut AstNode);
210                LUAU_ASSERT!(!fi.is_null());
211                let reg = self.get_expr_local_reg((*fi).expr);
212                if reg >= 0 {
213                    selfreg = reg as u8;
214                } else {
215                    selfreg = regs;
216                    self.compile_expr_temp_top((*fi).expr, selfreg);
217                }
218            } else if bfid < 0 {
219                self.compile_expr_temp_top(expr_ref.func, regs);
220            }
221
222            let mut mult_call = false;
223            for i in 0..expr_ref.args.size {
224                let arg = *expr_ref.args.data.add(i);
225                if i + 1 == expr_ref.args.size {
226                    mult_call = self.compile_expr_temp_mult_ret(
227                        arg,
228                        regs + 1 + (expr_ref.self_ as u8) + i as u8,
229                    );
230                } else {
231                    self.compile_expr_temp_top(arg, regs + 1 + (expr_ref.self_ as u8) + i as u8);
232                }
233            }
234
235            self.set_debug_line_end(expr_ref.func as *mut AstNode);
236
237            if expr_ref.self_ {
238                let fi =
239                    luaur_ast::rtti::ast_node_as::<AstExprIndexName>(expr_ref.func as *mut AstNode);
240                self.set_debug_line_location(&(*fi).index_location);
241                let iname = sref_ast_name((*fi).index);
242                let cid = (*self.bytecode).add_constant_string(iname);
243                (*self.bytecode).emit_abc(
244                    LuauOpcode::LOP_NAMECALL,
245                    regs,
246                    selfreg,
247                    bytecode_builder_get_string_hash(iname) as u8,
248                );
249                (*self.bytecode).emit_aux(cid as u32);
250                self.hint_temporary_expr_reg_type(
251                    (*fi).expr,
252                    selfreg as i32,
253                    LuauBytecodeType(4),
254                    2,
255                );
256            } else if bfid >= 0 {
257                let fastcall_label = (*self.bytecode).emit_label();
258                (*self.bytecode).emit_abc(LuauOpcode::LOP_FASTCALL, bfid as u8, 0, 0);
259                self.compile_expr_temp(expr_ref.func, regs);
260                let call_label = (*self.bytecode).emit_label();
261                (*self.bytecode).patch_skip_c(fastcall_label, call_label);
262            }
263
264            // C++ `canInline = currentFunction->functionDepth != 0 && !multCall && !multRet`
265            // (Compiler.cpp:1394): call feedback is only emitted for calls in *nested* functions,
266            // not the depth-0 main chunk. The port had a stray null-check instead of the depth test.
267            if luaur_common::FFlag::LuauEmitCallFeedback.get()
268                && bfid < 0
269                && (*self.current_function).function_depth != 0
270                && !mult_call
271                && !mult_ret
272            {
273                let fb_slot = (*self.bytecode).add_fb_slot(LuauFeedbackType::LFT_CALLTARGET);
274                (*self.bytecode).emit_abc(
275                    LuauOpcode::LOP_CALLFB,
276                    regs,
277                    if mult_call {
278                        0
279                    } else {
280                        (expr_ref.self_ as u8) + expr_ref.args.size as u8 + 1
281                    },
282                    if mult_ret { 0 } else { target_count + 1 },
283                );
284                (*self.bytecode).emit_aux(fb_slot);
285            } else {
286                (*self.bytecode).emit_abc(
287                    LuauOpcode::LOP_CALL,
288                    regs,
289                    if mult_call {
290                        0
291                    } else {
292                        (expr_ref.self_ as u8) + expr_ref.args.size as u8 + 1
293                    },
294                    if mult_ret { 0 } else { target_count + 1 },
295                );
296            }
297
298            if !target_top {
299                for i in 0..target_count {
300                    (*self.bytecode).emit_abc(LuauOpcode::LOP_MOVE, target + i, regs + i, 0);
301                }
302            }
303        }
304    }
305}