- split JitCompiler into multiple files

This commit is contained in:
Magnus Norddahl 2018-09-13 02:29:04 +02:00
parent 567a069df5
commit ef170883ef
13 changed files with 3638 additions and 3738 deletions

View File

@ -1172,6 +1172,12 @@ set (PCH_SOURCES
scripting/vm/vmexec.cpp
scripting/vm/vmframe.cpp
scripting/vm/jit.cpp
scripting/vm/jit_call.cpp
scripting/vm/jit_flow.cpp
scripting/vm/jit_load.cpp
scripting/vm/jit_math.cpp
scripting/vm/jit_move.cpp
scripting/vm/jit_store.cpp
scripting/zscript/ast.cpp
scripting/zscript/zcc_compile.cpp
scripting/zscript/zcc_parser.cpp

View File

@ -42,7 +42,7 @@ struct VMRemap
};
#define xx(op, name, mode, alt, kreg, ktype) {OP_##alt, kreg, ktype }
#define xx(op, name, mode, alt, kreg, ktype) {OP_##alt, kreg, ktype },
VMRemap opRemap[NUM_OPS] = {
#include "vmops.h"
};

View File

@ -149,7 +149,7 @@
const VMOpInfo OpInfo[NUM_OPS] =
{
#define xx(op, name, mode, alt, kreg, ktype) { #name, mode }
#define xx(op, name, mode, alt, kreg, ktype) { #name, mode },
#include "vmops.h"
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,348 @@
#include "jitintern.h"
void JitCompiler::EmitPARAM()
{
using namespace asmjit;
int index = NumParam++;
ParamOpcodes.Push(pc);
X86Gp stackPtr, tmp;
X86Xmm tmp2;
switch (B)
{
case REGT_NIL:
cc.mov(x86::ptr(params, index * sizeof(VMValue) + offsetof(VMValue, a)), (int64_t)0);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_NIL);
break;
case REGT_INT:
cc.mov(x86::dword_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, i)), regD[C]);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_INT);
break;
case REGT_INT | REGT_ADDROF:
stackPtr = cc.newIntPtr();
cc.mov(stackPtr, frameD);
cc.add(stackPtr, C * sizeof(int32_t));
cc.mov(x86::ptr(params, index * sizeof(VMValue) + offsetof(VMValue, a)), stackPtr);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_POINTER);
break;
case REGT_INT | REGT_KONST:
cc.mov(x86::dword_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, i)), konstd[C]);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_INT);
break;
//case REGT_STRING:
//case REGT_STRING | REGT_ADDROF:
//case REGT_STRING | REGT_KONST:
case REGT_POINTER:
cc.mov(x86::ptr(params, index * sizeof(VMValue) + offsetof(VMValue, a)), regA[C]);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_POINTER);
break;
case REGT_POINTER | REGT_ADDROF:
stackPtr = cc.newIntPtr();
cc.mov(stackPtr, frameA);
cc.add(stackPtr, C * sizeof(void*));
cc.mov(x86::ptr(params, index * sizeof(VMValue) + offsetof(VMValue, a)), stackPtr);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_POINTER);
break;
case REGT_POINTER | REGT_KONST:
tmp = cc.newIntPtr();
cc.mov(tmp, ToMemAddress(konsta[C].v));
cc.mov(x86::ptr(params, index * sizeof(VMValue) + offsetof(VMValue, a)), tmp);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_POINTER);
break;
case REGT_FLOAT:
cc.movsd(x86::qword_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, f)), regF[C]);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_FLOAT);
break;
case REGT_FLOAT | REGT_MULTIREG2:
cc.movsd(x86::qword_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, f)), regF[C]);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_FLOAT);
index = NumParam++;
ParamOpcodes.Push(pc);
cc.movsd(x86::qword_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, f)), regF[C + 1]);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_FLOAT);
break;
case REGT_FLOAT | REGT_MULTIREG3:
cc.movsd(x86::qword_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, f)), regF[C]);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_FLOAT);
index = NumParam++;
ParamOpcodes.Push(pc);
cc.movsd(x86::qword_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, f)), regF[C + 1]);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_FLOAT);
index = NumParam++;
ParamOpcodes.Push(pc);
cc.movsd(x86::qword_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, f)), regF[C + 2]);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_FLOAT);
break;
case REGT_FLOAT | REGT_ADDROF:
stackPtr = cc.newIntPtr();
cc.mov(stackPtr, frameF);
cc.add(stackPtr, C * sizeof(double));
cc.mov(x86::ptr(params, index * sizeof(VMValue) + offsetof(VMValue, a)), stackPtr);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_POINTER);
break;
case REGT_FLOAT | REGT_KONST:
tmp = cc.newIntPtr();
tmp2 = cc.newXmmSd();
cc.mov(tmp, ToMemAddress(konstf + C));
cc.movsd(tmp2, asmjit::x86::qword_ptr(tmp));
cc.movsd(x86::qword_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, f)), tmp2);
cc.mov(x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_FLOAT);
break;
default:
I_FatalError("Unknown REGT value passed to EmitPARAM\n");
break;
}
}
void JitCompiler::EmitPARAMI()
{
int index = NumParam++;
ParamOpcodes.Push(pc);
cc.mov(asmjit::x86::dword_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, i)), (int)ABCs);
cc.mov(asmjit::x86::byte_ptr(params, index * sizeof(VMValue) + offsetof(VMValue, Type)), (int)REGT_INT);
}
void JitCompiler::EmitCALL()
{
EmitDoCall(regA[A]);
}
void JitCompiler::EmitCALL_K()
{
auto ptr = cc.newIntPtr();
cc.mov(ptr, ToMemAddress(konsta[A].o));
EmitDoCall(ptr);
}
void JitCompiler::EmitTAIL()
{
I_FatalError("EmitTAIL not implemented\n");
}
void JitCompiler::EmitTAIL_K()
{
I_FatalError("EmitTAIL_K not implemented\n");
}
void JitCompiler::EmitDoCall(asmjit::X86Gp ptr)
{
using namespace asmjit;
if (NumParam < B)
I_FatalError("OP_CALL parameter count does not match the number of preceding OP_PARAM instructions");
StoreInOuts(B);
FillReturns(pc + 1, C);
X86Gp paramsptr;
if (B != NumParam)
{
paramsptr = cc.newIntPtr();
cc.mov(paramsptr, params);
cc.add(paramsptr, (NumParam - B) * sizeof(VMValue));
}
else
{
paramsptr = params;
}
auto result = cc.newInt32();
auto call = cc.call(ToMemAddress(&JitCompiler::DoCall), FuncSignature7<int, void*, void*, int, int, void*, void*, void*>());
call->setRet(0, result);
call->setArg(0, stack);
call->setArg(1, ptr);
call->setArg(2, asmjit::Imm(B));
call->setArg(3, asmjit::Imm(C));
call->setArg(4, paramsptr);
call->setArg(5, callReturns);
call->setArg(6, exceptInfo);
auto noexception = cc.newLabel();
auto exceptResult = cc.newInt32();
cc.mov(exceptResult, x86::dword_ptr(exceptInfo, 0 * 4));
cc.cmp(exceptResult, (int)-1);
cc.je(noexception);
X86Gp vReg = cc.newInt32();
cc.mov(vReg, 0);
cc.ret(vReg);
cc.bind(noexception);
LoadReturns(pc - B, B, true);
LoadReturns(pc + 1, C, false);
NumParam -= B;
ParamOpcodes.Resize(ParamOpcodes.Size() - B);
}
void JitCompiler::StoreInOuts(int b)
{
using namespace asmjit;
for (unsigned int i = ParamOpcodes.Size() - b; i < ParamOpcodes.Size(); i++)
{
asmjit::X86Gp stackPtr;
switch (ParamOpcodes[i]->b)
{
case REGT_INT | REGT_ADDROF:
stackPtr = cc.newIntPtr();
cc.mov(stackPtr, frameD);
cc.add(stackPtr, C * sizeof(int32_t));
cc.mov(x86::dword_ptr(stackPtr), regD[C]);
break;
//case REGT_STRING | REGT_ADDROF:
// break;
case REGT_POINTER | REGT_ADDROF:
stackPtr = cc.newIntPtr();
cc.mov(stackPtr, frameA);
cc.add(stackPtr, C * sizeof(void*));
cc.mov(x86::ptr(stackPtr), regA[C]);
break;
case REGT_FLOAT | REGT_ADDROF:
stackPtr = cc.newIntPtr();
cc.mov(stackPtr, frameF);
cc.add(stackPtr, C * sizeof(double));
cc.movsd(x86::qword_ptr(stackPtr), regF[C]);
break;
default:
break;
}
}
}
void JitCompiler::LoadReturns(const VMOP *retval, int numret, bool inout)
{
for (int i = 0; i < numret; ++i)
{
if (!inout && retval[i].op != OP_RESULT)
I_FatalError("Expected OP_RESULT to follow OP_CALL\n");
else if (inout && retval[i].op != OP_PARAMI)
continue;
else if (inout && retval[i].op != OP_PARAM)
I_FatalError("Expected OP_PARAM to precede OP_CALL\n");
int type = retval[i].b;
int regnum = retval[i].c;
if (inout && !(type & REGT_ADDROF))
continue;
switch (type & REGT_TYPE)
{
case REGT_INT:
cc.mov(regD[regnum], asmjit::x86::dword_ptr(frameD, regnum * sizeof(int32_t)));
break;
case REGT_FLOAT:
cc.movsd(regF[regnum], asmjit::x86::qword_ptr(frameF, regnum * sizeof(double)));
break;
/*case REGT_STRING:
break;*/
case REGT_POINTER:
cc.mov(regA[regnum], asmjit::x86::ptr(frameA, regnum * sizeof(void*)));
break;
default:
I_FatalError("Unknown OP_RESULT type encountered in LoadReturns\n");
break;
}
}
}
void JitCompiler::FillReturns(const VMOP *retval, int numret)
{
using namespace asmjit;
for (int i = 0; i < numret; ++i)
{
if (retval[i].op != OP_RESULT)
{
I_FatalError("Expected OP_RESULT to follow OP_CALL\n");
}
int type = retval[i].b;
int regnum = retval[i].c;
if (type & REGT_KONST)
{
I_FatalError("OP_RESULT with REGT_KONST is not allowed\n");
}
auto regPtr = cc.newIntPtr();
switch (type & REGT_TYPE)
{
case REGT_INT:
cc.mov(regPtr, frameD);
cc.add(regPtr, regnum * sizeof(int32_t));
break;
case REGT_FLOAT:
cc.mov(regPtr, frameF);
cc.add(regPtr, regnum * sizeof(double));
break;
/*case REGT_STRING:
cc.mov(regPtr, frameS);
cc.add(regPtr, regnum * sizeof(FString));
break;*/
case REGT_POINTER:
cc.mov(regPtr, frameA);
cc.add(regPtr, regnum * sizeof(void*));
break;
default:
I_FatalError("Unknown OP_RESULT type encountered in FillReturns\n");
break;
}
cc.mov(x86::ptr(callReturns, i * sizeof(VMReturn) + offsetof(VMReturn, Location)), regPtr);
cc.mov(x86::byte_ptr(callReturns, i * sizeof(VMReturn) + offsetof(VMReturn, RegType)), type);
}
}
int JitCompiler::DoCall(VMFrameStack *stack, VMFunction *call, int b, int c, VMValue *param, VMReturn *returns, JitExceptionInfo *exceptinfo)
{
try
{
int numret;
if (call->VarFlags & VARF_Native)
{
try
{
VMCycles[0].Unclock();
numret = static_cast<VMNativeFunction *>(call)->NativeCall(param, call->DefaultArgs, b, returns, c);
VMCycles[0].Clock();
}
catch (CVMAbortException &err)
{
err.MaybePrintMessage();
err.stacktrace.AppendFormat("Called from %s\n", call->PrintableName.GetChars());
throw;
}
}
else
{
VMCalls[0]++;
VMScriptFunction *script = static_cast<VMScriptFunction *>(call);
VMFrame *newf = stack->AllocFrame(script);
VMFillParams(param, newf, b);
try
{
numret = VMExec(stack, script->Code, returns, c);
}
catch (...)
{
stack->PopFrame();
throw;
}
stack->PopFrame();
}
return numret;
}
catch (...)
{
// To do: store full exception in exceptinfo
exceptinfo->reason = X_OTHER;
return 0;
}
}

