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                // Widen the add: `fi`/`wi` are folded user constants and `fi + wi`
149                // overflows `int` for huge fields (UB in C++; panic w/ overflow-checks).
150                if fi >= 0 && wi > 0 && fi as i64 + wi as i64 <= 32 {
151                    let fwp = fi | ((wi - 1) << 5);
152                    let cid = (*self.bytecode).add_constant_number(fwp as f64);
153                    if cid >= 0 {
154                        return self.compile_expr_fastcall_n(
155                            expr,
156                            target,
157                            target_count,
158                            target_top,
159                            mult_ret,
160                            regs,
161                            LuauBuiltinFunction::LBF_BIT32_EXTRACTK as i32,
162                            cid,
163                        );
164                    }
165                }
166            }
167
168            let mut max_fastcall_args = 2;
169            if bfid >= 0 && expr_ref.args.size == 3 {
170                for i in 0..expr_ref.args.size {
171                    if self.get_expr_local_reg(*expr_ref.args.data.add(i)) >= 0 {
172                        max_fastcall_args = 3;
173                        break;
174                    }
175                }
176            }
177
178            if bfid >= 0 && expr_ref.args.size >= 1 && expr_ref.args.size <= max_fastcall_args {
179                if !self.is_expr_mult_ret(*expr_ref.args.data.add(expr_ref.args.size - 1)) {
180                    return self.compile_expr_fastcall_n(
181                        expr,
182                        target,
183                        target_count,
184                        target_top,
185                        mult_ret,
186                        regs,
187                        bfid,
188                        -1,
189                    );
190                } else if self.options.optimization_level >= 2 {
191                    let info = get_builtin_info(bfid);
192                    if expr_ref.args.size as i32 == info.params
193                        && (info.flags & BuiltinInfo::Flag_NoneSafe) != 0
194                    {
195                        return self.compile_expr_fastcall_n(
196                            expr,
197                            target,
198                            target_count,
199                            target_top,
200                            mult_ret,
201                            regs,
202                            bfid,
203                            -1,
204                        );
205                    }
206                }
207            }
208
209            if expr_ref.self_ {
210                let fi =
211                    luaur_ast::rtti::ast_node_as::<AstExprIndexName>(expr_ref.func as *mut AstNode);
212                LUAU_ASSERT!(!fi.is_null());
213                let reg = self.get_expr_local_reg((*fi).expr);
214                if reg >= 0 {
215                    selfreg = reg as u8;
216                } else {
217                    selfreg = regs;
218                    self.compile_expr_temp_top((*fi).expr, selfreg);
219                }
220            } else if bfid < 0 {
221                self.compile_expr_temp_top(expr_ref.func, regs);
222            }
223
224            let mut mult_call = false;
225            for i in 0..expr_ref.args.size {
226                let arg = *expr_ref.args.data.add(i);
227                if i + 1 == expr_ref.args.size {
228                    mult_call = self.compile_expr_temp_mult_ret(
229                        arg,
230                        regs + 1 + (expr_ref.self_ as u8) + i as u8,
231                    );
232                } else {
233                    self.compile_expr_temp_top(arg, regs + 1 + (expr_ref.self_ as u8) + i as u8);
234                }
235            }
236
237            self.set_debug_line_end(expr_ref.func as *mut AstNode);
238
239            if expr_ref.self_ {
240                let fi =
241                    luaur_ast::rtti::ast_node_as::<AstExprIndexName>(expr_ref.func as *mut AstNode);
242                self.set_debug_line_location(&(*fi).index_location);
243                let iname = sref_ast_name((*fi).index);
244                let cid = (*self.bytecode).add_constant_string(iname);
245                (*self.bytecode).emit_abc(
246                    LuauOpcode::LOP_NAMECALL,
247                    regs,
248                    selfreg,
249                    bytecode_builder_get_string_hash(iname) as u8,
250                );
251                (*self.bytecode).emit_aux(cid as u32);
252                self.hint_temporary_expr_reg_type(
253                    (*fi).expr,
254                    selfreg as i32,
255                    LuauBytecodeType(4),
256                    2,
257                );
258            } else if bfid >= 0 {
259                let fastcall_label = (*self.bytecode).emit_label();
260                (*self.bytecode).emit_abc(LuauOpcode::LOP_FASTCALL, bfid as u8, 0, 0);
261                self.compile_expr_temp(expr_ref.func, regs);
262                let call_label = (*self.bytecode).emit_label();
263                (*self.bytecode).patch_skip_c(fastcall_label, call_label);
264            }
265
266            // C++ `canInline = currentFunction->functionDepth != 0 && !multCall && !multRet`
267            // (Compiler.cpp:1394): call feedback is only emitted for calls in *nested* functions,
268            // not the depth-0 main chunk. The port had a stray null-check instead of the depth test.
269            if luaur_common::FFlag::LuauEmitCallFeedback.get()
270                && bfid < 0
271                && (*self.current_function).function_depth != 0
272                && !mult_call
273                && !mult_ret
274            {
275                let fb_slot = (*self.bytecode).add_fb_slot(LuauFeedbackType::LFT_CALLTARGET);
276                (*self.bytecode).emit_abc(
277                    LuauOpcode::LOP_CALLFB,
278                    regs,
279                    if mult_call {
280                        0
281                    } else {
282                        (expr_ref.self_ as u8) + expr_ref.args.size as u8 + 1
283                    },
284                    if mult_ret { 0 } else { target_count + 1 },
285                );
286                (*self.bytecode).emit_aux(fb_slot);
287            } else {
288                (*self.bytecode).emit_abc(
289                    LuauOpcode::LOP_CALL,
290                    regs,
291                    if mult_call {
292                        0
293                    } else {
294                        (expr_ref.self_ as u8) + expr_ref.args.size as u8 + 1
295                    },
296                    if mult_ret { 0 } else { target_count + 1 },
297                );
298            }
299
300            if !target_top {
301                for i in 0..target_count {
302                    (*self.bytecode).emit_abc(LuauOpcode::LOP_MOVE, target + i, regs + i, 0);
303                }
304            }
305        }
306    }
307}