luaur-code-gen 0.1.0

Native (A64/X64) code generation for Luau (Rust).
Documentation
use crate::enums::abix_64::ABIX64;
use crate::enums::condition_x_64::ConditionX64;
use crate::enums::size_x_64::SizeX64;
use crate::functions::luau_reg::luau_reg;
use crate::functions::luau_reg_address::luau_reg_address;
use crate::functions::luau_reg_tag::luau_reg_tag;
use crate::functions::luau_reg_value::luau_reg_value;
use crate::functions::set_luau_reg::set_luau_reg;
use crate::records::assembly_builder_x_64::AssemblyBuilderX64;
use crate::records::ir_call_wrapper_x_64::IrCallWrapperX64;
use crate::records::ir_data::k_invalid_inst_idx;
use crate::records::ir_op::IrOp;
use crate::records::ir_reg_alloc_x_64::IrRegAllocX64;
use crate::records::label::Label;
use crate::records::native_context::NativeContext;
use crate::records::operand_x_64::OperandX64;
use crate::records::register_x_64::RegisterX64;
use luaur_vm::enums::lua_type::lua_Type;
use luaur_vm::records::lua_table::LuaTable;
use luaur_vm::type_aliases::t_value::TValue;

pub fn emit_inst_for_g_loop(
    regs: &mut IrRegAllocX64,
    build: &mut AssemblyBuilderX64,
    ra: i32,
    aux: i32,
    loop_repeat: &mut Label,
) {
    crate::macros::codegen_assert::CODEGEN_ASSERT!(aux >= 0);

    let (table, index) = if luaur_common::FFlag::LuauCodegenSuggestArgumentRegisterX64.get() {
        (
            IrCallWrapperX64::suggest_argument_register::<1>(SizeX64::qword, build),
            IrCallWrapperX64::suggest_argument_register::<2>(SizeX64::qword, build),
        )
    } else if build.abi == ABIX64::Windows {
        (RegisterX64::rdx, RegisterX64::r8)
    } else {
        (RegisterX64::rsi, RegisterX64::rdx)
    };

    let elem_ptr = RegisterX64::rax;

    build.mov(OperandX64::reg(table), luau_reg_value(ra + 1));
    build.mov(OperandX64::reg(index), luau_reg_value(ra + 2));

    build.mov(
        OperandX64::reg(sized(elem_ptr, SizeX64::dword)),
        OperandX64::reg(sized(index, SizeX64::dword)),
    );
    build.shl(
        OperandX64::reg(sized(elem_ptr, SizeX64::dword)),
        OperandX64::imm(K_TVALUE_SIZE_LOG2),
    );
    build.add(
        OperandX64::reg(elem_ptr),
        mem(
            SizeX64::qword,
            table,
            core::mem::offset_of!(LuaTable, array) as i32,
        ),
    );

    for i in 2..aux {
        build.mov(
            luau_reg_tag(ra + 3 + i),
            OperandX64::imm(lua_Type::LUA_TNIL as i32),
        );
    }

    let mut skip_array = Label::default();
    let mut skip_array_nil = Label::default();

    let mut array_loop = Label::default();
    build.set_label(&mut array_loop);
    build.cmp(
        OperandX64::reg(sized(index, SizeX64::dword)),
        mem(
            SizeX64::dword,
            table,
            core::mem::offset_of!(LuaTable, sizearray) as i32,
        ),
    );
    build.jcc(ConditionX64::NotBelow, &mut skip_array);

    build.inc(OperandX64::reg(index));

    build.cmp(
        mem(
            SizeX64::dword,
            elem_ptr,
            core::mem::offset_of!(TValue, tt) as i32,
        ),
        OperandX64::imm(lua_Type::LUA_TNIL as i32),
    );
    build.jcc(ConditionX64::Equal, &mut skip_array_nil);

    build.mov(luau_reg_value(ra + 2), OperandX64::reg(index));

    build.vcvtsi2sd(
        OperandX64::reg(xmm(0)),
        OperandX64::reg(xmm(0)),
        OperandX64::reg(sized(index, SizeX64::dword)),
    );
    build.vmovsd_operand_x_64_operand_x_64(luau_reg_value(ra + 3), OperandX64::reg(xmm(0)));
    build.mov(
        luau_reg_tag(ra + 3),
        OperandX64::imm(lua_Type::LUA_TNUMBER as i32),
    );

    set_luau_reg(build, xmm(2), ra + 4, mem(SizeX64::xmmword, elem_ptr, 0));

    build.jmp_label(loop_repeat);

    build.set_label_label(&mut skip_array_nil);
    build.add(
        OperandX64::reg(elem_ptr),
        OperandX64::imm(core::mem::size_of::<TValue>() as i32),
    );
    build.jmp_label(&mut array_loop);

    build.set_label_label(&mut skip_array);

    if luaur_common::FFlag::LuauCodeGenCallWrapperEmitInst.get() {
        regs.take_reg(table, k_invalid_inst_idx);
        regs.take_reg(index, k_invalid_inst_idx);

        let mut call_wrapper = IrCallWrapperX64::ir_call_wrapper_x_64_ir_call_wrapper_x_64(
            regs,
            build,
            k_invalid_inst_idx,
        );
        call_wrapper.add_argument_size_x_64_operand_x_64_ir_op(
            SizeX64::qword,
            OperandX64::reg(r_state()),
            IrOp::ir_op(),
        );
        call_wrapper.add_argument_size_x_64_operand_x_64_ir_op(
            SizeX64::qword,
            OperandX64::reg(table),
            IrOp::ir_op(),
        );
        call_wrapper.add_argument_size_x_64_operand_x_64_ir_op(
            SizeX64::qword,
            OperandX64::reg(index),
            IrOp::ir_op(),
        );
        call_wrapper.add_argument_size_x_64_operand_x_64_ir_op(
            SizeX64::qword,
            luau_reg_address(ra),
            IrOp::ir_op(),
        );
        call_wrapper
            .call(&native_context_slot(
                core::mem::offset_of!(NativeContext, forgLoopNodeIter) as i32,
            ));
    } else {
        let r_arg1 = if build.abi == ABIX64::Windows {
            RegisterX64::rcx
        } else {
            RegisterX64::rdi
        };
        let r_arg4 = if build.abi == ABIX64::Windows {
            RegisterX64::r9
        } else {
            RegisterX64::rcx
        };

        build.mov(OperandX64::reg(r_arg1), OperandX64::reg(r_state()));
        build.lea_operand_x_64_operand_x_64(OperandX64::reg(r_arg4), luau_reg_address(ra));
        build.call_operand_x_64(native_context_slot(core::mem::offset_of!(
            NativeContext,
            forgLoopNodeIter
        ) as i32));
    }

    build.test(
        OperandX64::reg(sized(RegisterX64::rax, SizeX64::byte)),
        OperandX64::reg(sized(RegisterX64::rax, SizeX64::byte)),
    );
    build.jcc(ConditionX64::NotZero, loop_repeat);
}

const K_TVALUE_SIZE_LOG2: i32 = 4;

const fn reg(index: u8, size: SizeX64) -> RegisterX64 {
    RegisterX64 {
        bits: (index << RegisterX64::INDEX_SHIFT) | size as u8,
    }
}

const fn sized(reg: RegisterX64, size: SizeX64) -> RegisterX64 {
    RegisterX64 {
        bits: (reg.index() << RegisterX64::INDEX_SHIFT) | size as u8,
    }
}

const fn xmm(index: u8) -> RegisterX64 {
    reg(index, SizeX64::xmmword)
}

const fn r_state() -> RegisterX64 {
    reg(15, SizeX64::qword)
}

const fn r_native_context() -> RegisterX64 {
    reg(13, SizeX64::qword)
}

fn mem(size: SizeX64, base: RegisterX64, disp: i32) -> OperandX64 {
    OperandX64::mem(size, RegisterX64::noreg, 1, base, disp)
}

fn native_context_slot(disp: i32) -> OperandX64 {
    mem(SizeX64::qword, r_native_context(), disp)
}