Files
starruler-linux/source/game/as/as_jit.cpp
T
2018-07-17 14:15:37 +02:00

4455 lines
116 KiB
C++

#include "as_jit.h"
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <map>
#include <functional>
#include <cstdint>
#include "../source/as_scriptfunction.h"
#include "../source/as_objecttype.h"
#include "../source/as_callfunc.h"
#include "../source/as_scriptengine.h"
#include "../source/as_scriptobject.h"
#include "../source/as_texts.h"
#include "../source/as_context.h"
#include "virtual_asm.h"
using namespace assembler;
#ifdef __amd64__
#define stdcall
#define JIT_64
#endif
#ifdef _M_AMD64
#define JIT_64
#endif
#ifdef JIT_64
#define stdcall
#else
#ifdef _MSC_VER
#define stdcall __stdcall
#else
#define stdcall __attribute__((stdcall))
#endif
#endif
//#define JIT_PRINT_UNHANDLED_CALLS
#ifdef JIT_PRINT_UNHANDLED_CALLS
#include <string>
#include <set>
static std::set<asCScriptFunction*> unhandledCalls;
#endif
const unsigned codePageSize = 65535 * 4;
static const void* JUMP_DESTINATION = (void*)(size_t)0x1;
#define offset0 (asBC_SWORDARG0(pOp)*sizeof(asDWORD))
#define offset1 (asBC_SWORDARG1(pOp)*sizeof(asDWORD))
#define offset2 (asBC_SWORDARG2(pOp)*sizeof(asDWORD))
//#define JIT_DEBUG
#ifdef JIT_DEBUG
static asEBCInstr DBG_CurrentOP;
static asEBCInstr DBG_LastOP;
static void* DBG_Entry = 0;
static void* DBG_Instr = 0;
static void* DBG_LastInstr = 0;
static void* DBG_LastCall = 0;
static void* DBG_FuncEntry = 0;
static asCScriptFunction* DBG_CurrentFunction;
#endif
short offset(asDWORD* op, unsigned n) {
return *(((short*)op) + (n+1)) * sizeof(asDWORD);
}
//Returns true if the op will clear the temporary var
// Used to determine if we need to perform a full test in a Test-Jump pair
bool clearsTemporary(asEBCInstr op) {
switch(op) {
case asBC_TZ:
case asBC_TNZ:
case asBC_TS:
case asBC_TNS:
case asBC_TP:
case asBC_TNP:
case asBC_CMPd:
case asBC_CMPu:
case asBC_CMPf:
case asBC_CMPi:
case asBC_CMPIi:
case asBC_CMPIf:
case asBC_CMPIu:
case asBC_CmpPtr:
case asBC_CpyVtoR4:
case asBC_CpyVtoR8:
case asBC_CMPi64:
case asBC_CMPu64:
return true;
}
return false;
}
//Wrappers so we can deal with complex pointers/calling conventions
void stdcall allocScriptObject(asCObjectType* type, asCScriptFunction* constructor, asIScriptEngine* engine, asSVMRegisters* registers);
void* stdcall allocArray(asDWORD bytes);
void* stdcall engineAlloc(asCScriptEngine* engine, asCObjectType* type);
void stdcall engineRelease(asCScriptEngine* engine, void* memory, asCScriptFunction* release);
void stdcall engineListFree(asCScriptEngine* engine, asCObjectType* objType, void* memory);
void stdcall engineDestroyFree(asCScriptEngine* engine, void* memory, asCScriptFunction* destruct);
void stdcall engineFree(asCScriptEngine* engine, void* memory);
void stdcall engineCallMethod(asCScriptEngine* engine, void* object, asCScriptFunction* method);
void stdcall callScriptFunction(asIScriptContext* ctx, asCScriptFunction* func);
asCScriptFunction* stdcall callInterfaceMethod(asIScriptContext* ctx, asCScriptFunction* func);
asCScriptFunction* stdcall callBoundFunction(asIScriptContext* ctx, unsigned short fid);
void stdcall receiveAutoObjectHandle(asIScriptContext* ctx, asCScriptObject* obj);
asCScriptObject* stdcall castObject(asCScriptObject* obj, asCObjectType* to);
bool stdcall doSuspend(asIScriptContext* ctx);
void stdcall returnScriptFunction(asCContext* ctx);
//Wrapper functions to cast between types, or perform math on large types, where doing so is overly complicated in the ASM
#ifdef _MSC_VER
template<class F, class T>
void stdcall directConvert(F* from, T* to) {
*to = (T)*from;
}
#else
//stdcall doesn't work with templates on GCC
template<class F, class T>
void directConvert(F* from, T* to) {
*to = (T)*from;
}
#endif
void fpow_wrapper(float* base, float* exponent, bool* overflow, float* ret) {
float r = pow(*base, *exponent);
bool over = (r == float(HUGE_VAL));
*overflow = over;
if(!over)
*ret = r;
}
void dpow_wrapper(double* base, double* exponent, bool* overflow, double* ret) {
double r = pow(*base, *exponent);
bool over = (r == HUGE_VAL);
*overflow = over;
if(!over)
*ret = r;
}
void dipow_wrapper(double* base, int exponent, bool* overflow, double* ret) {
double r = pow(*base, exponent);
bool over = (r == HUGE_VAL);
*overflow = over;
if(!over)
*ret = r;
}
void i64pow_wrapper(asINT64* base, asINT64* exponent, bool* overflow, asINT64* ret) {
auto r = as_powi64(*base, *exponent, *overflow);
if(!*overflow)
*ret = r;
}
void u64pow_wrapper(asQWORD* base, asQWORD* exponent, bool* overflow, asQWORD* ret) {
auto r = as_powu64(*base, *exponent, *overflow);
if(!*overflow)
*ret = r;
}
float stdcall fmod_wrapper_f(float* div, float* mod) {
return fmod(*div, *mod);
}
double stdcall fmod_wrapper(double* div, double* mod) {
return fmod(*div, *mod);
}
void stdcall i64_add(long long* a, long long* b, long long* r) {
*r = *a + *b;
}
void stdcall i64_sub(long long* a, long long* b, long long* r) {
*r = *a - *b;
}
void stdcall i64_mul(long long* a, long long* b, long long* r) {
*r = *a * *b;
}
void stdcall i64_div(long long* a, long long* b, long long* r) {
*r = *a / *b;
}
void stdcall i64_mod(long long* a, long long* b, long long* r) {
*r = *a % *b;
}
void stdcall i64_sll(unsigned long long* a, asDWORD* b, unsigned long long* r) {
*r = *a << *b;
}
void stdcall i64_srl(unsigned long long* a, asDWORD* b, unsigned long long* r) {
*r = *a >> *b;
}
void stdcall i64_sra(long long* a, asDWORD* b, long long* r) {
*r = *a >> *b;
}
int stdcall cmp_int64(long long* a, long long* b) {
if(*a == *b )
return 0;
else if(*a < *b)
return -1;
else
return 1;
}
int stdcall cmp_uint64(unsigned long long* a, unsigned long long* b) {
if(*a == *b )
return 0;
else if(*a < *b)
return -1;
else
return 1;
}
size_t stdcall div_ull(unsigned long long* div, unsigned long long* by, unsigned long long* result) {
if(*by == 0)
return 1;
*result = *div / *by;
return 0;
}
size_t stdcall mod_ull(unsigned long long* div, unsigned long long* by, unsigned long long* result) {
if(*by == 0)
return 1;
*result = *div % *by;
return 0;
}
enum ObjectPosition {
OP_This,
OP_First,
OP_Last,
OP_None
};
enum EAXContains {
EAX_Unknown,
EAX_Stack,
EAX_Offset,
};
enum SysCallFlags {
SC_Safe = 0x01,
SC_ValidObj = 0x02,
SC_NoSuspend = 0x04,
SC_FastFPU = 0x08,
SC_NoReturn = 0x10,
SC_Simple = 0x20,
};
struct SystemCall {
Processor& cpu;
FloatingPointUnit& fpu;
asDWORD* const & pOp;
unsigned flags;
bool callIsSafe;
bool checkNullObj;
bool handleSuspend;
bool acceptReturn;
bool isSimple;
std::function<void(JumpType,bool)> returnHandler;
SystemCall(Processor& CPU, FloatingPointUnit& FPU,
std::function<void(JumpType,bool)> ConditionalReturn, asDWORD* const & bytecode, unsigned JitFlags)
: cpu(CPU), fpu(FPU), returnHandler(ConditionalReturn), pOp(bytecode), flags(0)
{
if((JitFlags & JIT_SYSCALL_NO_ERRORS) != 0)
flags |= SC_Safe;
if((JitFlags & JIT_NO_SUSPEND) != 0)
flags |= SC_NoSuspend;
if((JitFlags & JIT_SYSCALL_FPU_NORESET) != 0)
flags |= SC_FastFPU;
}
void callSystemFunction(asCScriptFunction* func, Register* objPointer = 0, unsigned callFlags = 0);
private:
void call_viaAS(asCScriptFunction* func, Register* objPointer);
void call_generic(asCScriptFunction* func, Register* objPointer);
void call_stdcall(asSSystemFunctionInterface* func, asCScriptFunction* sFunc);
void call_cdecl(asSSystemFunctionInterface* func, asCScriptFunction* sFunc);
void call_cdecl_obj(asSSystemFunctionInterface* func, asCScriptFunction* sFunc, Register* objPointer, bool last);
void call_thiscall(asSSystemFunctionInterface* func, asCScriptFunction* sFunc, Register* objPointer);
void call_simple(Register& objPointer, asCScriptFunction* func);
void call_64conv(asSSystemFunctionInterface* func, asCScriptFunction* sFunc, Register* objPointer, ObjectPosition pos);
void call_getReturn(asSSystemFunctionInterface* func, asCScriptFunction* sFunc);
//Handles error handling
void call_entry(asSSystemFunctionInterface* func, asCScriptFunction* sFunc);
void call_error();
void call_exit(asSSystemFunctionInterface* func);
};
struct SwitchRegion {
unsigned char** buffer;
unsigned count, remaining;
SwitchRegion() : buffer(0), count(0), remaining(0) {}
};
struct FutureJump {
void* jump;
FutureJump* next;
FutureJump() : jump(0), next(0) {}
FutureJump* advance() {
FutureJump* n = next;
delete this;
return n;
}
};
unsigned toSize(asEBCInstr instr) {
return asBCTypeSize[asBCInfo[instr].type];
}
asCJITCompiler::asCJITCompiler(unsigned Flags)
: activePage(0), lock(new assembler::CriticalSection()), flags(Flags), activeJumpTable(0), currentTableSize(0)
{
}
asCJITCompiler::~asCJITCompiler() {
if(activeJumpTable)
delete[] activeJumpTable;
delete lock;
}
//Returns the total number of bytes that will be pushed, until the next op that doesn't push
unsigned findTotalPushBatchSize(asDWORD* firstPush, asDWORD* endOfBytecode);
//Offsets on the stack for function local variables
namespace local {
//Used in alloc
const unsigned allocMem = 2 * sizeof(void*);
//Used in function calls
const unsigned pIsSystem = 3 * sizeof(void*);
const unsigned retPointer = 4 * sizeof(void*);
//Copy of value register in 32 bit mode when returns are being ignored, not used alongside retPointer
const unsigned regCopy = 4 * sizeof(void*);
//Used in REFCPY
const unsigned object1 = 0;
const unsigned object2 = sizeof(void*);
//Used in power calls to check for overflows
const unsigned overflowRet = 0;
};
const unsigned functionReserveSpace = 5 * sizeof(void*);
int asCJITCompiler::CompileFunction(asIScriptFunction *function, asJITFunction *output) {
asUINT length;
asDWORD *pOp = function->GetByteCode(&length);
//No bytecode for this function, don't bother making any jit for it
if(pOp == 0 || length == 0) {
output = 0;
return 1;
}
asDWORD *end = pOp + length, *start = pOp;
std::vector<SwitchRegion> switches;
SwitchRegion* activeSwitch = 0;
lock->enter();
//Get the jump table, or make a new one if necessary, and then zero it out
unsigned char** jumpTable = 0;
if(activeJumpTable) {
if(length <= currentTableSize) {
jumpTable = activeJumpTable;
}
else {
delete[] activeJumpTable;
jumpTable = new unsigned char*[length];
activeJumpTable = jumpTable;
}
}
else {
jumpTable = new unsigned char*[length];
activeJumpTable = jumpTable;
}
memset(jumpTable, 0, length * sizeof(void*));
//Do a first pass through the bytecode to mark all locations we are going to be jumping to,
//that way we can prevent running multi-op optimizations on them.
asDWORD* passOp = pOp;
while(passOp < end) {
asEBCInstr op = asEBCInstr(*(asBYTE*)passOp);
switch(op) {
case asBC_JMP:
case asBC_JLowZ:
case asBC_JZ:
case asBC_JLowNZ:
case asBC_JNZ:
case asBC_JS:
case asBC_JNS:
case asBC_JP:
case asBC_JNP: {
asDWORD* target = passOp + asBC_INTARG(passOp) + 2;
jumpTable[target - start] = (unsigned char*)JUMP_DESTINATION;
} break;
}
passOp += toSize(op);
}
//Get the active page, or create a new one if the current one is missing or too small (256 bytes for the entry and a few ops)
if(activePage == 0 || activePage->final || activePage->getFreeSize() < 256)
activePage = new CodePage(codePageSize, reinterpret_cast<void*>(&toSize));
activePage->grab();
void* curJitFunction = activePage->getFunctionPointer<void*>();
void* firstJitEntry = 0;
*output = activePage->getFunctionPointer<asJITFunction>();
pages.insert(std::pair<asJITFunction,assembler::CodePage*>(*output,activePage));
//If we are outside of opcodes we can execute, ignore all ops until a new JIT entry is found
bool waitingForEntry = true;
//Special case for a common op-pairing (*esi = eax; eax = *esi;)
unsigned currentEAX = EAX_Unknown, nextEAX = EAX_Unknown;
//Setup the processor as a 32 bit processor, as most angelscript ops work on integers
Processor cpu(*activePage, 32);
byte* byteStart = (byte*)cpu.op;
FloatingPointUnit fpu(cpu);
unsigned pBits = sizeof(void*) * 8;
#ifdef JIT_64
//32-bit registers
Register eax(cpu,EAX), ebx(cpu,EBX), ecx(cpu,ECX), edx(cpu,EDX), ebp(cpu,EBP,pBits), edi(cpu,R12);
//8-bit registers
Register al(cpu,EAX,8), bl(cpu,EBX,8), cl(cpu,ECX,8), dl(cpu,EDX,8);
//Pointer-sized registers
Register pax(cpu,EAX,pBits), pbx(cpu,EBX,pBits), pcx(cpu,ECX,pBits), pdx(cpu,EDX,pBits), esp(cpu,ESP,pBits),
pdi(cpu, R12, pBits), esi(cpu, R13, pBits);
Register rarg(cpu, R10, pBits);
//Don't use EDI and ESI, they're used for integer
//arguments to functions, despite being nonvolatile
#else
//32-bit registers
Register eax(cpu,EAX), ebx(cpu,EBX), ecx(cpu,ECX), edx(cpu,EDX), ebp(cpu,EBP,pBits), edi(cpu,EDI);
//8-bit registers
Register al(cpu,EAX,8), bl(cpu,EBX,8), cl(cpu,ECX,8), dl(cpu,EDX,8);
//Pointer-sized registers
Register pax(cpu,EAX,pBits), pbx(cpu,EBX,pBits), pcx(cpu,ECX,pBits), pdx(cpu,EDX,pBits), esp(cpu,ESP,pBits),
pdi(cpu, EDI, pBits), esi(cpu, ESI, pBits);
Register rarg(cpu, EDX, pBits);
#endif
//JIT FUNCTION ENTRY
//==================
//Push unmutable registers (these registers must retain their value after we leave our function)
cpu.push(esi);
cpu.push(edi);
cpu.push(ebx);
cpu.push(ebp);
//Reserve two pointers for various things
esp -= functionReserveSpace;
cpu.stackDepth += (cpu.pushSize() * 4) + functionReserveSpace;
#ifdef JIT_DEBUG
pbx = (void*)&DBG_FuncEntry;
as<void*>(*pbx) = pax;
#endif
//Function initialization {
#ifdef JIT_64
ebp = cpu.intArg64(0, 0);
pax = cpu.intArg64(1, 1);
#else
ebp = as<void*>(*esp+cpu.stackDepth); //Register pointer
pax = as<void*>(*esp+cpu.stackDepth+cpu.pushSize()); //Entry jump pointer
#endif
#ifdef JIT_DEBUG
pbx = (void*)&DBG_CurrentFunction;
as<void*>(*pbx) = (void*)function;
pbx = (void*)&DBG_Entry;
as<void*>(*pbx) = pax;
#endif
pdi = as<void*>(*ebp+offsetof(asSVMRegisters,stackFramePointer)); //VM Frame pointer
esi = as<void*>(*ebp+offsetof(asSVMRegisters,stackPointer)); //VM Stack pointer
pbx = as<void*>(*ebp+offsetof(asSVMRegisters,valueRegister)); //VM Temporary
//}
//Jump to the section of the function we'll actually be executing this time
cpu.jump(pax);
//Function return {
volatile byte* ret_pos = cpu.op;
as<void*>(*ebp+offsetof(asSVMRegisters,programPointer)) = rarg; //Set the bytecode pointer based on our exit
as<void*>(*ebp+offsetof(asSVMRegisters,stackFramePointer)) = pdi; //Return the frame pointer
as<void*>(*ebp+offsetof(asSVMRegisters,stackPointer)) = esi; //Return the stack pointer
as<void*>(*ebp+offsetof(asSVMRegisters,valueRegister)) = pbx; //Return the temporary
//Pop reserved pointers and saved pointers
esp += functionReserveSpace;
cpu.pop(ebp);
cpu.pop(ebx);
cpu.pop(edi);
cpu.pop(esi);
cpu.ret();
//}
auto Return = [&](bool expected) {
//Set EDX to the bytecode pointer so the vm can be returned to the correct state
rarg = (void*)pOp;
cpu.jump(Jump,ret_pos);
waitingForEntry = expected;
};
auto ReturnCondition = [&](JumpType condition) {
if(condition != Zero) {
rarg = (void*)pOp;
cpu.jump(condition,ret_pos);
}
else {
auto* j = cpu.prep_short_jump(NotZero);
rarg = (void*)pOp;
cpu.jump(Jump,ret_pos);
cpu.end_short_jump(j);
}
};
auto ReturnPosition = [&](JumpType condition, bool nextOp) {
auto retBC = pOp;
if(nextOp) {
asEBCInstr op = (asEBCInstr)*(asBYTE*)pOp;
retBC += toSize(op);
}
if(condition != Zero) {
rarg = (void*)retBC;
cpu.jump(condition,ret_pos);
}
else {
auto* j = cpu.prep_short_jump(NotZero);
rarg = (void*)retBC;
cpu.jump(Jump,ret_pos);
cpu.end_short_jump(j);
}
};
SystemCall sysCall(cpu, fpu, ReturnPosition, pOp, flags);
volatile byte* script_ret = 0;
auto ReturnFromScriptCall = [&]() {
if(script_ret) {
cpu.jump(Jump,script_ret);
}
else {
script_ret = cpu.op;
//The VM Registers are already in the correct state, so just do a simple return here
esp += functionReserveSpace;
cpu.pop(ebp);
cpu.pop(ebx);
cpu.pop(edi);
cpu.pop(esi);
cpu.ret();
}
waitingForEntry = true;
};
auto PrepareJitScriptCall = [&](asCScriptFunction* func) -> bool {
asDWORD* bc = func->scriptData->byteCode.AddressOf();
#ifdef JIT_64
Register arg0 = cpu.intArg64(0, 0, pax);
#else
Register arg0 = pax;
#endif
arg0 = as<void*>(*ebp + offsetof(asSVMRegisters,ctx));
//Prepare the vm state
cpu.call_stdcall((void*)callScriptFunction,"rp", &arg0, func);
if(flags & JIT_NO_SCRIPT_CALLS)
return false;
return *(asBYTE*)bc == asBC_JitEntry;
};
auto JitScriptCall = [&](asCScriptFunction* func) {
//Call the first jit entry in the target function
asDWORD* bc = func->scriptData->byteCode.AddressOf();
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
Register ptr = pax;
#else
Register arg0 = ecx;
Register arg1 = ebx;
Register ptr = pax;
#endif
arg0 = as<void*>(ebp);
asPWORD entryPoint = asBC_PTRARG(bc);
if(entryPoint && func->scriptData->jitFunction) {
arg1 = (void*)entryPoint;
ptr = (void*)func->scriptData->jitFunction;
}
else {
DeferredCodePointer def;
def.jitEntry = (void**)arg1.setDeferred();
def.jitFunction = (void**)ptr.setDeferred();
deferredPointers.insert(std::pair<asIScriptFunction*,DeferredCodePointer>(func,def));
}
unsigned sb = cpu.call_cdecl_args("rr", &arg0, &arg1);
cpu.call(ptr);
cpu.call_cdecl_end(sb);
};
auto DynamicJitScriptCall = [&]() {
//Expects the asCScriptFunction* to be in eax
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
Register ptr = pax;
#else
Register arg0 = ecx;
Register arg1 = ebx;
Register ptr = pax;
#endif
arg0 = as<void*>(ebp);
//Read the first pointer from where byteCode is, which is the
//array pointer from asCArray, skip the asBC_JitEntry byte and
//then read the first entry pointer
pax = as<void*>(*pax + offsetof(asCScriptFunction, scriptData));
arg1 = as<void*>(*pax + offsetof(asCScriptFunction::ScriptFunctionData, byteCode));
arg1 = as<void*>(*arg1 + sizeof(asDWORD));
//Read the jit function pointer from the asCScriptFunction
ptr = as<void*>(*pax + offsetof(asCScriptFunction::ScriptFunctionData, jitFunction));
unsigned sb = cpu.call_cdecl_args("rr", &arg0, &arg1);
cpu.call(ptr);
cpu.call_cdecl_end(sb);
};
auto JitScriptCallIntf = [&](asCScriptFunction* func) {
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
#else
Register arg0 = ecx;
#endif
arg0 = as<void*>(*ebp + offsetof(asSVMRegisters,ctx));
//Prepare the vm state
cpu.call_stdcall((void*)callInterfaceMethod,"rp", &arg0, func);
//This returns the asCScriptFunction* in pax
pax &= pax;
auto okay = cpu.prep_short_jump(NotZero);
ReturnFromScriptCall();
cpu.end_short_jump(okay);
DynamicJitScriptCall();
};
auto JitScriptCallBnd = [&](int fid) {
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
#else
Register arg0 = ecx;
#endif
arg0 = as<void*>(*ebp + offsetof(asSVMRegisters,ctx));
//Prepare the vm state
cpu.call_stdcall((void*)callBoundFunction,"rc", &arg0, (unsigned)fid);
//This returns the asCScriptFunction* in pax
pax &= pax;
auto okay = cpu.prep_short_jump(NotZero);
ReturnFromScriptCall();
cpu.end_short_jump(okay);
DynamicJitScriptCall();
};
auto ReturnFromJittedScriptCall = [&](void* expectedPC) {
//Check if we need to return to the vm
// If the program pointer is what we expect, we don't need to return
pcx = (void*)(expectedPC == 0 ? pOp+2 : expectedPC);
pcx == as<void*>(*ebp + offsetof(asSVMRegisters,programPointer));
auto skip_ret = cpu.prep_short_jump(Equal);
ReturnFromScriptCall();
cpu.end_short_jump(skip_ret);
// If execution is finished, return to the vm as well so it can clean up
as<asEContextState>(ecx) = asEXECUTION_FINISHED;
pax = as<void*>(*ebp + offsetof(asSVMRegisters,ctx));
as<asEContextState>(ecx) == as<asEContextState>(*pax + offsetof(asCContext, m_status));
auto skip_finish = cpu.prep_short_jump(NotEqual);
ReturnFromScriptCall();
cpu.end_short_jump(skip_finish);
esi = as<void*>(*ebp+offsetof(asSVMRegisters,stackPointer)); //update stack pointer
pbx = as<void*>(*ebp+offsetof(asSVMRegisters,valueRegister)); //update value register
};
auto do_jump = [&](JumpType type) {
asDWORD* bc = pOp + asBC_INTARG(pOp) + 2;
auto& jmp = jumpTable[bc - start];
if(bc > pOp) {
//Prep the jump for a future instruction
auto* jumpData = new FutureJump;
jumpData->jump = cpu.prep_long_jump(type);
jumpData->next = jmp ? (FutureJump*)jmp : 0;
jmp = (byte*)jumpData;
}
else if(jmp != 0 && jmp != JUMP_DESTINATION) {
//Jump to code that already exists
cpu.jump(type, jmp);
}
else {
//We can't handle this address, so generate a special return that does the jump ahead of time
rarg = bc;
cpu.jump(type, ret_pos);
}
};
auto do_jump_from = [&](JumpType type, asDWORD* op) {
asDWORD* bc = op + asBC_INTARG(op) + 2;
auto& jmp = jumpTable[bc - start];
if(bc > op) {
//Prep the jump for a future instruction
auto* jumpData = new FutureJump;
jumpData->jump = cpu.prep_long_jump(type);
jumpData->next = jmp ? (FutureJump*)jmp : 0;
jmp = (byte*)jumpData;
}
else if(jmp != 0 && jmp != JUMP_DESTINATION) {
//Jump to code that already exists
cpu.jump(type, jmp);
}
else {
//We can't handle this address, so generate a special return that does the jump ahead of time
rarg = bc;
cpu.jump(type, ret_pos);
}
};
auto check_space = [&](unsigned bytes) {
unsigned remaining = activePage->getFreeSize() - (unsigned)(cpu.op - byteStart);
if(remaining < bytes + cpu.jumpSpace) {
CodePage* newPage = new CodePage(codePageSize, ((char*)activePage->page + activePage->size));
cpu.migrate(*activePage, *newPage);
activePage->drop();
activePage = newPage;
activePage->grab();
pages.insert(std::pair<asJITFunction,assembler::CodePage*>(*output,activePage));
byteStart = (byte*)cpu.op;
}
};
unsigned reservedPushBytes = 0;
asEBCInstr op;
#ifdef JIT_DEBUG
volatile void* lastop = 0;
#endif
while(pOp < end) {
currentEAX = nextEAX;
nextEAX = EAX_Unknown;
if(cpu.op > activePage->getActivePage() + activePage->getFreeSize())
throw "Page exceeded...";
op = asEBCInstr(*(asBYTE*)pOp);
auto* futureJump = (FutureJump*)jumpTable[pOp - start];
//Handle jumps from earlier ops
if(futureJump) {
if(waitingForEntry && op != asBC_JitEntry) {
check_space(48);
jumpTable[pOp - start] = (unsigned char*)cpu.op;
while(futureJump && futureJump != JUMP_DESTINATION) {
cpu.end_long_jump(futureJump->jump);
futureJump = futureJump->advance();
}
Return(true);
pOp += toSize(op);
continue;
}
}
//Check for remaining space of at least 64 bytes (roughly 3 max-sized ops)
// Do so before building jumps to save a jump when crossing pages
#ifdef JIT_DEBUG
check_space(128);
#else
check_space(64);
#endif
//Deal with the most recent switch
if(activeSwitch) {
activeSwitch->buffer[int(activeSwitch->count) - int(activeSwitch->remaining)] = (unsigned char*)cpu.op;
if(--activeSwitch->remaining == 0)
activeSwitch = 0;
}
jumpTable[pOp - start] = (unsigned char*)cpu.op;
#ifdef JIT_DEBUG
void* beg = (void*)cpu.op;
pdx = (void*)&DBG_CurrentOP;
as<asEBCInstr>(*pdx) = op;
pdx = (void*)&DBG_LastInstr;
*pdx = (void*)lastop;
pdx = (void*)&DBG_Instr;
*pdx = (void*)beg;
lastop = beg;
#endif
//Handle jumps to code we hadn't made yet
while(futureJump && futureJump != JUMP_DESTINATION) {
cpu.end_long_jump(futureJump->jump);
futureJump = futureJump->advance();
}
//Multi-op optimization - special cases where specific sets of ops serve a common purpose
auto pNextOp = pOp + toSize(op);
if(pNextOp < end && jumpTable[pNextOp - start] == nullptr) {
auto nextOp = asEBCInstr(*(asBYTE*)pNextOp);
auto pThirdOp = pNextOp + toSize(nextOp);
auto thirdOp = asBC_MAXBYTECODE;
if(pThirdOp < end && jumpTable[pThirdOp - start] == nullptr) {
thirdOp = asEBCInstr(*(asBYTE*)pThirdOp);
switch(op) {
case asBC_SetV8:
if(thirdOp == asBC_CpyVtoV8 &&
(nextOp == asBC_ADDd || nextOp == asBC_DIVd ||
nextOp == asBC_SUBd || nextOp == asBC_MULd)) {
if(asBC_SWORDARG0(pOp) != asBC_SWORDARG2(pNextOp) || asBC_SWORDARG0(pOp) != asBC_SWORDARG0(pNextOp))
break;
//Optimize <Variable Double> <op>= <Constant Double>
fpu.load_double(*edi-offset(pNextOp,1));
MemAddress doubleConstant(cpu, &asBC_QWORDARG(pOp));
switch(nextOp) {
case asBC_ADDd:
fpu.add_double(doubleConstant); break;
case asBC_SUBd:
fpu.sub_double(doubleConstant); break;
case asBC_MULd:
fpu.mult_double(doubleConstant); break;
case asBC_DIVd:
fpu.div_double(doubleConstant); break;
}
if(asBC_SWORDARG0(pOp) == asBC_SWORDARG1(pThirdOp)) {
fpu.store_double(*edi-offset(pOp,0),false);
fpu.store_double(*edi-offset(pThirdOp,0));
pOp = pThirdOp + toSize(thirdOp);
}
else {
fpu.store_double(*edi-offset(pOp,0));
pOp = pThirdOp;
}
continue;
}
break;
case asBC_SetV4:
if(nextOp == asBC_SetV4 && thirdOp == asBC_SetV4 && asBC_DWORDARG(pOp) == asBC_DWORDARG(pNextOp) && asBC_DWORDARG(pNextOp) == asBC_DWORDARG(pThirdOp)) {
//Optimize intializing 3 variables to the same value (often 0)
if(asBC_DWORDARG(pOp) == 0)
eax ^= eax;
else
eax = asBC_DWORDARG(pOp);
*edi-offset(pOp,0) = eax;
*edi-offset(pNextOp,0) = eax;
*edi-offset(pThirdOp,0) = eax;
pOp = pThirdOp + toSize(thirdOp);
continue;
}
break;
case asBC_PshVPtr:
//Optimize PshVPtr, ADDSi, RDSPtr to avoid many interim ops
if(nextOp == asBC_ADDSi && thirdOp == asBC_RDSPtr) {
pax = as<void*>(*edi-offset0);
if(reservedPushBytes != 0)
reservedPushBytes = 0;
else
esi -= sizeof(void*);
pax &= pax;
auto notNull = cpu.prep_short_jump(NotZero);
as<void*>(*esi) = pax;
Return(false);
cpu.end_short_jump(notNull);
pax = as<void*>(*pax+asBC_SWORDARG0(pNextOp));
as<void*>(*esi) = pax;
nextEAX = EAX_Stack;
pOp = pThirdOp + toSize(thirdOp);
continue;
}
break;
}
}
switch(op) {
case asBC_SetV4:
if(nextOp == asBC_SetV4 && asBC_DWORDARG(pOp) == asBC_DWORDARG(pNextOp)) {
//Optimize intializing 2 variables to the same value (often 0)
if(asBC_DWORDARG(pOp) == 0)
eax ^= eax;
else
eax = asBC_DWORDARG(pOp);
*edi-offset(pOp,0) = eax;
*edi-offset(pNextOp,0) = eax;
pOp = pThirdOp;
continue;
}
break;
case asBC_RDR4:
if(nextOp == asBC_PshV4 && asBC_SWORDARG0(pOp) == asBC_SWORDARG0(pNextOp)) {
//Optimize:
//Store temporary int
//Push stored temporary
eax = *ebx;
*edi-offset0 = eax;
reservedPushBytes = findTotalPushBatchSize(pNextOp, end);
esi -= reservedPushBytes;
reservedPushBytes -= sizeof(asDWORD);
*esi + reservedPushBytes = eax;
if(reservedPushBytes == 0)
nextEAX = EAX_Stack;
pOp = pThirdOp;
continue;
}
break;
//TODO: Update this to use inline memcpy improvement
/*case asBC_PSF:
case asBC_PshVPtr:
if(reservedPushBytes == 0 && nextOp == asBC_COPY) {
//Optimize:
//Push Pointer
//Copy Pointer
//To:
//Copy Pointer
check_space(256);
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
#else
Register arg0 = pcx;
#endif
if(op == asBC_PSF)
arg0.copy_address(as<void*>(*edi-offset0));
else //if(op == asBC_PshVPtr)
arg0 = as<void*>(*edi-offset0);
if(currentEAX != EAX_Stack)
pax = as<void*>(*esi);
//Check for null pointers
pax &= pax;
void* test1 = cpu.prep_short_jump(Zero);
arg0 &= arg0;
void* test2 = cpu.prep_short_jump(Zero);
as<void*>(*esi) = arg0;
nextEAX = EAX_Stack;
cpu.call_cdecl((void*)memcpy,"rrc", &arg0, &pax, unsigned(asBC_WORDARG0(pNextOp))*4);
void* skip_ret = cpu.prep_short_jump(Jump);
//ERR
cpu.end_short_jump(test1); cpu.end_short_jump(test2);
Return(false);
cpu.end_short_jump(skip_ret);
pOp = pThirdOp;
continue;
}
break;*/
case asBC_CpyRtoV4:
if(nextOp == asBC_CpyVtoV4 && offset(pOp,0) == offset(pNextOp,1)) {
//Optimize
//Copy Temp to Var X
//Copy Var X to Var Y
//To:
//Copy Temp to Var X
//Copy Temp to Var Y
*edi-offset(pOp,0) = ebx;
*edi-offset(pNextOp,0) = ebx;
pOp = pThirdOp;
continue;
}
break;
case asBC_CpyVtoV4:
if(nextOp == asBC_iTOf && offset(pOp,0) == offset(pNextOp,0)) {
//Optimize:
//Load integer
//Convert integer to float in-place
//To:
//Load integer
//Save float
fpu.load_dword(*edi-offset(pOp,1));
fpu.store_float(*edi-offset(pOp,0));
pOp = pThirdOp;
continue;
}
else if(nextOp == asBC_fTOd && offset(pOp,0) == offset(pNextOp,1)) {
//Optimize:
//Copy float
//Convert float to double
//To:
//Copy float
//Store double
fpu.load_float(*edi-offset(pOp,1));
fpu.store_float(*edi-offset(pOp,0),false);
fpu.store_double(as<double>(*edi-offset(pNextOp,0)));
pOp = pThirdOp;
continue;
}
break;
case asBC_ADDSi:
//Optimize ADDSi, RDSPtr to avoid duplicate checks and copies
if(nextOp == asBC_RDSPtr) {
if(currentEAX != EAX_Stack)
pax = as<void*>(*esi);
pax &= pax;
auto notNull = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(notNull);
pax = as<void*>(*pax+asBC_SWORDARG0(pOp));
as<void*>(*esi) = pax;
nextEAX = EAX_Stack;
pOp = pThirdOp;
continue;
}
case asBC_CMPi:
case asBC_CMPIi:
case asBC_CMPu:
case asBC_CMPIu:
{
JumpType jump = Jump;
bool isUnsigned = op == asBC_CMPu || op == asBC_CMPIu;
//Optimize various CMPi, JConditional to avoid additional logic checks
switch(nextOp) {
case asBC_JZ: case asBC_JLowZ:
jump = Equal; break;
case asBC_JNZ: case asBC_JLowNZ:
jump = NotEqual; break;
case asBC_JS:
jump = isUnsigned ? Below : Sign; break;
case asBC_JNS:
jump = isUnsigned ? NotBelow : NotSign; break;
case asBC_JP:
jump = isUnsigned ? Above : Greater; break;
case asBC_JNP:
jump = isUnsigned ? NotAbove : LessOrEqual; break;
}
//Conditional tests never use plain Jump
if(jump != Jump) {
eax = *edi-offset0;
if(op == asBC_CMPIi || op == asBC_CMPIu)
eax == asBC_DWORDARG(pOp);
else
eax == *edi-offset1;
do_jump_from(jump, pNextOp);
//Perform comparison if it could have an effect
/*if(!clearsTemporary(thirdOp)) {
if(op == asBC_CMPi || op == asBC_CMPIi) {
bl.setIf(Greater);
auto t2 = cpu.prep_short_jump(GreaterOrEqual);
~bl;
cpu.end_short_jump(t2);
}
else {//CMPu/Iu
bl.setIf(Above);
auto t2 = cpu.prep_short_jump(NotBelow);
~bl;
cpu.end_short_jump(t2);
}
}*/
pOp = pThirdOp;
continue;
}
}
}
}
//Build ops
switch(op) {
case asBC_JitEntry:
if(!firstJitEntry)
firstJitEntry = (void*)cpu.op;
asBC_PTRARG(pOp) = (asPWORD)cpu.op;
waitingForEntry = false;
break;
case asBC_PopPtr:
esi += sizeof(void*);
break;
//Handle all pushes here by allocating all contiguous push memory at once
#define pushPrep(use) \
if(reservedPushBytes == 0) {\
reservedPushBytes = findTotalPushBatchSize(pOp, end);\
esi -= reservedPushBytes;\
}\
reservedPushBytes -= use;
case asBC_PshC4:
pushPrep(sizeof(asDWORD));
*esi + reservedPushBytes = asBC_DWORDARG(pOp);
break;
case asBC_PshV4:
pushPrep(sizeof(asDWORD));
eax = *edi-offset0;
*esi + reservedPushBytes = eax;
if(reservedPushBytes == 0)
nextEAX = EAX_Stack;
break;
case asBC_PSF:
pushPrep(sizeof(void*));
pax.copy_address(as<void*>(*edi-offset0));
as<void*>(*esi + reservedPushBytes) = pax;
if(reservedPushBytes == 0)
nextEAX = EAX_Stack;
break;
case asBC_PshG4:
pushPrep(sizeof(asDWORD));
eax = MemAddress(cpu, (void*)asBC_PTRARG(pOp));
*esi + reservedPushBytes = eax;
if(reservedPushBytes == 0)
nextEAX = EAX_Stack;
break;
case asBC_PshGPtr:
pushPrep(sizeof(void*));
pax = as<void*>(MemAddress(cpu, (void*)asBC_PTRARG(pOp)));
as<void*>(*esi + reservedPushBytes) = pax;
if(reservedPushBytes == 0)
nextEAX = EAX_Stack;
break;
case asBC_PshC8:
{
pushPrep(sizeof(asQWORD));
asQWORD qword = asBC_QWORDARG(pOp);
#ifdef JIT_64
as<asQWORD>(eax) = qword;
as<asQWORD>(*esi + reservedPushBytes) = eax;
#else
asDWORD* as_dword = (asDWORD*)&qword;
*esi + reservedPushBytes+4 = as_dword[1];
*esi + reservedPushBytes = as_dword[0];
#endif
} break;
case asBC_PshVPtr:
pushPrep(sizeof(void*));
pax = as<void*>(*edi-offset0);
as<void*>(*esi + reservedPushBytes) = pax;
if(reservedPushBytes == 0)
nextEAX = EAX_Stack;
break;
case asBC_PshRPtr:
pushPrep(sizeof(void*));
as<void*>(*esi + reservedPushBytes) = pbx;
break;
case asBC_PshNull:
pushPrep(sizeof(void*));
pax ^= pax;
as<void*>(*esi + reservedPushBytes) = pax;
if(reservedPushBytes == 0)
nextEAX = EAX_Stack;
break;
case asBC_OBJTYPE:
pushPrep(sizeof(void*));
as<void*>(*esi + reservedPushBytes) = (void*)asBC_PTRARG(pOp);
break;
case asBC_TYPEID:
pushPrep(sizeof(asDWORD));
*esi + reservedPushBytes = asBC_DWORDARG(pOp);
break;
case asBC_FuncPtr:
pushPrep(sizeof(void*));
as<void*>(*esi + reservedPushBytes) = (void*)asBC_PTRARG(pOp);
break;
case asBC_PshV8:
pushPrep(sizeof(asQWORD));
cpu.setBitMode(64);
(*esi + reservedPushBytes).direct_copy(*edi-offset0, eax);
cpu.resetBitMode();
break;
case asBC_PGA:
pushPrep(sizeof(void*));
as<void*>(*esi + reservedPushBytes) = (void*)asBC_PTRARG(pOp);
break;
case asBC_VAR:
pushPrep(sizeof(void*));
as<void*>(*esi + reservedPushBytes) = (void*)(size_t)asBC_SWORDARG0(pOp);
break;
////Now the normally-ordered ops
case asBC_SwapPtr:
if(currentEAX != EAX_Stack)
pax = as<void*>(*esi);
pax.swap(as<void*>(*esi+sizeof(void*)));
as<void*>(*esi) = pax;
nextEAX = EAX_Stack;
break;
case asBC_NOT:
{
if(currentEAX != EAX_Offset + offset0)
al = as<byte>(*edi-offset0);
al &= al;
al.setIf(Zero);
eax.copy_zeroing(al);
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
} break;
//case asBC_PshG4: //All pushes are handled above, near asBC_PshC4
case asBC_LdGRdR4:
pbx = (void*) asBC_PTRARG(pOp);
eax = *pbx;
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_CALL:
{
check_space(256);
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = pOp+2;
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = esi;
asCScriptFunction* func = (asCScriptFunction*)function->GetEngine()->GetFunctionById(asBC_INTARG(pOp));
if(PrepareJitScriptCall(func)) {
JitScriptCall(func);
ReturnFromJittedScriptCall(0);
}
else {
ReturnFromScriptCall();
}
} break;
case asBC_RET: {
//Not implemented if script call jitting is off,
//since it's dependent on how calls are made
if(flags & JIT_NO_SCRIPT_CALLS) {
Return(true);
break;
}
#ifdef JIT_64
Register arg0 = cpu.intArg64(0, 0, pax);
#else
Register arg0 = pax;
#endif
arg0 = as<void*>(*ebp + offsetof(asSVMRegisters,ctx));
cpu.call_stdcall((void*)returnScriptFunction,"r", &arg0);
//Pop arguments off the stack
esi = as<void*>(*ebp+offsetof(asSVMRegisters,stackPointer));
esi += asBC_WORDARG0(pOp) * sizeof(asDWORD);
as<void*>(*ebp+offsetof(asSVMRegisters,stackPointer)) = esi;
//Update value register
as<void*>(*ebp+offsetof(asSVMRegisters,valueRegister)) = pbx;
ReturnFromScriptCall();
} break;
case asBC_JMP:
do_jump(Jump);
break;
case asBC_JLowZ: //ClrHi is a NOP, so JlowZ is JZ (same with NZ)
case asBC_JZ:
bl &= bl; do_jump(Zero); break;
case asBC_JLowNZ:
case asBC_JNZ:
bl &= bl; do_jump(NotZero); break;
case asBC_JS:
bl &= bl; do_jump(Sign); break;
case asBC_JNS:
bl &= bl; do_jump(NotSign); break;
case asBC_JP:
bl == 0; do_jump(Greater); break;
case asBC_JNP:
bl == 0; do_jump(LessOrEqual); break;
case asBC_TZ:
bl &= bl; ebx.setIf(Zero); ebx.copy_zeroing(ebx); break;
case asBC_TNZ:
bl &= bl; ebx.setIf(NotZero); ebx.copy_zeroing(ebx); break;
case asBC_TS:
bl &= bl; ebx.setIf(Sign); ebx.copy_zeroing(ebx); break;
case asBC_TNS:
bl &= bl; ebx.setIf(NotSign); ebx.copy_zeroing(ebx); break;
case asBC_TP:
bl == 0; ebx.setIf(Greater); ebx.copy_zeroing(ebx); break;
case asBC_TNP:
bl == 0; ebx.setIf(LessOrEqual); ebx.copy_zeroing(ebx); break;
case asBC_NEGi:
-(*edi-offset0);
break;
case asBC_NEGf:
fpu.load_float(*edi-offset0);
fpu.negate();
fpu.store_float(*edi-offset0);
break;
case asBC_NEGd:
fpu.load_double(*edi-offset0);
fpu.negate();
fpu.store_double(*edi-offset0);
break;
case asBC_INCi16:
++as<short>(*ebx);
break;
case asBC_INCi8:
++as<char>(*ebx);
break;
case asBC_DECi16:
--as<short>(*ebx);
break;
case asBC_DECi8:
--as<char>(*ebx);
break;
case asBC_INCi:
++*ebx;
break;
case asBC_DECi:
--*ebx;
break;
case asBC_INCf:
fpu.load_const_1();
fpu.add_float(*ebx);
fpu.store_float(*ebx);
break;
case asBC_DECf:
fpu.load_const_1();
fpu.negate();
fpu.add_float(*ebx);
fpu.store_float(*ebx);
break;
case asBC_INCd:
fpu.load_const_1();
fpu.add_double(*ebx);
fpu.store_double(*ebx);
break;
case asBC_DECd:
fpu.load_const_1();
fpu.negate();
fpu.add_double(*ebx);
fpu.store_double(*ebx);
break;
case asBC_IncVi:
++(*edi-offset0);
break;
case asBC_DecVi:
--(*edi-offset0);
break;
case asBC_BNOT:
~(*edi-offset0);
break;
case asBC_BAND:
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
eax &= *edi-offset2;
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_BOR:
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
eax |= *edi-offset2;
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_BXOR:
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
eax ^= *edi-offset2;
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_BSLL: {
Register c(cpu, ECX);
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
c = *edi-offset2;
eax <<= c;
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
} break;
case asBC_BSRL: {
Register c(cpu, ECX);
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
c = *edi-offset2;
eax.rightshift_logical(c);
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
} break;
case asBC_BSRA: {
Register c(cpu, ECX);
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
c = *edi-offset2;
eax >>= c;
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
} break;
case asBC_COPY:
{
check_space(128);
unsigned bytes = unsigned(asBC_WORDARG0(pOp))*4;
if(currentEAX != EAX_Stack)
pax = as<void*>(*esi);
esi += sizeof(void*);
void* skip_err_return, *test1, *test2;
//Assuming memcpy() with function overhead is faster over 128 bytes
if(bytes <= 128) {
Register from(cpu, ESI, sizeof(void*)*8), to(cpu, EDI, sizeof(void*)*8);
pax &= pax;
test1 = cpu.prep_short_jump(Zero);
pdx = as<void*>(*esi);
pdx &= pdx;
test2 = cpu.prep_short_jump(Zero);
if(bytes == 4) {
as<asDWORD>(*pax).direct_copy(as<asDWORD>(*pdx), ecx);
}
else if(bytes == 8) {
as<asQWORD>(*pax).direct_copy(as<asQWORD>(*pdx), ecx);
}
else {
//Loop to copy all bytes for larger types
pdx.swap(from);
pax.swap(to);
unsigned copySize = (bytes % 8) == 0 ? 8 : 4;
unsigned iterations = bytes / copySize;
//Avoid tiny loops
bool unroll = iterations <= 4;
if(!unroll)
pcx = iterations;
cpu.setDirFlag(true);
auto* loop = cpu.op;
cpu.string_copy(copySize);
if(unroll) {
for(unsigned i = 1; i < iterations; ++i)
cpu.string_copy(copySize);
}
else {
cpu.loop(loop);
}
from = pdx;
to = pax;
}
skip_err_return = cpu.prep_short_jump(Jump);
}
else {
#ifdef JIT_64
Register arg1 = as<void*>(cpu.intArg64(1, 1));
#else
Register arg1 = pdx;
#endif
arg1 = as<void*>(*esi);
//Check for null pointers
pax &= pax;
test1 = cpu.prep_short_jump(Zero);
arg1 &= arg1;
test2 = cpu.prep_short_jump(Zero);
as<void*>(*esi) = pax;
cpu.call_cdecl((void*)memcpy,"rrc", &pax, &arg1, bytes);
skip_err_return = cpu.prep_short_jump(Jump);
}
//ERR
cpu.end_short_jump(test1); cpu.end_short_jump(test2);
//Need to restore stack pointer for AS to handle the error
esi -= sizeof(void*);
Return(false);
cpu.end_short_jump(skip_err_return);
} break;
//case asBC_PshC8: //All pushes are handled above, near asBC_PshC4
//case asBC_PshVPtr:
case asBC_RDSPtr:
{
if(currentEAX != EAX_Stack)
pax = as<void*>(*esi);
pax &= pax;
auto notNull = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(notNull);
pax = as<void*>(*pax);
as<void*>(*esi) = pax;
nextEAX = EAX_Stack;
} break;
case asBC_CMPd:
{
fpu.load_double(*edi-offset1);
fpu.load_double(*edi-offset0);
fpu.compare_toCPU(FPU_1);
bl.setIf(Above);
auto t2 = cpu.prep_short_jump(NotCarry);
~bl; //0xff if < 0
cpu.end_short_jump(t2);
fpu.pop();
} break;
case asBC_CMPu:
{
eax = *edi-offset0;
eax == *edi-offset1;
bl.setIf(Above);
auto t2 = cpu.prep_short_jump(NotBelow);
~bl; //0xff if < 0
cpu.end_short_jump(t2);
} break;
case asBC_CMPf:
{
fpu.load_float(*edi-offset1);
fpu.load_float(*edi-offset0);
fpu.compare_toCPU(FPU_1);
bl.setIf(Above);
auto t2 = cpu.prep_short_jump(NotCarry);
~bl; //0xff if < 0
cpu.end_short_jump(t2);
fpu.pop();
} break;
case asBC_CMPi:
{
eax = *edi-offset0;
eax == *edi-offset1;
bl.setIf(Greater);
auto t2 = cpu.prep_short_jump(GreaterOrEqual);
~bl; //0xff if < 0
cpu.end_short_jump(t2);
} break;
case asBC_CMPIi:
{
eax = *edi-offset0;
eax == asBC_DWORDARG(pOp);
bl.setIf(Greater);
auto t2 = cpu.prep_short_jump(GreaterOrEqual);
~bl; //0xff if < 0
cpu.end_short_jump(t2);
} break;
case asBC_CMPIf:
{
fpu.load_float(MemAddress(cpu,&asBC_FLOATARG(pOp)));
fpu.load_float(*edi-offset0);
fpu.compare_toCPU(FPU_1);
bl.setIf(Above);
auto t2 = cpu.prep_short_jump(NotCarry);
~bl; //0xff if < 0
cpu.end_short_jump(t2);
fpu.pop();
} break;
case asBC_CMPIu:
{
eax = *edi-offset0;
eax == asBC_DWORDARG(pOp);
bl.setIf(Above);
auto t2 = cpu.prep_short_jump(NotBelow);
~bl; //0xff if < 0
cpu.end_short_jump(t2);
} break;
case asBC_JMPP:
if((flags & JIT_NO_SWITCHES) == 0) {
unsigned cases = 1;
{
//This information isn't stored for us to recover, so we rely on the format of switch cases
//Each one is a series of asBC_JMP ops and a single remaining case at the end (default)
asDWORD* pNextOp = pOp + toSize(op);
asEBCInstr nextOp = asEBCInstr(*(asBYTE*)pNextOp);
while(nextOp == asBC_JMP) {
++cases;
pNextOp += toSize(asBC_JMP);
nextOp = asEBCInstr(*(asBYTE*)pNextOp);
}
}
SwitchRegion region;
region.count = cases;
region.remaining = region.count;
region.buffer = new unsigned char*[region.count];
memset(region.buffer, 0, region.count * sizeof(void*));
switches.push_back(region);
activeSwitch = &switches.back();
pax = (void*)(region.buffer);
pdx.copy_expanding(as<int>(*edi - offset0));
pcx = as<void*>(*pax + pdx*sizeof(void*));
//Check for a pointer in the jump table to executable code
pcx &= pcx;
auto unhandled_jump = cpu.prep_short_jump(Zero);
cpu.jump(pcx);
cpu.end_short_jump(unhandled_jump);
//Copy the offsetted pointer to edx and return
ecx = (void*)(pOp + 1);
rarg.copy_address(*pcx + pdx*(2*sizeof(asDWORD)));
cpu.jump(Jump,ret_pos);
}
else {
Return(true);
}
break;
case asBC_PopRPtr:
pbx = as<void*>(*esi);
esi += sizeof(void*);
break;
//case asBC_PshRPtr: //All pushes are handled above, near asBC_PshC4
case asBC_STR:
{
const asCString &str = ((asCScriptEngine*)function->GetEngine())->GetConstantString(asBC_WORDARG0(pOp));
esi -= sizeof(void*) + sizeof(asDWORD);
as<void*>(*esi + sizeof(asDWORD)) = (void*)str.AddressOf();
as<asDWORD>(*esi) = (asDWORD)str.GetLength();
} break;
case asBC_CALLSYS:
case asBC_Thiscall1:
{
check_space(512);
asCScriptFunction* func = (asCScriptFunction*)function->GetEngine()->GetFunctionById(asBC_INTARG(pOp));
sysCall.callSystemFunction(func);
} break;
case asBC_CALLBND:
{
check_space(512);
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = pOp+2;
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = esi;
if(flags & JIT_NO_SCRIPT_CALLS) {
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0, pax));
#else
Register arg0 = pax;
#endif
arg0 = as<void*>(*ebp + offsetof(asSVMRegisters,ctx));
cpu.call_stdcall((void*)callBoundFunction,"rc",
&arg0,
(unsigned int)asBC_INTARG(pOp));
pax &= pax;
auto p2 = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(p2);
ReturnFromScriptCall();
}
else {
JitScriptCallBnd(asBC_INTARG(pOp));
ReturnFromJittedScriptCall(0);
}
} break;
case asBC_SUSPEND:
if(flags & JIT_NO_SUSPEND) {
//Do nothing
}
else {
//Check if we should suspend
cl = as<byte>(*ebp+offsetof(asSVMRegisters,doProcessSuspend));
cl &= cl;
auto skip = cpu.prep_short_jump(Zero);
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = pOp;
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = esi;
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
#else
Register arg0 = pdx;
#endif
arg0 = as<void*>(*ebp + offsetof(asSVMRegisters,ctx));
cpu.call_stdcall((void*)doSuspend, "r", &arg0);
//If doSuspend return true, return to AngelScript for a suspension
rarg = (void*)pOp;
al &= al;
cpu.jump(NotZero, ret_pos);
cpu.end_short_jump(skip);
}
break;
case asBC_ALLOC:
{
check_space(512);
asCObjectType *objType = (asCObjectType*)(size_t)asBC_PTRARG(pOp);
int func = asBC_INTARG(pOp+AS_PTR_SIZE);
if(objType->flags & asOBJ_SCRIPT_OBJECT) {
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = pOp;
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = esi;
as<void*>(*ebp + offsetof(asSVMRegisters,stackFramePointer)) = pdi;
asIScriptEngine* engine = function->GetEngine();
asCScriptFunction* f = ((asCScriptEngine*)engine)->GetScriptFunction(func);
cpu.call_stdcall((void*)allocScriptObject,"pppr",objType,f,engine,&ebp);
if(PrepareJitScriptCall(f)) {
JitScriptCall(f);
ReturnFromJittedScriptCall((void*)(pOp+(2+AS_PTR_SIZE)));
}
else {
ReturnFromScriptCall();
}
}
else {
cpu.call_stdcall((void*)engineAlloc,"pp",
(asCScriptEngine*)function->GetEngine(),
objType);
if( func ) {
as<void*>(*esp + local::allocMem) = pax;
auto pFunc = (asCScriptFunction*)function->GetEngine()->GetFunctionById(func);
pcx = pax;
sysCall.callSystemFunction(pFunc, &pcx);
pax = as<void*>(*esp + local::allocMem);
}
//Pop pointer destination from vm stack
pcx = as<void*>(*esi);
esi += sizeof(void*);
//Set it if not zero
pcx &= pcx;
auto p = cpu.prep_short_jump(Zero);
as<void*>(*pcx) = pax;
cpu.end_short_jump(p);
}
} break;
case asBC_FREE:
{
asCObjectType *objType = (asCObjectType*)(size_t)asBC_PTRARG(pOp);
if(!(objType->flags & asOBJ_REF) || !(objType->flags & asOBJ_NOCOUNT)) { //Only do FREE on non-reference types, or reference types without fake reference counting
check_space(128);
asSTypeBehaviour *beh = &objType->beh;
#ifdef JIT_64
Register arg1 = as<void*>(cpu.intArg64(1, 1));
#else
Register arg1 = pcx;
#endif
//Check the pointer to see if it's already zero
arg1 = as<void*>(*edi-offset0);
arg1 &= arg1;
auto p = cpu.prep_long_jump(Zero);
if(beh->release) {
unsigned callFlags = SC_ValidObj | SC_NoReturn | SC_Simple;
if((flags & JIT_FAST_REFCOUNT) != 0)
callFlags |= SC_NoSuspend | SC_Safe;
asCScriptFunction* func = (asCScriptFunction*)function->GetEngine()->GetFunctionById(beh->release);
sysCall.callSystemFunction(func, &arg1, callFlags);
}
else if(beh->destruct) {
//Copy over registers to the vm in case the called functions observe the call stack
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = pOp;
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = esi;
cpu.call_stdcall((void*)engineDestroyFree,"prp",
(asCScriptEngine*)function->GetEngine(),
&arg1,
(asCScriptFunction*)function->GetEngine()->GetFunctionById(beh->destruct) );
}
else if(objType->flags & asOBJ_LIST_PATTERN) {
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = pOp;
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = esi;
cpu.call_stdcall((void*)engineListFree,"ppr",
(asCScriptEngine*)function->GetEngine(),
objType, &arg1);
}
else {
//Copy over registers to the vm in case the called functions observe the call stack
if((flags & JIT_ALLOC_SIMPLE) == 0) {
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = pOp;
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = esi;
}
cpu.call_stdcall((void*)engineFree,"pr",
(asCScriptEngine*)function->GetEngine(),
&arg1);
}
//Null out pointer on the stack
pax ^= pax;
as<void*>(*edi-offset0) = pax;
cpu.end_long_jump(p);
}
else {
//Null out pointer on the stack
pax ^= pax;
as<void*>(*edi-offset0) = pax;
}
}break;
case asBC_LOADOBJ:
{
cpu.setBitMode(sizeof(void*)*8);
eax = *edi-offset0;
pcx ^= pcx;
*ebp+offsetof(asSVMRegisters,objectType) = pcx;
*ebp+offsetof(asSVMRegisters,objectRegister) = eax;
*edi-offset0 = pcx;
cpu.resetBitMode();
} break;
case asBC_STOREOBJ:
{
cpu.setBitMode(sizeof(void*) * 8);
pcx ^= pcx;
(*edi-offset0).direct_copy( (*ebp+offsetof(asSVMRegisters,objectRegister)), eax);
*ebp+offsetof(asSVMRegisters,objectRegister) = pcx;
cpu.resetBitMode();
} break;
case asBC_GETOBJ:
{
pax.copy_address(*esi+offset0);
pdx = as<asDWORD>(*eax); //-Offset
-pdx;
pcx.copy_address(*edi+pdx*4);
as<void*>(*pax).direct_copy(as<void*>(*pcx), pdx);
pdx ^= pdx;
as<void*>(*pcx) = pdx;
} break;
case asBC_RefCpyV:
case asBC_REFCPY:
{
asCObjectType *objType = (asCObjectType*)(size_t)asBC_PTRARG(pOp);
if(objType->flags & asOBJ_NOCOUNT) {
if(op == asBC_REFCPY) {
pax = as<void*>(*esi);
esi += sizeof(void*);
}
else { //Inline PSF
pax.copy_address(as<void*>(*edi-offset0));
}
pcx = as<void*>(*esi);
as<void*>(*pax) = pcx;
}
else {
check_space(512);
//Copy over registers to the vm in case the called functions observe the call stack
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = pOp;
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = esi;
#if defined(JIT_64) && !defined(_MSC_VER)
Register arg1 = as<void*>(cpu.intArg64(1, 1));
#else
Register arg1 = pcx;
#endif
asSTypeBehaviour *beh = &objType->beh;
if(op == asBC_REFCPY) {
pax = as<void*>(*esi);
as<void*>(*esp + local::object2) = pax;
esi += sizeof(void*);
}
else { //Inline PSF
pax.copy_address(as<void*>(*edi-offset0));
as<void*>(*esp + local::object2) = pax;
}
arg1 = as<void*>(*esi);
as<void*>(*esp + local::object1) = arg1;
unsigned callFlags = SC_ValidObj | SC_NoReturn | SC_Simple;
if((flags & JIT_FAST_REFCOUNT) != 0)
callFlags |= SC_NoSuspend | SC_Safe;
//Add reference to object 1, if not null
arg1 &= arg1;
auto prev = cpu.prep_long_jump(Zero);
{
asCScriptFunction* func = (asCScriptFunction*)function->GetEngine()->GetFunctionById(beh->addref);
sysCall.callSystemFunction(func, &arg1, callFlags);
}
cpu.end_long_jump(prev);
//Release reference from object 2, if not null
arg1 = as<void*>(*esp+local::object2);
arg1 = as<void*>(*arg1);
arg1 &= arg1;
auto dest = cpu.prep_long_jump(Zero);
{
asCScriptFunction* func = (asCScriptFunction*)function->GetEngine()->GetFunctionById(beh->release);
sysCall.callSystemFunction(func, &arg1, callFlags);
}
cpu.end_long_jump(dest);
pax = as<void*>(*esp + local::object1);
pdx = as<void*>(*esp + local::object2);
as<void*>(*pdx) = pax;
}
}break;
case asBC_CHKREF:
{
if(currentEAX != EAX_Stack)
pax = as<void*>(*esi);
pax &= pax;
auto p = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(p);
} break;
case asBC_GETOBJREF:
pax.copy_address(*esi + (asBC_WORDARG0(pOp)*sizeof(asDWORD)));
pcx = as<void*>(*pax); //-Offset
-pcx;
as<void*>(*pax).direct_copy(as<void*>(*pdi+pcx*sizeof(asDWORD)), pdx);
break;
case asBC_GETREF:
pax.copy_address(*esi + (asBC_WORDARG0(pOp)*sizeof(asDWORD)));
pcx = as<void*>(*pax); //-Offset
-pcx;
pcx.copy_address(*pdi+pcx*sizeof(asDWORD));
as<void*>(*pax) = pcx;
break;
//case asBC_PshNull: //All pushes are handled above, near asBC_PshC4
case asBC_ClrVPtr:
pax ^= pax;
as<void*>(*edi-offset0) = pax;
break;
//case asBC_OBJTYPE: //All pushes are handled above, near asBC_PshC4
//case asBC_TYPEID:
case asBC_SetV1: //V1 and V2 are identical on little-endian processors
case asBC_SetV2:
case asBC_SetV4:
*edi-offset0 = asBC_DWORDARG(pOp);
break;
case asBC_SetV8:
{
#ifdef JIT_64
pax = asBC_QWORDARG(pOp);
as<asQWORD>(*edi-offset0) = pax;
#else
asQWORD* input = &asBC_QWORDARG(pOp);
asDWORD* data = (asDWORD*)input;
*edi-offset0+4 = *(data+1);
*edi-offset0 = *data;
#endif
} break;
case asBC_ADDSi:
{
if(currentEAX != EAX_Stack)
pax = as<void*>(*esi);
pax &= pax;
auto notNull = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(notNull);
pax += asBC_SWORDARG0(pOp);
as<void*>(*esi) = pax;
nextEAX = EAX_Stack;
} break;
case asBC_CpyVtoV4:
as<asDWORD>(*edi-offset0).direct_copy(as<asDWORD>(*edi-offset1), eax);
break;
case asBC_CpyVtoV8:
as<long long>(*edi-offset0).direct_copy(as<long long>(*edi-offset1), eax);
break;
case asBC_CpyVtoR4:
ebx = *edi - offset0;
break;
case asBC_CpyVtoR8:
#ifdef JIT_64
ebx = as<void*>(*edi-offset0);
#else
ebx = *edi-offset0;
eax = *edi-offset0+4;
as<int>(*ebp+offsetof(asSVMRegisters,valueRegister)+4) = eax;
#endif
break;
case asBC_CpyVtoG4:
eax = *edi-offset0;
MemAddress(cpu, (void*)asBC_PTRARG(pOp)) = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_CpyRtoV4:
as<unsigned>(*edi-offset0) = as<unsigned>(ebx);
break;
case asBC_CpyRtoV8:
#ifdef JIT_64
as<void*>(*edi-offset0) = pbx;
#else
*edi-offset0 = ebx;
eax = *ebp + offsetof(asSVMRegisters,valueRegister)+4;
*edi-offset0+4 = eax;
#endif
break;
case asBC_CpyGtoV4:
eax = MemAddress(cpu, (void*)asBC_PTRARG(pOp));
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_WRTV1:
cpu.setBitMode(8);
(*ebx).direct_copy(*edi-offset0, eax);
cpu.resetBitMode();
nextEAX = EAX_Offset + offset0;
break;
case asBC_WRTV2:
cpu.setBitMode(16);
(*ebx).direct_copy(*edi-offset0, eax);
cpu.resetBitMode();;
nextEAX = EAX_Offset + offset0;
break;
case asBC_WRTV4:
cpu.setBitMode(32);
(*ebx).direct_copy(*edi-offset0, eax);
cpu.resetBitMode();
nextEAX = EAX_Offset + offset0;
break;
case asBC_WRTV8:
cpu.setBitMode(64);
(*ebx).direct_copy(*edi-offset0, eax);
cpu.resetBitMode();
break;
case asBC_RDR1:
eax = *ebx;
eax &= 0x000000ff;
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_RDR2:
eax = *ebx;
eax &= 0x0000ffff;
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_RDR4:
as<asDWORD>(*edi-offset0).direct_copy(as<asDWORD>(*ebx), eax);
nextEAX = EAX_Offset + offset0;
break;
case asBC_RDR8:
as<asQWORD>(*edi-offset0).direct_copy(as<asQWORD>(*ebx), eax); break;
case asBC_LDG:
pbx = (void*)asBC_PTRARG(pOp);
break;
case asBC_LDV:
pbx.copy_address(*edi-offset0);
break;
//case asBC_PGA: //All pushes are handled above, near asBC_PshC4
case asBC_CmpPtr:
{
if(currentEAX != EAX_Offset + offset0)
pax = as<void*>(*edi-offset0);
pax == as<void*>(*edi-offset1);
bl.setIf(Above);
auto t2 = cpu.prep_short_jump(NotBelow);
~bl; //0xff if < 0
cpu.end_short_jump(t2);
}
break;
//case asBC_VAR: //All pushes are handled above, near asBC_PshC4
case asBC_sbTOi:
eax.copy_expanding(as<char>(*edi-offset0));
*edi-offset0 = eax;
break;
case asBC_swTOi:
eax.copy_expanding(as<short>(*edi-offset0));
*edi-offset0 = eax;
break;
case asBC_ubTOi:
*edi-offset0 &= 0xff;
break;
case asBC_uwTOi:
*edi-offset0 &= 0xffff;
break;
case asBC_ADDi:
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
eax += *edi-offset2;
*edi-offset0 = eax;
break;
case asBC_SUBi:
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
eax -= *edi-offset2;
*edi-offset0 = eax;
break;
case asBC_MULi:
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
eax *= *edi-offset2;
*edi-offset0 = eax;
break;
case asBC_DIVi:
ecx = *edi-offset2;
ecx &= ecx;
{
void* zero_test = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(zero_test);
}
eax = *edi-offset1;
edx ^= edx;
{
eax == 0;
auto notSigned = cpu.prep_short_jump(NotSign);
~edx;
cpu.end_short_jump(notSigned);
}
as<int>(ecx).divide_signed();
*edi-offset0 = eax;
break;
case asBC_MODi:
ecx = *edi-offset2;
ecx &= ecx;
{
void* zero_test = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(zero_test);
}
eax = *edi-offset1;
edx ^= edx;
{
eax == 0;
auto notSigned = cpu.prep_short_jump(NotSign);
~edx;
cpu.end_short_jump(notSigned);
}
ecx.divide_signed();
*edi-offset0 = edx;
break;
case asBC_ADDf:
fpu.load_float(*edi-offset1);
fpu.add_float(*edi-offset2);
fpu.store_float(*edi-offset0);
break;
case asBC_SUBf:
fpu.load_float(*edi-offset1);
fpu.sub_float(*edi-offset2);
fpu.store_float(*edi-offset0);
break;
case asBC_MULf:
fpu.load_float(*edi-offset1);
fpu.mult_float(*edi-offset2);
fpu.store_float(*edi-offset0);
break;
case asBC_DIVf:
fpu.load_float(*edi-offset1);
fpu.div_float(*edi-offset2);
fpu.store_float(*edi-offset0);
break;
case asBC_MODf: {
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
#else
Register arg0 = ecx;
Register arg1 = eax;
#endif
arg0.copy_address(*edi-offset1);
arg1.copy_address(*edi-offset2);
cpu.call_stdcall((void*)fmod_wrapper_f,"rr",&arg0,&arg1);
#ifdef JIT_64
*edi-offset0 = cpu.floatReturn64();
#else
fpu.store_float(*edi-offset0);
#endif
} break;
case asBC_ADDd:
fpu.load_double(*edi-offset1);
fpu.add_double(*edi-offset2);
fpu.store_double(*edi-offset0);
break;
case asBC_SUBd:
fpu.load_double(*edi-offset1);
fpu.sub_double(*edi-offset2);
fpu.store_double(*edi-offset0);
break;
case asBC_MULd:
fpu.load_double(*edi-offset1);
fpu.mult_double(*edi-offset2);
fpu.store_double(*edi-offset0);
break;
case asBC_DIVd:
//TODO: AngelScript considers division by 0 an error, should we?
fpu.load_double(*edi-offset1);
fpu.div_double(*edi-offset2);
fpu.store_double(*edi-offset0);
break;
case asBC_MODd: {
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
#else
Register arg0 = ecx;
Register arg1 = eax;
#endif
arg0.copy_address(*edi-offset1);
arg1.copy_address(*edi-offset2);
cpu.call_stdcall((void*)fmod_wrapper,"rr",&arg0,&arg1);
#ifdef JIT_64
as<double>(*edi-offset0) = cpu.floatReturn64();
#else
fpu.store_double(*edi-offset0);
#endif
} break;
case asBC_ADDIi:
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
eax += asBC_INTARG(pOp+1);
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_SUBIi:
if(currentEAX != EAX_Offset + offset1)
eax = *edi-offset1;
eax -= asBC_INTARG(pOp+1);
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_MULIi:
eax.multiply_signed(*edi-offset1,asBC_INTARG(pOp+1));
*edi-offset0 = eax;
nextEAX = EAX_Offset + offset0;
break;
case asBC_ADDIf:
fpu.load_float(*edi-offset1);
fpu.add_float( MemAddress(cpu,&asBC_FLOATARG(pOp+1)) );
fpu.store_float(*edi-offset0);
break;
case asBC_SUBIf:
fpu.load_float(*edi-offset1);
fpu.sub_float( MemAddress(cpu,&asBC_FLOATARG(pOp+1)) );
fpu.store_float(*edi-offset0);
break;
case asBC_MULIf:
fpu.load_float(*edi-offset1);
fpu.mult_float( MemAddress(cpu,&asBC_FLOATARG(pOp+1)) );
fpu.store_float(*edi-offset0);
break;
case asBC_SetG4:
MemAddress(cpu,(void*)asBC_PTRARG(pOp)) = asBC_DWORDARG(pOp+AS_PTR_SIZE);
break;
case asBC_ChkRefS:
//Return if *(*esi) == 0
if(currentEAX != EAX_Stack)
pax = as<void*>(*esi);
eax = as<int>(*pax);
eax &= eax;
ReturnCondition(Zero);
break;
case asBC_ChkNullV:
//Return if (*edi-offset0) == 0
if(currentEAX != EAX_Offset + offset0)
eax = *edi-offset0;
eax &= eax;
ReturnCondition(Zero);
break;
case asBC_CALLINTF:
{
check_space(256);
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = (void*)(pOp+2);
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = as<void*>(esi);
asCScriptFunction* func = (asCScriptFunction*)function->GetEngine()->GetFunctionById(asBC_INTARG(pOp));
//This assumes all interface calls can be jitted since
//there's no way to tell beforehand. It's probably
//a safe assumption considering all functions are passed
//through the jit at _some_ point, but that may change in the future
if(flags & JIT_NO_SCRIPT_CALLS) {
MemAddress ctxPtr( as<void*>(*ebp + offsetof(asSVMRegisters,ctx)) );
cpu.call_stdcall((void*)callInterfaceMethod,"mp", &ctxPtr, func);
ReturnFromScriptCall();
}
else {
JitScriptCallIntf(func);
ReturnFromJittedScriptCall(0);
}
} break;
//asBC_SetV1 and asBC_SetV2 are aliased to asBC_SetV4
case asBC_Cast:
{
check_space(512);
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0, eax));
#else
Register arg0 = ecx;
#endif
arg0 = as<void*>(*esi);
arg0 &= arg0;
auto toEnd1 = cpu.prep_short_jump(Zero);
arg0 = as<void*>(*arg0);
arg0 &= arg0;
auto toEnd2 = cpu.prep_short_jump(Zero);
asCObjectType *to = ((asCScriptEngine*)function->GetEngine())->GetObjectTypeFromTypeId(asBC_DWORDARG(pOp));
cpu.call_stdcall((void*)castObject,"rp",&arg0,to);
pax &= pax;
auto toEnd3 = cpu.prep_short_jump(Zero);
as<void*>(*ebp + offsetof(asSVMRegisters,objectRegister)) = pax;
cpu.end_short_jump(toEnd1);
cpu.end_short_jump(toEnd2);
cpu.end_short_jump(toEnd3);
esi += sizeof(void*);
} break;
case asBC_iTOb:
*edi-offset0 &= 0xff;
break;
case asBC_iTOw:
*edi-offset0 &= 0xffff;
break;
#ifdef JIT_64
#ifdef _MSC_VER
#define cast(f,t) {\
Register arg0 = as<void*>(cpu.intArg64(0, 0));\
Register arg1 = as<void*>(cpu.intArg64(1, 1));\
void* func = (void*)(void (*)(f*,t*))(directConvert<f,t>);\
arg1.copy_address(*edi-offset0);\
if(sizeof(f) != sizeof(t))\
{ arg0.copy_address(*edi-offset1); cpu.call_stdcall(func,"rr",&arg0,&arg1); }\
else\
{ arg0 = arg1; cpu.call_stdcall(func,"rr",&arg0,&arg1); }\
}
#else
#define cast(f,t) {\
Register arg0 = as<void*>(cpu.intArg64(0, 0));\
Register arg1 = as<void*>(cpu.intArg64(1, 1));\
void* func = (void*)(void (*)(f*,t*))(directConvert<f,t>);\
arg1.copy_address(*edi-offset0);\
if(sizeof(f) != sizeof(t))\
{ arg0.copy_address(*edi-offset1); cpu.call_cdecl(func,"rr",&arg0,&arg1); }\
else\
{ arg0 = arg1; cpu.call_cdecl(func,"rr",&arg0,&arg1); }\
}
#endif
#else
#ifdef _MSC_VER
#define cast(f,t) {\
void* func = (void*)(void (*)(f*,t*))(directConvert<f,t>);\
pax.copy_address(*edi-offset0);\
if(sizeof(f) != sizeof(t))\
{ pcx.copy_address(*edi-offset1); cpu.call_stdcall(func,"rr",&pcx,&pax); }\
else\
cpu.call_stdcall(func,"rr",&pax,&pax);\
}
#else
#define cast(f,t) {\
void* func = (void*)(void (*)(f*,t*))(directConvert<f,t>);\
pax.copy_address(*edi-offset0);\
if(sizeof(f) != sizeof(t))\
{ pcx.copy_address(*edi-offset1); cpu.call_cdecl(func,"rr",&pcx,&pax); }\
else\
cpu.call_cdecl(func,"rr",&pax,&pax);\
}
#endif
#endif
////All type conversions of QWORD to/from DWORD and Float to/from Int are here
case asBC_iTOf:
fpu.load_dword(*edi-offset0);
fpu.store_float(*edi-offset0);
break;
case asBC_fTOi:
cast(float,int); break;
case asBC_uTOf:
cast(unsigned, float); break;
case asBC_fTOu:
cast(float, unsigned); break;
case asBC_dTOi:
cast(double,int); break;
case asBC_dTOu:
cast(double, unsigned); break;
case asBC_dTOf:
fpu.load_double(*edi-offset1);
fpu.store_float(*edi-offset0);
break;
case asBC_iTOd:
fpu.load_dword(*edi-offset1);
fpu.store_double(*edi-offset0);
break;
case asBC_uTOd:
cast(unsigned, double); break;
case asBC_fTOd:
fpu.load_float(*edi-offset1);
fpu.store_double(*edi-offset0);
break;
case asBC_i64TOi:
cast(long long, int) break;
case asBC_uTOi64:
cast(unsigned int, long long) break;
case asBC_iTOi64:
cast(int, long long) break;
case asBC_fTOi64:
cast(float, long long) break;
case asBC_fTOu64:
cast(float, unsigned long long) break;
case asBC_i64TOf:
cast(long long, float) break;
case asBC_u64TOf:
cast(unsigned long long, float) break;
case asBC_dTOi64:
cast(double, long long) break;
case asBC_dTOu64:
cast(double, unsigned long long) break;
case asBC_i64TOd:
cast(long long, double) break;
case asBC_u64TOd:
cast(unsigned long long, double) break;
case asBC_NEGi64:
-as<long long>(*edi-offset0);
break;
case asBC_INCi64:
++as<long long>(*ebx);
break;
case asBC_DECi64:
--as<long long>(*ebx);
break;
case asBC_BNOT64:
~as<long long>(*edi-offset0);
break;
case asBC_ADDi64:
{
#ifdef JIT_64
pax = as<int64_t>(*edi-offset1);
pax += as<int64_t>(*edi-offset2);
as<int64_t>(*edi-offset0) = pax;
#else
eax = *edi-offset1;
eax += *edi-offset2;
*edi-offset0 = eax;
eax.setIf(Carry);
eax.copy_zeroing(eax);
eax += *edi-offset1+4;
eax += *edi-offset2+4;
*edi-offset0+4 = eax;
#endif
} break;
case asBC_SUBi64:
{
#ifdef JIT_64
pax = as<int64_t>(*edi-offset1);
pax -= as<int64_t>(*edi-offset2);
as<int64_t>(*edi-offset0) = pax;
#else
eax = *edi-offset1;
eax -= *edi-offset2;
*edi-offset0 = eax;
eax = *edi-offset1+4;
auto p = cpu.prep_short_jump(NotCarry);
--eax;
cpu.end_short_jump(p);
eax -= *edi-offset2+4;
*edi-offset0+4 = eax;
#endif
} break;
case asBC_MULi64:
#ifdef JIT_64
pax = as<int64_t>(*edi-offset1);
pax *= as<int64_t>(*edi-offset2);
as<int64_t>(*edi-offset0) = pax;
#else
ecx.copy_address(*edi-offset1);
edx.copy_address(*edi-offset2);
eax.copy_address(*edi-offset0);
cpu.call_stdcall((void*)i64_mul,"rrr",&ecx,&edx,&eax);
#endif
break;
case asBC_DIVi64: {
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
Register arg2 = as<void*>(cpu.intArg64(2, 2));
#else
Register arg0 = pcx;
Register arg1 = pdx;
Register arg2 = pax;
#endif
arg0.copy_address(*edi-offset1);
arg1.copy_address(*edi-offset2);
arg2.copy_address(*edi-offset0);
cpu.call_stdcall((void*)i64_div,"rrr",&arg0,&arg1,&arg2);
} break;
case asBC_MODi64: {
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
Register arg2 = as<void*>(cpu.intArg64(2, 2));
#else
Register arg0 = pcx;
Register arg1 = pdx;
Register arg2 = pax;
#endif
arg0.copy_address(*edi-offset1);
arg1.copy_address(*edi-offset2);
arg2.copy_address(*edi-offset0);
cpu.call_stdcall((void*)i64_mod,"rrr",&arg0,&arg1,&arg2);
} break;
case asBC_BAND64:
#ifdef JIT_64
pax = as<uint64_t>(*edi-offset1);
pax &= as<uint64_t>(*edi-offset2);
as<uint64_t>(*edi-offset0) = pax;
#else
ecx = *edi-offset1;
edx = *edi-offset1+4;
ecx &= *edi-offset2;
edx &= *edi-offset2+4;
*edi-offset0 = ecx;
*edi-offset0+4 = edx;
#endif
break;
case asBC_BOR64:
#ifdef JIT_64
pax = as<uint64_t>(*edi-offset1);
pax |= as<uint64_t>(*edi-offset2);
as<uint64_t>(*edi-offset0) = pax;
#else
ecx = *edi-offset1;
edx = *edi-offset1+4;
ecx |= *edi-offset2;
edx |= *edi-offset2+4;
*edi-offset0 = ecx;
*edi-offset0+4 = edx;
#endif
break;
case asBC_BXOR64:
#ifdef JIT_64
pax = as<uint64_t>(*edi-offset1);
pax ^= as<uint64_t>(*edi-offset2);
as<uint64_t>(*edi-offset0) = pax;
#else
ecx = *edi-offset1;
edx = *edi-offset1+4;
ecx ^= *edi-offset2;
edx ^= *edi-offset2+4;
*edi-offset0 = ecx;
*edi-offset0+4 = edx;
#endif
break;
case asBC_BSLL64: {
#ifdef JIT_64
Register c(cpu, ECX, sizeof(uint64_t) * 8);
pax = as<uint64_t>(*edi-offset1);
c = as<uint32_t>(*edi-offset2);
pax <<= c;
as<uint64_t>(*edi-offset0) = pax;
#else
ecx.copy_address(*edi-offset1);
edx.copy_address(*edi-offset2);
eax.copy_address(*edi-offset0);
cpu.call_stdcall((void*)i64_sll,"rrr",&ecx,&edx,&eax);
#endif
} break;
case asBC_BSRL64: {
#ifdef JIT_64
Register c(cpu, ECX, sizeof(uint64_t) * 8);
pax = as<uint64_t>(*edi-offset1);
c = as<uint32_t>(*edi-offset2);
pax.rightshift_logical(c);
as<uint64_t>(*edi-offset0) = pax;
#else
ecx.copy_address(*edi-offset1);
edx.copy_address(*edi-offset2);
eax.copy_address(*edi-offset0);
cpu.call_stdcall((void*)i64_srl,"rrr",&ecx,&edx,&eax);
#endif
} break;
case asBC_BSRA64: {
#ifdef JIT_64
Register c(cpu, ECX, sizeof(uint64_t) * 8);
pax = as<uint64_t>(*edi-offset1);
c = as<uint32_t>(*edi-offset2);
pax >>= c;
as<uint64_t>(*edi-offset0) = pax;
#else
ecx.copy_address(*edi-offset1);
edx.copy_address(*edi-offset2);
eax.copy_address(*edi-offset0);
cpu.call_stdcall((void*)i64_sra,"rrr",&ecx,&edx,&eax);
#endif
} break;
case asBC_CMPi64: {
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
#else
Register arg0 = ecx;
Register arg1 = eax;
#endif
arg0.copy_address(*edi-offset0);
arg1.copy_address(*edi-offset1);
cpu.call_stdcall((void*)cmp_int64,"rr",&arg0,&arg1);
ebx = eax;
} break;
case asBC_CMPu64: {
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
#else
Register arg0 = ecx;
Register arg1 = eax;
#endif
arg0.copy_address(*edi-offset0);
arg1.copy_address(*edi-offset1);
cpu.call_stdcall((void*)cmp_uint64,"rr",&arg0,&arg1);
ebx = eax;
} break;
case asBC_ChkNullS:
{
if(asBC_WORDARG0(pOp) != 0 && currentEAX != EAX_Stack)
eax = *esi+(asBC_WORDARG0(pOp) * sizeof(asDWORD));
eax &= eax;
void* not_zero = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(not_zero);
} break;
case asBC_ClrHi:
//Due to the way logic is handled, the upper bytes area always ignored, and don't need to be cleared
//ebx &= 0x000000ff;
break;
case asBC_CallPtr:
{
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
Register temp = as<int>(cpu.intArg64(2, 2));
#else
Register arg0 = eax;
Register arg1 = ecx;
Register temp = edx;
#endif
arg1 = as<void*>(*pdi-offset0);
arg1 &= arg1;
auto nullFunc = cpu.prep_short_jump(NotZero);
temp = *arg1 + offsetof(asCScriptFunction,funcType);
temp == asFUNC_SCRIPT;
auto isScript = cpu.prep_short_jump(Zero);
cpu.end_short_jump(nullFunc);
Return(false);
cpu.end_short_jump(isScript);
*ebp + offsetof(asSVMRegisters,programPointer) = pOp+1;
*ebp + offsetof(asSVMRegisters,stackPointer) = esi;
arg0 = as<void*>(*ebp + offsetof(asSVMRegisters,ctx));
cpu.call_stdcall((void*)callScriptFunction,"rr",&arg0,&arg1);
ReturnFromScriptCall();
} break;
//case asBC_FuncPtr: //All pushes are handled above, near asBC_PshC4
case asBC_LoadThisR:
{
pbx = as<void*>(*edi);
pbx &= pbx;
auto j = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(j);
short off = asBC_SWORDARG0(pOp);
if(off > 0)
pbx += off;
else
pbx -= -off;
} break;
//case asBC_PshV8: //All pushes are handled above, near asBC_PshC4
case asBC_DIVu:
ecx = *edi-offset2;
ecx &= ecx;
{
void* zero_test = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(zero_test);
}
eax = *edi-offset1;
edx ^= edx;
ecx.divide();
*edi-offset0 = eax;
break;
case asBC_MODu:
ecx = *edi-offset2;
ecx &= ecx;
{
void* zero_test = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(zero_test);
}
eax = *edi-offset1;
edx ^= edx;
ecx.divide();
*edi-offset0 = edx;
break;
case asBC_DIVu64:
{
#ifdef JIT_64
pcx = as<uint64_t>(*edi-offset2);
pcx &= pcx;
{
void* zero_test = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(zero_test);
}
pax = as<uint64_t>(*edi-offset1);
pdx ^= pdx;
pcx.divide();
as<uint64_t>(*edi-offset0) = pax;
#else
ecx.copy_address(*edi-offset1);
edx.copy_address(*edi-offset2);
eax.copy_address(*edi-offset0);
cpu.call_stdcall((void*)div_ull,"rrr",&ecx,&edx,&eax);
eax &= eax;
auto p = cpu.prep_short_jump(Zero);
//If 1 is returned, this is a divide by 0 error
Return(false);
cpu.end_short_jump(p);
#endif
} break;
case asBC_MODu64:
{
#ifdef JIT_64
pcx = as<uint64_t>(*edi-offset2);
pcx &= pcx;
{
void* zero_test = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(zero_test);
}
pax = as<uint64_t>(*edi-offset1);
pdx ^= pdx;
pcx.divide();
as<uint64_t>(*edi-offset0) = pdx;
#else
ecx.copy_address(*edi-offset1);
edx.copy_address(*edi-offset2);
eax.copy_address(*edi-offset0);
cpu.call_stdcall((void*)mod_ull,"rrr",&ecx,&edx,&eax);
eax &= eax;
auto p = cpu.prep_short_jump(Zero);
//If 1 is returned, this is a divide by 0 error
Return(false);
cpu.end_short_jump(p);
#endif
} break;
case asBC_LoadRObjR:
{
pbx = as<void*>(*edi-offset0);
pbx &= pbx;
auto j = cpu.prep_short_jump(NotZero);
Return(false);
cpu.end_short_jump(j);
pbx += asBC_SWORDARG1(pOp);
} break;
case asBC_LoadVObjR:
pbx.copy_address(*edi+(asBC_SWORDARG1(pOp) - offset0));
break;
case asBC_AllocMem:
{
//Allocate the array (and sets its contents to 0)
cpu.call_stdcall((void*)allocArray,"c",asBC_DWORDARG(pOp));
as<void*>(*edi-offset0) = pax;
nextEAX = EAX_Offset + offset0;
} break;
//List size and type are identical ops (oversight?)
case asBC_SetListSize:
case asBC_SetListType:
{
if(currentEAX != EAX_Offset + offset0)
pax = as<void*>(*edi-offset0);
as<unsigned>(*pax+asBC_DWORDARG(pOp)) = asBC_DWORDARG(pOp+1);
nextEAX = EAX_Offset + offset0;
} break;
case asBC_PshListElmnt:
{
//TODO: Should this be grouped with the batched pushes?
if(currentEAX != EAX_Offset + offset0)
pax = as<void*>(*edi-offset0);
esi -= sizeof(void*);
pax.copy_address(*pax+asBC_DWORDARG(pOp));
as<void*>(*esi) = pax;
nextEAX = EAX_Stack;
} break;
case asBC_POWi:
{
#ifdef JIT_64
Register arg2 = as<void*>(cpu.intArg64(2, 2));
#else
Register arg2 = edx;
#endif
arg2.copy_address(*esp+local::overflowRet);
MemAddress base(*edi-offset1);
MemAddress exp(*edi-offset2);
cpu.call_cdecl((void*)as_powi, "mmr", &base, &exp, &arg2);
ecx = as<char>(*esp + local::overflowRet);
as<char>(ecx) &= as<char>(ecx);
ReturnCondition(NotZero);
as<int>(*edi-offset0) = eax;
} break;
case asBC_POWu:
{
#ifdef JIT_64
Register arg2 = as<void*>(cpu.intArg64(2, 2));
#else
Register arg2 = edx;
#endif
arg2.copy_address(*esp+local::overflowRet);
MemAddress base(*edi-offset1);
MemAddress exp(*edi-offset2);
cpu.call_cdecl((void*)as_powu, "mmr", &base, &exp, &arg2);
ecx = as<char>(*esp + local::overflowRet);
as<char>(ecx) &= as<char>(ecx);
ReturnCondition(NotZero);
as<int>(*edi-offset0) = eax;
} break;
case asBC_POWf:
{
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
Register arg2 = as<void*>(cpu.intArg64(2, 2));
Register arg3 = as<void*>(cpu.intArg64(3, 3));
#else
Register arg0 = eax;
Register arg1 = ecx;
Register arg2 = edx;
Register arg3 = ebx;
#endif
arg0.copy_address(*edi-offset1);
arg1.copy_address(*edi-offset2);
arg2.copy_address(*esp+local::overflowRet);
arg3.copy_address(*edi-offset0);
cpu.call_cdecl((void*)fpow_wrapper, "rrrr", &arg0, &arg1, &arg2, &arg3);
ecx = as<char>(*esp + local::overflowRet);
as<char>(ecx) &= as<char>(ecx);
ReturnCondition(NotZero);
} break;
case asBC_POWd:
{
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
Register arg2 = as<void*>(cpu.intArg64(2, 2));
Register arg3 = as<void*>(cpu.intArg64(3, 3));
#else
Register arg0 = eax;
Register arg1 = ecx;
Register arg2 = edx;
Register arg3 = ebx;
#endif
arg0.copy_address(*edi-offset1);
arg1.copy_address(*edi-offset2);
arg2.copy_address(*esp+local::overflowRet);
arg3.copy_address(*edi-offset0);
cpu.call_cdecl((void*)dpow_wrapper, "rrrr", &arg0, &arg1, &arg2, &arg3);
ecx = as<char>(*esp + local::overflowRet);
as<char>(ecx) &= as<char>(ecx);
ReturnCondition(NotZero);
} break;
case asBC_POWdi:
{
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg2 = as<void*>(cpu.intArg64(2, 2));
Register arg3 = as<void*>(cpu.intArg64(3, 3));
#else
Register arg0 = eax;
Register arg2 = edx;
Register arg3 = ebx;
#endif
arg0.copy_address(*edi-offset1);
arg2.copy_address(*esp+local::overflowRet);
arg3.copy_address(*edi-offset0);
MemAddress exp(*edi-offset2);
cpu.call_cdecl((void*)dipow_wrapper, "rmrr", &arg0, &exp, &arg2, &arg3);
ecx = as<char>(*esp + local::overflowRet);
as<char>(ecx) &= as<char>(ecx);
ReturnCondition(NotZero);
} break;
case asBC_POWi64:
{
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
Register arg2 = as<void*>(cpu.intArg64(2, 2));
Register arg3 = as<void*>(cpu.intArg64(3, 3));
#else
Register arg0 = eax;
Register arg1 = ecx;
Register arg2 = edx;
Register arg3 = ebx;
#endif
arg0.copy_address(*edi-offset1);
arg1.copy_address(*edi-offset2);
arg2.copy_address(*esp+local::overflowRet);
arg3.copy_address(*edi-offset0);
cpu.call_cdecl((void*)i64pow_wrapper, "rrrr", &arg0, &arg1, &arg2, &arg3);
ecx = as<char>(*esp + local::overflowRet);
as<char>(ecx) &= as<char>(ecx);
ReturnCondition(NotZero);
} break;
case asBC_POWu64:
{
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
Register arg2 = as<void*>(cpu.intArg64(2, 2));
Register arg3 = as<void*>(cpu.intArg64(3, 3));
#else
Register arg0 = eax;
Register arg1 = ecx;
Register arg2 = edx;
Register arg3 = ebx;
#endif
arg0.copy_address(*edi-offset1);
arg1.copy_address(*edi-offset2);
arg2.copy_address(*esp+local::overflowRet);
arg3.copy_address(*edi-offset0);
cpu.call_cdecl((void*)u64pow_wrapper, "rrrr", &arg0, &arg1, &arg2, &arg3);
ecx = as<char>(*esp + local::overflowRet);
as<char>(ecx) &= as<char>(ecx);
ReturnCondition(NotZero);
} break;
default:
//printf("Unhandled op: %i\n", op);
Return(true);
break;
}
#ifdef JIT_DEBUG
pdx = (void*)&DBG_LastOP;
as<asEBCInstr>(*pdx) = op;
#endif
pOp += toSize(op);
}
//Fill out all deferred pointers for this function
if(curJitFunction && firstJitEntry) {
auto range = deferredPointers.equal_range(function);
for(auto it = range.first; it != range.second; ++it) {
*it->second.jitFunction = curJitFunction;
*it->second.jitEntry = firstJitEntry;
}
}
if(waitingForEntry == false)
Return(true);
for(auto i = switches.begin(), end = switches.end(); i != end; ++i)
jumpTables.insert(std::pair<asJITFunction,unsigned char**>(*output, i->buffer));
activePage->markUsedAddress((void*)cpu.op);
lock->leave();
return 0;
}
void asCJITCompiler::finalizePages() {
lock->enter();
for(auto page = pages.begin(); page != pages.end(); ++page)
if(!page->second->final)
page->second->finalize();
lock->leave();
}
void asCJITCompiler::ReleaseJITFunction(asJITFunction func) {
lock->enter();
{
auto start = pages.lower_bound(func);
while(start != pages.end() && start->first == func) {
if(start->second == activePage) {
activePage->drop();
activePage = 0;
}
start->second->drop();
start = pages.erase(start);
}
}
{
auto start = jumpTables.lower_bound(func);
while(start != jumpTables.end() && start->first == func) {
delete[] start->second;
start = jumpTables.erase(start);
}
}
lock->leave();
}
unsigned findTotalPushBatchSize(asDWORD* nextOp, asDWORD* endOfBytecode) {
unsigned bytes = 0;
while(nextOp < endOfBytecode) {
asEBCInstr op = (asEBCInstr)*(asBYTE*)nextOp;
switch(op) {
case asBC_PshC4:
case asBC_PshV4:
case asBC_PshG4:
case asBC_TYPEID:
bytes += sizeof(asDWORD); break;
case asBC_PshV8:
case asBC_PshC8:
bytes += sizeof(asQWORD); break;
case asBC_PSF:
case asBC_PshVPtr:
case asBC_PshRPtr:
case asBC_PshNull:
case asBC_FuncPtr:
case asBC_OBJTYPE:
case asBC_PGA:
case asBC_VAR:
case asBC_PshGPtr:
bytes += sizeof(void*); break;
default:
return bytes;
}
nextOp += toSize(op);
}
return bytes;
}
void stdcall allocScriptObject(asCObjectType* type, asCScriptFunction* constructor, asIScriptEngine* engine, asSVMRegisters* registers) {
//Allocate and prepare memory
void* mem = ((asCScriptEngine*)engine)->CallAlloc(type);
ScriptObject_Construct(type, (asCScriptObject*)mem);
//Store at address on the stack
void** dest = *(void***)(registers->stackPointer + constructor->GetSpaceNeededForArguments());
if(dest)
*dest = mem;
//Push pointer so the constructor can be called
registers->stackPointer -= AS_PTR_SIZE;
*(void**)registers->stackPointer = mem;
registers->programPointer += 2 + AS_PTR_SIZE;
//((asCContext*)registers->ctx)->CallScriptFunction(constructor);
}
void* stdcall allocArray(asDWORD bytes) {
void* arr = asNEWARRAY(asBYTE, bytes);
memset(arr, 0, bytes);
return arr;
}
void* stdcall engineAlloc(asCScriptEngine* engine, asCObjectType* type) {
return engine->CallAlloc(type);
}
void stdcall engineRelease(asCScriptEngine* engine, void* memory, asCScriptFunction* release) {
engine->CallObjectMethod(memory, release->sysFuncIntf, release);
}
void stdcall engineListFree(asCScriptEngine* engine, asCObjectType* objType, void* memory) {
engine->DestroyList((asBYTE*)memory, objType);
engine->CallFree(memory);
}
void stdcall engineDestroyFree(asCScriptEngine* engine, void* memory, asCScriptFunction* destruct) {
engine->CallObjectMethod(memory, destruct->sysFuncIntf, destruct);
engine->CallFree(memory);
}
void stdcall engineFree(asCScriptEngine* engine, void* memory) {
engine->CallFree(memory);
}
void stdcall engineCallMethod(asCScriptEngine* engine, void* object, asCScriptFunction* method) {
engine->CallObjectMethod(object, method->sysFuncIntf, method);
}
void stdcall callScriptFunction(asIScriptContext* ctx, asCScriptFunction* func) {
asCContext* context = (asCContext*)ctx;
context->CallScriptFunction(func);
}
asCScriptFunction* stdcall callInterfaceMethod(asIScriptContext* ctx, asCScriptFunction* func) {
asCContext* context = (asCContext*)ctx;
context->CallInterfaceMethod(func);
if(context->m_status != asEXECUTION_ACTIVE)
return 0;
return context->m_currentFunction;
}
asCScriptFunction* stdcall callBoundFunction(asIScriptContext* ctx, unsigned short fid) {
asCContext* context = (asCContext*)ctx;
asCScriptEngine* engine = (asCScriptEngine*)context->GetEngine();
int funcID = engine->importedFunctions[fid]->boundFunctionId;
if(funcID == -1) {
context->SetInternalException(TXT_UNBOUND_FUNCTION);
return 0;
}
asCScriptFunction* func = engine->GetScriptFunction(funcID);
//Imported functions can be bound to non-script functions; we just exit to the vm to handle these for now
if(func->funcType != asFUNC_SCRIPT)
return 0;
context->CallScriptFunction(func);
if(context->m_status != asEXECUTION_ACTIVE)
return 0;
return func;
}
void stdcall receiveObjectHandle(asIScriptContext* ctx, asCScriptObject* obj) {
asCContext* context = (asCContext*)ctx;
if(obj) {
asCObjectType* objType = (asCObjectType*)obj->GetObjectType();
((asCScriptEngine*)context->GetEngine())->CallObjectMethod(obj, objType->beh.addref);
}
context->m_regs.objectRegister = obj;
}
asCScriptObject* stdcall castObject(asCScriptObject* obj, asCObjectType* to) {
asCObjectType* from = (asCObjectType*)obj->GetObjectType();
if( from->DerivesFrom(to) || from->Implements(to) ) {
obj->AddRef();
return obj;
}
else {
return nullptr;
}
}
bool stdcall doSuspend(asIScriptContext* ctx) {
asCContext* Ctx = (asCContext*)ctx;
if(Ctx->m_lineCallback)
Ctx->CallLineCallback();
if(Ctx->m_doSuspend) {
Ctx->m_regs.programPointer += 1;
if(Ctx->m_status == asEXECUTION_ACTIVE)
Ctx->m_status = asEXECUTION_SUSPENDED;
return true;
}
else {
return false;
}
}
void SystemCall::callSystemFunction(asCScriptFunction* func, Register* objPointer, unsigned callFlags) {
callFlags |= flags;
callIsSafe = ((callFlags & SC_Safe) != 0);
checkNullObj = ((callFlags & SC_ValidObj) == 0);
handleSuspend = ((callFlags & SC_NoSuspend) == 0);
acceptReturn = ((callFlags & SC_NoReturn) == 0);
isSimple = ((callFlags & SC_Simple) != 0);
auto* sys = func->sysFuncIntf;
#ifdef JIT_PRINT_UNHANDLED_CALLS
auto unhandled = [&]() {
if(unhandledCalls.find(func) == unhandledCalls.end()) {
printf("Unhandled JIT Call: %s\n", func->GetDeclaration());
unhandledCalls.insert(func);
}
};
#endif
bool hasAutoHandles = false;
for(unsigned i = 0, cnt = sys->paramAutoHandles.GetLength(); i < cnt; ++i) {
if(sys->paramAutoHandles[i]) {
hasAutoHandles = true;
break;
}
}
#ifdef JIT_64
if( sys->takesObjByVal || hasAutoHandles || sys->hostReturnSize > 4 ||
(sys->paramAutoHandles.GetLength() != 0 && sys->paramSize == 0) )
#else
if( sys->takesObjByVal || hasAutoHandles || sys->hostReturnSize > 2 ||
(sys->paramAutoHandles.GetLength() != 0 && sys->paramSize == 0))
#endif
{
//Handle various cases that we cannot yet
//Note: We do not know parameter sizes for template factories, so we cannot compile them
//However, they all receive a magic int& that we can detect (paramAutoHandles is not empty, paramSize is)
#ifdef JIT_PRINT_UNHANDLED_CALLS
unhandled();
#endif
call_viaAS(func, objPointer);
}
else {
switch(sys->callConv) {
#ifdef JIT_64
case ICC_CDECL:
case ICC_STDCALL:
call_64conv(sys, func, 0, OP_None); break;
case ICC_CDECL_OBJLAST:
call_64conv(sys, func, objPointer, OP_Last); break;
case ICC_CDECL_OBJFIRST:
call_64conv(sys, func, objPointer, OP_First); break;
case ICC_THISCALL:
case ICC_THISCALL_RETURNINMEM:
case ICC_VIRTUAL_THISCALL:
case ICC_VIRTUAL_THISCALL_RETURNINMEM:
#ifdef _MSC_VER
call_64conv(sys, func, objPointer, OP_This); break;
#else
call_64conv(sys, func, objPointer, OP_First); break;
#endif
#else
case ICC_CDECL:
call_cdecl(sys, func); break;
case ICC_STDCALL:
call_stdcall(sys, func); break;
case ICC_THISCALL:
case ICC_THISCALL_RETURNINMEM:
call_thiscall(sys, func, objPointer); break;
case ICC_CDECL_OBJLAST:
call_cdecl_obj(sys, func, objPointer, true); break;
case ICC_CDECL_OBJFIRST:
call_cdecl_obj(sys, func, objPointer, false); break;
case ICC_VIRTUAL_THISCALL:
case ICC_VIRTUAL_THISCALL_RETURNINMEM:
call_viaAS(func, objPointer); break;
#endif
case ICC_GENERIC_FUNC:
case ICC_GENERIC_FUNC_RETURNINMEM:
case ICC_GENERIC_METHOD:
case ICC_GENERIC_METHOD_RETURNINMEM:
//call_generic(func, objPointer); break;
//break;
default:
//Probably can't reach here, but handle it anyway
#ifdef JIT_PRINT_UNHANDLED_CALLS
unhandled();
#endif
call_viaAS(func, objPointer); break;
}
}
}
void SystemCall::call_entry(asSSystemFunctionInterface* func, asCScriptFunction* sFunc) {
unsigned pBits = sizeof(void*) * 8;
#ifdef JIT_64
Register esi(cpu,R13,pBits);
#else
Register esi(cpu,ESI,pBits);
#endif
Register ebp(cpu,EBP), esp(cpu,ESP,pBits);
Register pax(cpu,EAX,pBits);
if((flags & SC_FastFPU) == 0)
fpu.init();
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = pOp;
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = esi;
if(!callIsSafe) {
pax = as<void*>(*ebp + offsetof(asSVMRegisters,ctx));
pax += offsetof(asCContext,m_callingSystemFunction); //&callingSystemFunction
as<void*>(*pax) = sFunc;
as<void*>(*esp + local::pIsSystem) = pax;
}
}
//Undoes things performed in call_entry in the case of an error
void SystemCall::call_error() {
Register pax(cpu,EAX,sizeof(void*)*8), esp(cpu,ESP);
if(!callIsSafe) {
pax = as<void*>(*esp + local::pIsSystem);
as<void*>(*pax) = (void*)0;
}
}
void SystemCall::call_exit(asSSystemFunctionInterface* func) {
Register eax(cpu,EAX), edx(cpu,EDX), esp(cpu,ESP), ebp(cpu,EBP), cl(cpu,ECX,8);
Register pax(cpu,EAX,sizeof(void*)*8);
if(!callIsSafe) {
//Clear IsSystem*
pax = as<void*>(*esp + local::pIsSystem);
as<void*>(*pax) = (void*)0;
}
if(!callIsSafe || handleSuspend) {
cl = as<bool>(*ebp+offsetof(asSVMRegisters,doProcessSuspend));
cl &= cl;
auto* dontSuspend = cpu.prep_short_jump(Zero);
pax = as<void*>(*ebp+offsetof(asSVMRegisters,ctx));
if(!callIsSafe) {
edx = as<int>(*pax+offsetof(asCContext,m_status));
edx == (int)asEXECUTION_ACTIVE;
auto* activeContext = cpu.prep_short_jump(Equal);
returnHandler(Jump, true);
cpu.end_short_jump(activeContext);
}
if(handleSuspend) {
cl = as<bool>(*pax+offsetof(asCContext,m_doSuspend));
cl &= cl;
auto* noSuspend = cpu.prep_short_jump(Zero);
as<int>(*pax+offsetof(asCContext,m_status)) = (int)asEXECUTION_SUSPENDED;
returnHandler(Jump, true);
cpu.end_short_jump(noSuspend);
}
cpu.end_short_jump(dontSuspend);
}
}
#ifdef JIT_64
void SystemCall::call_64conv(asSSystemFunctionInterface* func,
asCScriptFunction* sFunc, Register* objPointer, ObjectPosition pos) {
Register eax(cpu, EAX), edx(cpu, EDX);
Register xmm0(cpu, XMM0), xmm1(cpu, XMM1);
Register pax(cpu, EAX, sizeof(void*) * 8), esp(cpu, ESP, sizeof(void*) * 8);
Register esi(cpu, R13, sizeof(void*) * 8), ebx(cpu, EBX, sizeof(void*) * 8);
Register temp(cpu, R10, sizeof(void*) * 8), ebp(cpu, EBP, sizeof(void*) * 8);
call_entry(func, sFunc);
int argCount = (int)sFunc->parameterTypes.GetLength();
unsigned stackBytes = 0;
unsigned argOffset = 0;
bool stackObject = false;
Register containingObj(cpu, EAX);
bool isVirtual = func->callConv == ICC_VIRTUAL_THISCALL || func->callConv == ICC_VIRTUAL_THISCALL_RETURNINMEM;
int intCount = 0;
int floatCount = 0;
int i = 0, a = 0;
bool retPointer = false;
bool retOnStack = false;
int firstPos = 0;
//'this' before 'return pointer' on MSVC
if(pos == OP_This) {
Register reg = as<void*>(cpu.intArg64(0, 0));
if(func->callConv >= ICC_THISCALL && func->auxiliary) {
reg = func->auxiliary;
}
else if(objPointer) {
reg = *objPointer;
if(checkNullObj) {
reg &= reg;
returnHandler(Zero, false);
}
reg += func->baseOffset;
}
else {
reg = as<void*>(*esi);
argOffset += sizeof(void*);
stackObject = true;
if(checkNullObj) {
reg &= reg;
returnHandler(Zero, false);
}
reg += func->baseOffset;
}
containingObj.set_regCode(reg);
++intCount;
++a;
firstPos = 1;
}
if(sFunc->DoesReturnOnStack()) {
Register arg0 = as<void*>(cpu.intArg64(firstPos, firstPos, pax));
if(pos == OP_None || objPointer)
arg0 = as<void*>(*esi);
else
arg0 = as<void*>(*esi + sizeof(asPWORD));
if(acceptReturn)
as<void*>(*esp + local::retPointer) = arg0;
retPointer = true;
argOffset += sizeof(void*);
if(func->hostReturnInMemory) {
if(!cpu.isIntArg64Register(firstPos, firstPos)) {
stackBytes += cpu.pushSize();
retOnStack = true;
}
++intCount;
++a;
firstPos += 1;
}
}
if(pos == OP_First) {
if(!cpu.isIntArg64Register(firstPos, firstPos))
stackBytes += cpu.pushSize();
++intCount;
++a;
}
for(; i < argCount; ++i, ++a) {
auto& type = sFunc->parameterTypes[i];
if(type.GetTokenType() == ttQuestion) {
if(!cpu.isIntArg64Register(intCount, a))
stackBytes += cpu.pushSize();
++intCount; ++a;
if(!cpu.isIntArg64Register(intCount, a))
stackBytes += cpu.pushSize();
++intCount;
argOffset += sizeof(void*);
argOffset += sizeof(int);
}
else if(type.IsReference() || type.IsObjectHandle()) {
if(!cpu.isIntArg64Register(intCount, a))
stackBytes += cpu.pushSize();
++intCount;
argOffset += sizeof(void*);
}
else if(type.IsFloatType()) {
if(!cpu.isFloatArg64Register(floatCount, a))
stackBytes += cpu.pushSize();
++floatCount;
argOffset += sizeof(float);
}
else if(type.IsDoubleType()) {
if(!cpu.isFloatArg64Register(floatCount, a))
stackBytes += cpu.pushSize();
++floatCount;
argOffset += sizeof(double);
}
else if(type.IsPrimitive()) {
if(!cpu.isIntArg64Register(intCount, a))
stackBytes += cpu.pushSize();
++intCount;
argOffset += type.GetSizeOnStackDWords() * sizeof(asDWORD);
}
else {
throw "Unsupported argument type in system call.";
}
}
if(pos == OP_Last) {
if(!cpu.isIntArg64Register(intCount, a))
stackBytes += cpu.pushSize();
}
--i; --a; --intCount; --floatCount;
cpu.call_cdecl_prep(stackBytes);
if(pos != OP_None && pos != OP_This) {
if(func->callConv >= ICC_THISCALL && func->auxiliary) {
if(pos == OP_First) {
if(cpu.isIntArg64Register(firstPos, firstPos)) {
Register reg = as<void*>(cpu.intArg64(firstPos, firstPos));
reg = func->auxiliary;
containingObj.set_regCode(reg);
}
else {
temp = func->auxiliary;
containingObj.set_regCode(temp);
}
}
else if(pos == OP_Last) {
if(cpu.isIntArg64Register(intCount+1, a+1)) {
Register reg = as<void*>(cpu.intArg64(intCount+1, a+1));
reg = func->auxiliary;
containingObj.set_regCode(reg);
}
else {
temp = func->auxiliary;
containingObj.set_regCode(temp);
cpu.push(temp);
}
}
}
else if(objPointer) {
if(checkNullObj) {
*objPointer &= *objPointer;
returnHandler(Zero, false);
}
if(pos == OP_First) {
if(cpu.isIntArg64Register(firstPos, firstPos)) {
Register reg = as<void*>(cpu.intArg64(firstPos, firstPos));
reg = as<void*>(*objPointer);
containingObj.set_regCode(reg);
}
else {
temp = *objPointer;
containingObj.set_regCode(temp);
}
}
else if(pos == OP_Last) {
if(cpu.isIntArg64Register(intCount+1, a+1)) {
Register reg = as<void*>(cpu.intArg64(intCount+1, a+1));
reg = as<void*>(*objPointer);
containingObj.set_regCode(reg);
}
else {
cpu.push(*objPointer);
if(isVirtual) {
temp = objPointer;
containingObj.set_regCode(temp);
}
}
}
}
else {
stackObject = true;
if(pos == OP_First) {
if(cpu.isIntArg64Register(firstPos, firstPos)) {
Register reg = as<void*>(cpu.intArg64(firstPos, firstPos));
reg = as<void*>(*esi);
if(checkNullObj) {
reg &= reg;
returnHandler(Zero, false);
}
reg += func->baseOffset;
containingObj.set_regCode(reg);
}
else {
temp = as<void*>(*esi);
if(checkNullObj) {
temp &= temp;
returnHandler(Zero, false);
}
temp += func->baseOffset;
containingObj.set_regCode(temp);
}
}
else if(pos == OP_Last) {
if(cpu.isIntArg64Register(intCount+1, a+1)) {
Register reg = as<void*>(cpu.intArg64(intCount+1, a+1));
reg = as<void*>(*esi);
if(checkNullObj) {
reg &= reg;
returnHandler(Zero, false);
}
reg += func->baseOffset;
containingObj.set_regCode(reg);
}
else {
temp = as<void*>(*esi);
if(checkNullObj) {
temp &= temp;
returnHandler(Zero, false);
}
temp += func->baseOffset;
cpu.push(temp);
containingObj.set_regCode(temp);
}
}
argOffset += sizeof(void*);
}
}
auto Arg = [&](Register* reg, bool dword) {
if(dword)
argOffset -= sizeof(asDWORD);
else
argOffset -= sizeof(asQWORD);
if(reg) {
if(dword)
as<asDWORD>(*reg) = as<asDWORD>(*esi+argOffset);
else
as<asQWORD>(*reg) = as<asQWORD>(*esi+argOffset);
}
else {
if(dword)
cpu.push(as<asDWORD>(*esi+argOffset));
else
cpu.push(as<asQWORD>(*esi+argOffset));
}
};
auto IntArg = [&](bool dword) {
if(cpu.isIntArg64Register(intCount, a)) {
Register arg = cpu.intArg64(intCount, a);
Arg(&arg, dword);
}
else
Arg(0, dword);
--intCount;
};
auto FloatArg = [&](bool dword) {
if(cpu.isFloatArg64Register(floatCount, a)) {
Register arg = cpu.floatArg64(floatCount, a);
Arg(&arg, dword);
}
else
Arg(0, dword);
--floatCount;
};
for(; i >= 0; --i, --a) {
auto& type = sFunc->parameterTypes[i];
if(type.GetTokenType() == ttQuestion) {
IntArg(true);
--a;
IntArg(false);
}
else if(type.IsReference() || type.IsObjectHandle()) {
IntArg(false);
}
else if(type.IsFloatType()) {
FloatArg(true);
}
else if(type.IsDoubleType()) {
FloatArg(false);
}
else if(type.IsPrimitive()) {
IntArg(type.GetSizeOnStackDWords() == 1);
}
}
if(pos == OP_First && !cpu.isIntArg64Register(firstPos, firstPos))
cpu.push(temp);
if(retPointer && !cpu.isIntArg64Register(0, 0) && func->hostReturnInMemory)
cpu.push(pax);
#ifdef _MSC_VER
stackBytes += 32;
esp -= 32;
#endif
if(isVirtual) {
//Look up pointer from vftable
if(containingObj.code == EAX)
throw "Virtual function resolver doesn't know object register.";
as<void*>(pax) = as<void*>(*containingObj);
#ifdef __GNUC__
unsigned offset = (unsigned)((size_t)func->func) >> 3;
offset *= sizeof(void*);
as<void*>(pax) += offset;
as<void*>(pax) = as<void*>(*pax);
#endif
#ifdef _MSC_VER
unsigned offset = (unsigned)((size_t)func->func) >> 2;
offset *= sizeof(void*);
as<void*>(pax) += offset;
as<void*>(pax) = as<void*>(*pax);
#endif
cpu.call(pax);
}
else {
cpu.call((void*)func->func);
}
cpu.call_cdecl_end(stackBytes, retOnStack);
size_t addParams = 0;
if(retPointer)
addParams += sizeof(void*);
if(stackObject)
addParams += sizeof(void*);
if(func->paramSize > 0 || addParams > 0)
esi += func->paramSize * sizeof(asDWORD) + (unsigned)addParams;
if(sFunc->returnType.IsObject() && !sFunc->returnType.IsReference()) {
if(sFunc->returnType.IsObjectHandle()) {
Register ret = as<void*>(cpu.intReturn64());
as<void*>(*ebp + offsetof(asSVMRegisters,objectRegister)) = ret;
//Add reference for returned auto handle
if(func->returnAutoHandle) {
ret &= ret;
auto noGrab = cpu.prep_short_jump(Zero);
int addref = sFunc->returnType.GetBehaviour()->addref;
asCScriptFunction* addrefFunc = (asCScriptFunction*)sFunc->GetEngine()->GetFunctionById(addref);
cpu.call_stdcall((void*)engineCallMethod, "prp", sFunc->GetEngine(), &ret, addrefFunc);
cpu.end_short_jump(noGrab);
}
}
else {
//Recover ret pointer
if(acceptReturn) {
temp = as<void*>(*esp + local::retPointer);
//Store value
if(!func->hostReturnInMemory) {
if(func->hostReturnFloat) {
if(func->hostReturnSize == 1) {
as<float>(*temp) = as<float>(xmm0);
}
else if(func->hostReturnSize == 2) {
as<double>(*temp) = as<double>(xmm0);
}
else if(func->hostReturnSize == 3) {
as<double>(*temp) = as<double>(xmm0);
temp += 8;
as<float>(*temp) = as<float>(xmm1);
}
else if(func->hostReturnSize == 4) {
as<double>(*temp) = as<double>(xmm0);
temp += 8;
as<double>(*temp) = as<double>(xmm1);
}
else {
throw "Not supported.";
}
}
else {
if(func->hostReturnSize == 1) {
as<asDWORD>(*temp) = as<asDWORD>(eax);
}
else if(func->hostReturnSize == 2) {
as<asQWORD>(*temp) = as<asQWORD>(eax);
}
else if(func->hostReturnSize == 3) {
as<asQWORD>(*temp) = as<asQWORD>(eax);
temp += 8;
as<asDWORD>(*temp) = as<asDWORD>(edx);
}
else if(func->hostReturnSize == 4) {
as<asQWORD>(*temp) = as<asQWORD>(eax);
temp += 8;
as<asQWORD>(*temp) = as<asQWORD>(edx);
}
else {
throw "Not supported.";
}
}
}
//Technically need to clear the objectRegister
//However, anything that tries to read this when it isn't valid is making a mistake
//as<void*>(*ebp + offsetof(asSVMRegisters,objectRegister)) = nullptr;
int destruct = sFunc->returnType.GetBehaviour()->destruct;
if(destruct > 0) {
asCScriptFunction* destructFunc = (asCScriptFunction*)sFunc->GetEngine()->GetFunctionById(destruct);
Register arg0 = as<void*>(cpu.intArg64(0, 0));
arg0 = as<void*>(*ebp+offsetof(asSVMRegisters,ctx));
eax = as<int>(*arg0+offsetof(asCContext,m_status));
eax == (int)asEXECUTION_EXCEPTION;
auto noError = cpu.prep_short_jump(NotEqual);
cpu.call_stdcall((void*)engineCallMethod, "prp", sFunc->GetEngine(), &temp, destructFunc);
cpu.end_short_jump(noError);
}
}
else if(sFunc->returnType.GetBehaviour()->destruct > 0) {
throw "Destructible returns not permitted here."; //Reference counting and deletion
}
}
}
else if(func->hostReturnSize > 0 && acceptReturn) {
if(func->hostReturnFloat) {
Register ret = cpu.floatReturn64();
if(func->hostReturnSize == 1) {
esp -= cpu.pushSize();
as<float>(*esp) = as<float>(ret);
as<float>(ebx) = as<float>(*esp);
esp += cpu.pushSize();
}
else if(func->hostReturnSize == 2) {
esp -= cpu.pushSize();
as<double>(*esp) = as<double>(ret);
cpu.pop(ebx);
}
else
throw "Not supported.";
}
else {
if(func->hostReturnSize == 1)
as<uint32_t>(ebx) = as<uint32_t>(cpu.intReturn64());
else if(func->hostReturnSize == 2)
as<uint64_t>(ebx) = as<uint64_t>(cpu.intReturn64());
else
throw "Not supported.";
}
}
call_exit(func);
}
#else
void SystemCall::call_getReturn(asSSystemFunctionInterface* func, asCScriptFunction* sFunc) {
Register eax(cpu,EAX), ecx(cpu,ECX), ebx(cpu,EBX), edx(cpu,EDX), ebp(cpu,EBP), esp(cpu,ESP);
if(sFunc->returnType.IsObject() && !sFunc->returnType.IsReference()) {
if(sFunc->returnType.IsObjectHandle()) {
if(!acceptReturn) {
if(func->returnAutoHandle)
throw "Auto handle returns not permitted here."; //Reference counting and deletion
return;
}
as<void*>(*ebp + offsetof(asSVMRegisters,objectRegister)) = eax;
//Add reference for returned auto handle
if(func->returnAutoHandle) {
eax &= eax;
auto noGrab = cpu.prep_short_jump(Zero);
int addref = sFunc->returnType.GetBehaviour()->addref;
asCScriptFunction* addrefFunc = (asCScriptFunction*)sFunc->GetEngine()->GetFunctionById(addref);
cpu.call_stdcall((void*)engineCallMethod, "prp", sFunc->GetEngine(), &eax, addrefFunc);
cpu.end_short_jump(noGrab);
}
}
else {
if(!acceptReturn) {
if(sFunc->DoesReturnOnStack() && sFunc->returnType.GetBehaviour()->destruct > 0)
throw "Destructible returns not permitted here."; //Reference counting and deletion
return;
}
//Recover ret pointer
ecx = as<void*>(*esp + local::retPointer);
//Store value
if(!func->hostReturnInMemory) {
if(func->hostReturnSize >= 1)
*ecx = eax;
if(func->hostReturnSize == 2) {
ecx += 4;
*ecx = edx;
}
}
if(sFunc->DoesReturnOnStack()) {
//Technically need to clear the objectRegister
//However, anything that tries to read this when it isn't valid is making a mistake
//as<void*>(*ebp + offsetof(asSVMRegisters,objectRegister)) = nullptr;
int destruct = sFunc->returnType.GetBehaviour()->destruct;
if(destruct > 0) {
asCScriptFunction* destructFunc = (asCScriptFunction*)sFunc->GetEngine()->GetFunctionById(destruct);
edx = as<void*>(*ebp+offsetof(asSVMRegisters,ctx));
eax = as<int>(*edx+offsetof(asCContext,m_status));
eax == (int)asEXECUTION_EXCEPTION;
auto noError = cpu.prep_short_jump(NotEqual);
cpu.call_stdcall((void*)engineCallMethod, "prp", sFunc->GetEngine(), &ecx, destructFunc);
cpu.end_short_jump(noError);
}
}
else {
//Store object pointer
as<void*>(*ebp + offsetof(asSVMRegisters,objectRegister)) = ecx;
}
}
}
else if(func->hostReturnSize > 0 && acceptReturn) {
if(func->hostReturnFloat) {
if(func->hostReturnSize == 1) {
esp -= cpu.pushSize();
fpu.store_float(*esp);
cpu.pop(ebx);
}
else {
fpu.store_double(*ebp+offsetof(asSVMRegisters,valueRegister));
ebx = *ebp+offsetof(asSVMRegisters,valueRegister);
}
}
else {
if(func->hostReturnSize == 1) {
ebx = eax;
}
else {
ebx = eax;
*ebp+offsetof(asSVMRegisters,valueRegister)+4 = edx;
}
}
}
}
void SystemCall::call_stdcall(asSSystemFunctionInterface* func, asCScriptFunction* sFunc) {
Register eax(cpu,EAX), ebx(cpu,EBX), edx(cpu,EDX), esp(cpu,ESP), esi(cpu,ESI);
Register cl(cpu,ECX,8);
call_entry(func,sFunc);
int firstArg = 0, lastArg = func->paramSize;
unsigned popCount = func->paramSize * sizeof(asDWORD);
//Copy out retPointer; will be pushed normally as an argument in correct order
if(sFunc->DoesReturnOnStack()) {
eax = as<void*>(*esi);
if(acceptReturn)
as<void*>(*esp + local::retPointer) = eax;
lastArg += 1; popCount += sizeof(asDWORD);
}
for(int i = lastArg-1; i >= firstArg; --i)
cpu.push(*esi+(i*sizeof(asDWORD)));
cpu.call((void*)func->func);
if(popCount > 0)
esi += popCount;
call_getReturn(func,sFunc);
call_exit(func);
}
void SystemCall::call_cdecl(asSSystemFunctionInterface* func, asCScriptFunction* sFunc) {
Register eax(cpu,EAX), ebx(cpu,EBX), edx(cpu,EDX), esp(cpu,ESP), esi(cpu,ESI);
Register cl(cpu,ECX,8);
call_entry(func,sFunc);
int firstArg = 0, lastArg = func->paramSize;
unsigned popCount = func->paramSize * sizeof(asDWORD);
//Copy out retPointer; will be pushed normally as an argument in correct order
if(sFunc->DoesReturnOnStack()) {
eax = as<void*>(*esi);
if(acceptReturn)
as<void*>(*esp + local::retPointer) = eax;
lastArg += 1; popCount += sizeof(asDWORD);
}
int argBytes = (lastArg-firstArg) * cpu.pushSize();
cpu.call_cdecl_prep(argBytes);
for(int i = lastArg-1; i >= firstArg; --i)
cpu.push(*esi+(i*sizeof(asDWORD)));
cpu.call((void*)func->func);
cpu.call_cdecl_end(argBytes, sFunc->DoesReturnOnStack());
if(popCount > 0)
esi += popCount;
call_getReturn(func,sFunc);
call_exit(func);
}
void SystemCall::call_cdecl_obj(asSSystemFunctionInterface* func, asCScriptFunction* sFunc, Register* objPointer, bool last) {
Register eax(cpu,EAX), ebx(cpu,EBX), ecx(cpu,ECX), edx(cpu,EDX), esp(cpu,ESP), esi(cpu,ESI);
Register cl(cpu,ECX,8);
call_entry(func,sFunc);
int firstArg = 0, lastArg = func->paramSize;
int argBytes = (lastArg-firstArg + 1) * cpu.pushSize();
unsigned popCount = func->paramSize * sizeof(asDWORD);
if(!objPointer) {
firstArg = 1;
lastArg += 1;
popCount += sizeof(void*);
}
//retPointer takes up an extra space
if(sFunc->DoesReturnOnStack()) {
argBytes += sizeof(asDWORD);
popCount += sizeof(asDWORD);
//Copy out retPointer
edx = as<void*>(*esi + (firstArg * sizeof(asDWORD)));
if(acceptReturn)
as<void*>(*esp + local::retPointer) = edx;
firstArg += 1; lastArg += 1;
}
cpu.call_cdecl_prep(argBytes);
if(objPointer) {
if(checkNullObj) {
*objPointer &= *objPointer;
auto j = cpu.prep_short_jump(NotZero);
call_error();
returnHandler(Jump, false);
cpu.end_short_jump(j);
}
if(last)
cpu.push(*objPointer);
}
else {
ecx = as<void*>(*esi);
if(checkNullObj) {
ecx &= ecx;
auto j = cpu.prep_short_jump(NotZero);
call_error();
returnHandler(Jump, false);
cpu.end_short_jump(j);
}
ecx += func->baseOffset;
if(last)
cpu.push(ecx);
}
for(int i = lastArg-1; i >= firstArg; --i)
cpu.push(*esi+(i*sizeof(asDWORD)));
if(!last) {
if(objPointer)
cpu.push(*objPointer);
else
cpu.push(ecx);
}
//retPointer is always last thing pushed
if(sFunc->DoesReturnOnStack())
cpu.push(edx);
cpu.call((void*)func->func);
cpu.call_cdecl_end(argBytes, sFunc->DoesReturnOnStack());
if(popCount > 0)
esi += popCount;
call_getReturn(func,sFunc);
call_exit(func);
}
void SystemCall::call_thiscall(asSSystemFunctionInterface* func, asCScriptFunction* sFunc, Register* objPointer) {
Register eax(cpu,EAX), ebx(cpu,EBX), ecx(cpu,ECX), edx(cpu,EDX), esp(cpu,ESP), esi(cpu,ESI);
Register cl(cpu,ECX,8);
call_entry(func,sFunc);
int firstArg = 0, lastArg = func->paramSize, argBytes;
bool popThis = false, returnPointer = false;
//Check object pointer for nulls
if(func->callConv < ICC_THISCALL || !func->auxiliary) {
if(objPointer) {
if(checkNullObj) {
*objPointer &= *objPointer;
auto j = cpu.prep_short_jump(NotZero);
call_error();
returnHandler(Jump, false);
cpu.end_short_jump(j);
}
}
else {
popThis = true;
ecx = as<void*>(*esi);
firstArg = 1; lastArg += 1;
if(checkNullObj) {
ecx &= ecx;
auto j = cpu.prep_short_jump(NotZero);
call_error();
returnHandler(Jump, false);
cpu.end_short_jump(j);
}
}
}
argBytes = (lastArg-firstArg) * cpu.pushSize();
//Get return pointer
if(sFunc->DoesReturnOnStack()) {
edx = as<void*>(*esi+(firstArg * sizeof(asDWORD)));
if(acceptReturn)
as<void*>(*esp + local::retPointer) = edx;
firstArg += 1; lastArg += 1;
argBytes += sizeof(asDWORD);
}
cpu.call_thiscall_prep(argBytes);
for(int i = lastArg-1; i >= firstArg; --i)
cpu.push(*esi+(i*sizeof(asDWORD)));
if(!sFunc->DoesReturnOnStack()) {
if(func->callConv >= ICC_THISCALL && func->auxiliary) {
ecx = func->auxiliary;
cpu.call_thiscall_this(ecx);
}
else if(objPointer) {
cpu.call_thiscall_this(*objPointer);
}
else {
ecx = *esi;
ecx += func->baseOffset;
cpu.call_thiscall_this(ecx);
}
}
else {
returnPointer = true;
if(func->callConv >= ICC_THISCALL && func->auxiliary) {
ecx = func->auxiliary;
cpu.call_thiscall_this_mem(ecx, edx);
}
else if(objPointer) {
cpu.call_thiscall_this_mem(*objPointer, edx);
}
else {
ecx = *esi;
ecx += func->baseOffset;
cpu.call_thiscall_this_mem(ecx, edx);
}
}
cpu.call((void*)func->func);
cpu.call_thiscall_end(argBytes, returnPointer);
unsigned popCount = func->paramSize * sizeof(asDWORD);
if(popThis)
popCount += sizeof(void*);
if(sFunc->DoesReturnOnStack())
popCount += sizeof(void*);
if(popCount > 0)
esi += popCount;
call_getReturn(func,sFunc);
call_exit(func);
}
#endif
void SystemCall::call_generic(asCScriptFunction* func, Register* objPointer) {
//Copy the state to the vm so asCContext::CallGeneric works
unsigned pBits = sizeof(void*) * 8;
#ifdef JIT_64
Register esi(cpu,R13,pBits);
#else
Register esi(cpu,ESI,pBits);
Register pdx(cpu, EDX, pBits);
#endif
Register ebp(cpu,EBP), esp(cpu,ESP,pBits);
Register pax(cpu,EAX,pBits), ebx(cpu,EBX);
Register pcx(cpu, ECX, pBits);
#ifndef JIT_64
//If we are not accepting returns, we have to save the value register as the call may change the register
if(!acceptReturn) {
pax = as<int>(*ebp + offsetof(asSVMRegisters,valueRegister) + 4);
as<int>(*esp + local::regCopy) = pax;
}
#endif
call_entry(func->sysFuncIntf, func);
//Trigger generic call on the context
#ifdef JIT_64
Register arg0 = as<void*>(cpu.intArg64(0, 0));
Register arg1 = as<void*>(cpu.intArg64(1, 1));
Register arg2 = as<void*>(cpu.intArg64(2, 2));
#else
Register arg0 = pcx;
Register arg1 = pdx;
Register arg2 = pax;
#endif
if(objPointer)
arg2 = as<void*>(*objPointer);
else
arg2 ^= arg2;
arg0 = as<void*>(*ebp + offsetof(asSVMRegisters,ctx));
as<int>(arg1) = func->id;
//TODO: Implement in msvc 32 bit
//unsigned sb = cpu.call_thiscall_args(&arg0, "rr", &arg1, &arg2);
//cpu.call((void*)&asCContext::CallGeneric);
//cpu.call_cdecl_end(sb);
//Pop the returned amount of dwords from the stack
esi.copy_address(*esi+pax*4);
if(acceptReturn && (!func->returnType.IsObject() || func->returnType.IsReference())) {
#ifdef JIT_64
as<asQWORD>(ebx) = as<asQWORD>(*ebp + offsetof(asSVMRegisters,valueRegister));
#else
ebx = *ebp + offsetof(asSVMRegisters,valueRegister);
#endif
}
else {
#ifndef JIT_64
pax = as<int>(*esp + local::regCopy);
as<int>(*ebp + offsetof(asSVMRegisters,valueRegister) + 4) = pax;
#endif
}
call_exit(func->sysFuncIntf);
}
void SystemCall::call_viaAS(asCScriptFunction* func, Register* objPointer) {
if(isSimple && objPointer) {
call_simple(*objPointer, func);
return;
}
unsigned pBits = sizeof(void*) * 8;
#ifdef JIT_64
Register esi(cpu,R13,pBits);
#else
Register esi(cpu,ESI,pBits);
#endif
Register ebp(cpu,EBP,pBits), pax(cpu,EAX,pBits), esp(cpu,ESP,pBits), ebx(cpu,EBX);
Register cl(cpu,ECX,8);
#ifndef JIT_64
//If we are not accepting returns, we have to save the value register or AngelScript will change it regardless of the return type
if(!acceptReturn) {
pax = as<int>(*ebp + offsetof(asSVMRegisters,valueRegister) + 4);
as<int>(*esp + local::regCopy) = pax;
}
#endif
if(objPointer) {
//Push the object pointer onto the script stack, the function will pop it
esi -= sizeof(void*);
as<void*>(*esi) = as<void*>(*objPointer);
}
//Copy state to VM state in case the call inspects the context
call_entry(func->sysFuncIntf,func);
MemAddress ctxPtr(as<void*>(*ebp + offsetof(asSVMRegisters,ctx)));
cpu.call_cdecl((void*)CallSystemFunction,"cm",func->GetId(),&ctxPtr);
//Pop the returned amount of dwords from the stack
esi.copy_address(*esi+pax*4);
//Check that there is a return in the valueRegister
bool isGeneric = func->sysFuncIntf->callConv == ICC_GENERIC_FUNC || func->sysFuncIntf->callConv == ICC_GENERIC_FUNC_RETURNINMEM
|| func->sysFuncIntf->callConv == ICC_GENERIC_METHOD || func->sysFuncIntf->callConv == ICC_GENERIC_METHOD_RETURNINMEM;
if(acceptReturn) {
if(((func->sysFuncIntf->hostReturnSize >= 1 && !func->sysFuncIntf->hostReturnInMemory) || isGeneric)
&& !(func->returnType.IsObject() && !func->returnType.IsReference()))
{
#ifdef JIT_64
as<asQWORD>(ebx) = as<asQWORD>(*ebp + offsetof(asSVMRegisters,valueRegister));
#else
ebx = *ebp + offsetof(asSVMRegisters,valueRegister);
#endif
}
}
else {
#ifndef JIT_64
pax = as<int>(*esp + local::regCopy);
as<int>(*ebp + offsetof(asSVMRegisters,valueRegister) + 4) = pax;
#endif
}
call_exit(func->sysFuncIntf);
}
void stdcall engineSimpleMethod(asCScriptEngine* engine, void* obj, asSSystemFunctionInterface* i, asCScriptFunction* func) {
engine->CallObjectMethod(obj, i, func);
}
void SystemCall::call_simple(Register& objPointer, asCScriptFunction* func) {
unsigned pBits = sizeof(void*) * 8;
#ifdef JIT_64
Register esi(cpu,R13,pBits);
#else
Register esi(cpu,ESI,pBits);
#endif
Register ebp(cpu,EBP);
as<void*>(*ebp + offsetof(asSVMRegisters,programPointer)) = pOp;
as<void*>(*ebp + offsetof(asSVMRegisters,stackPointer)) = esi;
auto* sys = func->sysFuncIntf;
cpu.call_stdcall((void*)engineSimpleMethod,"prpp",
(asCScriptEngine*)func->GetEngine(),
&objPointer, sys, func);
}
void stdcall returnScriptFunction(asCContext* ctx) {
// Return if this was the first function, or a nested execution
if( ctx->m_callStack.GetLength() == 0 ||
ctx->m_callStack[ctx->m_callStack.GetLength() - 9] == 0 )
{
ctx->m_status = asEXECUTION_FINISHED;
return;
}
ctx->PopCallState();
}