View File

@ -0,0 +1,368 @@
#include "jitintern.h"
void JitCompiler::EmitTEST()
{
int i = (int)(ptrdiff_t)(pc - sfunc->Code);
cc.cmp(regD[A], BC);
cc.jne(labels[i + 2]);
}
void JitCompiler::EmitTESTN()
{
int bc = BC;
int i = (int)(ptrdiff_t)(pc - sfunc->Code);
cc.cmp(regD[A], -bc);
cc.jne(labels[i + 2]);
}
void JitCompiler::EmitJMP()
{
auto dest = pc + JMPOFS(pc) + 1;
int i = (int)(ptrdiff_t)(dest - sfunc->Code);
cc.jmp(labels[i]);
}
void JitCompiler::EmitIJMP()
{
I_FatalError("EmitIJMP not implemented\n");
}
void JitCompiler::EmitVTBL()
{
auto notnull = cc.newLabel();
cc.test(regA[B], regA[B]);
cc.jnz(notnull);
EmitThrowException(X_READ_NIL);
cc.bind(notnull);
auto result = cc.newInt32();
typedef VMFunction*(*FuncPtr)(DObject*, int);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](DObject *o, int c) -> VMFunction* {
auto p = o->GetClass();
assert(c < (int)p->Virtuals.Size());
return p->Virtuals[c];
}))), asmjit::FuncSignature2<void*, void*, int>());
call->setRet(0, result);
call->setArg(0, regA[A]);
call->setArg(1, asmjit::Imm(C));
}
void JitCompiler::EmitSCOPE()
{
auto notnull = cc.newLabel();
cc.test(regA[A], regA[A]);
cc.jnz(notnull);
EmitThrowException(X_READ_NIL);
cc.bind(notnull);
auto f = cc.newIntPtr();
cc.mov(f, ToMemAddress(konsta[C].v));
auto result = cc.newInt32();
typedef int(*FuncPtr)(DObject*, VMFunction*, int);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](DObject *o, VMFunction *f, int b) -> int {
try
{
FScopeBarrier::ValidateCall(o->GetClass(), f, b - 1);
return 1;
}
catch (const CVMAbortException &)
{
// To do: pass along the exception info
return 0;
}
}))), asmjit::FuncSignature3<int, void*, void*, int>());
call->setRet(0, result);
call->setArg(0, regA[A]);
call->setArg(1, f);
call->setArg(2, asmjit::Imm(B));
auto notzero = cc.newLabel();
cc.test(result, result);
cc.jnz(notzero);
EmitThrowException(X_OTHER);
cc.bind(notzero);
}
void JitCompiler::EmitRESULT()
{
// This instruction is just a placeholder to indicate where a return
// value should be stored. It does nothing on its own and should not
// be executed.
}
void JitCompiler::EmitRET()
{
using namespace asmjit;
if (B == REGT_NIL)
{
X86Gp vReg = cc.newInt32();
cc.mov(vReg, 0);
cc.ret(vReg);
}
else
{
int a = A;
int retnum = a & ~RET_FINAL;
X86Gp reg_retnum = cc.newInt32();
X86Gp location = cc.newIntPtr();
Label L_endif = cc.newLabel();
cc.mov(reg_retnum, retnum);
cc.test(reg_retnum, numret);
cc.jg(L_endif);
cc.mov(location, x86::ptr(ret, retnum * sizeof(VMReturn)));
int regtype = B;
int regnum = C;
switch (regtype & REGT_TYPE)
{
case REGT_INT:
if (regtype & REGT_KONST)
cc.mov(x86::dword_ptr(location), konstd[regnum]);
else
cc.mov(x86::dword_ptr(location), regD[regnum]);
break;
case REGT_FLOAT:
if (regtype & REGT_KONST)
{
auto tmp = cc.newInt64();
if (regtype & REGT_MULTIREG3)
{
cc.mov(tmp, (((int64_t *)konstf)[regnum]));
cc.mov(x86::qword_ptr(location), tmp);
cc.mov(tmp, (((int64_t *)konstf)[regnum + 1]));
cc.mov(x86::qword_ptr(location, 8), tmp);
cc.mov(tmp, (((int64_t *)konstf)[regnum + 2]));
cc.mov(x86::qword_ptr(location, 16), tmp);
}
else if (regtype & REGT_MULTIREG2)
{
cc.mov(tmp, (((int64_t *)konstf)[regnum]));
cc.mov(x86::qword_ptr(location), tmp);
cc.mov(tmp, (((int64_t *)konstf)[regnum + 1]));
cc.mov(x86::qword_ptr(location, 8), tmp);
}
else
{
cc.mov(tmp, (((int64_t *)konstf)[regnum]));
cc.mov(x86::qword_ptr(location), tmp);
}
}
else
{
if (regtype & REGT_MULTIREG3)
{
cc.movsd(x86::qword_ptr(location), regF[regnum]);
cc.movsd(x86::qword_ptr(location, 8), regF[regnum + 1]);
cc.movsd(x86::qword_ptr(location, 16), regF[regnum + 2]);
}
else if (regtype & REGT_MULTIREG2)
{
cc.movsd(x86::qword_ptr(location), regF[regnum]);
cc.movsd(x86::qword_ptr(location, 8), regF[regnum + 1]);
}
else
{
cc.movsd(x86::qword_ptr(location), regF[regnum]);
}
}
break;
case REGT_STRING:
break;
case REGT_POINTER:
#ifdef ASMJIT_ARCH_X64
if (regtype & REGT_KONST)
cc.mov(x86::qword_ptr(location), ToMemAddress(konsta[regnum].v));
else
cc.mov(x86::qword_ptr(location), regA[regnum]);
#else
if (regtype & REGT_KONST)
cc.mov(x86::dword_ptr(location), ToMemAddress(konsta[regnum].v));
else
cc.mov(x86::dword_ptr(location), regA[regnum]);
#endif
break;
}
if (a & RET_FINAL)
{
cc.add(reg_retnum, 1);
cc.ret(reg_retnum);
}
cc.bind(L_endif);
if (a & RET_FINAL)
cc.ret(numret);
}
}
void JitCompiler::EmitRETI()
{
using namespace asmjit;
int a = A;
int retnum = a & ~RET_FINAL;
X86Gp reg_retnum = cc.newInt32();
X86Gp location = cc.newIntPtr();
Label L_endif = cc.newLabel();
cc.mov(reg_retnum, retnum);
cc.test(reg_retnum, numret);
cc.jg(L_endif);
cc.mov(location, x86::ptr(ret, retnum * sizeof(VMReturn)));
cc.mov(x86::dword_ptr(location), BCs);
if (a & RET_FINAL)
{
cc.add(reg_retnum, 1);
cc.ret(reg_retnum);
}
cc.bind(L_endif);
if (a & RET_FINAL)
cc.ret(numret);
}
void JitCompiler::EmitNEW()
{
auto result = cc.newIntPtr();
typedef DObject*(*FuncPtr)(PClass*, int);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](PClass *cls, int c) -> DObject* {
try
{
if (!cls->ConstructNative)
{
ThrowAbortException(X_OTHER, "Class %s requires native construction", cls->TypeName.GetChars());
}
else if (cls->bAbstract)
{
ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars());
}
else if (cls->IsDescendantOf(NAME_Actor)) // Creating actors here must be outright prohibited
{
ThrowAbortException(X_OTHER, "Cannot create actors with 'new'");
}
// [ZZ] validate readonly and between scope construction
if (c) FScopeBarrier::ValidateNew(cls, c - 1);
return cls->CreateNew();
}
catch (const CVMAbortException &)
{
// To do: pass along the exception info
return nullptr;
}
}))), asmjit::FuncSignature2<void*, void*, int>());
call->setRet(0, result);
call->setArg(0, regA[B]);
call->setArg(1, asmjit::Imm(C));
auto notnull = cc.newLabel();
cc.test(result, result);
cc.jnz(notnull);
EmitThrowException(X_OTHER);
cc.bind(notnull);
cc.mov(regA[A], result);
}
void JitCompiler::EmitNEW_K()
{
PClass *cls = (PClass*)konsta[B].v;
if (!cls->ConstructNative)
{
EmitThrowException(X_OTHER); // "Class %s requires native construction", cls->TypeName.GetChars()
}
else if (cls->bAbstract)
{
EmitThrowException(X_OTHER); // "Cannot instantiate abstract class %s", cls->TypeName.GetChars()
}
else if (cls->IsDescendantOf(NAME_Actor)) // Creating actors here must be outright prohibited
{
EmitThrowException(X_OTHER); // "Cannot create actors with 'new'"
}
else
{
auto result = cc.newIntPtr();
auto regcls = cc.newIntPtr();
cc.mov(regcls, ToMemAddress(konsta[B].v));
typedef DObject*(*FuncPtr)(PClass*, int);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](PClass *cls, int c) -> DObject* {
try
{
if (c) FScopeBarrier::ValidateNew(cls, c - 1);
return cls->CreateNew();
}
catch (const CVMAbortException &)
{
// To do: pass along the exception info
return nullptr;
}
}))), asmjit::FuncSignature2<void*, void*, int>());
call->setRet(0, result);
call->setArg(0, regcls);
call->setArg(1, asmjit::Imm(C));
auto notnull = cc.newLabel();
cc.test(result, result);
cc.jnz(notnull);
EmitThrowException(X_OTHER);
cc.bind(notnull);
cc.mov(regA[A], result);
}
}
void JitCompiler::EmitTHROW()
{
EmitThrowException(EVMAbortException(BC));
}
void JitCompiler::EmitBOUND()
{
auto label1 = cc.newLabel();
auto label2 = cc.newLabel();
cc.cmp(regD[A], (int)BC);
cc.jl(label1);
EmitThrowException(X_ARRAY_OUT_OF_BOUNDS); // "Max.index = %u, current index = %u\n", BC, reg.d[A]
cc.bind(label1);
cc.cmp(regD[A], (int)0);
cc.jge(label2);
EmitThrowException(X_ARRAY_OUT_OF_BOUNDS); // "Negative current index = %i\n", reg.d[A]
cc.bind(label2);
}
void JitCompiler::EmitBOUND_K()
{
auto label1 = cc.newLabel();
auto label2 = cc.newLabel();
cc.cmp(regD[A], (int)konstd[BC]);
cc.jl(label1);
EmitThrowException(X_ARRAY_OUT_OF_BOUNDS); // "Max.index = %u, current index = %u\n", konstd[BC], reg.d[A]
cc.bind(label1);
cc.cmp(regD[A], (int)0);
cc.jge(label2);
EmitThrowException(X_ARRAY_OUT_OF_BOUNDS); // "Negative current index = %i\n", reg.d[A]
cc.bind(label2);
}
void JitCompiler::EmitBOUND_R()
{
auto label1 = cc.newLabel();
auto label2 = cc.newLabel();
cc.cmp(regD[A], regD[B]);
cc.jl(label1);
EmitThrowException(X_ARRAY_OUT_OF_BOUNDS); // "Max.index = %u, current index = %u\n", reg.d[B], reg.d[A]
cc.bind(label1);
cc.cmp(regD[A], (int)0);
cc.jge(label2);
EmitThrowException(X_ARRAY_OUT_OF_BOUNDS); // "Negative current index = %i\n", reg.d[A]
cc.bind(label2);
}

