Skip to main content

luau_bytecode/builder/
support.rs

1use super::BytecodeBuilder;
2use super::BytecodeStringRef;
3use crate::dump::{append_closure_name, append_string_constant};
4use crate::model::{
5    BytecodeClass, BytecodeFeedbackSlot, BytecodeImportId, BytecodeTypedLocal, BytecodeVector,
6    BytecodeVectorDouble, ClosureIndex, ConstantIndex, Instruction, InstructionWord, Register,
7    TableShape,
8};
9use crate::wire::{BytecodeFunctionWire, ClosureNameLookup};
10use bitfields::bitfield;
11use luau_common::{BStr, BString, ByteSlice, DenseHashHasher, DenseHashMap, flags};
12use std::hash::{Hash, Hasher};
13use std::io::Write;
14
15#[cfg(debug_assertions)]
16use crate::model::InstructionAux;
17#[cfg(debug_assertions)]
18use crate::opcodes::{CaptureType, Opcode};
19
20pub trait BytecodeEncoder: std::fmt::Debug {
21    fn encode(&self, data: &mut [InstructionWord]);
22}
23
24#[derive(Debug, Clone, Copy)]
25pub struct BytecodeStringHasher;
26
27pub(super) fn builder_string_hash(bytes: &[u8]) -> u64 {
28    let mut hash = 2166136261u32;
29
30    for byte in bytes {
31        hash ^= u32::from(*byte);
32        hash = hash.wrapping_mul(16777619);
33    }
34
35    u64::from(hash)
36}
37
38impl DenseHashHasher<BytecodeStringRef<'_>> for BytecodeStringHasher {
39    fn hash(key: &BytecodeStringRef<'_>) -> u64 {
40        builder_string_hash(key.as_bytes())
41    }
42}
43
44pub(super) fn constant_cache_hash(key: &ConstantCacheKey) -> u64 {
45    match key.kind {
46        ConstantCacheKeyKind::Vector => {
47            let mut values = [
48                key.value as u32,
49                (key.value >> 32) as u32,
50                key.extra as u32,
51                (key.extra >> 32) as u32,
52            ];
53
54            // BytecodeBuilder.cpp: scramble integer-aligned coordinates before spatial hashing.
55            for value in &mut values {
56                *value ^= *value >> 17;
57            }
58
59            let hash = values[0].wrapping_mul(73856093)
60                ^ values[1].wrapping_mul(19349663)
61                ^ values[2].wrapping_mul(83492791)
62                ^ values[3].wrapping_mul(39916801);
63
64            u64::from(hash)
65        }
66        ConstantCacheKeyKind::VectorDouble => {
67            let mut values = [key.value, key.extra, key.extra2, key.extra3];
68            for value in &mut values {
69                *value ^= *value >> 32;
70            }
71
72            let hash = (values[0] as u32).wrapping_mul(73856093)
73                ^ (values[1] as u32).wrapping_mul(19349663)
74                ^ (values[2] as u32).wrapping_mul(83492791)
75                ^ (values[3] as u32).wrapping_mul(39916801);
76
77            u64::from(hash)
78        }
79        _ => {
80            let ty = match key.kind {
81                ConstantCacheKeyKind::Nil => 0u32,
82                ConstantCacheKeyKind::Boolean => 1u32,
83                ConstantCacheKeyKind::Number => 2u32,
84                ConstantCacheKeyKind::Integer64 => 3u32,
85                ConstantCacheKeyKind::String => 5u32,
86                ConstantCacheKeyKind::Import => 6u32,
87                ConstantCacheKeyKind::Closure => 8u32,
88                ConstantCacheKeyKind::Vector => unreachable!(),
89                ConstantCacheKeyKind::VectorDouble => unreachable!(),
90            };
91            let value = key.value;
92
93            // BytecodeBuilder.cpp: 32-bit half of the MurmurHash64B finalizer.
94            let m = 0x5bd1e995u32;
95
96            let mut h1 = value as u32;
97            let mut h2 = ((value >> 32) as u32) ^ ty.wrapping_mul(m);
98
99            h1 ^= h2 >> 18;
100            h1 = h1.wrapping_mul(m);
101            h2 ^= h1 >> 22;
102            h2 = h2.wrapping_mul(m);
103            h1 ^= h2 >> 17;
104            h1 = h1.wrapping_mul(m);
105            h2 ^= h1 >> 19;
106            h2 = h2.wrapping_mul(m);
107
108            u64::from(h2)
109        }
110    }
111}
112
113#[derive(Debug, Clone, Copy)]
114pub(super) struct ConstantCacheHasher;
115
116impl DenseHashHasher<ConstantCacheKey> for ConstantCacheHasher {
117    fn hash(key: &ConstantCacheKey) -> u64 {
118        constant_cache_hash(key)
119    }
120}
121
122pub(super) fn table_shape_hash(shape: &TableShape) -> u64 {
123    let mut hash = 2166136261u32;
124
125    for entry in shape.entries() {
126        hash ^= entry.key as u32;
127        hash = hash.wrapping_mul(16777619);
128
129        if let Some(value) = entry.value {
130            hash ^= value as u32;
131            hash = hash.wrapping_mul(16777619);
132        }
133    }
134
135    u64::from(hash)
136}
137
138#[derive(Debug, Clone, Copy)]
139pub(super) struct TableShapeCacheHasher;
140
141impl DenseHashHasher<TableShapeCacheKey> for TableShapeCacheHasher {
142    fn hash(key: &TableShapeCacheKey) -> u64 {
143        table_shape_hash(&key.shape)
144    }
145}
146
147#[derive(Debug, Clone, Copy)]
148pub(super) struct U32IdentityHasher;
149
150impl DenseHashHasher<u32> for U32IdentityHasher {
151    fn hash(key: &u32) -> u64 {
152        u64::from(*key)
153    }
154}
155
156#[derive(Debug, Default)]
157pub(super) struct BytecodeBuilderFunction {
158    pub(super) data: Vec<u8>,
159    pub(super) max_stack_size: u8,
160    pub(super) num_params: u8,
161    pub(super) upvalue_count: u8,
162    pub(super) is_vararg: bool,
163    pub(super) flags: u8,
164    pub(super) cost: u64,
165    pub(super) type_info: Vec<u8>,
166    pub(super) debug_name: Option<u32>,
167    pub(super) line_defined: i32,
168    pub(super) dump: BString,
169    pub(super) dump_name: BString,
170    pub(super) dump_instruction_offsets: Vec<i32>,
171}
172
173impl BytecodeBuilderFunction {
174    pub(super) fn new(num_params: u8, is_vararg: bool) -> Self {
175        Self {
176            num_params,
177            is_vararg,
178            max_stack_size: num_params,
179            ..Self::default()
180        }
181    }
182}
183
184pub(super) struct BytecodeBuilderScratch<'src> {
185    pub(super) upvalue_types: Vec<u8>,
186    pub(super) local_types: Vec<BytecodeTypedLocal>,
187    pub(super) code: Vec<Instruction>,
188    pub(super) constants: Vec<BytecodeBuilderConstant>,
189    pub(super) constant_index: DenseHashMap<ConstantCacheKey, ConstantIndex, ConstantCacheHasher>,
190    pub(super) table_shapes: Vec<TableShape>,
191    pub(super) table_shape_index:
192        DenseHashMap<TableShapeCacheKey, ConstantIndex, TableShapeCacheHasher>,
193    pub(super) child_functions: Vec<u32>,
194    pub(super) child_function_map: DenseHashMap<u32, i16, U32IdentityHasher>,
195    pub(super) jumps: Vec<Jump>,
196    pub(super) has_long_jumps: bool,
197    pub(super) lines: Vec<i32>,
198    pub(super) local_vars: Vec<BytecodeBuilderLocal>,
199    pub(super) upvalues: Vec<u32>,
200    pub(super) feedback_slots: Vec<BytecodeFeedbackSlot>,
201    pub(super) debug_remarks: Vec<StoredDebugRemark>,
202    _src: std::marker::PhantomData<&'src ()>,
203}
204
205impl<'src> Default for BytecodeBuilderScratch<'src> {
206    fn default() -> Self {
207        Self {
208            upvalue_types: Vec::new(),
209            local_types: Vec::new(),
210            code: Vec::new(),
211            constants: Vec::new(),
212            constant_index: DenseHashMap::new(ConstantCacheKey::empty()),
213            table_shapes: Vec::new(),
214            table_shape_index: DenseHashMap::new(TableShapeCacheKey::empty()),
215            child_functions: Vec::new(),
216            child_function_map: DenseHashMap::new(u32::MAX),
217            jumps: Vec::new(),
218            has_long_jumps: false,
219            lines: Vec::new(),
220            local_vars: Vec::new(),
221            upvalues: Vec::new(),
222            feedback_slots: Vec::new(),
223            debug_remarks: Vec::new(),
224            _src: std::marker::PhantomData,
225        }
226    }
227}
228
229impl<'src> BytecodeBuilderScratch<'src> {
230    pub(super) fn borrowed_wire<'a>(
231        &'a self,
232        strings: &'a [BytecodeStringRef<'a>],
233        class_shapes: &'a [BytecodeClass],
234    ) -> BorrowedBytecodeBuilderFunction<'a, 'a> {
235        BorrowedBytecodeBuilderFunction {
236            code: &self.code,
237            constants: &self.constants,
238            table_shapes: &self.table_shapes,
239            class_shapes,
240            lines: &self.lines,
241            strings,
242            _src: std::marker::PhantomData,
243        }
244    }
245
246    pub(super) fn clear(&mut self) {
247        self.upvalue_types.clear();
248        self.local_types.clear();
249        self.code.clear();
250        self.constants.clear();
251        self.table_shapes.clear();
252        self.constant_index.clear_with_threshold(32);
253        self.table_shape_index.clear_with_threshold(32);
254        self.child_functions.clear();
255        self.child_function_map.clear_with_threshold(32);
256        self.jumps.clear();
257        self.has_long_jumps = false;
258        self.lines.clear();
259        self.local_vars.clear();
260        self.upvalues.clear();
261        self.feedback_slots.clear();
262        self.debug_remarks.clear();
263    }
264
265    #[cfg(debug_assertions)]
266    pub(super) fn validate(
267        &self,
268        function: &BytecodeBuilderFunction,
269        functions: &[BytecodeBuilderFunction],
270    ) {
271        self.validate_instructions(function, functions);
272        self.validate_variadic();
273    }
274
275    #[cfg(debug_assertions)]
276    fn validate_instructions(
277        &self,
278        function: &BytecodeBuilderFunction,
279        functions: &[BytecodeBuilderFunction],
280    ) {
281        let mut instruction_valid = vec![false; self.code.len()];
282        let mut pc = 0usize;
283        while pc < self.code.len() {
284            let opcode = unsafe { self.code[pc].opcode_unchecked() };
285            instruction_valid[pc] = true;
286            pc += opcode.length();
287            debug_assert!(pc <= self.code.len());
288        }
289
290        let mut open_captures = Vec::new();
291        let mut pc = 0usize;
292        while pc < self.code.len() {
293            let instruction = self.code[pc];
294            let opcode = unsafe { instruction.opcode_unchecked() };
295            let aux = || {
296                self.code
297                    .get(pc + 1)
298                    .copied()
299                    .map(Instruction::word)
300                    .map(InstructionAux::new)
301                    .expect("instruction requires aux word")
302            };
303            let aux_word = || aux().word();
304            let reg = |register: u8| debug_assert!(register < function.max_stack_size);
305            let reg_range = |register: u8, count: i32| {
306                let end = i32::from(register) + count.max(0);
307                debug_assert!(end <= i32::from(function.max_stack_size));
308            };
309            let upvalue = |index: u8| debug_assert!(index < function.upvalue_count);
310            let any_constant = |index: u32| {
311                debug_assert!((index as usize) < self.constants.len());
312            };
313            let constant = |index: u32, kind: ConstantKind| {
314                any_constant(index);
315                debug_assert_eq!(self.constants[index as usize].kind(), kind);
316            };
317            let jump = |offset: i32| {
318                let target = pc as i32 + 1 + offset;
319                debug_assert!(target >= 0);
320                let target = target as usize;
321                debug_assert!(target < self.code.len());
322                debug_assert!(instruction_valid[target]);
323            };
324
325            match opcode {
326                Opcode::Nop | Opcode::Break | Opcode::Coverage | Opcode::NativeCall => {}
327                Opcode::LoadNil | Opcode::LoadN | Opcode::NewTable => reg(instruction.a()),
328                Opcode::LoadB => {
329                    reg(instruction.a());
330                    debug_assert!(instruction.b() == 0 || instruction.b() == 1);
331                    jump(i32::from(instruction.c()));
332                }
333                Opcode::LoadK => {
334                    reg(instruction.a());
335                    any_constant(instruction.d() as u16 as u32);
336                }
337                Opcode::Move => {
338                    reg(instruction.a());
339                    reg(instruction.b());
340                }
341                Opcode::GetGlobal | Opcode::SetGlobal => {
342                    reg(instruction.a());
343                    constant(aux_word(), ConstantKind::String);
344                }
345                Opcode::GetUpval | Opcode::SetUpval => {
346                    reg(instruction.a());
347                    upvalue(instruction.b());
348                }
349                Opcode::CloseUpvals => {
350                    reg(instruction.a());
351                    while open_captures
352                        .last()
353                        .is_some_and(|capture| *capture >= instruction.a())
354                    {
355                        open_captures.pop();
356                    }
357                }
358                Opcode::GetImport => {
359                    reg(instruction.a());
360                    constant(instruction.d() as u16 as u32, ConstantKind::Import);
361                    let import_id = aux_word();
362                    debug_assert!((import_id >> 30) != 0);
363                    for index in 0..(import_id >> 30) {
364                        constant(
365                            (import_id >> (20 - 10 * index)) & 1023,
366                            ConstantKind::String,
367                        );
368                    }
369                }
370                Opcode::GetTable | Opcode::SetTable => {
371                    reg(instruction.a());
372                    reg(instruction.b());
373                    reg(instruction.c());
374                }
375                Opcode::GetTableKs | Opcode::SetTableKs => {
376                    reg(instruction.a());
377                    reg(instruction.b());
378                    constant(aux_word(), ConstantKind::String);
379                }
380                Opcode::GetTableN | Opcode::SetTableN => {
381                    reg(instruction.a());
382                    reg(instruction.b());
383                }
384                Opcode::NewClosure => {
385                    reg(instruction.a());
386                    let child = instruction.d() as u16 as usize;
387                    debug_assert!(child < self.child_functions.len());
388                    let child_id = self.child_functions[child] as usize;
389                    debug_assert!(child_id < functions.len());
390                    let upvalues = functions[child_id].upvalue_count;
391                    for capture in 0..upvalues as usize {
392                        debug_assert!(pc + 1 + capture < self.code.len());
393                        debug_assert_eq!(
394                            unsafe { self.code[pc + 1 + capture].opcode_unchecked() },
395                            Opcode::Capture
396                        );
397                    }
398                }
399                Opcode::NameCall => {
400                    reg(instruction.a());
401                    reg(instruction.b());
402                    constant(aux_word(), ConstantKind::String);
403                    debug_assert!(self.code.get(pc + 2).is_some_and(|next| matches!(
404                        unsafe { next.opcode_unchecked() },
405                        Opcode::Call | Opcode::CallFb
406                    )));
407                }
408                Opcode::Call | Opcode::CallFb => {
409                    let params = i32::from(instruction.b()) - 1;
410                    let results = i32::from(instruction.c()) - 1;
411                    reg(instruction.a());
412                    reg_range(instruction.a().saturating_add(1), params);
413                    reg_range(instruction.a(), results);
414                }
415                Opcode::Return => {
416                    reg_range(instruction.a(), i32::from(instruction.b()) - 1);
417                }
418                Opcode::Jump | Opcode::JumpBack => jump(i32::from(instruction.d())),
419                Opcode::CmpProto => {
420                    reg(instruction.a());
421                    jump(i32::from(instruction.d()));
422                }
423                Opcode::JumpIf | Opcode::JumpIfNot => {
424                    reg(instruction.a());
425                    jump(i32::from(instruction.d()));
426                }
427                Opcode::JumpIfEq
428                | Opcode::JumpIfLe
429                | Opcode::JumpIfLt
430                | Opcode::JumpIfNotEq
431                | Opcode::JumpIfNotLe
432                | Opcode::JumpIfNotLt => {
433                    reg(instruction.a());
434                    reg(aux_word() as u8);
435                    jump(i32::from(instruction.d()));
436                }
437                Opcode::JumpXEqKNil | Opcode::JumpXEqKB => {
438                    reg(instruction.a());
439                    jump(i32::from(instruction.d()));
440                }
441                Opcode::JumpXEqKN => {
442                    reg(instruction.a());
443                    constant(aux_word() & 0x00ff_ffff, ConstantKind::Number);
444                    jump(i32::from(instruction.d()));
445                }
446                Opcode::JumpXEqKS => {
447                    reg(instruction.a());
448                    constant(aux_word() & 0x00ff_ffff, ConstantKind::String);
449                    jump(i32::from(instruction.d()));
450                }
451                Opcode::Add
452                | Opcode::Sub
453                | Opcode::Mul
454                | Opcode::Div
455                | Opcode::IDiv
456                | Opcode::Mod
457                | Opcode::Pow
458                | Opcode::And
459                | Opcode::Or
460                | Opcode::Concat => {
461                    reg(instruction.a());
462                    reg(instruction.b());
463                    reg(instruction.c());
464                    if opcode == Opcode::Concat {
465                        debug_assert!(instruction.b() <= instruction.c());
466                    }
467                }
468                Opcode::AddK
469                | Opcode::SubK
470                | Opcode::MulK
471                | Opcode::DivK
472                | Opcode::IDivK
473                | Opcode::ModK
474                | Opcode::PowK => {
475                    reg(instruction.a());
476                    reg(instruction.b());
477                    constant(u32::from(instruction.c()), ConstantKind::Number);
478                }
479                Opcode::SubRK | Opcode::DivRK => {
480                    reg(instruction.a());
481                    constant(u32::from(instruction.b()), ConstantKind::Number);
482                    reg(instruction.c());
483                }
484                Opcode::AndK | Opcode::OrK => {
485                    reg(instruction.a());
486                    reg(instruction.b());
487                    any_constant(u32::from(instruction.c()));
488                }
489                Opcode::Not | Opcode::Minus | Opcode::Length => {
490                    reg(instruction.a());
491                    reg(instruction.b());
492                }
493                Opcode::DupTable => {
494                    reg(instruction.a());
495                    constant(instruction.d() as u16 as u32, ConstantKind::Table);
496                }
497                Opcode::SetList => {
498                    reg(instruction.a());
499                    reg_range(instruction.b(), i32::from(instruction.c()) - 1);
500                }
501                Opcode::ForNPrep | Opcode::ForNLoop => {
502                    reg(instruction.a().saturating_add(2));
503                    jump(i32::from(instruction.d()));
504                }
505                Opcode::ForGPrep => {
506                    reg(instruction.a().saturating_add(3));
507                    jump(i32::from(instruction.d()));
508                }
509                Opcode::ForGLoop => {
510                    reg(instruction.a().saturating_add(2 + aux_word() as u8));
511                    jump(i32::from(instruction.d()));
512                    debug_assert!(aux_word() as u8 >= 1);
513                }
514                Opcode::ForGPrepInext | Opcode::ForGPrepNext => {
515                    reg(instruction.a().saturating_add(4));
516                    jump(i32::from(instruction.d()));
517                }
518                Opcode::GetVarargs => {
519                    reg_range(instruction.a(), i32::from(instruction.b()) - 1);
520                }
521                Opcode::DupClosure => {
522                    reg(instruction.a());
523                    constant(instruction.d() as u16 as u32, ConstantKind::Closure);
524                    let child = match &self.constants[instruction.d() as u16 as usize] {
525                        BytecodeBuilderConstant::Closure(index) => index.as_usize(),
526                        _ => unreachable!(),
527                    };
528                    debug_assert!(child < functions.len());
529                    let upvalues = functions[child].upvalue_count;
530                    for capture in 0..upvalues as usize {
531                        debug_assert!(pc + 1 + capture < self.code.len());
532                        let capture_instruction = self.code[pc + 1 + capture];
533                        debug_assert_eq!(
534                            unsafe { capture_instruction.opcode_unchecked() },
535                            Opcode::Capture
536                        );
537                        debug_assert!(matches!(
538                            CaptureType::try_from(capture_instruction.a()),
539                            Ok(CaptureType::Val | CaptureType::Upval)
540                        ));
541                    }
542                }
543                Opcode::PrepVarargs => {
544                    debug_assert_eq!(instruction.a(), function.num_params);
545                    debug_assert!(function.is_vararg);
546                }
547                Opcode::LoadKx => {
548                    reg(instruction.a());
549                    any_constant(aux_word());
550                }
551                Opcode::JumpX => jump(instruction.e()),
552                Opcode::FastCall => {
553                    jump(i32::from(instruction.c()));
554                    debug_assert_eq!(
555                        unsafe {
556                            self.code[pc + 1 + usize::from(instruction.c())].opcode_unchecked()
557                        },
558                        Opcode::Call
559                    );
560                }
561                Opcode::FastCall1 => {
562                    reg(instruction.b());
563                    jump(i32::from(instruction.c()));
564                    debug_assert_eq!(
565                        unsafe {
566                            self.code[pc + 1 + usize::from(instruction.c())].opcode_unchecked()
567                        },
568                        Opcode::Call
569                    );
570                }
571                Opcode::FastCall2 => {
572                    reg(instruction.b());
573                    jump(i32::from(instruction.c()));
574                    debug_assert_eq!(
575                        unsafe {
576                            self.code[pc + 1 + usize::from(instruction.c())].opcode_unchecked()
577                        },
578                        Opcode::Call
579                    );
580                    reg(aux_word() as u8);
581                }
582                Opcode::FastCall2K => {
583                    reg(instruction.b());
584                    jump(i32::from(instruction.c()));
585                    debug_assert_eq!(
586                        unsafe {
587                            self.code[pc + 1 + usize::from(instruction.c())].opcode_unchecked()
588                        },
589                        Opcode::Call
590                    );
591                    any_constant(aux_word());
592                }
593                Opcode::FastCall3 => {
594                    reg(instruction.b());
595                    jump(i32::from(instruction.c()));
596                    debug_assert_eq!(
597                        unsafe {
598                            self.code[pc + 1 + usize::from(instruction.c())].opcode_unchecked()
599                        },
600                        Opcode::Call
601                    );
602                    reg((aux_word() & 0xff) as u8);
603                    reg(((aux_word() >> 8) & 0xff) as u8);
604                }
605                Opcode::Capture => match CaptureType::try_from(instruction.a()) {
606                    Ok(CaptureType::Val) => reg(instruction.b()),
607                    Ok(CaptureType::Ref) => {
608                        reg(instruction.b());
609                        open_captures.push(instruction.b());
610                    }
611                    Ok(CaptureType::Upval) => upvalue(instruction.b()),
612                    Err(_) => debug_assert!(false, "unsupported capture type"),
613                },
614                Opcode::NewClassMember => {
615                    reg(instruction.a());
616                    debug_assert_eq!(instruction.b(), 0);
617                    reg(instruction.c());
618                    constant(aux_word(), ConstantKind::String);
619                }
620                Opcode::NewClass => {
621                    reg(instruction.a());
622                    debug_assert!(
623                        instruction.b() == u8::MAX || instruction.b() < function.max_stack_size
624                    );
625                    debug_assert_eq!(instruction.c(), 0);
626                    constant(aux_word(), ConstantKind::Class);
627                }
628                Opcode::GetUDataKs | Opcode::SetUDataKs => {
629                    reg(instruction.a());
630                    reg(instruction.b());
631                    constant(aux().kv16().into(), ConstantKind::String);
632                }
633                Opcode::NameCallUData => {
634                    reg(instruction.a());
635                    reg(instruction.b());
636                    constant(aux().kv16().into(), ConstantKind::String);
637                    debug_assert!(
638                        self.code
639                            .get(pc + 2)
640                            .is_some_and(|next| unsafe { next.opcode_unchecked() } == Opcode::Call)
641                    );
642                }
643            }
644
645            pc += opcode.length();
646            debug_assert!(pc <= self.code.len());
647        }
648
649        debug_assert!(open_captures.is_empty());
650    }
651
652    #[cfg(debug_assertions)]
653    fn validate_variadic(&self) {
654        let mut variadic_sequence = false;
655        let mut instruction_targets = vec![false; self.code.len()];
656
657        let mut pc = 0usize;
658        while pc < self.code.len() {
659            let instruction = self.code[pc];
660            let opcode = unsafe { instruction.opcode_unchecked() };
661
662            if let Some(target) = unsafe { instruction.jump_target_unchecked(pc as u32) }
663                .filter(|_| !opcode.is_fast_call())
664                .filter(|target| *target >= 0)
665            {
666                debug_assert!((target as usize) < self.code.len());
667                instruction_targets[target as usize] = true;
668            }
669
670            pc += opcode.length();
671            debug_assert!(pc <= self.code.len());
672        }
673
674        let mut pc = 0usize;
675        while pc < self.code.len() {
676            let instruction = self.code[pc];
677            let opcode = unsafe { instruction.opcode_unchecked() };
678
679            if variadic_sequence {
680                debug_assert!(!instruction_targets[pc]);
681            }
682
683            if matches!(opcode, Opcode::Call | Opcode::CallFb) {
684                if instruction.b() == 0 {
685                    debug_assert!(variadic_sequence);
686                    variadic_sequence = false;
687                } else {
688                    debug_assert!(!variadic_sequence);
689                }
690
691                if instruction.c() == 0 {
692                    debug_assert!(!variadic_sequence);
693                    variadic_sequence = true;
694                }
695            } else if opcode == Opcode::GetVarargs && instruction.b() == 0 {
696                debug_assert!(!variadic_sequence);
697                variadic_sequence = true;
698            } else if (opcode == Opcode::Return && instruction.b() == 0)
699                || (opcode == Opcode::SetList && instruction.c() == 0)
700            {
701                debug_assert!(variadic_sequence);
702                variadic_sequence = false;
703            } else if opcode == Opcode::FastCall {
704                let call_pc = pc + usize::from(instruction.c()) + 1;
705                debug_assert!(call_pc < self.code.len());
706                debug_assert_eq!(
707                    unsafe { self.code[call_pc].opcode_unchecked() },
708                    Opcode::Call
709                );
710
711                if self.code[call_pc].b() == 0 {
712                    debug_assert!(variadic_sequence);
713                } else {
714                    debug_assert!(!variadic_sequence);
715                }
716            } else if matches!(
717                opcode,
718                Opcode::CloseUpvals
719                    | Opcode::NameCall
720                    | Opcode::GetImport
721                    | Opcode::Move
722                    | Opcode::GetUpval
723                    | Opcode::GetGlobal
724                    | Opcode::GetTableKs
725                    | Opcode::Coverage
726            ) {
727            } else {
728                debug_assert!(!variadic_sequence);
729            }
730
731            pc += opcode.length();
732            debug_assert!(pc <= self.code.len());
733        }
734
735        debug_assert!(!variadic_sequence);
736    }
737}
738
739#[derive(Debug, Clone, PartialEq)]
740pub(super) enum BytecodeBuilderConstant {
741    Boolean(bool),
742    Class(u32),
743    Closure(ClosureIndex),
744    Import(BytecodeImportId),
745    Integer64(i64),
746    Nil,
747    Number(f64),
748    String(u32),
749    Table(u32),
750    Vector(BytecodeVector),
751    VectorDouble(BytecodeVectorDouble),
752}
753
754impl BytecodeBuilderConstant {
755    pub(super) fn cache_key(&self) -> ConstantCacheKey {
756        match self {
757            Self::Boolean(value) => ConstantCacheKey {
758                kind: ConstantCacheKeyKind::Boolean,
759                value: u64::from(*value),
760                extra: 0,
761                extra2: 0,
762                extra3: 0,
763            },
764            Self::Class(_) => unreachable!("class constants use dedicated class-shape storage"),
765            Self::Closure(value) => ConstantCacheKey {
766                kind: ConstantCacheKeyKind::Closure,
767                value: u64::from(value.get()),
768                extra: 0,
769                extra2: 0,
770                extra3: 0,
771            },
772            Self::Import(value) => ConstantCacheKey {
773                kind: ConstantCacheKeyKind::Import,
774                value: u64::from(value.raw()),
775                extra: 0,
776                extra2: 0,
777                extra3: 0,
778            },
779            Self::Integer64(value) => ConstantCacheKey {
780                kind: ConstantCacheKeyKind::Integer64,
781                value: *value as u64,
782                extra: 0,
783                extra2: 0,
784                extra3: 0,
785            },
786            Self::Nil => ConstantCacheKey::nil(),
787            Self::Number(value) => ConstantCacheKey {
788                kind: ConstantCacheKeyKind::Number,
789                value: value.to_bits(),
790                extra: 0,
791                extra2: 0,
792                extra3: 0,
793            },
794            Self::String(value) => ConstantCacheKey {
795                kind: ConstantCacheKeyKind::String,
796                value: u64::from(*value),
797                extra: 0,
798                extra2: 0,
799                extra3: 0,
800            },
801            Self::Table(_) => unreachable!("table constants use dedicated table-shape storage"),
802            Self::Vector(value) => {
803                let bits = value.to_bits();
804                ConstantCacheKey {
805                    kind: ConstantCacheKeyKind::Vector,
806                    value: u64::from(bits[0]) | (u64::from(bits[1]) << 32),
807                    extra: u64::from(bits[2]) | (u64::from(bits[3]) << 32),
808                    extra2: 0,
809                    extra3: 0,
810                }
811            }
812            Self::VectorDouble(value) => {
813                let bits = value.to_bits();
814                ConstantCacheKey {
815                    kind: ConstantCacheKeyKind::VectorDouble,
816                    value: bits[0],
817                    extra: bits[1],
818                    extra2: bits[2],
819                    extra3: bits[3],
820                }
821            }
822        }
823    }
824
825    #[cfg(debug_assertions)]
826    fn kind(&self) -> ConstantKind {
827        match self {
828            Self::Boolean(_) => ConstantKind::Boolean,
829            Self::Class(_) => ConstantKind::Class,
830            Self::Closure(_) => ConstantKind::Closure,
831            Self::Import(_) => ConstantKind::Import,
832            Self::Integer64(_) => ConstantKind::Integer64,
833            Self::Nil => ConstantKind::Nil,
834            Self::Number(_) => ConstantKind::Number,
835            Self::String(_) => ConstantKind::String,
836            Self::Table(_) => ConstantKind::Table,
837            Self::Vector(_) => ConstantKind::Vector,
838            Self::VectorDouble(_) => ConstantKind::VectorDouble,
839        }
840    }
841}
842
843#[derive(Debug, Clone)]
844pub(super) struct BytecodeBuilderLocal {
845    pub(super) name: u32,
846    pub(super) start_pc: u32,
847    pub(super) end_pc: u32,
848    pub(super) register: Register,
849}
850
851#[derive(Debug)]
852pub(super) struct BorrowedBytecodeBuilderFunction<'a, 'src> {
853    code: &'a [Instruction],
854    constants: &'a [BytecodeBuilderConstant],
855    table_shapes: &'a [TableShape],
856    class_shapes: &'a [BytecodeClass],
857    lines: &'a [i32],
858    strings: &'a [BytecodeStringRef<'src>],
859    _src: std::marker::PhantomData<&'src ()>,
860}
861
862pub(super) struct BuilderClosureNames<'a> {
863    pub(super) functions: &'a [BytecodeBuilderFunction],
864}
865
866impl ClosureNameLookup for BuilderClosureNames<'_> {
867    fn closure_name(&self, id: ClosureIndex) -> Option<&BStr> {
868        self.functions
869            .get(id.as_usize())
870            .map(|function| function.dump_name.as_bstr())
871            .filter(|name| !name.is_empty())
872    }
873}
874
875impl BorrowedBytecodeBuilderFunction<'_, '_> {
876    fn string_bytes(&self, index: u32) -> Option<&BStr> {
877        index
878            .checked_sub(1)
879            .and_then(|index| self.strings.get(index as usize))
880            .map(|value| value.as_bytes().as_bstr())
881    }
882}
883
884#[derive(Debug, Clone, Copy, PartialEq, Eq)]
885#[cfg(debug_assertions)]
886enum ConstantKind {
887    Boolean,
888    Class,
889    Closure,
890    Import,
891    Integer64,
892    Nil,
893    Number,
894    String,
895    Table,
896    Vector,
897    VectorDouble,
898}
899
900#[derive(Debug, Clone, Copy, PartialEq, Eq)]
901pub(super) enum ConstantCacheKeyKind {
902    Nil,
903    Boolean,
904    Number,
905    Integer64,
906    String,
907    Import,
908    Closure,
909    Vector,
910    VectorDouble,
911}
912
913#[derive(Debug, Clone, PartialEq, Eq)]
914pub(super) struct ConstantCacheKey {
915    kind: ConstantCacheKeyKind,
916    value: u64,
917    extra: u64,
918    extra2: u64,
919    extra3: u64,
920}
921
922impl ConstantCacheKey {
923    pub(super) const fn empty() -> Self {
924        Self {
925            kind: ConstantCacheKeyKind::Nil,
926            value: u64::MAX,
927            extra: 0,
928            extra2: 0,
929            extra3: 0,
930        }
931    }
932
933    pub(super) const fn nil() -> Self {
934        Self {
935            kind: ConstantCacheKeyKind::Nil,
936            value: 0,
937            extra: 0,
938            extra2: 0,
939            extra3: 0,
940        }
941    }
942}
943
944impl Hash for ConstantCacheKey {
945    fn hash<H: Hasher>(&self, state: &mut H) {
946        state.write_u64(constant_cache_hash(self));
947    }
948}
949
950#[derive(Debug, Clone)]
951pub(super) struct TableShapeCacheKey {
952    shape: TableShape,
953}
954
955impl TableShapeCacheKey {
956    pub(super) fn empty() -> Self {
957        Self {
958            shape: TableShape::new(Vec::new()),
959        }
960    }
961
962    pub(super) fn new(shape: TableShape) -> Self {
963        Self { shape }
964    }
965}
966
967impl PartialEq for TableShapeCacheKey {
968    fn eq(&self, other: &Self) -> bool {
969        self.shape == other.shape
970    }
971}
972
973impl Eq for TableShapeCacheKey {}
974
975impl Hash for TableShapeCacheKey {
976    fn hash<H: Hasher>(&self, state: &mut H) {
977        state.write_u64(table_shape_hash(&self.shape));
978    }
979}
980
981impl BytecodeFunctionWire for BorrowedBytecodeBuilderFunction<'_, '_> {
982    fn code(&self) -> &[Instruction] {
983        self.code
984    }
985
986    fn constant_count(&self) -> usize {
987        self.constants.len()
988    }
989
990    fn append_constant(
991        &self,
992        result: &mut Vec<u8>,
993        index: usize,
994        closure_names: &dyn ClosureNameLookup,
995        detailed: bool,
996    ) {
997        match self.constants.get(index) {
998            Some(BytecodeBuilderConstant::Nil) => result.extend_from_slice(b"nil"),
999            Some(BytecodeBuilderConstant::Boolean(true)) => result.extend_from_slice(b"true"),
1000            Some(BytecodeBuilderConstant::Boolean(false)) => result.extend_from_slice(b"false"),
1001            Some(BytecodeBuilderConstant::Number(n)) => {
1002                luau_printf::sprintf!(=> result, "%.17g", *n);
1003            }
1004            Some(BytecodeBuilderConstant::Integer64(n)) => {
1005                write!(result, "{n}").unwrap();
1006            }
1007            Some(BytecodeBuilderConstant::String(value)) => {
1008                if let Some(bytes) = self.string_bytes(*value) {
1009                    append_string_constant(result, bytes);
1010                } else {
1011                    write!(result, "K{index}").unwrap();
1012                }
1013            }
1014            Some(BytecodeBuilderConstant::Import(import_id)) => {
1015                let count = import_id.raw() >> 30;
1016
1017                for component in 0..count {
1018                    let constant = (import_id.raw() >> (20 - 10 * component)) & 1023;
1019
1020                    let Some(BytecodeBuilderConstant::String(value)) =
1021                        self.constants.get(constant as usize)
1022                    else {
1023                        continue;
1024                    };
1025
1026                    if component > 0 {
1027                        result.push(b'.');
1028                    }
1029
1030                    if let Some(bytes) = self.string_bytes(*value) {
1031                        result.extend_from_slice(bytes);
1032                    }
1033                }
1034            }
1035            Some(BytecodeBuilderConstant::Table(shape_index)) => {
1036                let Some(shape) = self.table_shapes.get(*shape_index as usize) else {
1037                    result.extend_from_slice(b"{...}");
1038                    return;
1039                };
1040                if detailed {
1041                    let entries = shape.entries();
1042                    let sizenode = if entries.is_empty() {
1043                        0
1044                    } else {
1045                        1u32 << (i32::BITS - (entries.len() as i32 - 1).leading_zeros())
1046                    };
1047                    let mask = sizenode.saturating_sub(1);
1048
1049                    let mut slots = vec![0u32; entries.len()];
1050                    let mut slot_owner = vec![usize::MAX; sizenode as usize];
1051
1052                    for (shape_index, entry) in entries.iter().enumerate() {
1053                        let Some(BytecodeBuilderConstant::String(key)) =
1054                            self.constants.get(entry.key as usize)
1055                        else {
1056                            result.extend_from_slice(b"{...}");
1057                            return;
1058                        };
1059
1060                        let Some(key_bytes) = self.string_bytes(*key) else {
1061                            result.extend_from_slice(b"{...}");
1062                            return;
1063                        };
1064
1065                        slots[shape_index] = BytecodeBuilder::get_string_hash(key_bytes) & mask;
1066
1067                        if slot_owner[slots[shape_index] as usize] == usize::MAX {
1068                            slot_owner[slots[shape_index] as usize] = shape_index;
1069                        }
1070                    }
1071
1072                    result.push(b'{');
1073
1074                    for (shape_index, entry) in entries.iter().enumerate() {
1075                        if shape_index > 0 {
1076                            result.extend_from_slice(b", ");
1077                        }
1078
1079                        result.push(b'[');
1080                        self.append_constant(result, entry.key as usize, closure_names, false);
1081                        result.extend_from_slice(b"]");
1082
1083                        if let Some(value) = entry.value {
1084                            result.extend_from_slice(b" = ");
1085                            self.append_constant(result, value as usize, closure_names, false);
1086                        }
1087
1088                        write!(result, " #{}", slots[shape_index]).unwrap();
1089
1090                        if slot_owner[slots[shape_index] as usize] != shape_index {
1091                            result.extend_from_slice(b" (conflict)");
1092                        }
1093                    }
1094
1095                    write!(result, "}} sizenode={sizenode}").unwrap();
1096                } else {
1097                    result.extend_from_slice(b"{...}");
1098                }
1099            }
1100            Some(BytecodeBuilderConstant::Closure(id)) => {
1101                append_closure_name(result, *id, closure_names)
1102            }
1103            Some(BytecodeBuilderConstant::Vector(value)) => {
1104                if value.w() == 0.0 {
1105                    luau_printf::sprintf!(
1106                        => result,
1107                        "%.9g, %.9g, %.9g",
1108                        f64::from(value.x()),
1109                        f64::from(value.y()),
1110                        f64::from(value.z())
1111                    );
1112                } else {
1113                    luau_printf::sprintf!(
1114                        => result,
1115                        "%.9g, %.9g, %.9g, %.9g",
1116                        f64::from(value.x()),
1117                        f64::from(value.y()),
1118                        f64::from(value.z()),
1119                        f64::from(value.w())
1120                    );
1121                }
1122            }
1123            Some(BytecodeBuilderConstant::VectorDouble(value)) => {
1124                if flags::LuauCompileEmitVectorDouble.get() {
1125                    if value.w() == 0.0 {
1126                        luau_printf::sprintf!(
1127                            => result,
1128                            "%.17g, %.17g, %.17g",
1129                            value.x(),
1130                            value.y(),
1131                            value.z()
1132                        );
1133                    } else {
1134                        luau_printf::sprintf!(
1135                            => result,
1136                            "%.17g, %.17g, %.17g, %.17g",
1137                            value.x(),
1138                            value.y(),
1139                            value.z(),
1140                            value.w()
1141                        );
1142                    }
1143                } else if value.w() == 0.0 {
1144                    luau_printf::sprintf!(
1145                        => result,
1146                        "%.9g, %.9g, %.9g",
1147                        value.x() as f32 as f64,
1148                        value.y() as f32 as f64,
1149                        value.z() as f32 as f64
1150                    );
1151                } else {
1152                    luau_printf::sprintf!(
1153                        => result,
1154                        "%.9g, %.9g, %.9g, %.9g",
1155                        value.x() as f32 as f64,
1156                        value.y() as f32 as f64,
1157                        value.z() as f32 as f64,
1158                        value.w() as f32 as f64
1159                    );
1160                }
1161            }
1162            Some(BytecodeBuilderConstant::Class(class_index)) => {
1163                let Some(class) = self.class_shapes.get(*class_index as usize) else {
1164                    result.extend_from_slice(b"class ?");
1165                    return;
1166                };
1167                result.extend_from_slice(b"class ");
1168                match self.constants.get(class.class_name as usize) {
1169                    Some(BytecodeBuilderConstant::String(value)) => {
1170                        if let Some(bytes) = self.string_bytes(*value) {
1171                            result.extend_from_slice(bytes);
1172                        } else {
1173                            write!(result, "K{}", class.class_name).unwrap();
1174                        }
1175                    }
1176                    _ => write!(result, "K{}", class.class_name).unwrap(),
1177                }
1178                write!(
1179                    result,
1180                    " (props: {}, methods: {})",
1181                    class.property_names.len(),
1182                    class.method_names.len()
1183                )
1184                .unwrap();
1185            }
1186            None => result.extend_from_slice(b"?"),
1187        }
1188    }
1189
1190    fn line_for_pc(&self, pc: usize) -> Option<i32> {
1191        self.lines.get(pc).copied()
1192    }
1193}
1194
1195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1196pub(super) struct Jump {
1197    pub(super) source: usize,
1198    pub(super) target: usize,
1199}
1200
1201#[derive(Debug, Clone, PartialEq, Eq)]
1202pub(super) struct StoredDebugRemark {
1203    pub(super) pc: usize,
1204    pub(super) line: i32,
1205    pub(super) text: BString,
1206}
1207
1208#[bitfield(u32)]
1209#[derive(PartialEq, Eq)]
1210pub struct BytecodeDumpFlags {
1211    #[bits(default = false)]
1212    code: bool,
1213    #[bits(default = false)]
1214    lines: bool,
1215    #[bits(default = false)]
1216    source: bool,
1217    #[bits(default = false)]
1218    locals: bool,
1219    #[bits(default = false)]
1220    remarks: bool,
1221    #[bits(default = false)]
1222    types: bool,
1223    #[bits(default = false)]
1224    constants: bool,
1225    #[bits(25, default = 0)]
1226    _reserved: u32,
1227}