Skip to main content

luaur_compiler/methods/
compiler_compile_expr_table.rs

1use crate::enums::type_constant_folding::Type;
2use crate::functions::sref_compiler::sref_ast_name;
3use crate::functions::sref_compiler_alt_c::sref_ast_array_c_char;
4use crate::records::compile_error::CompileError;
5use crate::records::compiler::Compiler;
6use crate::records::constant::Constant;
7use crate::records::reg_scope::RegScope;
8use luaur_ast::records::ast_expr::AstExpr;
9use luaur_ast::records::ast_expr_constant_string::AstExprConstantString;
10use luaur_ast::records::ast_expr_table::AstExprTable;
11use luaur_ast::records::ast_expr_varargs::AstExprVarargs;
12use luaur_ast::rtti;
13use luaur_common::enums::luau_opcode::LuauOpcode;
14use luaur_common::macros::luau_assert::LUAU_ASSERT;
15use luaur_common::records::insertion_ordered_map::InsertionOrderedMap;
16
17impl Compiler {
18    pub fn compile_expr_table(&mut self, expr: *mut AstExprTable, target: u8, target_temp: bool) {
19        unsafe {
20            let expr_ref = &*expr;
21            if expr_ref.items.size == 0 {
22                // C++ `TableShape shape = tableShapes[expr];` — `operator[]`
23                // default-constructs a zero (hash_size=0, array_size=0) shape when
24                // the table had no predicted shape (an empty `{}` with no later
25                // field writes). The model translated this as panic-on-miss.
26                let shape = self.table_shapes.find(&expr).copied().unwrap_or_default();
27                (*self.bytecode)
28                    .add_debug_remark(format_args!("allocation: table hash {}", shape.hash_size));
29                (*self.bytecode).emit_abc(
30                    LuauOpcode::LOP_NEWTABLE,
31                    target,
32                    Self::encode_hash_size(shape.hash_size),
33                    0,
34                );
35                (*self.bytecode).emit_aux(shape.array_size);
36                return;
37            }
38
39            let mut array_size = 0;
40            let mut hash_size = 0;
41            let mut record_size = 0;
42            for item in expr_ref.items.iter() {
43                array_size +=
44                    (item.kind == luaur_ast::records::ast_expr_table::ItemKind::List) as u32;
45                hash_size +=
46                    (item.kind != luaur_ast::records::ast_expr_table::ItemKind::List) as u32;
47                record_size +=
48                    (item.kind == luaur_ast::records::ast_expr_table::ItemKind::Record) as u32;
49            }
50
51            let mut index_size = 0;
52            if array_size == 0 && hash_size > 0 {
53                for item in expr_ref.items.iter() {
54                    LUAU_ASSERT!(!item.key.is_null());
55                    if let Some(ckey) = self.constants.find(&item.key) {
56                        if ckey.r#type == Type::Type_Number {
57                            let val = unsafe { ckey.data.value_number };
58                            if val == (index_size + 1) as f64 {
59                                index_size += 1;
60                            }
61                        }
62                    }
63                }
64                if hash_size == record_size + index_size {
65                    hash_size = record_size;
66                } else {
67                    index_size = 0;
68                }
69            }
70
71            let encoded_hash_size = Self::encode_hash_size(hash_size);
72            let _rs = self.reg_scope_compiler();
73            // Optimization: if target is a temp register, compute the result directly into it.
74            let reg = if target_temp {
75                target
76            } else {
77                self.alloc_reg(expr as *mut _, 1)
78            };
79
80            use luaur_ast::records::ast_expr_table::ItemKind;
81            type BcTableShape = luaur_bytecode::records::table_shape::TableShape;
82
83            // flattening map for record fields (template-table path).
84            let mut last_key_val: InsertionOrderedMap<i32, i32> = InsertionOrderedMap::new();
85
86            // Optimization: when all items are record fields, use template tables (DUPTABLE).
87            if array_size == 0
88                && index_size == 0
89                && hash_size == record_size
90                && record_size >= 1
91                && record_size <= BcTableShape::kMaxLength
92            {
93                let mut shape = BcTableShape::default();
94
95                if luaur_common::FFlag::LuauCompileDuptableConstantPack2.get() {
96                    for i in 0..expr_ref.items.size {
97                        let item = &*expr_ref.items.data.add(i as usize);
98                        LUAU_ASSERT!(item.kind == ItemKind::Record);
99                        let ckey = rtti::ast_node_as::<AstExprConstantString>(item.key as *mut _);
100                        LUAU_ASSERT!(!ckey.is_null());
101                        let key_cid = (*self.bytecode)
102                            .add_constant_string(sref_ast_array_c_char((*ckey).value));
103                        if key_cid < 0 {
104                            CompileError::raise(
105                                &(*ckey).base.base.location,
106                                format_args!(
107                                    "Exceeded constant limit; simplify the code to compile"
108                                ),
109                            );
110                        }
111                        let value_cid = self.get_constant_index(item.value);
112                        if let Some(existing) = last_key_val.get(&key_cid) {
113                            if *existing == -1 {
114                                continue;
115                            }
116                        }
117                        // C++ `lastKeyVal[keyCid] = valueCid` — operator[] OVERWRITES an
118                        // existing entry. InsertionOrderedMap::insert is a no-op when the key
119                        // exists, so a duplicate key whose value later becomes -1 (non-constant,
120                        // e.g. a closure) failed to downgrade and the table wrongly packed its
121                        // constants. get_or_default is the operator[] equivalent.
122                        *last_key_val.get_or_default(key_cid) = value_cid;
123                    }
124
125                    for (key_cid, value_cid) in last_key_val.iter() {
126                        LUAU_ASSERT!(shape.length < BcTableShape::kMaxLength);
127                        let idx = shape.length as usize;
128                        shape.keys[idx] = *key_cid;
129                        shape.constants[idx] = *value_cid;
130                        if *value_cid >= 0 {
131                            shape.hasConstants = true;
132                        }
133                        shape.length += 1;
134                    }
135                } else {
136                    for i in 0..expr_ref.items.size {
137                        let item = &*expr_ref.items.data.add(i as usize);
138                        LUAU_ASSERT!(item.kind == ItemKind::Record);
139                        let ckey = rtti::ast_node_as::<AstExprConstantString>(item.key as *mut _);
140                        LUAU_ASSERT!(!ckey.is_null());
141                        let cid = (*self.bytecode)
142                            .add_constant_string(sref_ast_array_c_char((*ckey).value));
143                        if cid < 0 {
144                            CompileError::raise(
145                                &(*ckey).base.base.location,
146                                format_args!(
147                                    "Exceeded constant limit; simplify the code to compile"
148                                ),
149                            );
150                        }
151                        LUAU_ASSERT!(shape.length < BcTableShape::kMaxLength);
152                        shape.keys[shape.length as usize] = cid;
153                        shape.length += 1;
154                    }
155                }
156
157                let tid = (*self.bytecode).add_constant_table(&shape);
158                if tid < 0 {
159                    CompileError::raise(
160                        &expr_ref.base.base.location,
161                        format_args!("Exceeded constant limit; simplify the code to compile"),
162                    );
163                }
164                (*self.bytecode)
165                    .add_debug_remark(format_args!("allocation: table template {}", hash_size));
166
167                if tid < 32768 {
168                    (*self.bytecode).emit_ad(LuauOpcode::LOP_DUPTABLE, reg, tid as i16);
169                } else {
170                    // must disable duptable constant optimization here, as we default back to new table
171                    if luaur_common::FFlag::LuauCompileDuptableConstantPack2.get() {
172                        shape.hasConstants = false;
173                        last_key_val.clear();
174                    }
175                    (*self.bytecode).emit_abc(LuauOpcode::LOP_NEWTABLE, reg, encoded_hash_size, 0);
176                    (*self.bytecode).emit_aux(0);
177                }
178            } else {
179                // Optimization: when the last element is `...`, let SETLIST allocate storage.
180                let last: *const luaur_ast::records::ast_expr_table::Item =
181                    if expr_ref.items.size > 0 {
182                        &*expr_ref.items.data.add((expr_ref.items.size - 1) as usize)
183                    } else {
184                        core::ptr::null()
185                    };
186                let trailing_varargs = !last.is_null()
187                    && (*last).kind == ItemKind::List
188                    && !rtti::ast_node_as::<AstExprVarargs>((*last).value as *mut _).is_null();
189                LUAU_ASSERT!(!trailing_varargs || array_size > 0);
190
191                let array_allocation = array_size - (trailing_varargs as u32) + index_size;
192
193                if hash_size == 0 {
194                    (*self.bytecode).add_debug_remark(format_args!(
195                        "allocation: table array {}",
196                        array_allocation
197                    ));
198                } else if array_allocation == 0 {
199                    (*self.bytecode)
200                        .add_debug_remark(format_args!("allocation: table hash {}", hash_size));
201                } else {
202                    (*self.bytecode).add_debug_remark(format_args!(
203                        "allocation: table hash {} array {}",
204                        hash_size, array_allocation
205                    ));
206                }
207
208                (*self.bytecode).emit_abc(LuauOpcode::LOP_NEWTABLE, reg, encoded_hash_size, 0);
209                (*self.bytecode).emit_aux(array_allocation);
210            }
211
212            let array_chunk_size = core::cmp::min(16u32, array_size);
213            let array_chunk_reg = self.alloc_reg(expr as *mut _, array_chunk_size);
214            let mut array_chunk_current: u32 = 0;
215            let mut array_index: u32 = 1;
216            let mut mult_ret = false;
217
218            for i in 0..expr_ref.items.size {
219                let item = &*expr_ref.items.data.add(i as usize);
220                let key = item.key;
221                let value = item.value;
222
223                if luaur_common::FFlag::LuauCompileDuptableConstantPack2.get()
224                    && last_key_val.size() > 0
225                    && !key.is_null()
226                    && !rtti::ast_node_as::<AstExprConstantString>(key as *mut _).is_null()
227                {
228                    let ckey = rtti::ast_node_as::<AstExprConstantString>(item.key as *mut _);
229                    LUAU_ASSERT!(!ckey.is_null());
230                    let key_cid =
231                        (*self.bytecode).add_constant_string(sref_ast_array_c_char((*ckey).value));
232                    if let Some(value_cid) = last_key_val.get(&key_cid) {
233                        // do not generate assignments for constants
234                        if *value_cid >= 0 {
235                            continue;
236                        }
237                    }
238                }
239
240                // some key/value pairs don't require compiling the expressions, set up line info here
241                self.set_debug_line_ast_node(value as *mut luaur_ast::records::ast_node::AstNode);
242
243                if self.options.coverage_level >= 2 {
244                    (*self.bytecode).emit_abc(LuauOpcode::LOP_COVERAGE, 0, 0, 0);
245                }
246
247                // flush array chunk on overflow or before hash keys to maintain insertion order
248                if array_chunk_current > 0
249                    && (!key.is_null() || array_chunk_current == array_chunk_size)
250                {
251                    (*self.bytecode).emit_abc(
252                        LuauOpcode::LOP_SETLIST,
253                        reg,
254                        array_chunk_reg,
255                        (array_chunk_current + 1) as u8,
256                    );
257                    (*self.bytecode).emit_aux(array_index);
258                    array_index += array_chunk_current;
259                    array_chunk_current = 0;
260                }
261
262                if !key.is_null() {
263                    // items with a key are set via SETTABLE/SETTABLEKS/SETTABLEN
264                    let mut rsi = self.reg_scope_compiler();
265                    let lv = self.compile_l_value_index(reg, key, &mut rsi);
266                    let rv = self.compile_expr_auto(value, &mut rsi);
267                    self.compile_assign(&lv, rv, core::ptr::null_mut());
268                } else {
269                    // items without a key are set via SETLIST to init large arrays quickly
270                    let temp = (array_chunk_reg as u32 + array_chunk_current) as u8;
271                    if i + 1 == expr_ref.items.size {
272                        mult_ret = self.compile_expr_temp_mult_ret(value, temp);
273                    } else {
274                        self.compile_expr_temp_top(value, temp);
275                    }
276                    array_chunk_current += 1;
277                }
278            }
279
280            // flush last array chunk; needs multret handling if the last expression was multret
281            if array_chunk_current != 0 {
282                (*self.bytecode).emit_abc(
283                    LuauOpcode::LOP_SETLIST,
284                    reg,
285                    array_chunk_reg,
286                    if mult_ret {
287                        0
288                    } else {
289                        (array_chunk_current + 1) as u8
290                    },
291                );
292                (*self.bytecode).emit_aux(array_index);
293            }
294
295            if target != reg {
296                (*self.bytecode).emit_abc(LuauOpcode::LOP_MOVE, target, reg, 0);
297            }
298        }
299    }
300}