mirror of
https://github.com/luau-lang/luau.git
synced 2025-01-07 11:59:11 +00:00
926 lines
28 KiB
C++
926 lines
28 KiB
C++
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||
|
#include "EmitInstructionX64.h"
|
||
|
|
||
|
#include "Luau/AssemblyBuilderX64.h"
|
||
|
|
||
|
#include "CustomExecUtils.h"
|
||
|
#include "EmitBuiltinsX64.h"
|
||
|
#include "EmitCommonX64.h"
|
||
|
#include "NativeState.h"
|
||
|
|
||
|
#include "lobject.h"
|
||
|
#include "ltm.h"
|
||
|
|
||
|
namespace Luau
|
||
|
{
|
||
|
namespace CodeGen
|
||
|
{
|
||
|
|
||
|
void emitInstLoadNil(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
|
||
|
build.mov(luauRegTag(ra), LUA_TNIL);
|
||
|
}
|
||
|
|
||
|
void emitInstLoadB(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
|
||
|
build.mov(luauRegValue(ra), LUAU_INSN_B(*pc));
|
||
|
build.mov(luauRegTag(ra), LUA_TBOOLEAN);
|
||
|
|
||
|
if (int target = LUAU_INSN_C(*pc))
|
||
|
build.jmp(labelarr[pcpos + target + 1]);
|
||
|
}
|
||
|
|
||
|
void emitInstLoadN(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
|
||
|
build.vmovsd(xmm0, build.f64(double(LUAU_INSN_D(*pc))));
|
||
|
build.vmovsd(luauRegValue(ra), xmm0);
|
||
|
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||
|
}
|
||
|
|
||
|
void emitInstLoadK(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
|
||
|
build.vmovups(xmm0, luauConstant(LUAU_INSN_D(*pc)));
|
||
|
build.vmovups(luauReg(ra), xmm0);
|
||
|
}
|
||
|
|
||
|
void emitInstMove(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = LUAU_INSN_B(*pc);
|
||
|
|
||
|
build.vmovups(xmm0, luauReg(rb));
|
||
|
build.vmovups(luauReg(ra), xmm0);
|
||
|
}
|
||
|
|
||
|
void emitInstJump(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
build.jmp(labelarr[pcpos + LUAU_INSN_D(*pc) + 1]);
|
||
|
}
|
||
|
|
||
|
void emitInstJumpBack(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
emitInterrupt(build, pcpos);
|
||
|
|
||
|
build.jmp(labelarr[pcpos + LUAU_INSN_D(*pc) + 1]);
|
||
|
}
|
||
|
|
||
|
void emitInstJumpIf(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr, bool not_)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
|
||
|
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||
|
Label& exit = labelarr[pcpos + 1];
|
||
|
|
||
|
if (not_)
|
||
|
jumpIfFalsy(build, ra, target, exit);
|
||
|
else
|
||
|
jumpIfTruthy(build, ra, target, exit);
|
||
|
}
|
||
|
|
||
|
void emitInstJumpIfEq(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr, bool not_)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = pc[1];
|
||
|
|
||
|
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||
|
Label& exit = labelarr[pcpos + 2];
|
||
|
Label any;
|
||
|
|
||
|
build.mov(eax, luauRegTag(ra));
|
||
|
build.cmp(eax, luauRegTag(rb));
|
||
|
build.jcc(Condition::NotEqual, not_ ? target : exit);
|
||
|
|
||
|
// fast-path: number
|
||
|
build.cmp(eax, LUA_TNUMBER);
|
||
|
build.jcc(Condition::NotEqual, any);
|
||
|
|
||
|
jumpOnNumberCmp(build, xmm0, luauRegValue(ra), luauRegValue(rb), Condition::NotEqual, not_ ? target : exit);
|
||
|
build.jmp(not_ ? exit : target);
|
||
|
|
||
|
// slow-path
|
||
|
// TODO: move to the end of the function
|
||
|
build.setLabel(any);
|
||
|
jumpOnAnyCmpFallback(build, ra, rb, not_ ? Condition::NotEqual : Condition::Equal, target, pcpos);
|
||
|
}
|
||
|
|
||
|
void emitInstJumpIfCond(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr, Condition cond)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = pc[1];
|
||
|
|
||
|
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||
|
Label& exit = labelarr[pcpos + 2];
|
||
|
Label any;
|
||
|
|
||
|
// fast-path: number
|
||
|
jumpIfTagIsNot(build, ra, LUA_TNUMBER, any);
|
||
|
jumpIfTagIsNot(build, rb, LUA_TNUMBER, any);
|
||
|
|
||
|
jumpOnNumberCmp(build, xmm0, luauRegValue(ra), luauRegValue(rb), cond, target);
|
||
|
build.jmp(exit);
|
||
|
|
||
|
// slow-path
|
||
|
// TODO: move to the end of the function
|
||
|
build.setLabel(any);
|
||
|
jumpOnAnyCmpFallback(build, ra, rb, cond, target, pcpos);
|
||
|
}
|
||
|
|
||
|
void emitInstJumpX(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
emitInterrupt(build, pcpos);
|
||
|
|
||
|
build.jmp(labelarr[pcpos + LUAU_INSN_E(*pc) + 1]);
|
||
|
}
|
||
|
|
||
|
void emitInstJumpxEqNil(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
bool not_ = (pc[1] & 0x80000000) != 0;
|
||
|
|
||
|
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||
|
|
||
|
build.cmp(luauRegTag(ra), LUA_TNIL);
|
||
|
build.jcc(not_ ? Condition::NotEqual : Condition::Equal, target);
|
||
|
}
|
||
|
|
||
|
void emitInstJumpxEqB(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
uint32_t aux = pc[1];
|
||
|
bool not_ = (aux & 0x80000000) != 0;
|
||
|
|
||
|
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||
|
Label& exit = labelarr[pcpos + 2];
|
||
|
|
||
|
jumpIfTagIsNot(build, ra, LUA_TBOOLEAN, not_ ? target : exit);
|
||
|
|
||
|
build.test(luauRegValueBoolean(ra), 1);
|
||
|
build.jcc((aux & 0x1) ^ not_ ? Condition::NotZero : Condition::Zero, target);
|
||
|
}
|
||
|
|
||
|
void emitInstJumpxEqN(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
uint32_t aux = pc[1];
|
||
|
bool not_ = (aux & 0x80000000) != 0;
|
||
|
TValue kv = k[aux & 0xffffff];
|
||
|
|
||
|
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||
|
Label& exit = labelarr[pcpos + 2];
|
||
|
|
||
|
jumpIfTagIsNot(build, ra, LUA_TNUMBER, not_ ? target : exit);
|
||
|
|
||
|
if (not_)
|
||
|
{
|
||
|
jumpOnNumberCmp(build, xmm0, luauRegValue(ra), build.f64(kv.value.n), Condition::NotEqual, target);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Compact equality check requires two labels, so it's not supported in generic 'jumpOnNumberCmp'
|
||
|
build.vmovsd(xmm0, luauRegValue(ra));
|
||
|
build.vucomisd(xmm0, build.f64(kv.value.n));
|
||
|
build.jcc(Condition::Parity, exit); // We first have to check PF=1 for NaN operands, because it also sets ZF=1
|
||
|
build.jcc(Condition::Zero, target); // Now that NaN is out of the way, we can check ZF=1 for equality
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void emitInstJumpxEqS(AssemblyBuilderX64& build, NativeState& data, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
uint32_t aux = pc[1];
|
||
|
bool not_ = (aux & 0x80000000) != 0;
|
||
|
|
||
|
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||
|
Label& exit = labelarr[pcpos + 2];
|
||
|
|
||
|
jumpIfTagIsNot(build, ra, LUA_TSTRING, not_ ? target : exit);
|
||
|
|
||
|
build.mov(rax, luauRegValue(ra));
|
||
|
build.cmp(rax, luauConstantValue(aux & 0xffffff));
|
||
|
build.jcc(not_ ? Condition::NotEqual : Condition::Equal, target);
|
||
|
}
|
||
|
|
||
|
static void emitInstBinaryNumeric(AssemblyBuilderX64& build, int ra, int rb, int rc, OperandX64 opc, int pcpos, TMS tm)
|
||
|
{
|
||
|
Label common, exit;
|
||
|
|
||
|
jumpIfTagIsNot(build, rb, LUA_TNUMBER, common);
|
||
|
|
||
|
if (rc != -1 && rc != rb)
|
||
|
jumpIfTagIsNot(build, rc, LUA_TNUMBER, common);
|
||
|
|
||
|
// fast-path: number
|
||
|
build.vmovsd(xmm0, luauRegValue(rb));
|
||
|
|
||
|
switch (tm)
|
||
|
{
|
||
|
case TM_ADD:
|
||
|
build.vaddsd(xmm0, xmm0, opc);
|
||
|
break;
|
||
|
case TM_SUB:
|
||
|
build.vsubsd(xmm0, xmm0, opc);
|
||
|
break;
|
||
|
case TM_MUL:
|
||
|
build.vmulsd(xmm0, xmm0, opc);
|
||
|
break;
|
||
|
case TM_DIV:
|
||
|
build.vdivsd(xmm0, xmm0, opc);
|
||
|
break;
|
||
|
case TM_MOD:
|
||
|
// This follows the implementation of 'luai_nummod' which is less precise than 'fmod' for better performance
|
||
|
build.vmovsd(xmm1, opc);
|
||
|
build.vdivsd(xmm2, xmm0, xmm1);
|
||
|
build.vroundsd(xmm2, xmm2, xmm2, RoundingModeX64::RoundToNegativeInfinity);
|
||
|
build.vmulsd(xmm1, xmm2, xmm1);
|
||
|
build.vsubsd(xmm0, xmm0, xmm1);
|
||
|
break;
|
||
|
case TM_POW:
|
||
|
build.vmovsd(xmm1, luauRegValue(rc));
|
||
|
build.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]);
|
||
|
break;
|
||
|
default:
|
||
|
LUAU_ASSERT(!"unsupported binary op");
|
||
|
}
|
||
|
|
||
|
build.vmovsd(luauRegValue(ra), xmm0);
|
||
|
|
||
|
if (ra != rb && ra != rc)
|
||
|
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||
|
|
||
|
build.jmp(exit);
|
||
|
|
||
|
// slow-path
|
||
|
// TODO: move to the end of the function
|
||
|
build.setLabel(common);
|
||
|
callArithHelper(build, ra, rb, opc, pcpos, tm);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
|
||
|
void emitInstAdd(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_ADD);
|
||
|
}
|
||
|
|
||
|
void emitInstSub(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_SUB);
|
||
|
}
|
||
|
|
||
|
void emitInstMul(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_MUL);
|
||
|
}
|
||
|
|
||
|
void emitInstDiv(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_DIV);
|
||
|
}
|
||
|
|
||
|
void emitInstMod(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_MOD);
|
||
|
}
|
||
|
|
||
|
void emitInstPow(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), luauRegValue(LUAU_INSN_C(*pc)), pcpos, TM_POW);
|
||
|
}
|
||
|
|
||
|
void emitInstAddK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_ADD);
|
||
|
}
|
||
|
|
||
|
void emitInstSubK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_SUB);
|
||
|
}
|
||
|
|
||
|
void emitInstMulK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_MUL);
|
||
|
}
|
||
|
|
||
|
void emitInstDivK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_DIV);
|
||
|
}
|
||
|
|
||
|
void emitInstModK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||
|
{
|
||
|
emitInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_MOD);
|
||
|
}
|
||
|
|
||
|
void emitInstPowK(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = LUAU_INSN_B(*pc);
|
||
|
double kv = nvalue(&k[LUAU_INSN_C(*pc)]);
|
||
|
|
||
|
Label common, exit;
|
||
|
|
||
|
jumpIfTagIsNot(build, rb, LUA_TNUMBER, common);
|
||
|
|
||
|
// fast-path: number
|
||
|
build.vmovsd(xmm0, luauRegValue(rb));
|
||
|
|
||
|
// Specialize for a few constants, similar to how it's done in the VM
|
||
|
if (kv == 2.0)
|
||
|
{
|
||
|
build.vmulsd(xmm0, xmm0, xmm0);
|
||
|
}
|
||
|
else if (kv == 0.5)
|
||
|
{
|
||
|
build.vsqrtsd(xmm0, xmm0, xmm0);
|
||
|
}
|
||
|
else if (kv == 3.0)
|
||
|
{
|
||
|
build.vmulsd(xmm1, xmm0, xmm0);
|
||
|
build.vmulsd(xmm0, xmm0, xmm1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
build.vmovsd(xmm1, build.f64(kv));
|
||
|
build.call(qword[rNativeContext + offsetof(NativeContext, libm_pow)]);
|
||
|
}
|
||
|
|
||
|
build.vmovsd(luauRegValue(ra), xmm0);
|
||
|
|
||
|
if (ra != rb)
|
||
|
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||
|
|
||
|
build.jmp(exit);
|
||
|
|
||
|
// slow-path
|
||
|
// TODO: move to the end of the function
|
||
|
build.setLabel(common);
|
||
|
callArithHelper(build, ra, rb, luauConstantValue(LUAU_INSN_C(*pc)), pcpos, TM_POW);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
|
||
|
void emitInstNot(AssemblyBuilderX64& build, const Instruction* pc)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = LUAU_INSN_B(*pc);
|
||
|
|
||
|
Label saveone, savezero, exit;
|
||
|
|
||
|
jumpIfFalsy(build, rb, saveone, savezero);
|
||
|
|
||
|
build.setLabel(savezero);
|
||
|
build.mov(luauRegValueBoolean(ra), 0);
|
||
|
build.jmp(exit);
|
||
|
|
||
|
build.setLabel(saveone);
|
||
|
build.mov(luauRegValueBoolean(ra), 1);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
build.mov(luauRegTag(ra), LUA_TBOOLEAN);
|
||
|
}
|
||
|
|
||
|
void emitInstMinus(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = LUAU_INSN_B(*pc);
|
||
|
|
||
|
Label any, exit;
|
||
|
|
||
|
jumpIfTagIsNot(build, rb, LUA_TNUMBER, any);
|
||
|
|
||
|
// fast-path: number
|
||
|
build.vxorpd(xmm0, xmm0, xmm0);
|
||
|
build.vsubsd(xmm0, xmm0, luauRegValue(rb));
|
||
|
build.vmovsd(luauRegValue(ra), xmm0);
|
||
|
|
||
|
if (ra != rb)
|
||
|
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||
|
|
||
|
build.jmp(exit);
|
||
|
|
||
|
// slow-path
|
||
|
// TODO: move to the end of the function
|
||
|
build.setLabel(any);
|
||
|
callArithHelper(build, ra, rb, luauRegValue(rb), pcpos, TM_UNM);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
|
||
|
void emitInstLength(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = LUAU_INSN_B(*pc);
|
||
|
|
||
|
Label any, exit;
|
||
|
|
||
|
jumpIfTagIsNot(build, rb, LUA_TTABLE, any);
|
||
|
|
||
|
// fast-path: table without __len
|
||
|
build.mov(rArg1, luauRegValue(rb));
|
||
|
jumpIfMetatablePresent(build, rArg1, any);
|
||
|
|
||
|
// First argument (Table*) is already in rArg1
|
||
|
build.call(qword[rNativeContext + offsetof(NativeContext, luaH_getn)]);
|
||
|
|
||
|
build.vcvtsi2sd(xmm0, xmm0, eax);
|
||
|
build.vmovsd(luauRegValue(ra), xmm0);
|
||
|
build.mov(luauRegTag(ra), LUA_TNUMBER);
|
||
|
build.jmp(exit);
|
||
|
|
||
|
// slow-path
|
||
|
// TODO: move to the end of the function
|
||
|
build.setLabel(any);
|
||
|
callLengthHelper(build, ra, rb, pcpos);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
|
||
|
void emitInstGetUpval(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int up = LUAU_INSN_B(*pc);
|
||
|
|
||
|
build.mov(rax, sClosure);
|
||
|
build.add(rax, offsetof(Closure, l.uprefs) + sizeof(TValue) * up);
|
||
|
|
||
|
// uprefs[] is either an actual value, or it points to UpVal object which has a pointer to value
|
||
|
Label skip;
|
||
|
// TODO: jumpIfTagIsNot can be generalized to take OperandX64 and then we can use it here; let's wait until we see this more though
|
||
|
build.cmp(dword[rax + offsetof(TValue, tt)], LUA_TUPVAL);
|
||
|
build.jcc(Condition::NotEqual, skip);
|
||
|
|
||
|
// UpVal.v points to the value (either on stack, or on heap inside each UpVal, but we can deref it unconditionally)
|
||
|
build.mov(rax, qword[rax + offsetof(TValue, value.gc)]);
|
||
|
build.mov(rax, qword[rax + offsetof(UpVal, v)]);
|
||
|
|
||
|
build.setLabel(skip);
|
||
|
|
||
|
build.vmovups(xmm0, xmmword[rax]);
|
||
|
build.vmovups(luauReg(ra), xmm0);
|
||
|
}
|
||
|
|
||
|
static void emitInstFastCallN(
|
||
|
AssemblyBuilderX64& build, const Instruction* pc, bool customParams, int customParamCount, OperandX64 customArgs, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
int bfid = LUAU_INSN_A(*pc);
|
||
|
int skip = LUAU_INSN_C(*pc);
|
||
|
|
||
|
Instruction call = pc[skip + 1];
|
||
|
LUAU_ASSERT(LUAU_INSN_OP(call) == LOP_CALL);
|
||
|
int ra = LUAU_INSN_A(call);
|
||
|
|
||
|
int nparams = customParams ? customParamCount : LUAU_INSN_B(call) - 1;
|
||
|
int nresults = LUAU_INSN_C(call) - 1;
|
||
|
int arg = customParams ? LUAU_INSN_B(*pc) : ra + 1;
|
||
|
OperandX64 args = customParams ? customArgs : luauRegValue(ra + 2);
|
||
|
|
||
|
Label exit;
|
||
|
|
||
|
jumpIfUnsafeEnv(build, rax, exit);
|
||
|
|
||
|
BuiltinImplResult br = emitBuiltin(build, LuauBuiltinFunction(bfid), nparams, ra, arg, args, nresults, exit);
|
||
|
|
||
|
if (br.type == BuiltinImplType::UsesFallback)
|
||
|
{
|
||
|
if (nresults == LUA_MULTRET)
|
||
|
{
|
||
|
// L->top = ra + n;
|
||
|
build.lea(rax, qword[rBase + (ra + br.actualResultCount) * sizeof(TValue)]);
|
||
|
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||
|
}
|
||
|
else if (nparams == LUA_MULTRET)
|
||
|
{
|
||
|
// L->top = L->ci->top;
|
||
|
build.mov(rax, qword[rState + offsetof(lua_State, ci)]);
|
||
|
build.mov(rax, qword[rax + offsetof(CallInfo, top)]);
|
||
|
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||
|
}
|
||
|
|
||
|
// TODO: once we start outlining the fallback, we will be able to fallthrough to the next instruction
|
||
|
build.jmp(labelarr[pcpos + skip + 2]);
|
||
|
build.setLabel(exit);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// TODO: we can skip saving pc for some well-behaved builtins which we didn't inline
|
||
|
emitSetSavedPc(build, pcpos); // uses rax/rdx
|
||
|
|
||
|
build.mov(rax, qword[rNativeContext + offsetof(NativeContext, luauF_table) + bfid * sizeof(luau_FastFunction)]);
|
||
|
|
||
|
// 5th parameter (args) is left unset for LOP_FASTCALL1
|
||
|
if (args.cat == CategoryX64::mem)
|
||
|
{
|
||
|
if (getCurrentX64ABI() == X64ABI::Windows)
|
||
|
{
|
||
|
build.lea(rcx, args);
|
||
|
build.mov(sArg5, rcx);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
build.lea(rArg5, args);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (nparams == LUA_MULTRET)
|
||
|
{
|
||
|
// TODO: for SystemV ABI we can compute the result directly into rArg6
|
||
|
// L->top - (ra + 1)
|
||
|
build.mov(rcx, qword[rState + offsetof(lua_State, top)]);
|
||
|
build.lea(rdx, qword[rBase + (ra + 1) * sizeof(TValue)]);
|
||
|
build.sub(rcx, rdx);
|
||
|
build.shr(rcx, kTValueSizeLog2);
|
||
|
|
||
|
if (getCurrentX64ABI() == X64ABI::Windows)
|
||
|
build.mov(sArg6, rcx);
|
||
|
else
|
||
|
build.mov(rArg6, rcx);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (getCurrentX64ABI() == X64ABI::Windows)
|
||
|
build.mov(sArg6, nparams);
|
||
|
else
|
||
|
build.mov(rArg6, nparams);
|
||
|
}
|
||
|
|
||
|
build.mov(rArg1, rState);
|
||
|
build.lea(rArg2, luauRegValue(ra));
|
||
|
build.lea(rArg3, luauRegValue(arg));
|
||
|
build.mov(rArg4, nresults);
|
||
|
|
||
|
build.call(rax);
|
||
|
|
||
|
build.test(eax, eax); // test here will set SF=1 for a negative number and it always sets OF to 0
|
||
|
build.jcc(Condition::Less, exit); // jl jumps if SF != OF
|
||
|
|
||
|
if (nresults == LUA_MULTRET)
|
||
|
{
|
||
|
// L->top = ra + n;
|
||
|
build.shl(rax, kTValueSizeLog2);
|
||
|
build.lea(rax, qword[rBase + rax + ra * sizeof(TValue)]);
|
||
|
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||
|
}
|
||
|
else if (nparams == LUA_MULTRET)
|
||
|
{
|
||
|
// L->top = L->ci->top;
|
||
|
build.mov(rax, qword[rState + offsetof(lua_State, ci)]);
|
||
|
build.mov(rax, qword[rax + offsetof(CallInfo, top)]);
|
||
|
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||
|
}
|
||
|
|
||
|
build.jmp(labelarr[pcpos + skip + 2]);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
|
||
|
// TODO: fallback to LOP_CALL after a fast call should be outlined
|
||
|
}
|
||
|
|
||
|
void emitInstFastCall1(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
emitInstFastCallN(build, pc, /* customParams */ true, /* customParamCount */ 1, /* customArgs */ 0, pcpos, labelarr);
|
||
|
}
|
||
|
|
||
|
void emitInstFastCall2(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
emitInstFastCallN(build, pc, /* customParams */ true, /* customParamCount */ 2, /* customArgs */ luauRegValue(pc[1]), pcpos, labelarr);
|
||
|
}
|
||
|
|
||
|
void emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, const TValue* k, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
emitInstFastCallN(build, pc, /* customParams */ true, /* customParamCount */ 2, /* customArgs */ luauConstantValue(pc[1]), pcpos, labelarr);
|
||
|
}
|
||
|
|
||
|
void emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
emitInstFastCallN(build, pc, /* customParams */ false, /* customParamCount */ 0, /* customArgs */ 0, pcpos, labelarr);
|
||
|
}
|
||
|
|
||
|
void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
Label& loopExit = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||
|
|
||
|
Label tryConvert, exit;
|
||
|
|
||
|
jumpIfTagIsNot(build, ra + 0, LUA_TNUMBER, tryConvert);
|
||
|
jumpIfTagIsNot(build, ra + 1, LUA_TNUMBER, tryConvert);
|
||
|
jumpIfTagIsNot(build, ra + 2, LUA_TNUMBER, tryConvert);
|
||
|
|
||
|
// After successful conversion of arguments to number, we return here
|
||
|
Label retry = build.setLabel();
|
||
|
|
||
|
RegisterX64 limit = xmm0;
|
||
|
RegisterX64 step = xmm1;
|
||
|
RegisterX64 idx = xmm2;
|
||
|
RegisterX64 zero = xmm3;
|
||
|
|
||
|
build.vxorpd(zero, xmm0, xmm0);
|
||
|
build.vmovsd(limit, luauRegValue(ra + 0));
|
||
|
build.vmovsd(step, luauRegValue(ra + 1));
|
||
|
build.vmovsd(idx, luauRegValue(ra + 2));
|
||
|
|
||
|
Label reverse;
|
||
|
|
||
|
// step <= 0
|
||
|
jumpOnNumberCmp(build, noreg, step, zero, Condition::LessEqual, reverse);
|
||
|
|
||
|
// TODO: target branches can probably be arranged better, but we need tests for NaN behavior preservation
|
||
|
// false: idx <= limit
|
||
|
jumpOnNumberCmp(build, noreg, idx, limit, Condition::LessEqual, exit);
|
||
|
build.jmp(loopExit);
|
||
|
|
||
|
// true: limit <= idx
|
||
|
build.setLabel(reverse);
|
||
|
jumpOnNumberCmp(build, noreg, limit, idx, Condition::LessEqual, exit);
|
||
|
build.jmp(loopExit);
|
||
|
|
||
|
// TOOD: place at the end of the function
|
||
|
build.setLabel(tryConvert);
|
||
|
callPrepareForN(build, ra + 0, ra + 1, ra + 2, pcpos);
|
||
|
build.jmp(retry);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
|
||
|
void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||
|
{
|
||
|
emitInterrupt(build, pcpos);
|
||
|
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
Label& loopRepeat = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||
|
|
||
|
RegisterX64 limit = xmm0;
|
||
|
RegisterX64 step = xmm1;
|
||
|
RegisterX64 idx = xmm2;
|
||
|
RegisterX64 zero = xmm3;
|
||
|
|
||
|
build.vxorpd(zero, xmm0, xmm0);
|
||
|
build.vmovsd(limit, luauRegValue(ra + 0));
|
||
|
build.vmovsd(step, luauRegValue(ra + 1));
|
||
|
build.vmovsd(idx, luauRegValue(ra + 2));
|
||
|
build.vaddsd(idx, idx, step);
|
||
|
build.vmovsd(luauRegValue(ra + 2), idx);
|
||
|
|
||
|
Label reverse, exit;
|
||
|
|
||
|
// step <= 0
|
||
|
jumpOnNumberCmp(build, noreg, step, zero, Condition::LessEqual, reverse);
|
||
|
|
||
|
// false: idx <= limit
|
||
|
jumpOnNumberCmp(build, noreg, idx, limit, Condition::LessEqual, loopRepeat);
|
||
|
build.jmp(exit);
|
||
|
|
||
|
// true: limit <= idx
|
||
|
build.setLabel(reverse);
|
||
|
jumpOnNumberCmp(build, noreg, limit, idx, Condition::LessEqual, loopRepeat);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
|
||
|
static void emitInstAndX(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c)
|
||
|
{
|
||
|
Label target, fallthrough;
|
||
|
jumpIfFalsy(build, rb, target, fallthrough);
|
||
|
|
||
|
build.setLabel(fallthrough);
|
||
|
|
||
|
build.vmovups(xmm0, c);
|
||
|
build.vmovups(luauReg(ra), xmm0);
|
||
|
|
||
|
if (ra == rb)
|
||
|
{
|
||
|
build.setLabel(target);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Label exit;
|
||
|
build.jmp(exit);
|
||
|
|
||
|
build.setLabel(target);
|
||
|
|
||
|
build.vmovups(xmm0, luauReg(rb));
|
||
|
build.vmovups(luauReg(ra), xmm0);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void emitInstAnd(AssemblyBuilderX64& build, const Instruction* pc)
|
||
|
{
|
||
|
emitInstAndX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauReg(LUAU_INSN_C(*pc)));
|
||
|
}
|
||
|
|
||
|
void emitInstAndK(AssemblyBuilderX64& build, const Instruction* pc)
|
||
|
{
|
||
|
emitInstAndX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauConstant(LUAU_INSN_C(*pc)));
|
||
|
}
|
||
|
|
||
|
static void emitInstOrX(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c)
|
||
|
{
|
||
|
Label target, fallthrough;
|
||
|
jumpIfTruthy(build, rb, target, fallthrough);
|
||
|
|
||
|
build.setLabel(fallthrough);
|
||
|
|
||
|
build.vmovups(xmm0, c);
|
||
|
build.vmovups(luauReg(ra), xmm0);
|
||
|
|
||
|
if (ra == rb)
|
||
|
{
|
||
|
build.setLabel(target);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Label exit;
|
||
|
build.jmp(exit);
|
||
|
|
||
|
build.setLabel(target);
|
||
|
|
||
|
build.vmovups(xmm0, luauReg(rb));
|
||
|
build.vmovups(luauReg(ra), xmm0);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void emitInstOr(AssemblyBuilderX64& build, const Instruction* pc)
|
||
|
{
|
||
|
emitInstOrX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauReg(LUAU_INSN_C(*pc)));
|
||
|
}
|
||
|
|
||
|
void emitInstOrK(AssemblyBuilderX64& build, const Instruction* pc)
|
||
|
{
|
||
|
emitInstOrX(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), luauConstant(LUAU_INSN_C(*pc)));
|
||
|
}
|
||
|
|
||
|
void emitInstGetTableN(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = LUAU_INSN_B(*pc);
|
||
|
int c = LUAU_INSN_C(*pc);
|
||
|
|
||
|
Label fallback, exit;
|
||
|
|
||
|
jumpIfTagIsNot(build, rb, LUA_TTABLE, fallback);
|
||
|
|
||
|
RegisterX64 table = rcx;
|
||
|
build.mov(table, luauRegValue(rb));
|
||
|
|
||
|
// unsigned(c) < unsigned(h->sizearray)
|
||
|
build.cmp(dword[table + offsetof(Table, sizearray)], c);
|
||
|
build.jcc(Condition::BelowEqual, fallback);
|
||
|
|
||
|
jumpIfMetatablePresent(build, table, fallback);
|
||
|
|
||
|
build.mov(rax, qword[table + offsetof(Table, array)]);
|
||
|
setLuauReg(build, xmm0, ra, xmmword[rax + c * sizeof(TValue)]);
|
||
|
|
||
|
build.jmp(exit);
|
||
|
|
||
|
// slow-path
|
||
|
// TODO: move to the end of the function
|
||
|
build.setLabel(fallback);
|
||
|
TValue n;
|
||
|
setnvalue(&n, c + 1);
|
||
|
callGetTable(build, rb, build.bytes(&n, sizeof(n)), ra, pcpos);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
|
||
|
void emitInstSetTableN(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = LUAU_INSN_B(*pc);
|
||
|
int c = LUAU_INSN_C(*pc);
|
||
|
|
||
|
Label fallback, exit;
|
||
|
|
||
|
jumpIfTagIsNot(build, rb, LUA_TTABLE, fallback);
|
||
|
|
||
|
RegisterX64 table = rcx;
|
||
|
build.mov(table, luauRegValue(rb));
|
||
|
|
||
|
// unsigned(c) < unsigned(h->sizearray)
|
||
|
build.cmp(dword[table + offsetof(Table, sizearray)], c);
|
||
|
build.jcc(Condition::BelowEqual, fallback);
|
||
|
|
||
|
jumpIfMetatablePresent(build, table, fallback);
|
||
|
jumpIfTableIsReadOnly(build, table, fallback);
|
||
|
|
||
|
// setobj2t(L, &h->array[c], ra);
|
||
|
build.mov(rax, qword[table + offsetof(Table, array)]);
|
||
|
build.vmovups(xmm0, luauReg(ra));
|
||
|
build.vmovups(xmmword[rax + c * sizeof(TValue)], xmm0);
|
||
|
|
||
|
callBarrierTable(build, rax, table, ra, exit);
|
||
|
build.jmp(exit);
|
||
|
|
||
|
// slow-path
|
||
|
// TODO: move to the end of the function
|
||
|
build.setLabel(fallback);
|
||
|
TValue n;
|
||
|
setnvalue(&n, c + 1);
|
||
|
callSetTable(build, rb, build.bytes(&n, sizeof(n)), ra, pcpos);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
|
||
|
void emitInstGetTable(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = LUAU_INSN_B(*pc);
|
||
|
int rc = LUAU_INSN_C(*pc);
|
||
|
|
||
|
Label fallback, exit;
|
||
|
|
||
|
jumpIfTagIsNot(build, rb, LUA_TTABLE, fallback);
|
||
|
jumpIfTagIsNot(build, rc, LUA_TNUMBER, fallback);
|
||
|
|
||
|
// fast-path: table with a number index
|
||
|
RegisterX64 table = rcx;
|
||
|
build.mov(table, luauRegValue(rb));
|
||
|
|
||
|
convertNumberToIndexOrJump(build, xmm1, xmm0, eax, rc, fallback);
|
||
|
|
||
|
// index - 1
|
||
|
build.dec(eax);
|
||
|
|
||
|
// unsigned(index - 1) < unsigned(h->sizearray)
|
||
|
build.cmp(dword[table + offsetof(Table, sizearray)], eax);
|
||
|
build.jcc(Condition::BelowEqual, fallback);
|
||
|
|
||
|
jumpIfMetatablePresent(build, table, fallback);
|
||
|
|
||
|
// setobj2s(L, ra, &h->array[unsigned(index - 1)]);
|
||
|
build.mov(rdx, qword[table + offsetof(Table, array)]);
|
||
|
build.shl(eax, kTValueSizeLog2);
|
||
|
setLuauReg(build, xmm0, ra, xmmword[rdx + rax]);
|
||
|
|
||
|
build.jmp(exit);
|
||
|
|
||
|
build.setLabel(fallback);
|
||
|
|
||
|
// slow-path
|
||
|
// TODO: move to the end of the function
|
||
|
callGetTable(build, rb, luauRegValue(rc), ra, pcpos);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
|
||
|
void emitInstSetTable(AssemblyBuilderX64& build, const Instruction* pc, int pcpos)
|
||
|
{
|
||
|
int ra = LUAU_INSN_A(*pc);
|
||
|
int rb = LUAU_INSN_B(*pc);
|
||
|
int rc = LUAU_INSN_C(*pc);
|
||
|
|
||
|
Label fallback, exit;
|
||
|
|
||
|
jumpIfTagIsNot(build, rb, LUA_TTABLE, fallback);
|
||
|
jumpIfTagIsNot(build, rc, LUA_TNUMBER, fallback);
|
||
|
|
||
|
// fast-path: table with a number index
|
||
|
RegisterX64 table = rcx;
|
||
|
build.mov(table, luauRegValue(rb));
|
||
|
|
||
|
convertNumberToIndexOrJump(build, xmm1, xmm0, eax, rc, fallback);
|
||
|
|
||
|
// index - 1
|
||
|
build.dec(eax);
|
||
|
|
||
|
// unsigned(index - 1) < unsigned(h->sizearray)
|
||
|
build.cmp(dword[table + offsetof(Table, sizearray)], eax);
|
||
|
build.jcc(Condition::BelowEqual, fallback);
|
||
|
|
||
|
jumpIfMetatablePresent(build, table, fallback);
|
||
|
jumpIfTableIsReadOnly(build, table, fallback);
|
||
|
|
||
|
// setobj2t(L, &h->array[unsigned(index - 1)], ra);
|
||
|
build.mov(rdx, qword[table + offsetof(Table, array)]);
|
||
|
build.shl(eax, kTValueSizeLog2);
|
||
|
build.vmovups(xmm0, luauReg(ra));
|
||
|
build.vmovups(xmmword[rdx + rax], xmm0);
|
||
|
|
||
|
callBarrierTable(build, rdx, table, ra, exit);
|
||
|
build.jmp(exit);
|
||
|
|
||
|
build.setLabel(fallback);
|
||
|
|
||
|
// slow-path
|
||
|
// TODO: move to the end of the function
|
||
|
callSetTable(build, rb, luauRegValue(rc), ra, pcpos);
|
||
|
|
||
|
build.setLabel(exit);
|
||
|
}
|
||
|
|
||
|
} // namespace CodeGen
|
||
|
} // namespace Luau
|