View File

@ -0,0 +1,337 @@
#include "jitintern.h"
/////////////////////////////////////////////////////////////////////////////
// Load constants.
void JitCompiler::EmitLI()
{
cc.mov(regD[A], BCs);
}
void JitCompiler::EmitLK()
{
cc.mov(regD[A], konstd[BC]);
}
void JitCompiler::EmitLKF()
{
auto tmp = cc.newIntPtr();
cc.mov(tmp, ToMemAddress(konstf + BC));
cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
}
void JitCompiler::EmitLKS()
{
auto loadLambda = [] (FString* to, FString* from) -> void {
*to = *from;
};
auto call = cc.call(ToMemAddress(reinterpret_cast<void*>(static_cast<void(*)(FString*, FString*)>(loadLambda))),
asmjit::FuncSignature2<void, FString*, FString*>(asmjit::CallConv::kIdHostCDecl));
call->setArg(0, regS[A]);
call->setArg(1, asmjit::imm(ToMemAddress(konsts + BC)));
}
void JitCompiler::EmitLKP()
{
cc.mov(regA[A], (int64_t)konsta[BC].v);
}
void JitCompiler::EmitLK_R()
{
cc.mov(regD[A], asmjit::x86::ptr(ToMemAddress(konstd), regD[B], 2, C * sizeof(int32_t)));
}
void JitCompiler::EmitLKF_R()
{
auto tmp = cc.newIntPtr();
cc.mov(tmp, ToMemAddress(konstf + BC));
cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp, regD[B], 3, C * sizeof(double)));
}
void JitCompiler::EmitLKS_R()
{
I_FatalError("EmitLKS_R not implemented\n");
}
void JitCompiler::EmitLKP_R()
{
cc.mov(regA[A], asmjit::x86::ptr(ToMemAddress(konsta), regD[B], 2, C * sizeof(void*)));
}
void JitCompiler::EmitLFP()
{
I_FatalError("EmitLFP not implemented\n");
}
void JitCompiler::EmitMETA()
{
typedef void*(*FuncPtr)(void*);
auto label = cc.newLabel();
cc.test(regA[B], regA[B]);
cc.jne(label);
EmitThrowException(X_READ_NIL);
cc.bind(label);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](void *o) -> void*
{
return static_cast<DObject*>(o)->GetClass()->Meta;
}))), asmjit::FuncSignature1<void*, void*>());
call->setRet(0, regA[A]);
call->setArg(0, regA[B]);
}
void JitCompiler::EmitCLSS()
{
typedef void*(*FuncPtr)(void*);
auto label = cc.newLabel();
cc.test(regA[B], regA[B]);
cc.jne(label);
EmitThrowException(X_READ_NIL);
cc.bind(label);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](void *o) -> void*
{
return static_cast<DObject*>(o)->GetClass();
}))), asmjit::FuncSignature1<void*, void*>());
call->setRet(0, regA[A]);
call->setArg(0, regA[B]);
}
/////////////////////////////////////////////////////////////////////////////
// Load from memory. rA = *(rB + rkC)
void JitCompiler::EmitLB()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movsx(regD[A], asmjit::x86::byte_ptr(regA[B], konstd[C]));
}
void JitCompiler::EmitLB_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movsx(regD[A], asmjit::x86::byte_ptr(regA[B], regD[C]));
}
void JitCompiler::EmitLH()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movsx(regD[A], asmjit::x86::word_ptr(regA[B], konstd[C]));
}
void JitCompiler::EmitLH_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movsx(regD[A], asmjit::x86::word_ptr(regA[B], regD[C]));
}
void JitCompiler::EmitLW()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.mov(regD[A], asmjit::x86::dword_ptr(regA[B], konstd[C]));
}
void JitCompiler::EmitLW_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.mov(regD[A], asmjit::x86::dword_ptr(regA[B], regD[C]));
}
void JitCompiler::EmitLBU()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movzx(regD[A], asmjit::x86::byte_ptr(regA[B], konstd[C]));
}
void JitCompiler::EmitLBU_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movzx(regD[A].r8Lo(), asmjit::x86::byte_ptr(regA[B], regD[C]));
}
void JitCompiler::EmitLHU()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movzx(regD[A].r16(), asmjit::x86::word_ptr(regA[B], konstd[C]));
}
void JitCompiler::EmitLHU_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movzx(regD[A].r16(), asmjit::x86::word_ptr(regA[B], regD[C]));
}
void JitCompiler::EmitLSP()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movss(regF[A], asmjit::x86::dword_ptr(regA[B], konstd[C]));
}
void JitCompiler::EmitLSP_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movss(regF[A], asmjit::x86::dword_ptr(regA[B], regD[C]));
}
void JitCompiler::EmitLDP()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movsd(regF[A], asmjit::x86::qword_ptr(regA[B], konstd[C]));
}
void JitCompiler::EmitLDP_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movsd(regF[A], asmjit::x86::qword_ptr(regA[B], regD[C]));
}
void JitCompiler::EmitLS()
{
EmitNullPointerThrow(B, X_READ_NIL);
auto ptr = cc.newIntPtr();
cc.mov(ptr, regA[B]);
cc.add(ptr, konstd[C]);
auto loadLambda = [](FString* to, FString* from) -> void {
*to = *from;
};
auto call = cc.call(ToMemAddress(reinterpret_cast<void*>(static_cast<void(*)(FString*, FString*)>(loadLambda))),
asmjit::FuncSignature2<void, FString*, FString*>(asmjit::CallConv::kIdHostCDecl));
call->setArg(0, regS[A]);
call->setArg(1, ptr);
}
void JitCompiler::EmitLS_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
auto ptr = cc.newIntPtr();
cc.mov(ptr, regA[B]);
auto tmp = cc.newIntPtr();
cc.mov(tmp, regD[C]);
cc.add(ptr, tmp);
auto loadLambda = [](FString* to, FString* from) -> void {
*to = *from;
};
auto call = cc.call(ToMemAddress(reinterpret_cast<void*>(static_cast<void(*)(FString*, FString*)>(loadLambda))),
asmjit::FuncSignature2<void, FString*, FString*>(asmjit::CallConv::kIdHostCDecl));
call->setArg(0, regS[A]);
call->setArg(1, ptr);
}
void JitCompiler::EmitLO()
{
EmitNullPointerThrow(B, X_READ_NIL);
auto ptr = cc.newIntPtr();
cc.mov(ptr, asmjit::x86::ptr(regA[B], konstd[C]));
typedef void*(*FuncPtr)(void*);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](void *ptr) -> void*
{
DObject *p = static_cast<DObject *>(ptr);
return GC::ReadBarrier(p);
}))), asmjit::FuncSignature1<void*, void*>());
call->setRet(0, regA[A]);
call->setArg(0, ptr);
}
void JitCompiler::EmitLO_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
auto ptr = cc.newIntPtr();
cc.mov(ptr, asmjit::x86::ptr(regA[B], regD[C]));
typedef void*(*FuncPtr)(void*);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](void *ptr) -> void*
{
DObject *p = static_cast<DObject *>(ptr);
return GC::ReadBarrier(p);
}))), asmjit::FuncSignature1<void*, void*>());
call->setRet(0, regA[A]);
call->setArg(0, ptr);
}
void JitCompiler::EmitLP()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.mov(regA[A], asmjit::x86::ptr(regA[B], konstd[C]));
}
void JitCompiler::EmitLP_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.mov(regA[A], asmjit::x86::ptr(regA[B], regD[C]));
}
void JitCompiler::EmitLV2()
{
EmitNullPointerThrow(B, X_READ_NIL);
auto tmp = cc.newIntPtr();
cc.mov(tmp, regA[B]);
cc.add(tmp, konstd[C]);
cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
}
void JitCompiler::EmitLV2_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
auto tmp = cc.newIntPtr();
cc.mov(tmp, regA[B]);
cc.add(tmp, regD[C]);
cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
}
void JitCompiler::EmitLV3()
{
EmitNullPointerThrow(B, X_READ_NIL);
auto tmp = cc.newIntPtr();
cc.mov(tmp, regA[B]);
cc.add(tmp, konstd[C]);
cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
cc.movsd(regF[A + 2], asmjit::x86::qword_ptr(tmp, 16));
}
void JitCompiler::EmitLV3_R()
{
EmitNullPointerThrow(B, X_READ_NIL);
auto tmp = cc.newIntPtr();
cc.mov(tmp, regA[B]);
cc.add(tmp, regD[C]);
cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
cc.movsd(regF[A + 2], asmjit::x86::qword_ptr(tmp, 16));
}
void JitCompiler::EmitLCS()
{
EmitNullPointerThrow(B, X_READ_NIL);
auto ptr = cc.newIntPtr();
cc.mov(ptr, regA[B]);
cc.add(ptr, konstd[C]);
auto loadLambda = [](FString* to, char** from) -> void {
*to = *from;
};
auto call = cc.call(ToMemAddress(reinterpret_cast<void*>(static_cast<void(*)(FString*, char**)>(loadLambda))),
asmjit::FuncSignature2<void, FString*, char**>(asmjit::CallConv::kIdHostCDecl));
call->setArg(0, regS[A]);
call->setArg(1, ptr);
}
void JitCompiler::EmitLCS_R()
{
I_FatalError("EmitLCS_R not implemented\n");
}
void JitCompiler::EmitLBIT()
{
EmitNullPointerThrow(B, X_READ_NIL);
cc.movsx(regD[A], asmjit::x86::byte_ptr(regA[B]));
cc.and_(regD[A], C);
cc.cmp(regD[A], 0);
cc.setne(regD[A]);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,201 @@
#include "jitintern.h"
void JitCompiler::EmitMOVE()
{
cc.mov(regD[A], regD[B]);
}
void JitCompiler::EmitMOVEF()
{
cc.movsd(regF[A], regF[B]);
}
void JitCompiler::EmitMOVES()
{
I_FatalError("EmitMOVES not implemented\n");
}
void JitCompiler::EmitMOVEA()
{
cc.mov(regA[A], regA[B]);
}
void JitCompiler::EmitMOVEV2()
{
cc.movsd(regF[A], regF[B]);
cc.movsd(regF[A + 1], regF[B + 1]);
}
void JitCompiler::EmitMOVEV3()
{
cc.movsd(regF[A], regF[B]);
cc.movsd(regF[A + 1], regF[B + 1]);
cc.movsd(regF[A + 2], regF[B + 2]);
}
void JitCompiler::EmitCAST()
{
switch (C)
{
case CAST_I2F:
cc.cvtsi2sd(regF[A], regD[B]);
break;
case CAST_U2F:
{
auto tmp = cc.newInt64();
cc.xor_(tmp, tmp);
cc.mov(tmp.r32(), regD[B]);
cc.cvtsi2sd(regF[A], tmp);
break;
}
case CAST_F2I:
cc.cvttsd2si(regD[A], regF[B]);
break;
case CAST_F2U:
{
auto tmp = cc.newInt64();
cc.cvttsd2si(tmp, regF[B]);
cc.mov(regD[A], tmp.r32());
break;
}
/*case CAST_I2S:
reg.s[A].Format("%d", reg.d[B]);
break;
case CAST_U2S:
reg.s[A].Format("%u", reg.d[B]);
break;
case CAST_F2S:
reg.s[A].Format("%.5f", reg.f[B]); // keep this small. For more precise conversion there should be a conversion function.
break;
case CAST_V22S:
reg.s[A].Format("(%.5f, %.5f)", reg.f[B], reg.f[b + 1]);
break;
case CAST_V32S:
reg.s[A].Format("(%.5f, %.5f, %.5f)", reg.f[B], reg.f[b + 1], reg.f[b + 2]);
break;
case CAST_P2S:
{
if (reg.a[B] == nullptr) reg.s[A] = "null";
else reg.s[A].Format("%p", reg.a[B]);
break;
}
case CAST_S2I:
reg.d[A] = (VM_SWORD)reg.s[B].ToLong();
break;
case CAST_S2F:
reg.f[A] = reg.s[B].ToDouble();
break;
case CAST_S2N:
reg.d[A] = reg.s[B].Len() == 0 ? FName(NAME_None) : FName(reg.s[B]);
break;
case CAST_N2S:
{
FName name = FName(ENamedName(reg.d[B]));
reg.s[A] = name.IsValidName() ? name.GetChars() : "";
break;
}
case CAST_S2Co:
reg.d[A] = V_GetColor(NULL, reg.s[B]);
break;
case CAST_Co2S:
reg.s[A].Format("%02x %02x %02x", PalEntry(reg.d[B]).r, PalEntry(reg.d[B]).g, PalEntry(reg.d[B]).b);
break;
case CAST_S2So:
reg.d[A] = FSoundID(reg.s[B]);
break;
case CAST_So2S:
reg.s[A] = S_sfx[reg.d[B]].name;
break;
case CAST_SID2S:
reg.s[A] = unsigned(reg.d[B]) >= sprites.Size() ? "TNT1" : sprites[reg.d[B]].name;
break;
case CAST_TID2S:
{
auto tex = TexMan[*(FTextureID*)&(reg.d[B])];
reg.s[A] = tex == nullptr ? "(null)" : tex->Name.GetChars();
break;
}*/
default:
assert(0);
}
}
void JitCompiler::EmitCASTB()
{
if (C == CASTB_I)
{
cc.mov(regD[A], regD[B]);
cc.shr(regD[A], 31);
}
else if (C == CASTB_F)
{
auto zero = cc.newXmm();
auto one = cc.newInt32();
cc.xorpd(zero, zero);
cc.mov(one, 1);
cc.ucomisd(regF[A], zero);
cc.setp(regD[A]);
cc.cmovne(regD[A], one);
}
else if (C == CASTB_A)
{
cc.test(regA[A], regA[A]);
cc.setne(regD[A]);
}
else
{
//reg.d[A] = reg.s[B].Len() > 0;
}
}
void JitCompiler::EmitDYNCAST_R()
{
using namespace asmjit;
typedef DObject*(*FuncPtr)(DObject*, PClass*);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](DObject *obj, PClass *cls) -> DObject* {
return (obj && obj->IsKindOf(cls)) ? obj : nullptr;
}))), FuncSignature2<void*, void*, void*>());
call->setRet(0, regA[A]);
call->setArg(0, regA[B]);
call->setArg(1, regA[C]);
}
void JitCompiler::EmitDYNCAST_K()
{
using namespace asmjit;
auto c = cc.newIntPtr();
cc.mov(c, ToMemAddress(konsta[C].o));
typedef DObject*(*FuncPtr)(DObject*, PClass*);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](DObject *obj, PClass *cls) -> DObject* {
return (obj && obj->IsKindOf(cls)) ? obj : nullptr;
}))), FuncSignature2<void*, void*, void*>());
call->setRet(0, regA[A]);
call->setArg(0, regA[B]);
call->setArg(1, c);
}
void JitCompiler::EmitDYNCASTC_R()
{
using namespace asmjit;
typedef PClass*(*FuncPtr)(PClass*, PClass*);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](PClass *cls1, PClass *cls2) -> PClass* {
return (cls1 && cls1->IsDescendantOf(cls2)) ? cls1 : nullptr;
}))), FuncSignature2<void*, void*, void*>());
call->setRet(0, regA[A]);
call->setArg(0, regA[B]);
call->setArg(1, regA[C]);
}
void JitCompiler::EmitDYNCASTC_K()
{
using namespace asmjit;
auto c = cc.newIntPtr();
cc.mov(c, ToMemAddress(konsta[C].o));
typedef PClass*(*FuncPtr)(PClass*, PClass*);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>([](PClass *cls1, PClass *cls2) -> PClass* {
return (cls1 && cls1->IsDescendantOf(cls2)) ? cls1 : nullptr;
}))), FuncSignature2<void*, void*, void*>());
call->setRet(0, regA[A]);
call->setArg(0, regA[B]);
call->setArg(1, c);
}

