Skip to main content

luau_bytecode/builder/
serialize.rs

1use super::support::{
2    BytecodeBuilderConstant, BytecodeBuilderFunction, BytecodeBuilderScratch, TableShapeCacheKey,
3};
4use super::*;
5use crate::function::BytecodeStringTable;
6use crate::model::{
7    BytecodeClass, BytecodeFeedbackSlot, BytecodeFeedbackType, BytecodeImportId, BytecodeString,
8    BytecodeTypedLocal, BytecodeUserdataType, BytecodeVector, BytecodeVectorDouble, ClosureIndex,
9    ConstantIndex, Register, TableShape,
10};
11use crate::opcodes::{
12    BYTECODE_TYPE_VERSION_TARGET, BYTECODE_VERSION_CLASSES, BYTECODE_VERSION_TARGET,
13    BytecodeConstantTag, FEEDBACK_TYPE_CALLTARGET, PROTO_FLAG_INLINABLE,
14};
15use crate::wire::BytecodeWriter;
16use luau_common::flags;
17use std::borrow::Cow;
18
19impl<'src> BytecodeBuilder<'src> {
20    fn add_bytecode_constant(&mut self, value: BytecodeBuilderConstant) -> ConstantIndex {
21        let key = value.cache_key();
22        let proto = self.current_function();
23
24        if let Some(index) = proto.constant_index.get(&key) {
25            return *index;
26        }
27
28        if proto.constants.len() >= MAX_CONSTANT_COUNT {
29            return -1;
30        }
31
32        let index = proto.constants.len() as ConstantIndex;
33        proto.constants.push(value);
34        let (_, fresh) = proto.constant_index.insert(key, index);
35        debug_assert!(fresh);
36        index
37    }
38
39    pub fn add_constant_nil(&mut self) -> ConstantIndex {
40        self.add_bytecode_constant(BytecodeBuilderConstant::Nil)
41    }
42
43    pub fn add_constant_boolean(&mut self, value: bool) -> ConstantIndex {
44        self.add_bytecode_constant(BytecodeBuilderConstant::Boolean(value))
45    }
46
47    pub fn add_constant_number(&mut self, value: f64) -> ConstantIndex {
48        self.add_bytecode_constant(BytecodeBuilderConstant::Number(value))
49    }
50
51    pub fn add_constant_integer(&mut self, value: i64) -> ConstantIndex {
52        self.add_bytecode_constant(BytecodeBuilderConstant::Integer64(value))
53    }
54
55    pub fn add_constant_string(
56        &mut self,
57        value: impl Into<BytecodeStringRef<'src>>,
58    ) -> ConstantIndex {
59        let value = value.into();
60        let index = self.add_string_table_entry(&value);
61        self.add_bytecode_constant(BytecodeBuilderConstant::String(index))
62    }
63
64    pub fn add_import(&mut self, import_id: BytecodeImportId) -> ConstantIndex {
65        self.add_bytecode_constant(BytecodeBuilderConstant::Import(import_id))
66    }
67
68    pub fn add_constant_closure(&mut self, function_id: u32) -> ConstantIndex {
69        self.add_bytecode_constant(BytecodeBuilderConstant::Closure(ClosureIndex::new(
70            function_id,
71        )))
72    }
73
74    pub fn add_constant_table(&mut self, shape: &TableShape) -> ConstantIndex {
75        let proto = self.current_function();
76        let key = TableShapeCacheKey::new(shape.clone());
77
78        if let Some(index) = proto.table_shape_index.get(&key) {
79            return *index;
80        }
81
82        if proto.constants.len() >= MAX_CONSTANT_COUNT {
83            return -1;
84        }
85
86        let index = proto.constants.len() as ConstantIndex;
87        let table_shape_index = proto.table_shapes.len() as u32;
88        proto.table_shapes.push(shape.clone());
89        let (_, fresh) = proto.table_shape_index.insert(key, index);
90        debug_assert!(fresh);
91        proto
92            .constants
93            .push(BytecodeBuilderConstant::Table(table_shape_index));
94        index
95    }
96
97    pub fn add_constant_vector(&mut self, x: f32, y: f32, z: f32, w: f32) -> ConstantIndex {
98        self.add_bytecode_constant(BytecodeBuilderConstant::Vector(BytecodeVector::new(
99            x, y, z, w,
100        )))
101    }
102
103    pub fn add_constant_vector_double(&mut self, x: f64, y: f64, z: f64, w: f64) -> ConstantIndex {
104        self.add_bytecode_constant(BytecodeBuilderConstant::VectorDouble(
105            BytecodeVectorDouble::new(x, y, z, w),
106        ))
107    }
108
109    pub fn add_class_shape(&mut self, shape: BytecodeClass) -> ConstantIndex {
110        if self.scratch.constants.len() >= MAX_CONSTANT_COUNT {
111            return -1;
112        }
113
114        let index = self.scratch.constants.len() as ConstantIndex;
115        let class_shape_index = self.class_shapes.len() as u32;
116        self.class_shapes.push(shape);
117        self.scratch
118            .constants
119            .push(BytecodeBuilderConstant::Class(class_shape_index));
120        index
121    }
122
123    pub fn add_fb_slot(&mut self, ty: BytecodeFeedbackType) -> u32 {
124        debug_assert_eq!(ty, BytecodeFeedbackType::CallTarget);
125        let pc = self.current_function().code.len() as u32;
126        let proto = self.current_function();
127        proto.feedback_slots.push(BytecodeFeedbackSlot { pc });
128        proto.feedback_slots.len() as u32 - 1
129    }
130
131    pub fn set_function_type_info(&mut self, value: Vec<u8>) {
132        self.current_function_meta().type_info = value;
133    }
134
135    pub fn push_local_type_info(&mut self, ty: u8, register: Register, start_pc: u32, end_pc: u32) {
136        self.current_function()
137            .local_types
138            .push(BytecodeTypedLocal {
139                ty,
140                register,
141                start_pc,
142                end_pc,
143            });
144    }
145
146    pub fn push_upvalue_type_info(&mut self, ty: u8) {
147        self.current_function().upvalue_types.push(ty);
148    }
149
150    pub fn add_userdata_type(&mut self, name: impl Into<BytecodeString>) -> u32 {
151        let index = self.userdata_types.len();
152        self.userdata_types.push(BytecodeUserdataType {
153            name: name.into(),
154            name_ref: 0,
155            used: false,
156        });
157        u32::try_from(index).expect("userdata type index must fit bytecode varint")
158    }
159
160    pub fn use_userdata_type(&mut self, index: u32) {
161        self.userdata_types[index as usize].used = true;
162    }
163
164    pub fn finalize(&mut self) {
165        debug_assert!(
166            self.bytecode.is_empty(),
167            "BytecodeBuilder::finalize requires bytecode to be empty"
168        );
169        let main = u32::try_from(
170            self.main
171                .expect("main function must be set before finalize"),
172        )
173        .expect("main function id must fit varint");
174        self.assign_userdata_type_name_refs();
175        self.bytecode = self.finish_bytecode(main);
176    }
177
178    pub fn get_bytecode(&self) -> &[u8] {
179        debug_assert!(
180            !self.bytecode.is_empty(),
181            "BytecodeBuilder::get_bytecode requires finalize first"
182        );
183        &self.bytecode
184    }
185
186    /// Encodes a compiler diagnostic as a bytecode blob that the VM loader can
187    /// report using the requested chunk name.
188    pub fn get_error(message: impl AsRef<[u8]>) -> Vec<u8> {
189        let message = message.as_ref();
190        let mut result = Vec::with_capacity(message.len() + 1);
191        result.push(0);
192        result.extend_from_slice(message);
193        result
194    }
195
196    pub fn get_string_table(&self) -> BytecodeStringTable<'_> {
197        let mut strings = vec![Cow::Borrowed(&[][..]); self.string_index.len()];
198        for (value, index) in &self.string_index {
199            debug_assert!(*index > 0 && (*index as usize) <= strings.len());
200            strings[*index as usize - 1] = Cow::Borrowed(value.as_bytes());
201        }
202        BytecodeStringTable::new(strings)
203    }
204
205    pub fn get_function_data(&self, id: usize) -> Vec<u8> {
206        self.functions[id].data.clone()
207    }
208
209    pub(super) fn function_data(&self, id: usize) -> Vec<u8> {
210        let mut writer = BytecodeWriter::new();
211        self.write_function(&mut writer, &self.functions[id], &self.scratch);
212        writer.into_bytes()
213    }
214
215    fn finish_bytecode(&self, main: u32) -> Vec<u8> {
216        let version = self.version();
217        let mut writer = BytecodeWriter::new();
218
219        writer.write_u8(version);
220        writer.write_u8(BYTECODE_TYPE_VERSION_TARGET);
221        self.write_finalized_string_table(&mut writer);
222        self.write_userdata_remapping(&mut writer);
223        writer.write_varint(self.functions.len() as u32);
224
225        for function in &self.functions {
226            if version >= 12 {
227                writer.write_varint(function.data.len() as u32);
228            }
229            writer.write_bytes(&function.data);
230        }
231
232        writer.write_varint(main);
233        writer.into_bytes()
234    }
235
236    fn version(&self) -> u8 {
237        if flags::DebugLuauUserDefinedClasses.get() {
238            return BYTECODE_VERSION_CLASSES;
239        }
240
241        if flags::LuauCompileEmitVectorDouble.get() {
242            return 13;
243        }
244
245        if flags::LuauBytecodeCostModel.get() {
246            return 12;
247        }
248
249        if flags::LuauEmitCallFeedback.get() {
250            return 11;
251        }
252
253        BYTECODE_VERSION_TARGET
254    }
255
256    fn write_function(
257        &self,
258        writer: &mut BytecodeWriter,
259        function: &BytecodeBuilderFunction,
260        scratch: &BytecodeBuilderScratch<'src>,
261    ) {
262        writer.write_u8(function.max_stack_size);
263        writer.write_u8(function.num_params);
264        writer.write_u8(function.upvalue_count);
265        writer.write_u8(u8::from(function.is_vararg));
266        writer.write_u8(function.flags);
267
268        if function.type_info.is_empty()
269            && scratch.upvalue_types.is_empty()
270            && scratch.local_types.is_empty()
271        {
272            writer.write_varint(0);
273        } else {
274            let mut types = BytecodeWriter::new();
275            types.write_varint(function.type_info.len() as u32);
276            types.write_varint(scratch.upvalue_types.len() as u32);
277            types.write_varint(scratch.local_types.len() as u32);
278            types.write_bytes(&function.type_info);
279
280            for ty in &scratch.upvalue_types {
281                types.write_u8(*ty);
282            }
283
284            for local in &scratch.local_types {
285                types.write_u8(local.ty);
286                types.write_u8(local.register);
287                types.write_varint(local.start_pc);
288                debug_assert!(local.end_pc >= local.start_pc);
289                types.write_varint(local.end_pc - local.start_pc);
290            }
291
292            let types = types.into_bytes();
293            writer.write_varint(types.len() as u32);
294            writer.write_bytes(&types);
295        }
296
297        writer.write_varint(scratch.code.len() as u32);
298        for instruction in &scratch.code {
299            writer.write_u32(instruction.word());
300        }
301
302        writer.write_varint(scratch.constants.len() as u32);
303        for constant in &scratch.constants {
304            self.write_function_constant(writer, scratch, constant);
305        }
306
307        writer.write_varint(scratch.child_functions.len() as u32);
308        for child in &scratch.child_functions {
309            writer.write_varint(*child);
310        }
311
312        writer.write_varint(function.line_defined as u32);
313        writer.write_varint(function.debug_name.as_ref().copied().unwrap_or(0));
314
315        if scratch.lines.is_empty() || scratch.lines.contains(&0) {
316            writer.write_u8(0);
317        } else {
318            writer.write_u8(1);
319            Self::write_line_info(writer, &scratch.lines);
320        }
321
322        if scratch.local_vars.is_empty() && scratch.upvalues.is_empty() {
323            writer.write_u8(0);
324        } else {
325            writer.write_u8(1);
326            writer.write_varint(scratch.local_vars.len() as u32);
327            for local in &scratch.local_vars {
328                writer.write_varint(local.name);
329                writer.write_varint(local.start_pc);
330                writer.write_varint(local.end_pc);
331                writer.write_u8(local.register);
332            }
333
334            writer.write_varint(scratch.upvalues.len() as u32);
335            for upvalue in &scratch.upvalues {
336                writer.write_varint(*upvalue);
337            }
338        }
339
340        if flags::LuauEmitCallFeedback.get() {
341            writer.write_varint(scratch.feedback_slots.len() as u32);
342            for slot in &scratch.feedback_slots {
343                writer.write_u8(FEEDBACK_TYPE_CALLTARGET);
344                writer.write_varint(slot.pc);
345            }
346        } else if self.version() >= 12 {
347            writer.write_varint(0);
348        }
349
350        if self.version() >= 12 && function.flags & PROTO_FLAG_INLINABLE != 0 {
351            writer.write_varint64(function.cost);
352        }
353    }
354
355    fn write_function_constant(
356        &self,
357        writer: &mut BytecodeWriter,
358        scratch: &BytecodeBuilderScratch<'src>,
359        constant: &BytecodeBuilderConstant,
360    ) {
361        match constant {
362            BytecodeBuilderConstant::Nil => writer.write_u8(BytecodeConstantTag::Nil as u8),
363            BytecodeBuilderConstant::Boolean(value) => {
364                writer.write_u8(BytecodeConstantTag::Boolean as u8);
365                writer.write_u8(u8::from(*value));
366            }
367            BytecodeBuilderConstant::Number(value) => {
368                writer.write_u8(BytecodeConstantTag::Number as u8);
369                writer.write_f64(*value);
370            }
371            BytecodeBuilderConstant::Integer64(value) => {
372                writer.write_u8(BytecodeConstantTag::Integer as u8);
373                writer.write_integer_constant(*value);
374            }
375            BytecodeBuilderConstant::Vector(value) => {
376                writer.write_u8(BytecodeConstantTag::Vector as u8);
377                writer.write_f32(value.x());
378                writer.write_f32(value.y());
379                writer.write_f32(value.z());
380                writer.write_f32(value.w());
381            }
382            BytecodeBuilderConstant::VectorDouble(value) => {
383                if flags::LuauCompileEmitVectorDouble.get() {
384                    writer.write_u8(BytecodeConstantTag::VectorDouble as u8);
385                    writer.write_f64(value.x());
386                    writer.write_f64(value.y());
387                    writer.write_f64(value.z());
388                    writer.write_f64(value.w());
389                } else {
390                    writer.write_u8(BytecodeConstantTag::Vector as u8);
391                    writer.write_f32(value.x() as f32);
392                    writer.write_f32(value.y() as f32);
393                    writer.write_f32(value.z() as f32);
394                    writer.write_f32(value.w() as f32);
395                }
396            }
397            BytecodeBuilderConstant::String(value) => {
398                writer.write_u8(BytecodeConstantTag::String as u8);
399                writer.write_varint(*value);
400            }
401            BytecodeBuilderConstant::Import(value) => {
402                writer.write_u8(BytecodeConstantTag::Import as u8);
403                writer.write_u32(value.raw());
404            }
405            BytecodeBuilderConstant::Table(shape_index) => {
406                let shape = &scratch.table_shapes[*shape_index as usize];
407                let write_constants = shape.has_constants();
408                writer.write_u8(if write_constants {
409                    BytecodeConstantTag::TableWithConstants as u8
410                } else {
411                    BytecodeConstantTag::Table as u8
412                });
413                writer.write_varint(shape.len() as u32);
414                for entry in shape.entries() {
415                    writer
416                        .write_varint(u32::try_from(entry.key).expect("table key must fit varint"));
417                    if write_constants {
418                        writer.write_i32(entry.value.unwrap_or(-1));
419                    }
420                }
421            }
422            BytecodeBuilderConstant::Closure(id) => {
423                writer.write_u8(BytecodeConstantTag::Closure as u8);
424                writer.write_varint(id.get());
425            }
426            BytecodeBuilderConstant::Class(class_index) => {
427                let class = &self.class_shapes[*class_index as usize];
428                writer.write_u8(BytecodeConstantTag::ClassShape as u8);
429                writer.write_varint(
430                    u32::try_from(class.class_name).expect("class name must fit varint"),
431                );
432                writer.write_varint(class.property_names.len() as u32);
433                writer.write_varint(class.method_names.len() as u32);
434                for prop in &class.property_names {
435                    writer
436                        .write_varint(u32::try_from(*prop).expect("property name must fit varint"));
437                }
438                for method in &class.method_names {
439                    writer
440                        .write_varint(u32::try_from(*method).expect("method name must fit varint"));
441                }
442            }
443        }
444    }
445
446    fn write_line_info(writer: &mut BytecodeWriter, lines: &[i32]) {
447        debug_assert!(!lines.is_empty());
448
449        let mut span = 1usize << 24;
450
451        let mut offset = 0usize;
452        while offset < lines.len() {
453            let mut next = offset;
454            let mut min = lines[offset];
455            let mut max = lines[offset];
456
457            while next < lines.len() && next < offset + span {
458                min = min.min(lines[next]);
459                max = max.max(lines[next]);
460
461                if max - min > 255 {
462                    break;
463                }
464
465                next += 1;
466            }
467
468            if next < lines.len() && next - offset < span {
469                span = 1usize << (next - offset).ilog2();
470            } else {
471                offset += span;
472            }
473        }
474
475        let baseline_size = (lines.len() - 1) / span + 1;
476        let mut baseline = vec![0i32; baseline_size];
477        for offset in (0..lines.len()).step_by(span) {
478            let end = (offset + span).min(lines.len());
479            baseline[offset / span] = *lines[offset..end]
480                .iter()
481                .min()
482                .expect("line range must be non-empty");
483        }
484
485        let log_span = span.ilog2() as u8;
486        writer.write_u8(log_span);
487
488        let mut last_offset = 0u8;
489        for (index, line) in lines.iter().copied().enumerate() {
490            let delta = line - baseline[index >> usize::from(log_span)];
491            debug_assert!((0..=255).contains(&delta));
492            let delta = delta as u8;
493            writer.write_u8(delta.wrapping_sub(last_offset));
494            last_offset = delta;
495        }
496
497        let mut last_line = 0i32;
498        for line in baseline {
499            writer.write_i32(line.wrapping_sub(last_line));
500            last_line = line;
501        }
502    }
503
504    pub(super) fn add_string_table_entry(&mut self, value: &BytecodeStringRef<'src>) -> u32 {
505        let next_index = self.string_index.len() as u32 + 1;
506        let index = self.string_index.get_or_insert_default(*value);
507
508        // BytecodeBuilder.cpp: stringTable[value] default-constructs the slot to 0;
509        // assign the 1-based string table index only for fresh entries.
510        if *index == 0 {
511            *index = next_index;
512
513            if self.dump_flags.code() {
514                self.debug_strings.push(*value);
515            }
516        }
517
518        *index
519    }
520
521    fn base_string_table(&self) -> Vec<&[u8]> {
522        let mut strings = vec![None; self.string_index.len()];
523        for (value, index) in &self.string_index {
524            debug_assert!(*index > 0 && (*index as usize) <= strings.len());
525            strings[*index as usize - 1] = Some(value.as_bytes());
526        }
527        strings
528            .into_iter()
529            .map(|string| string.expect("base string table entry must exist"))
530            .collect()
531    }
532
533    fn assign_userdata_type_name_refs(&mut self) {
534        let base_string_refs = self
535            .string_index
536            .iter()
537            .map(|(value, index)| (value.as_bytes(), *index))
538            .collect::<Vec<_>>();
539        let mut next_index = self.string_index.len() as u32 + 1;
540
541        for index in 0..self.userdata_types.len() {
542            let (previous, current_and_rest) = self.userdata_types.split_at_mut(index);
543            let current = &mut current_and_rest[0];
544
545            if !current.used {
546                current.name_ref = 0;
547                continue;
548            }
549
550            if let Some((_, name_ref)) = base_string_refs
551                .iter()
552                .find(|(name, _)| *name == current.name.as_bytes())
553            {
554                current.name_ref = *name_ref;
555                continue;
556            }
557
558            if let Some(name_ref) = previous
559                .iter()
560                .find(|userdata_type| {
561                    userdata_type.used && userdata_type.name.as_bytes() == current.name.as_bytes()
562                })
563                .map(|userdata_type| userdata_type.name_ref)
564            {
565                current.name_ref = name_ref;
566                continue;
567            }
568
569            current.name_ref = next_index;
570            next_index += 1;
571        }
572    }
573
574    fn finalized_string_table(&self) -> Vec<&[u8]> {
575        let base_count = self.string_index.len();
576        let mut strings = self
577            .base_string_table()
578            .into_iter()
579            .map(Some)
580            .collect::<Vec<_>>();
581        let final_len = self
582            .userdata_types
583            .iter()
584            .map(|userdata_type| userdata_type.name_ref as usize)
585            .max()
586            .unwrap_or(strings.len())
587            .max(strings.len());
588        strings.resize(final_len, None);
589
590        for userdata_type in &self.userdata_types {
591            if userdata_type.used {
592                let slot = &mut strings[userdata_type.name_ref as usize - 1];
593
594                if slot.is_none() && userdata_type.name_ref as usize > base_count {
595                    *slot = Some(userdata_type.name.as_bytes());
596                } else {
597                    debug_assert_eq!(
598                        slot.expect("userdata string table entry must exist"),
599                        userdata_type.name.as_bytes()
600                    );
601                }
602            }
603        }
604
605        strings
606            .into_iter()
607            .map(|string| string.expect("finalized string table entry must exist"))
608            .collect()
609    }
610
611    fn write_finalized_string_table(&self, writer: &mut BytecodeWriter) {
612        let strings = self.finalized_string_table();
613
614        writer.write_varint(strings.len() as u32);
615        for string in strings {
616            writer.write_varint(string.len() as u32);
617            writer.write_bytes(string);
618        }
619    }
620
621    fn write_userdata_remapping(&self, writer: &mut BytecodeWriter) {
622        for (index, userdata_type) in self.userdata_types.iter().enumerate() {
623            if userdata_type.used {
624                let bytecode_index = u8::try_from(index + 1)
625                    .expect("userdata type remapping index must fit bytecode byte");
626                writer.write_u8(bytecode_index);
627                writer.write_varint(userdata_type.name_ref);
628            }
629        }
630
631        writer.write_u8(0);
632    }
633
634    pub fn get_string_hash(key: impl AsRef<[u8]>) -> u32 {
635        let bytes = key.as_ref();
636        let mut hash = bytes.len() as u32;
637
638        for byte in bytes.iter().rev() {
639            hash ^= (hash << 5)
640                .wrapping_add(hash >> 2)
641                .wrapping_add(u32::from(*byte));
642        }
643
644        hash
645    }
646}