#include "as_jit.h"
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <map>
#include <functional>
#include <cstdint>

#include "as_scriptfunction.h"
#include "as_objecttype.h"
#include "as_callfunc.h"
#include "as_scriptengine.h"
#include "as_scriptobject.h"
#include "as_texts.h"
#include "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;
        default: return false;
	}

	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;
    std::function<void(JumpType,bool)> returnHandler;
	asDWORD* const & pOp;
	unsigned flags;
	bool callIsSafe;
	bool checkNullObj;
	bool handleSuspend;
	bool acceptReturn;
	bool isSimple;


	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;
            default: 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;

                        fpu.load_double(*edi-offset(pNextOp,1)); //Optimize <Variable Double> <op>= <Constant Double>

						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;
                        default: 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;
                default: 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;
                default: 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;
				}
                }
            default: break;
			}
		}

		//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_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;
                if (beh->addref)
                {
                    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;
                if (beh->release)
                {
                    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;
            as<void*>(*ebp + offsetof(asSVMRegisters,objectType)) = sFunc->returnType.GetTypeInfo();

			//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;
            as<void*>(*ebp + offsetof(asSVMRegisters,objectType)) = sFunc->returnType.GetTypeInfo();

			//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;
                as<void*>(*ebp + offsetof(asSVMRegisters,objectType)) = sFunc->returnType.GetTypeInfo();
			}
		}
	}
	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();
}
