luau-bytecode 0.732.0

Luau bytecode model, builder, serializer, and dumper
Documentation
use crate::common::{compile_dumped_function_with_options, compile_function0_source};
use luau_bytecode::builder::BytecodeDumpFlags;
use luau_common::{BString, flags};
use luau_compiler::{CompileOptions, EmptyCompileHost, StaticCompileHost};

fn compile_debug_locals(source: &str, id: usize) -> BString {
    let _feedback = flags::LuauEmitCallFeedback.scoped(true);
    let options = CompileOptions {
        debug_level: 2,
        optimization_level: 1,
        ..CompileOptions::default()
    };
    let mut dump_flags = BytecodeDumpFlags::default();
    dump_flags.set_code(true);
    dump_flags.set_lines(true);
    dump_flags.set_locals(true);
    compile_dumped_function_with_options(
        source,
        id,
        options,
        &EmptyCompileHost,
        dump_flags,
        Some(source.as_bytes()),
    )
}

fn compile_debug_types(source: &str) -> BString {
    let _feedback = flags::LuauEmitCallFeedback.scoped(true);
    let host = StaticCompileHost::new()
        .with_vector_type_name(b"Vector3".as_slice())
        .with_userdata_type(b"vec2".as_slice())
        .with_userdata_type(b"color".as_slice())
        .with_userdata_type(b"mat3".as_slice());

    let options = CompileOptions {
        debug_level: 2,
        optimization_level: 1,
        type_info_level: 1,
        ..CompileOptions::default()
    };
    let mut dump_flags = BytecodeDumpFlags::default();
    dump_flags.set_code(true);
    dump_flags.set_types(true);

    compile_dumped_function_with_options(
        source,
        0,
        options,
        &host,
        dump_flags,
        Some(source.as_bytes()),
    )
}

// Compiler.test.cpp: DebugSource
#[test]
fn debug_source_dump_interleaves_source_lines_and_bytecode() {
    assert_eq!(
        compile_function0_source(
            r#"
local kSelectedBiomes = {
    ['Mountains'] = true,
    ['Canyons'] = true,
    ['Dunes'] = true,
    ['Arctic'] = true,
    ['Lavaflow'] = true,
    ['Hills'] = true,
    ['Plains'] = true,
    ['Marsh'] = true,
    ['Water'] = true,
}
local result = ""
for k in pairs(kSelectedBiomes) do
    result = result .. k
end
return result
"#
        ),
        br#"    2: local kSelectedBiomes = {
NEWTABLE R0 16 0
    3:     ['Mountains'] = true,
LOADB R1 1
SETTABLEKS R1 R0 K0 ['Mountains']
    4:     ['Canyons'] = true,
LOADB R1 1
SETTABLEKS R1 R0 K1 ['Canyons']
    5:     ['Dunes'] = true,
LOADB R1 1
SETTABLEKS R1 R0 K2 ['Dunes']
    6:     ['Arctic'] = true,
LOADB R1 1
SETTABLEKS R1 R0 K3 ['Arctic']
    7:     ['Lavaflow'] = true,
LOADB R1 1
SETTABLEKS R1 R0 K4 ['Lavaflow']
    8:     ['Hills'] = true,
LOADB R1 1
SETTABLEKS R1 R0 K5 ['Hills']
    9:     ['Plains'] = true,
LOADB R1 1
SETTABLEKS R1 R0 K6 ['Plains']
   10:     ['Marsh'] = true,
LOADB R1 1
SETTABLEKS R1 R0 K7 ['Marsh']
   11:     ['Water'] = true,
LOADB R1 1
SETTABLEKS R1 R0 K8 ['Water']
   13: local result = ""
LOADK R1 K9 ['']
   14: for k in pairs(kSelectedBiomes) do
GETIMPORT R2 11 [pairs]
MOVE R3 R0
CALL R2 1 3
FORGPREP_NEXT R2 L1
   15:     result = result .. k
L0: MOVE R7 R1
MOVE R8 R5
CONCAT R1 R7 R8
   14: for k in pairs(kSelectedBiomes) do
L1: FORGLOOP R2 L0 1
   17: return result
RETURN R1 1
"#
    );
}