View File

@ -0,0 +1,178 @@
#include "jitintern.h"
void JitCompiler::EmitSB()
{
EmitNullPointerThrow(A, X_WRITE_NIL);
cc.mov(asmjit::x86::byte_ptr(regA[A], konstd[C]), regD[B].r8Lo());
}
void JitCompiler::EmitSB_R()
{
EmitNullPointerThrow(A, X_WRITE_NIL);
cc.mov(asmjit::x86::byte_ptr(regA[A], regD[C]), regD[B].r8Lo());
}
void JitCompiler::EmitSH()
{
EmitNullPointerThrow(A, X_WRITE_NIL);
cc.mov(asmjit::x86::word_ptr(regA[A], konstd[C]), regD[B].r16());
}
void JitCompiler::EmitSH_R()
{
EmitNullPointerThrow(A, X_WRITE_NIL);
cc.mov(asmjit::x86::word_ptr(regA[A], regD[C]), regD[B].r16());
}
void JitCompiler::EmitSW()
{
EmitNullPointerThrow(A, X_WRITE_NIL);
cc.mov(asmjit::x86::dword_ptr(regA[A], konstd[C]), regD[B]);
}
void JitCompiler::EmitSW_R()
{
EmitNullPointerThrow(A, X_WRITE_NIL);
cc.mov(asmjit::x86::dword_ptr(regA[A], regD[C]), regD[B]);
}
void JitCompiler::EmitSSP()
{
EmitNullPointerThrow(A, X_WRITE_NIL);
cc.movss(asmjit::x86::dword_ptr(regA[A], konstd[C]), regF[B]);
}
void JitCompiler::EmitSSP_R()
{
EmitNullPointerThrow(A, X_WRITE_NIL);
cc.movss(asmjit::x86::dword_ptr(regA[A], regD[C]), regF[B]);
}
void JitCompiler::EmitSDP()
{
EmitNullPointerThrow(A, X_WRITE_NIL);
cc.movsd(asmjit::x86::qword_ptr(regA[A], konstd[C]), regF[B]);
}
void JitCompiler::EmitSDP_R()
{
EmitNullPointerThrow(A, X_WRITE_NIL);
cc.movsd(asmjit::x86::qword_ptr(regA[A], regD[C]), regF[B]);
}
void JitCompiler::EmitSS()
{
EmitNullPointerThrow(B, X_WRITE_NIL);
auto ptr = cc.newIntPtr();
cc.mov(ptr, regA[A]);
cc.add(ptr, konstd[C]);
auto loadLambda = [](FString* to, FString* from) -> void {
*to = *from;
};
auto call = cc.call(ToMemAddress(reinterpret_cast<void*>(static_cast<void(*)(FString*, FString*)>(loadLambda))),
asmjit::FuncSignature2<void, FString*, FString*>(asmjit::CallConv::kIdHostCDecl));
call->setArg(0, ptr);
call->setArg(1, regS[B]);
}
void JitCompiler::EmitSS_R()
{
EmitNullPointerThrow(B, X_WRITE_NIL);
auto ptr = cc.newIntPtr();
cc.mov(ptr, regA[A]);
auto tmp = cc.newIntPtr();
cc.mov(tmp, regD[C]);
cc.add(ptr, tmp);
auto loadLambda = [](FString* to, FString* from) -> void {
*to = *from;
};
auto call = cc.call(ToMemAddress(reinterpret_cast<void*>(static_cast<void(*)(FString*, FString*)>(loadLambda))),
asmjit::FuncSignature2<void, FString*, FString*>(asmjit::CallConv::kIdHostCDecl));
call->setArg(0, ptr);
call->setArg(1, regS[B]);
}
void JitCompiler::EmitSO()
{
cc.mov(asmjit::x86::ptr(regA[A], konstd[C]), regA[B]);
typedef void(*FuncPtr)(DObject*);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>(GC::WriteBarrier))), asmjit::FuncSignature1<void, void*>());
call->setArg(0, regA[B]);
}
void JitCompiler::EmitSO_R()
{
cc.mov(asmjit::x86::ptr(regA[A], regD[C]), regA[B]);
typedef void(*FuncPtr)(DObject*);
auto call = cc.call(ToMemAddress(reinterpret_cast<const void*>(static_cast<FuncPtr>(GC::WriteBarrier))), asmjit::FuncSignature1<void, void*>());
call->setArg(0, regA[B]);
}
void JitCompiler::EmitSP()
{
cc.mov(asmjit::x86::ptr(regA[A], konstd[C]), regA[B]);
}
void JitCompiler::EmitSP_R()
{
cc.mov(asmjit::x86::ptr(regA[A], regD[C]), regA[B]);
}
void JitCompiler::EmitSV2()
{
EmitNullPointerThrow(B, X_WRITE_NIL);
auto tmp = cc.newIntPtr();
cc.mov(tmp, regA[B]);
cc.add(tmp, konstd[C]);
cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]);
cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]);
}
void JitCompiler::EmitSV2_R()
{
EmitNullPointerThrow(B, X_WRITE_NIL);
auto tmp = cc.newIntPtr();
cc.mov(tmp, regA[B]);
cc.add(tmp, regD[C]);
cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]);
cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]);
}
void JitCompiler::EmitSV3()
{
EmitNullPointerThrow(B, X_WRITE_NIL);
auto tmp = cc.newIntPtr();
cc.mov(tmp, regA[B]);
cc.add(tmp, konstd[C]);
cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]);
cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]);
cc.movsd(asmjit::x86::qword_ptr(tmp, 16), regF[B + 2]);
}
void JitCompiler::EmitSV3_R()
{
EmitNullPointerThrow(B, X_WRITE_NIL);
auto tmp = cc.newIntPtr();
cc.mov(tmp, regA[B]);
cc.add(tmp, regD[C]);
cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]);
cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]);
cc.movsd(asmjit::x86::qword_ptr(tmp, 16), regF[B + 2]);
}
void JitCompiler::EmitSBIT()
{
EmitNullPointerThrow(B, X_WRITE_NIL);
auto tmp1 = cc.newInt32();
auto tmp2 = cc.newInt32();
cc.mov(tmp1, asmjit::x86::byte_ptr(regA[A]));
cc.mov(tmp2, tmp1);
cc.or_(tmp1, (int)C);
cc.and_(tmp2, ~(int)C);
cc.test(regD[B], regD[B]);
cc.cmove(tmp1, tmp2);
cc.mov(asmjit::x86::byte_ptr(regA[A]), tmp1);
}

View File