// Compiler.test.cpp: DebugLocals
#[test]
fn debug_locals_record_registers_and_live_ranges() {
    assert_eq!(
        compile_debug_locals(
            r#"
function foo(e, f)
    local a = 1
    for i=1,3 do
        print(i)
    end
    for k,v in pairs() do
        print(k, v)
    end
    do
        local b = 2
        print(b)
    end
    do
        local c = 2
        print(b)
    end
    local function inner()
        return inner, a
    end
    return a
end
"#,
            1,
        ),
        b"\
local 0: reg 5, start pc 5 line 5, end pc 9 line 5
local 1: reg 6, start pc 16 line 8, end pc 21 line 8
local 2: reg 7, start pc 16 line 8, end pc 21 line 8
local 3: reg 3, start pc 25 line 12, end pc 29 line 12
local 4: reg 3, start pc 31 line 16, end pc 36 line 16
local 5: reg 0, start pc 0 line 3, end pc 40 line 21
local 6: reg 1, start pc 0 line 3, end pc 40 line 21
local 7: reg 2, start pc 1 line 4, end pc 40 line 21
local 8: reg 3, start pc 40 line 21, end pc 40 line 21
3: LOADN R2 1
4: LOADN R5 1
4: LOADN R3 3
4: LOADN R4 1
4: FORNPREP R3 L1
5: L0: GETIMPORT R6 1 [print]
5: MOVE R7 R5
5: CALLFB R6 1 0 [0]
4: FORNLOOP R3 L0
7: L1: GETIMPORT R3 3 [pairs]
7: CALLFB R3 0 3 [1]
7: FORGPREP_NEXT R3 L3
8: L2: GETIMPORT R8 1 [print]
8: MOVE R9 R6
8: MOVE R10 R7
8: CALLFB R8 2 0 [2]
7: L3: FORGLOOP R3 L2 2
11: LOADN R3 2
12: GETIMPORT R4 1 [print]
12: LOADN R5 2
12: CALLFB R4 1 0 [3]
15: LOADN R3 2
16: GETIMPORT R4 1 [print]
16: GETIMPORT R5 5 [b]
16: CALLFB R4 1 0 [4]
18: NEWCLOSURE R3 P0
18: CAPTURE VAL R3
18: CAPTURE VAL R2
21: RETURN R2 1
"
    );
}

// Compiler.test.cpp: DebugLocals2
#[test]
fn debug_locals_record_repeat_until_ranges() {
    assert_eq!(
        compile_debug_locals(
            r#"
function foo(x)
    repeat
        local a, b
    until true
end
"#,
            0,
        ),
        b"\
local 0: reg 1, start pc 2 line 6, no live range
local 1: reg 2, start pc 2 line 6, no live range
local 2: reg 0, start pc 0 line 4, end pc 2 line 6
4: LOADNIL R1
4: LOADNIL R2
6: RETURN R0 0
"
    );
}

// Compiler.test.cpp: DebugLocals3
#[test]
fn debug_locals_record_repeat_until_continue_ranges() {
    assert_eq!(
        compile_debug_locals(
            r#"
function foo(x)
    repeat
        local a, b
        do continue end
        local c, d = 2
    until true
end
"#,
            0,
        ),
        b"\
local 0: reg 3, start pc 5 line 8, no live range
local 1: reg 4, start pc 5 line 8, no live range
local 2: reg 1, start pc 2 line 5, end pc 4 line 6
local 3: reg 2, start pc 2 line 5, end pc 4 line 6
local 4: reg 0, start pc 0 line 4, end pc 5 line 8
4: LOADNIL R1
4: LOADNIL R2
5: RETURN R0 0
6: LOADN R3 2
6: LOADNIL R4
8: RETURN R0 0
"
    );
}

// Compiler.test.cpp: DebugTypes
#[test]
fn debug_type_dump_records_argument_upvalue_and_register_types() {
    assert_eq!(
        compile_debug_types(
            r#"
local up: number = 2

function foo(e: vector, f: mat3, g: sequence)
    local h = e * e

    for i=1,3 do
        print(i)
    end

    print(e * f)
    print(g)
    print(h)

    up += a
    return a
end
"#
        ),
        "\
R0: vector [argument]
R1: mat3 [argument]
R2: userdata [argument]
U0: number
R6: number from 1 to 10
R3: vector from 0 to 34
MUL R3 R0 R0
LOADN R6 1
LOADN R4 3
LOADN R5 1
FORNPREP R4 L1
L0: GETIMPORT R7 1 [print]
MOVE R8 R6
CALLFB R7 1 0 [0]
FORNLOOP R4 L0
L1: GETIMPORT R4 1 [print]
MUL R5 R0 R1
CALLFB R4 1 0 [1]
GETIMPORT R4 1 [print]
MOVE R5 R2
CALLFB R4 1 0 [2]
GETIMPORT R4 1 [print]
MOVE R5 R3
CALLFB R4 1 0 [3]
GETUPVAL R4 0
GETIMPORT R5 3 [a]
ADD R4 R4 R5
SETUPVAL R4 0
GETIMPORT R4 3 [a]
RETURN R4 1
"
    );
}