@ -0,0 +1,149 @@
#include "jit.h"
#include "i_system.h"
#include "types.h"
#include "stats.h"
// To do: get cmake to define these..
#define ASMJIT_BUILD_EMBED
#define ASMJIT_STATIC
#include <asmjit/asmjit.h>
#include <asmjit/x86.h>
#include <functional>
extern cycle_t VMCycles[10];
extern int VMCalls[10];
#define A (pc[0].a)
#define B (pc[0].b)
#define C (pc[0].c)
#define Cs (pc[0].cs)
#define BC (pc[0].i16u)
#define BCs (pc[0].i16)
#define ABCs (pc[0].i24)
#define JMPOFS(x) ((x)->i24)
static const char *OpNames[NUM_OPS] =
{
#define xx(op, name, mode, alt, kreg, ktype) #op
#include "vmops.h"
#undef xx
};
class JitCompiler
{
public:
JitCompiler(asmjit::CodeHolder *code, VMScriptFunction *sfunc) : cc(code), sfunc(sfunc) { }
void Codegen();
static bool CanJit(VMScriptFunction *sfunc);
private:
// Declare EmitXX functions for the opcodes:
#define xx(op, name, mode, alt, kreg, ktype) void Emit##op();
#include "vmops.h"
#undef xx
void EmitOpcode();
void EmitDoCall(asmjit::X86Gp ptr);
void StoreInOuts(int b);
void LoadReturns(const VMOP *retval, int numret, bool inout);
void FillReturns(const VMOP *retval, int numret);
static int DoCall(VMFrameStack *stack, VMFunction *call, int b, int c, VMValue *param, VMReturn *returns, JitExceptionInfo *exceptinfo);
void Setup();
template <typename Func>
void EmitComparisonOpcode(Func jmpFunc)
{
using namespace asmjit;
int i = (int)(ptrdiff_t)(pc - sfunc->Code);
auto successLabel = cc.newLabel();
auto failLabel = labels[i + 2 + JMPOFS(pc + 1)];
jmpFunc(static_cast<bool>(A & CMP_CHECK), failLabel, successLabel);
cc.bind(successLabel);
pc++; // This instruction uses two instruction slots - skip the next one
}
static int64_t ToMemAddress(const void *d)
{
return (int64_t)(ptrdiff_t)d;
}
void CallSqrt(const asmjit::X86Xmm &a, const asmjit::X86Xmm &b);
void EmitNullPointerThrow(int index, EVMAbortException reason);
void EmitThrowException(EVMAbortException reason);
void EmitThrowException(EVMAbortException reason, asmjit::X86Gp arg1);
asmjit::X86Gp CheckRegD(int r0, int r1);
asmjit::X86Xmm CheckRegF(int r0, int r1);
asmjit::X86Xmm CheckRegF(int r0, int r1, int r2);
asmjit::X86Xmm CheckRegF(int r0, int r1, int r2, int r3);
asmjit::X86Gp CheckRegA(int r0, int r1);
asmjit::X86Compiler cc;
VMScriptFunction *sfunc;
asmjit::X86Gp stack;
asmjit::X86Gp vmregs;
asmjit::X86Gp ret;
asmjit::X86Gp numret;
asmjit::X86Gp exceptInfo;
asmjit::X86Gp frameD;
asmjit::X86Gp frameF;
asmjit::X86Gp frameS;
asmjit::X86Gp frameA;
asmjit::X86Gp params;
int NumParam = 0; // Actually part of vmframe (f->NumParam), but nobody seems to read that?
TArray<const VMOP *> ParamOpcodes;
asmjit::X86Gp callReturns;
const int *konstd;
const double *konstf;
const FString *konsts;
const FVoidObj *konsta;
TArray<asmjit::X86Gp> regD;
TArray<asmjit::X86Xmm> regF;
TArray<asmjit::X86Gp> regA;
TArray<asmjit::X86Gp> regS;
TArray<asmjit::Label> labels;
const VMOP *pc;
VM_UBYTE op;
};
class AsmJitException : public std::exception
{
public:
AsmJitException(asmjit::Error error, const char *message) noexcept : error(error), message(message)
{
}
const char* what() const noexcept override
{
return message.GetChars();
}
asmjit::Error error;
FString message;
};
class ThrowingErrorHandler : public asmjit::ErrorHandler
{
public:
bool handleError(asmjit::Error err, const char *message, asmjit::CodeEmitter *origin) override
{
throw AsmJitException(err, message);
}
};

View File

@ -42,7 +42,7 @@ static int Exec(VMFrameStack *stack, const VMOP *pc, VMReturn *ret, int numret)
#if COMPGOTO
static const void * const ops[256] =
{
#define xx(op,sym,mode,alt,kreg,ktype) &&op
#define xx(op,sym,mode,alt,kreg,ktype) &&op,
#include "vmops.h"
};
#endif

View File

@ -1,5 +1,5 @@
#ifndef xx
#define xx(op, name, mode, alt, kreg, ktype) OP_##op
#define xx(op, name, mode, alt, kreg, ktype) OP_##op,
#endif
// first row is the opcode
@ -10,249 +10,249 @@
// sixth row is the constant register type.
// OP_PARAM and OP_CMPS need special treatment because they encode this information in the instruction.
xx(NOP, nop, NOP, NOP, 0, 0), // no operation
xx(NOP, nop, NOP, NOP, 0, 0) // no operation
// Load constants.
xx(LI, li, LI, NOP, 0, 0), // load immediate signed 16-bit constant
xx(LK, lk, LKI, NOP, 0, 0), // load integer constant
xx(LKF, lk, LKF, NOP, 0, 0), // load float constant
xx(LKS, lk, LKS, NOP, 0, 0), // load string constant
xx(LKP, lk, LKP, NOP, 0, 0), // load pointer constant
xx(LK_R, lk, RIRII8, NOP, 0, 0), // load integer constant indexed
xx(LKF_R, lk, RFRII8, NOP, 0, 0), // load float constant indexed
xx(LKS_R, lk, RSRII8, NOP, 0, 0), // load string constant indexed
xx(LKP_R, lk, RPRII8, NOP, 0, 0), // load pointer constant indexed
xx(LFP, lf, LFP, NOP, 0, 0), // load frame pointer
xx(META, meta, RPRP, NOP, 0, 0), // load a class's meta data address
xx(CLSS, clss, RPRP, NOP, 0, 0), // load a class's descriptor address
xx(LI, li, LI, NOP, 0, 0) // load immediate signed 16-bit constant
xx(LK, lk, LKI, NOP, 0, 0) // load integer constant
xx(LKF, lk, LKF, NOP, 0, 0) // load float constant
xx(LKS, lk, LKS, NOP, 0, 0) // load string constant
xx(LKP, lk, LKP, NOP, 0, 0) // load pointer constant
xx(LK_R, lk, RIRII8, NOP, 0, 0) // load integer constant indexed
xx(LKF_R, lk, RFRII8, NOP, 0, 0) // load float constant indexed
xx(LKS_R, lk, RSRII8, NOP, 0, 0) // load string constant indexed
xx(LKP_R, lk, RPRII8, NOP, 0, 0) // load pointer constant indexed
xx(LFP, lf, LFP, NOP, 0, 0) // load frame pointer
xx(META, meta, RPRP, NOP, 0, 0) // load a class's meta data address
xx(CLSS, clss, RPRP, NOP, 0, 0) // load a class's descriptor address
// Load from memory. rA = *(rB + rkC)
xx(LB, lb, RIRPKI, LB_R, 4, REGT_INT), // load byte
xx(LB_R, lb, RIRPRI, NOP, 0, 0),
xx(LH, lh, RIRPKI, LH_R, 4, REGT_INT), // load halfword
xx(LH_R, lh, RIRPRI, NOP, 0, 0),
xx(LW, lw, RIRPKI, LW_R, 4, REGT_INT), // load word
xx(LW_R, lw, RIRPRI, NOP, 0, 0),
xx(LBU, lbu, RIRPKI, LBU_R, 4, REGT_INT), // load byte unsigned
xx(LBU_R, lbu, RIRPRI, NOP, 0, 0),
xx(LHU, lhu, RIRPKI, LHU_R, 4, REGT_INT), // load halfword unsigned
xx(LHU_R, lhu, RIRPRI, NOP, 0, 0),
xx(LSP, lsp, RFRPKI, LSP_R, 4, REGT_INT), // load single-precision fp
xx(LSP_R, lsp, RFRPRI, NOP, 0, 0),
xx(LDP, ldp, RFRPKI, LDP_R, 4, REGT_INT), // load double-precision fp
xx(LDP_R, ldp, RFRPRI, NOP, 0, 0),
xx(LS, ls, RSRPKI, LS_R, 4, REGT_INT), // load string
xx(LS_R, ls, RSRPRI, NOP, 0, 0),
xx(LO, lo, RPRPKI, LO_R, 4, REGT_INT), // load object
xx(LO_R, lo, RPRPRI, NOP, 0, 0),
xx(LP, lp, RPRPKI, LP_R, 4, REGT_INT), // load pointer
xx(LP_R, lp, RPRPRI, NOP, 0, 0),
xx(LV2, lv2, RVRPKI, LV2_R, 4, REGT_INT), // load vector2
xx(LV2_R, lv2, RVRPRI, NOP, 0, 0),
xx(LV3, lv3, RVRPKI, LV3_R, 4, REGT_INT), // load vector3
xx(LV3_R, lv3, RVRPRI, NOP, 0, 0),
xx(LCS, lcs, RSRPKI, LCS_R, 4, REGT_INT), // load string from char ptr.
xx(LCS_R, lcs, RSRPRI, NOP, 0, 0),
xx(LB, lb, RIRPKI, LB_R, 4, REGT_INT) // load byte
xx(LB_R, lb, RIRPRI, NOP, 0, 0)
xx(LH, lh, RIRPKI, LH_R, 4, REGT_INT) // load halfword
xx(LH_R, lh, RIRPRI, NOP, 0, 0)
xx(LW, lw, RIRPKI, LW_R, 4, REGT_INT) // load word
xx(LW_R, lw, RIRPRI, NOP, 0, 0)
xx(LBU, lbu, RIRPKI, LBU_R, 4, REGT_INT) // load byte unsigned
xx(LBU_R, lbu, RIRPRI, NOP, 0, 0)
xx(LHU, lhu, RIRPKI, LHU_R, 4, REGT_INT) // load halfword unsigned
xx(LHU_R, lhu, RIRPRI, NOP, 0, 0)
xx(LSP, lsp, RFRPKI, LSP_R, 4, REGT_INT) // load single-precision fp
xx(LSP_R, lsp, RFRPRI, NOP, 0, 0)
xx(LDP, ldp, RFRPKI, LDP_R, 4, REGT_INT) // load double-precision fp
xx(LDP_R, ldp, RFRPRI, NOP, 0, 0)
xx(LS, ls, RSRPKI, LS_R, 4, REGT_INT) // load string
xx(LS_R, ls, RSRPRI, NOP, 0, 0)
xx(LO, lo, RPRPKI, LO_R, 4, REGT_INT) // load object
xx(LO_R, lo, RPRPRI, NOP, 0, 0)
xx(LP, lp, RPRPKI, LP_R, 4, REGT_INT) // load pointer
xx(LP_R, lp, RPRPRI, NOP, 0, 0)
xx(LV2, lv2, RVRPKI, LV2_R, 4, REGT_INT) // load vector2
xx(LV2_R, lv2, RVRPRI, NOP, 0, 0)
xx(LV3, lv3, RVRPKI, LV3_R, 4, REGT_INT) // load vector3
xx(LV3_R, lv3, RVRPRI, NOP, 0, 0)
xx(LCS, lcs, RSRPKI, LCS_R, 4, REGT_INT) // load string from char ptr.
xx(LCS_R, lcs, RSRPRI, NOP, 0, 0)
xx(LBIT, lbit, RIRPI8, NOP, 0, 0), // rA = !!(*rB & C) -- *rB is a byte
xx(LBIT, lbit, RIRPI8, NOP, 0, 0) // rA = !!(*rB & C) -- *rB is a byte
// Store instructions. *(rA + rkC) = rB
xx(SB, sb, RPRIKI, SB_R, 4, REGT_INT), // store byte
xx(SB_R, sb, RPRIRI, NOP, 0, 0),
xx(SH, sh, RPRIKI, SH_R, 4, REGT_INT), // store halfword
xx(SH_R, sh, RPRIRI, NOP, 0, 0),
xx(SW, sw, RPRIKI, SW_R, 4, REGT_INT), // store word
xx(SW_R, sw, RPRIRI, NOP, 0, 0),
xx(SSP, ssp, RPRFKI, SSP_R, 4, REGT_INT), // store single-precision fp
xx(SSP_R, ssp, RPRFRI, NOP, 0, 0),
xx(SDP, sdp, RPRFKI, SDP_R, 4, REGT_INT), // store double-precision fp
xx(SDP_R, sdp, RPRFRI, NOP, 0, 0),
xx(SS, ss, RPRSKI, SS_R, 4, REGT_INT), // store string
xx(SS_R, ss, RPRSRI, NOP, 0, 0),
xx(SP, sp, RPRPKI, SP_R, 4, REGT_INT), // store pointer
xx(SP_R, sp, RPRPRI, NOP, 0, 0),
xx(SO, so, RPRPKI, SO_R, 4, REGT_INT), // store object pointer with write barrier (only needed for non thinkers and non types)
xx(SO_R, so, RPRPRI, NOP, 0, 0),
xx(SV2, sv2, RPRVKI, SV2_R, 4, REGT_INT), // store vector2
xx(SV2_R, sv2, RPRVRI, NOP, 0, 0),
xx(SV3, sv3, RPRVKI, SV3_R, 4, REGT_INT), // store vector3
xx(SV3_R, sv3, RPRVRI, NOP, 0, 0),
xx(SB, sb, RPRIKI, SB_R, 4, REGT_INT) // store byte
xx(SB_R, sb, RPRIRI, NOP, 0, 0)
xx(SH, sh, RPRIKI, SH_R, 4, REGT_INT) // store halfword
xx(SH_R, sh, RPRIRI, NOP, 0, 0)
xx(SW, sw, RPRIKI, SW_R, 4, REGT_INT) // store word
xx(SW_R, sw, RPRIRI, NOP, 0, 0)
xx(SSP, ssp, RPRFKI, SSP_R, 4, REGT_INT) // store single-precision fp
xx(SSP_R, ssp, RPRFRI, NOP, 0, 0)
xx(SDP, sdp, RPRFKI, SDP_R, 4, REGT_INT) // store double-precision fp
xx(SDP_R, sdp, RPRFRI, NOP, 0, 0)
xx(SS, ss, RPRSKI, SS_R, 4, REGT_INT) // store string
xx(SS_R, ss, RPRSRI, NOP, 0, 0)
xx(SP, sp, RPRPKI, SP_R, 4, REGT_INT) // store pointer
xx(SP_R, sp, RPRPRI, NOP, 0, 0)
xx(SO, so, RPRPKI, SO_R, 4, REGT_INT) // store object pointer with write barrier (only needed for non thinkers and non types)
xx(SO_R, so, RPRPRI, NOP, 0, 0)
xx(SV2, sv2, RPRVKI, SV2_R, 4, REGT_INT) // store vector2
xx(SV2_R, sv2, RPRVRI, NOP, 0, 0)
xx(SV3, sv3, RPRVKI, SV3_R, 4, REGT_INT) // store vector3
xx(SV3_R, sv3, RPRVRI, NOP, 0, 0)
xx(SBIT, sbit, RPRII8, NOP, 0, 0), // *rA |= C if rB is true, *rA &= ~C otherwise
xx(SBIT, sbit, RPRII8, NOP, 0, 0) // *rA |= C if rB is true, *rA &= ~C otherwise
// Move instructions.
xx(MOVE, mov, RIRI, NOP, 0, 0), // dA = dB
xx(MOVEF, mov, RFRF, NOP, 0, 0), // fA = fB
xx(MOVES, mov, RSRS, NOP, 0, 0), // sA = sB
xx(MOVEA, mov, RPRP, NOP, 0, 0), // aA = aB
xx(MOVEV2, mov2, RFRF, NOP, 0, 0), // fA = fB (2 elements)
xx(MOVEV3, mov3, RFRF, NOP, 0, 0), // fA = fB (3 elements)
xx(CAST, cast, CAST, NOP, 0, 0), // xA = xB, conversion specified by C
xx(CASTB, castb, CAST, NOP, 0, 0), // xA = !!xB, type specified by C
xx(DYNCAST_R, dyncast, RPRPRP, NOP, 0, 0), // aA = dyn_cast<aC>(aB);
xx(DYNCAST_K, dyncast, RPRPKP, NOP, 0, 0), // aA = dyn_cast<aKC>(aB);
xx(DYNCASTC_R, dyncastc, RPRPRP, NOP, 0, 0), // aA = dyn_cast<aC>(aB); for class types
xx(DYNCASTC_K, dyncastc, RPRPKP, NOP, 0, 0), // aA = dyn_cast<aKC>(aB);
xx(MOVE, mov, RIRI, NOP, 0, 0) // dA = dB
xx(MOVEF, mov, RFRF, NOP, 0, 0) // fA = fB
xx(MOVES, mov, RSRS, NOP, 0, 0) // sA = sB
xx(MOVEA, mov, RPRP, NOP, 0, 0) // aA = aB
xx(MOVEV2, mov2, RFRF, NOP, 0, 0) // fA = fB (2 elements)
xx(MOVEV3, mov3, RFRF, NOP, 0, 0) // fA = fB (3 elements)
xx(CAST, cast, CAST, NOP, 0, 0) // xA = xB, conversion specified by C
xx(CASTB, castb, CAST, NOP, 0, 0) // xA = !!xB, type specified by C
xx(DYNCAST_R, dyncast, RPRPRP, NOP, 0, 0) // aA = dyn_cast<aC>(aB);
xx(DYNCAST_K, dyncast, RPRPKP, NOP, 0, 0) // aA = dyn_cast<aKC>(aB);
xx(DYNCASTC_R, dyncastc, RPRPRP, NOP, 0, 0) // aA = dyn_cast<aC>(aB); for class types
xx(DYNCASTC_K, dyncastc, RPRPKP, NOP, 0, 0) // aA = dyn_cast<aKC>(aB);
// Control flow.
xx(TEST, test, RII16, NOP, 0, 0), // if (dA != BC) then pc++
xx(TESTN, testn, RII16, NOP, 0, 0), // if (dA != -BC) then pc++
xx(JMP, jmp, I24, NOP, 0, 0), // pc += ABC -- The ABC fields contain a signed 24-bit offset.
xx(IJMP, ijmp, RII16, NOP, 0, 0), // pc += dA + BC -- BC is a signed offset. The target instruction must be a JMP.
xx(PARAM, param, __BCP, NOP, 0, 0), // push parameter encoded in BC for function call (B=regtype, C=regnum)
xx(PARAMI, parami, I24, NOP, 0, 0), // push immediate, signed integer for function call
xx(CALL, call, RPI8I8, NOP, 0, 0), // Call function pkA with parameter count B and expected result count C
xx(CALL_K, call, KPI8I8, CALL, 1, REGT_POINTER),
xx(VTBL, vtbl, RPRPI8, NOP, 0, 0), // dereferences a virtual method table.
xx(SCOPE, scope, RPI8, NOP, 0, 0), // Scope check at runtime.
xx(TAIL, tail, RPI8, NOP, 0, 0), // Call+Ret in a single instruction
xx(TAIL_K, tail, KPI8, TAIL, 1, REGT_POINTER),
xx(RESULT, result, __BCP, NOP, 0, 0), // Result should go in register encoded in BC (in caller, after CALL)
xx(RET, ret, I8BCP, NOP, 0, 0), // Copy value from register encoded in BC to return value A, possibly returning
xx(RETI, reti, I8I16, NOP, 0, 0), // Copy immediate from BC to return value A, possibly returning
xx(NEW, new, RPRPI8, NOP, 0, 0),
xx(NEW_K, new, RPKP, NOP, 0, 0),
//xx(TRY, try, I24, NOP, 0, 0), // When an exception is thrown, start searching for a handler at pc + ABC
//xx(UNTRY, untry, I8, NOP, 0, 0), // Pop A entries off the exception stack
xx(THROW, throw, THROW, NOP, 0, 0), // A == 0: Throw exception object pB
xx(TEST, test, RII16, NOP, 0, 0) // if (dA != BC) then pc++
xx(TESTN, testn, RII16, NOP, 0, 0) // if (dA != -BC) then pc++
xx(JMP, jmp, I24, NOP, 0, 0) // pc += ABC -- The ABC fields contain a signed 24-bit offset.
xx(IJMP, ijmp, RII16, NOP, 0, 0) // pc += dA + BC -- BC is a signed offset. The target instruction must be a JMP.
xx(PARAM, param, __BCP, NOP, 0, 0) // push parameter encoded in BC for function call (B=regtype, C=regnum)
xx(PARAMI, parami, I24, NOP, 0, 0) // push immediate, signed integer for function call
xx(CALL, call, RPI8I8, NOP, 0, 0) // Call function pkA with parameter count B and expected result count C
xx(CALL_K, call, KPI8I8, CALL, 1, REGT_POINTER)
xx(VTBL, vtbl, RPRPI8, NOP, 0, 0) // dereferences a virtual method table.
xx(SCOPE, scope, RPI8, NOP, 0, 0) // Scope check at runtime.
xx(TAIL, tail, RPI8, NOP, 0, 0) // Call+Ret in a single instruction
xx(TAIL_K, tail, KPI8, TAIL, 1, REGT_POINTER)
xx(RESULT, result, __BCP, NOP, 0, 0) // Result should go in register encoded in BC (in caller, after CALL)
xx(RET, ret, I8BCP, NOP, 0, 0) // Copy value from register encoded in BC to return value A, possibly returning
xx(RETI, reti, I8I16, NOP, 0, 0) // Copy immediate from BC to return value A, possibly returning
xx(NEW, new, RPRPI8, NOP, 0, 0)
xx(NEW_K, new, RPKP, NOP, 0, 0)
//xx(TRY, try, I24, NOP, 0, 0) // When an exception is thrown, start searching for a handler at pc + ABC
//xx(UNTRY, untry, I8, NOP, 0, 0) // Pop A entries off the exception stack
xx(THROW, throw, THROW, NOP, 0, 0) // A == 0: Throw exception object pB
// A == 1: Throw exception object pkB
// A >= 2: Throw VM exception of type BC
//xx(CATCH, catch, CATCH, NOP, 0, 0), // A == 0: continue search on next try
//xx(CATCH, catch, CATCH, NOP, 0, 0) // A == 0: continue search on next try
// A == 1: continue execution at instruction immediately following CATCH (catches any exception)
// A == 2: (pB == <type of exception thrown>) then pc++ ; next instruction must JMP to another CATCH
// A == 3: (pkB == <type of exception thrown>) then pc++ ; next instruction must JMP to another CATCH
// for A > 0, exception is stored in pC
xx(BOUND, bound, RII16, NOP, 0, 0), // if rA < 0 or rA >= BC, throw exception
xx(BOUND_K, bound, LKI, NOP, 0, 0), // if rA < 0 or rA >= const[BC], throw exception
xx(BOUND_R, bound, RIRI, NOP, 0, 0), // if rA < 0 or rA >= rB, throw exception
xx(BOUND, bound, RII16, NOP, 0, 0) // if rA < 0 or rA >= BC, throw exception
xx(BOUND_K, bound, LKI, NOP, 0, 0) // if rA < 0 or rA >= const[BC], throw exception
xx(BOUND_R, bound, RIRI, NOP, 0, 0) // if rA < 0 or rA >= rB, throw exception
// String instructions.
xx(CONCAT, concat, RSRSRS, NOP, 0, 0), // sA = sB..sC
xx(LENS, lens, RIRS, NOP, 0, 0), // dA = sB.Length
xx(CMPS, cmps, I8RXRX, NOP, 0, 0), // if ((skB op skC) != (A & 1)) then pc++
xx(CONCAT, concat, RSRSRS, NOP, 0, 0) // sA = sB..sC
xx(LENS, lens, RIRS, NOP, 0, 0) // dA = sB.Length
xx(CMPS, cmps, I8RXRX, NOP, 0, 0) // if ((skB op skC) != (A & 1)) then pc++
// Integer math.
xx(SLL_RR, sll, RIRIRI, NOP, 0, 0), // dA = dkB << diC
xx(SLL_RI, sll, RIRII8, NOP, 0, 0),
xx(SLL_KR, sll, RIKIRI, SLL_RR, 2, REGT_INT),
xx(SRL_RR, srl, RIRIRI, NOP, 0, 0), // dA = dkB >> diC -- unsigned
xx(SRL_RI, srl, RIRII8, NOP, 0, 0),
xx(SRL_KR, srl, RIKIRI, SRL_RR, 2, REGT_INT),
xx(SRA_RR, sra, RIRIRI, NOP, 0, 0), // dA = dkB >> diC -- signed
xx(SRA_RI, sra, RIRII8, NOP, 0, 0),
xx(SRA_KR, sra, RIKIRI, SRA_RR, 2, REGT_INT),
xx(ADD_RR, add, RIRIRI, NOP, 0, 0), // dA = dB + dkC
xx(ADD_RK, add, RIRIKI, ADD_RR, 4, REGT_INT),
xx(ADDI, addi, RIRIIs, NOP, 0, 0), // dA = dB + C -- C is a signed 8-bit constant
xx(SUB_RR, sub, RIRIRI, NOP, 0, 0), // dA = dkB - dkC
xx(SUB_RK, sub, RIRIKI, SUB_RR, 4, REGT_INT),
xx(SUB_KR, sub, RIKIRI, SUB_RR, 2, REGT_INT),
xx(MUL_RR, mul, RIRIRI, NOP, 0, 0), // dA = dB * dkC
xx(MUL_RK, mul, RIRIKI, MUL_RR, 4, REGT_INT),
xx(DIV_RR, div, RIRIRI, NOP, 0, 0), // dA = dkB / dkC (signed)
xx(DIV_RK, div, RIRIKI, DIV_RR, 4, REGT_INT),
xx(DIV_KR, div, RIKIRI, DIV_RR, 2, REGT_INT),
xx(DIVU_RR, divu, RIRIRI, NOP, 0, 0), // dA = dkB / dkC (unsigned)
xx(DIVU_RK, divu, RIRIKI, DIVU_RR,4, REGT_INT),
xx(DIVU_KR, divu, RIKIRI, DIVU_RR,2, REGT_INT),
xx(MOD_RR, mod, RIRIRI, NOP, 0, 0), // dA = dkB % dkC (signed)
xx(MOD_RK, mod, RIRIKI, MOD_RR, 4, REGT_INT),
xx(MOD_KR, mod, RIKIRI, MOD_RR, 2, REGT_INT),
xx(MODU_RR, modu, RIRIRI, NOP, 0, 0), // dA = dkB % dkC (unsigned)
xx(MODU_RK, modu, RIRIKI, MODU_RR,4, REGT_INT),
xx(MODU_KR, modu, RIKIRI, MODU_RR,2, REGT_INT),
xx(AND_RR, and, RIRIRI, NOP, 0, 0), // dA = dB & dkC
xx(AND_RK, and, RIRIKI, AND_RR, 4, REGT_INT),
xx(OR_RR, or, RIRIRI, NOP, 0, 0), // dA = dB | dkC
xx(OR_RK, or, RIRIKI, OR_RR, 4, REGT_INT),
xx(XOR_RR, xor, RIRIRI, NOP, 0, 0), // dA = dB ^ dkC
xx(XOR_RK, xor, RIRIKI, XOR_RR, 4, REGT_INT),
xx(MIN_RR, min, RIRIRI, NOP, 0, 0), // dA = min(dB,dkC)
xx(MIN_RK, min, RIRIKI, MIN_RR, 4, REGT_INT),
xx(MAX_RR, max, RIRIRI, NOP, 0, 0), // dA = max(dB,dkC)
xx(MAX_RK, max, RIRIKI, MAX_RR, 4, REGT_INT),
xx(ABS, abs, RIRI, NOP, 0, 0), // dA = abs(dB)
xx(NEG, neg, RIRI, NOP, 0, 0), // dA = -dB
xx(NOT, not, RIRI, NOP, 0, 0), // dA = ~dB
xx(EQ_R, beq, CIRR, NOP, 0, 0), // if ((dB == dkC) != A) then pc++
xx(EQ_K, beq, CIRK, EQ_R, 4, REGT_INT),
xx(LT_RR, blt, CIRR, NOP, 0, 0), // if ((dkB < dkC) != A) then pc++
xx(LT_RK, blt, CIRK, LT_RR, 4, REGT_INT),
xx(LT_KR, blt, CIKR, LT_RR, 2, REGT_INT),
xx(LE_RR, ble, CIRR, NOP, 0, 0), // if ((dkB <= dkC) != A) then pc++
xx(LE_RK, ble, CIRK, LE_RR, 4, REGT_INT),
xx(LE_KR, ble, CIKR, LE_RR, 2, REGT_INT),
xx(LTU_RR, bltu, CIRR, NOP, 0, 0), // if ((dkB < dkC) != A) then pc++ -- unsigned
xx(LTU_RK, bltu, CIRK, LTU_RR, 4, REGT_INT),
xx(LTU_KR, bltu, CIKR, LTU_RR, 2, REGT_INT),
xx(LEU_RR, bleu, CIRR, NOP, 0, 0), // if ((dkB <= dkC) != A) then pc++ -- unsigned
xx(LEU_RK, bleu, CIRK, LEU_RR, 4, REGT_INT),
xx(LEU_KR, bleu, CIKR, LEU_RR, 2, REGT_INT),
xx(SLL_RR, sll, RIRIRI, NOP, 0, 0) // dA = dkB << diC
xx(SLL_RI, sll, RIRII8, NOP, 0, 0)
xx(SLL_KR, sll, RIKIRI, SLL_RR, 2, REGT_INT)
xx(SRL_RR, srl, RIRIRI, NOP, 0, 0) // dA = dkB >> diC -- unsigned
xx(SRL_RI, srl, RIRII8, NOP, 0, 0)
xx(SRL_KR, srl, RIKIRI, SRL_RR, 2, REGT_INT)
xx(SRA_RR, sra, RIRIRI, NOP, 0, 0) // dA = dkB >> diC -- signed
xx(SRA_RI, sra, RIRII8, NOP, 0, 0)
xx(SRA_KR, sra, RIKIRI, SRA_RR, 2, REGT_INT)
xx(ADD_RR, add, RIRIRI, NOP, 0, 0) // dA = dB + dkC
xx(ADD_RK, add, RIRIKI, ADD_RR, 4, REGT_INT)
xx(ADDI, addi, RIRIIs, NOP, 0, 0) // dA = dB + C -- C is a signed 8-bit constant
xx(SUB_RR, sub, RIRIRI, NOP, 0, 0) // dA = dkB - dkC
xx(SUB_RK, sub, RIRIKI, SUB_RR, 4, REGT_INT)
xx(SUB_KR, sub, RIKIRI, SUB_RR, 2, REGT_INT)
xx(MUL_RR, mul, RIRIRI, NOP, 0, 0) // dA = dB * dkC
xx(MUL_RK, mul, RIRIKI, MUL_RR, 4, REGT_INT)
xx(DIV_RR, div, RIRIRI, NOP, 0, 0) // dA = dkB / dkC (signed)
xx(DIV_RK, div, RIRIKI, DIV_RR, 4, REGT_INT)
xx(DIV_KR, div, RIKIRI, DIV_RR, 2, REGT_INT)
xx(DIVU_RR, divu, RIRIRI, NOP, 0, 0) // dA = dkB / dkC (unsigned)
xx(DIVU_RK, divu, RIRIKI, DIVU_RR,4, REGT_INT)
xx(DIVU_KR, divu, RIKIRI, DIVU_RR,2, REGT_INT)
xx(MOD_RR, mod, RIRIRI, NOP, 0, 0) // dA = dkB % dkC (signed)
xx(MOD_RK, mod, RIRIKI, MOD_RR, 4, REGT_INT)
xx(MOD_KR, mod, RIKIRI, MOD_RR, 2, REGT_INT)
xx(MODU_RR, modu, RIRIRI, NOP, 0, 0) // dA = dkB % dkC (unsigned)
xx(MODU_RK, modu, RIRIKI, MODU_RR,4, REGT_INT)
xx(MODU_KR, modu, RIKIRI, MODU_RR,2, REGT_INT)
xx(AND_RR, and, RIRIRI, NOP, 0, 0) // dA = dB & dkC
xx(AND_RK, and, RIRIKI, AND_RR, 4, REGT_INT)
xx(OR_RR, or, RIRIRI, NOP, 0, 0) // dA = dB | dkC
xx(OR_RK, or, RIRIKI, OR_RR, 4, REGT_INT)
xx(XOR_RR, xor, RIRIRI, NOP, 0, 0) // dA = dB ^ dkC
xx(XOR_RK, xor, RIRIKI, XOR_RR, 4, REGT_INT)
xx(MIN_RR, min, RIRIRI, NOP, 0, 0) // dA = min(dB,dkC)
xx(MIN_RK, min, RIRIKI, MIN_RR, 4, REGT_INT)
xx(MAX_RR, max, RIRIRI, NOP, 0, 0) // dA = max(dB,dkC)
xx(MAX_RK, max, RIRIKI, MAX_RR, 4, REGT_INT)
xx(ABS, abs, RIRI, NOP, 0, 0) // dA = abs(dB)
xx(NEG, neg, RIRI, NOP, 0, 0) // dA = -dB
xx(NOT, not, RIRI, NOP, 0, 0) // dA = ~dB
xx(EQ_R, beq, CIRR, NOP, 0, 0) // if ((dB == dkC) != A) then pc++
xx(EQ_K, beq, CIRK, EQ_R, 4, REGT_INT)
xx(LT_RR, blt, CIRR, NOP, 0, 0) // if ((dkB < dkC) != A) then pc++
xx(LT_RK, blt, CIRK, LT_RR, 4, REGT_INT)
xx(LT_KR, blt, CIKR, LT_RR, 2, REGT_INT)
xx(LE_RR, ble, CIRR, NOP, 0, 0) // if ((dkB <= dkC) != A) then pc++
xx(LE_RK, ble, CIRK, LE_RR, 4, REGT_INT)
xx(LE_KR, ble, CIKR, LE_RR, 2, REGT_INT)
xx(LTU_RR, bltu, CIRR, NOP, 0, 0) // if ((dkB < dkC) != A) then pc++ -- unsigned
xx(LTU_RK, bltu, CIRK, LTU_RR, 4, REGT_INT)
xx(LTU_KR, bltu, CIKR, LTU_RR, 2, REGT_INT)
xx(LEU_RR, bleu, CIRR, NOP, 0, 0) // if ((dkB <= dkC) != A) then pc++ -- unsigned
xx(LEU_RK, bleu, CIRK, LEU_RR, 4, REGT_INT)
xx(LEU_KR, bleu, CIKR, LEU_RR, 2, REGT_INT)
// Double-precision floating point math.
xx(ADDF_RR, add, RFRFRF, NOP, 0, 0), // fA = fB + fkC
xx(ADDF_RK, add, RFRFKF, ADDF_RR,4, REGT_FLOAT),
xx(SUBF_RR, sub, RFRFRF, NOP, 0, 0), // fA = fkB - fkC
xx(SUBF_RK, sub, RFRFKF, SUBF_RR,4, REGT_FLOAT),
xx(SUBF_KR, sub, RFKFRF, SUBF_RR,2, REGT_FLOAT),
xx(MULF_RR, mul, RFRFRF, NOP, 0, 0), // fA = fB * fkC
xx(MULF_RK, mul, RFRFKF, MULF_RR,4, REGT_FLOAT),
xx(DIVF_RR, div, RFRFRF, NOP, 0, 0), // fA = fkB / fkC
xx(DIVF_RK, div, RFRFKF, DIVF_RR,4, REGT_FLOAT),
xx(DIVF_KR, div, RFKFRF, DIVF_RR,2, REGT_FLOAT),
xx(MODF_RR, mod, RFRFRF, NOP, 0, 0), // fA = fkB % fkC
xx(MODF_RK, mod, RFRFKF, MODF_RR,4, REGT_FLOAT),
xx(MODF_KR, mod, RFKFRF, MODF_RR,4, REGT_FLOAT),
xx(POWF_RR, pow, RFRFRF, NOP, 0, 0), // fA = fkB ** fkC
xx(POWF_RK, pow, RFRFKF, POWF_RR,4, REGT_FLOAT),
xx(POWF_KR, pow, RFKFRF, POWF_RR,2, REGT_FLOAT),
xx(MINF_RR, min, RFRFRF, NOP, 0, 0), // fA = min(fB),fkC)
xx(MINF_RK, min, RFRFKF, MINF_RR,4, REGT_FLOAT),
xx(MAXF_RR, max, RFRFRF, NOP, 0, 0), // fA = max(fB),fkC)
xx(MAXF_RK, max, RFRFKF, MAXF_RR,4, REGT_FLOAT),
xx(ATAN2, atan2, RFRFRF, NOP, 0, 0), // fA = atan2(fB,fC), result is in degrees
xx(FLOP, flop, RFRFI8, NOP, 0, 0), // fA = f(fB), where function is selected by C
xx(EQF_R, beq, CFRR, NOP, 0, 0), // if ((fB == fkC) != (A & 1)) then pc++
xx(EQF_K, beq, CFRK, EQF_R, 4, REGT_FLOAT),
xx(LTF_RR, blt, CFRR, NOP, 0, 0), // if ((fkB < fkC) != (A & 1)) then pc++
xx(LTF_RK, blt, CFRK, LTF_RR, 4, REGT_FLOAT),
xx(LTF_KR, blt, CFKR, LTF_RR, 2, REGT_FLOAT),
xx(LEF_RR, ble, CFRR, NOP, 0, 0), // if ((fkb <= fkC) != (A & 1)) then pc++
xx(LEF_RK, ble, CFRK, LEF_RR, 4, REGT_FLOAT),
xx(LEF_KR, ble, CFKR, LEF_RR, 2, REGT_FLOAT),
xx(ADDF_RR, add, RFRFRF, NOP, 0, 0) // fA = fB + fkC
xx(ADDF_RK, add, RFRFKF, ADDF_RR,4, REGT_FLOAT)
xx(SUBF_RR, sub, RFRFRF, NOP, 0, 0) // fA = fkB - fkC
xx(SUBF_RK, sub, RFRFKF, SUBF_RR,4, REGT_FLOAT)
xx(SUBF_KR, sub, RFKFRF, SUBF_RR,2, REGT_FLOAT)
xx(MULF_RR, mul, RFRFRF, NOP, 0, 0) // fA = fB * fkC
xx(MULF_RK, mul, RFRFKF, MULF_RR,4, REGT_FLOAT)
xx(DIVF_RR, div, RFRFRF, NOP, 0, 0) // fA = fkB / fkC
xx(DIVF_RK, div, RFRFKF, DIVF_RR,4, REGT_FLOAT)
xx(DIVF_KR, div, RFKFRF, DIVF_RR,2, REGT_FLOAT)
xx(MODF_RR, mod, RFRFRF, NOP, 0, 0) // fA = fkB % fkC
xx(MODF_RK, mod, RFRFKF, MODF_RR,4, REGT_FLOAT)
xx(MODF_KR, mod, RFKFRF, MODF_RR,4, REGT_FLOAT)
xx(POWF_RR, pow, RFRFRF, NOP, 0, 0) // fA = fkB ** fkC
xx(POWF_RK, pow, RFRFKF, POWF_RR,4, REGT_FLOAT)
xx(POWF_KR, pow, RFKFRF, POWF_RR,2, REGT_FLOAT)
xx(MINF_RR, min, RFRFRF, NOP, 0, 0) // fA = min(fB)fkC)
xx(MINF_RK, min, RFRFKF, MINF_RR,4, REGT_FLOAT)
xx(MAXF_RR, max, RFRFRF, NOP, 0, 0) // fA = max(fB)fkC)
xx(MAXF_RK, max, RFRFKF, MAXF_RR,4, REGT_FLOAT)
xx(ATAN2, atan2, RFRFRF, NOP, 0, 0) // fA = atan2(fB,fC) result is in degrees
xx(FLOP, flop, RFRFI8, NOP, 0, 0) // fA = f(fB) where function is selected by C
xx(EQF_R, beq, CFRR, NOP, 0, 0) // if ((fB == fkC) != (A & 1)) then pc++
xx(EQF_K, beq, CFRK, EQF_R, 4, REGT_FLOAT)
xx(LTF_RR, blt, CFRR, NOP, 0, 0) // if ((fkB < fkC) != (A & 1)) then pc++
xx(LTF_RK, blt, CFRK, LTF_RR, 4, REGT_FLOAT)
xx(LTF_KR, blt, CFKR, LTF_RR, 2, REGT_FLOAT)
xx(LEF_RR, ble, CFRR, NOP, 0, 0) // if ((fkb <= fkC) != (A & 1)) then pc++
xx(LEF_RK, ble, CFRK, LEF_RR, 4, REGT_FLOAT)
xx(LEF_KR, ble, CFKR, LEF_RR, 2, REGT_FLOAT)
// Vector math. (2D)
xx(NEGV2, negv2, RVRV, NOP, 0, 0), // vA = -vB
xx(ADDV2_RR, addv2, RVRVRV, NOP, 0, 0), // vA = vB + vkC
xx(SUBV2_RR, subv2, RVRVRV, NOP, 0, 0), // vA = vkB - vkC
xx(DOTV2_RR, dotv2, RVRVRV, NOP, 0, 0), // va = vB dot vkC
xx(MULVF2_RR, mulv2, RVRVRF, NOP, 0, 0), // vA = vkB * fkC
xx(MULVF2_RK, mulv2, RVRVKF, MULVF2_RR,4, REGT_FLOAT),
xx(DIVVF2_RR, divv2, RVRVRF, NOP, 0, 0), // vA = vkB / fkC
xx(DIVVF2_RK, divv2, RVRVKF, DIVVF2_RR,4, REGT_FLOAT),
xx(LENV2, lenv2, RFRV, NOP, 0, 0), // fA = vB.Length
xx(EQV2_R, beqv2, CVRR, NOP, 0, 0), // if ((vB == vkC) != A) then pc++ (inexact if A & 32)
xx(EQV2_K, beqv2, CVRK, NOP, 0, 0), // this will never be used.
xx(NEGV2, negv2, RVRV, NOP, 0, 0) // vA = -vB
xx(ADDV2_RR, addv2, RVRVRV, NOP, 0, 0) // vA = vB + vkC
xx(SUBV2_RR, subv2, RVRVRV, NOP, 0, 0) // vA = vkB - vkC
xx(DOTV2_RR, dotv2, RVRVRV, NOP, 0, 0) // va = vB dot vkC
xx(MULVF2_RR, mulv2, RVRVRF, NOP, 0, 0) // vA = vkB * fkC
xx(MULVF2_RK, mulv2, RVRVKF, MULVF2_RR,4, REGT_FLOAT)
xx(DIVVF2_RR, divv2, RVRVRF, NOP, 0, 0) // vA = vkB / fkC
xx(DIVVF2_RK, divv2, RVRVKF, DIVVF2_RR,4, REGT_FLOAT)
xx(LENV2, lenv2, RFRV, NOP, 0, 0) // fA = vB.Length
xx(EQV2_R, beqv2, CVRR, NOP, 0, 0) // if ((vB == vkC) != A) then pc++ (inexact if A & 32)
xx(EQV2_K, beqv2, CVRK, NOP, 0, 0) // this will never be used.
// Vector math (3D)
xx(NEGV3, negv3, RVRV, NOP, 0, 0), // vA = -vB
xx(ADDV3_RR, addv3, RVRVRV, NOP, 0, 0), // vA = vB + vkC
xx(SUBV3_RR, subv3, RVRVRV, NOP, 0, 0), // vA = vkB - vkC
xx(DOTV3_RR, dotv3, RVRVRV, NOP, 0, 0), // va = vB dot vkC
xx(CROSSV_RR, crossv, RVRVRV, NOP, 0, 0), // vA = vkB cross vkC
xx(MULVF3_RR, mulv3, RVRVRF, NOP, 0, 0), // vA = vkB * fkC
xx(MULVF3_RK, mulv3, RVRVKF, MULVF3_RR,4, REGT_FLOAT),
xx(DIVVF3_RR, divv3, RVRVRF, NOP, 0, 0), // vA = vkB / fkC
xx(DIVVF3_RK, divv3, RVRVKF, DIVVF3_RR,4, REGT_FLOAT),
xx(LENV3, lenv3, RFRV, NOP, 0, 0), // fA = vB.Length
xx(EQV3_R, beqv3, CVRR, NOP, 0, 0), // if ((vB == vkC) != A) then pc++ (inexact if A & 33)
xx(EQV3_K, beqv3, CVRK, NOP, 0, 0), // this will never be used.
xx(NEGV3, negv3, RVRV, NOP, 0, 0) // vA = -vB
xx(ADDV3_RR, addv3, RVRVRV, NOP, 0, 0) // vA = vB + vkC
xx(SUBV3_RR, subv3, RVRVRV, NOP, 0, 0) // vA = vkB - vkC
xx(DOTV3_RR, dotv3, RVRVRV, NOP, 0, 0) // va = vB dot vkC
xx(CROSSV_RR, crossv, RVRVRV, NOP, 0, 0) // vA = vkB cross vkC
xx(MULVF3_RR, mulv3, RVRVRF, NOP, 0, 0) // vA = vkB * fkC
xx(MULVF3_RK, mulv3, RVRVKF, MULVF3_RR,4, REGT_FLOAT)
xx(DIVVF3_RR, divv3, RVRVRF, NOP, 0, 0) // vA = vkB / fkC
xx(DIVVF3_RK, divv3, RVRVKF, DIVVF3_RR,4, REGT_FLOAT)
xx(LENV3, lenv3, RFRV, NOP, 0, 0) // fA = vB.Length
xx(EQV3_R, beqv3, CVRR, NOP, 0, 0) // if ((vB == vkC) != A) then pc++ (inexact if A & 33)
xx(EQV3_K, beqv3, CVRK, NOP, 0, 0) // this will never be used.
// Pointer math.
xx(ADDA_RR, add, RPRPRI, NOP, 0, 0), // pA = pB + dkC
xx(ADDA_RK, add, RPRPKI, ADDA_RR,4, REGT_INT),
xx(SUBA, sub, RIRPRP, NOP, 0, 0), // dA = pB - pC
xx(EQA_R, beq, CPRR, NOP, 0, 0), // if ((pB == pkC) != A) then pc++
xx(EQA_K, beq, CPRK, EQA_R, 4, REGT_POINTER),
xx(ADDA_RR, add, RPRPRI, NOP, 0, 0) // pA = pB + dkC
xx(ADDA_RK, add, RPRPKI, ADDA_RR,4, REGT_INT)
xx(SUBA, sub, RIRPRP, NOP, 0, 0) // dA = pB - pC
xx(EQA_R, beq, CPRR, NOP, 0, 0) // if ((pB == pkC) != A) then pc++
xx(EQA_K, beq, CPRK, EQA_R, 4, REGT_POINTER)
#undef xx