/** * vim: set ts=4 : * ============================================================================= * SourceMod BinTools Extension * Copyright (C) 2004-2017 AlliedModders LLC. All rights reserved. * ============================================================================= * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 3.0, as published by the * Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . * * As a special exception, AlliedModders LLC gives you permission to link the * code of this program (as well as its derivative works) to "Half-Life 2," the * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software * by the Valve Corporation. You must obey the GNU General Public License in * all respects for all other code used. Additionally, AlliedModders LLC grants * this exception to all derivative works. AlliedModders LLC defines further * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), * or . */ #include #include "extension.h" #include #include #include "x64_macros.h" #include "jit_compile.h" #if defined PLATFORM_WINDOWS jit_uint32_t g_StackUsage = 32; // Shadow space #else jit_uint32_t g_StackUsage = 0; #endif jit_uint32_t g_StackAlign = 0; jit_uint32_t g_RegDecoder = 0; jit_uint32_t g_FloatRegDecoder = 0; jit_uint32_t g_PODCount = 0; jit_uint32_t g_FloatCount = 0; jit_uint32_t g_ParamCount = 0; jit_uint32_t g_StackOffset = 0; jit_uint8_t g_ThisPtrReg; const jit_uint8_t STACK_PARAM = 16; const jit_uint8_t INVALID_REG = 255; inline jit_uint8_t NextScratchReg() { switch (g_RegDecoder++ % 3) { case 0: return kREG_RAX; case 1: return kREG_R10; case 2: return kREG_R11; default: return INVALID_REG; } } #ifdef PLATFORM_POSIX const int MAX_CLASSES = 2; const int INT_REG_MAX = 6; inline jit_uint8_t NextPODReg(size_t size) { switch (g_PODCount++) { case 0: return kREG_RDI; case 1: return kREG_RSI; case 2: return kREG_RDX; case 3: return kREG_RCX; case 4: return kREG_R8; case 5: if (size == 16) { g_PODCount--; return STACK_PARAM; } return kREG_R9; default: return STACK_PARAM; } } const int FLOAT_REG_MAX = 8; inline jit_uint8_t NextFloatReg(size_t size) { switch (g_FloatCount++) { case 0: return kREG_XMM0; case 1: return kREG_XMM1; case 2: return kREG_XMM2; case 3: return kREG_XMM3; case 4: return kREG_XMM4; case 5: return kREG_XMM5; case 6: return kREG_XMM6; case 7: if (size == 16) { g_FloatCount--; return STACK_PARAM; } return kREG_XMM7; default: return STACK_PARAM; } } inline jit_uint8_t NextScratchFloatReg() { switch (g_FloatRegDecoder++ % 8) { case 0: return kREG_XMM8; case 1: return kREG_XMM9; case 2: return kREG_XMM10; case 3: return kREG_XMM11; case 4: return kREG_XMM12; case 5: return kREG_XMM13; case 6: return kREG_XMM14; case 7: return kREG_XMM15; default: return INVALID_REG; } } #elif defined PLATFORM_WINDOWS const int NUM_ARG_REGS = 4; inline jit_uint8_t NextPODReg(size_t size) { switch (g_ParamCount++) { case 0: return kREG_RCX; case 1: return kREG_RDX; case 2: return kREG_R8; case 3: return kREG_R9; default: return STACK_PARAM; } } inline jit_uint8_t NextFloatReg(size_t size) { switch (g_ParamCount++) { case 0: return kREG_XMM0; case 1: return kREG_XMM1; case 2: return kREG_XMM2; case 3: return kREG_XMM3; default: return STACK_PARAM; } } inline jit_uint8_t NextScratchFloatReg() { switch (g_FloatRegDecoder++ % 2) { case 0: return kREG_XMM4; case 1: return kREG_XMM5; default: return INVALID_REG; } } #endif /******************** * Assembly Opcodes * ********************/ inline void Write_Execution_Prologue(JitWriter *jit, bool is_void, bool has_params) { //push rbp //mov rbp, rsp //if !is_void // push r14 // mov r14, rsi | Windows: mov r14, rdx ; 2nd param (retbuf) //if has_params // push rbx // mov rbx, rdi | Windows: mov rbx, rcx ; 1st param (param stack) //push r15 //mov r15, rsp //and rsp, 0xFFFFFFF0 //sub rsp, X64_Push_Reg(jit, kREG_RBP); X64_Mov_Reg_Rm(jit, kREG_RBP, kREG_RSP, MOD_REG); if (!is_void) { X64_Push_Reg(jit, kREG_R14); #ifdef PLATFORM_POSIX X64_Mov_Reg_Rm(jit, kREG_R14, kREG_RSI, MOD_REG); #elif defined PLATFORM_WINDOWS X64_Mov_Reg_Rm(jit, kREG_R14, kREG_RDX, MOD_REG); #endif } if (has_params) { X64_Push_Reg(jit, kREG_RBX); #ifdef PLATFORM_POSIX X64_Mov_Reg_Rm(jit, kREG_RBX, kREG_RDI, MOD_REG); #elif defined PLATFORM_WINDOWS X64_Mov_Reg_Rm(jit, kREG_RBX, kREG_RCX, MOD_REG); #endif } X64_Push_Reg(jit, kREG_R15); X64_Mov_Reg_Rm(jit, kREG_R15, kREG_RSP, MOD_REG); X64_And_Rm_Imm8(jit, kREG_RSP, MOD_REG, -16); if (!jit->outbase) { /* Alloc this instruction before knowing the real stack usage */ X64_Sub_Rm_Imm32(jit, kREG_RSP, 1337, MOD_REG); } else { if (g_StackAlign) X64_Sub_Rm_Imm32(jit, kREG_RSP, g_StackAlign, MOD_REG); } } inline void Write_Function_Epilogue(JitWriter *jit, bool is_void, bool has_params) { //mov rsp, r15 //pop r15 //if has_params // pop rbx //if !is_void // pop r14 //mov rsp, rbp //pop rbp //ret X64_Mov_Reg_Rm(jit, kREG_RSP, kREG_R15, MOD_REG); X64_Pop_Reg(jit, kREG_R15); if (has_params) X64_Pop_Reg(jit, kREG_RBX); if (!is_void) X64_Pop_Reg(jit, kREG_R14); X64_Mov_Reg_Rm(jit, kREG_RSP, kREG_RBP, MOD_REG); X64_Pop_Reg(jit, kREG_RBP); X64_Return(jit); } inline jit_uint8_t Write_PushPOD(JitWriter *jit, const SourceHook::PassInfo *info, unsigned int offset) { bool needStack = false; jit_uint8_t reg = NextPODReg(info->size); jit_uint8_t reg2; if (reg == STACK_PARAM) { reg = NextScratchReg(); needStack = true; } if (info->flags & PASSFLAG_BYVAL) { switch (info->size) { case 1: { //movzx reg, BYTE PTR [ebx+] if (!offset) X64_Movzx_Reg64_Rm8(jit, reg, kREG_RBX, MOD_MEM_REG); else if (offset < SCHAR_MAX) X64_Movzx_Reg64_Rm8_Disp8(jit, reg, kREG_RBX, (jit_int8_t)offset); else X64_Movzx_Reg64_Rm8_Disp32(jit, reg, kREG_RBX, offset); break; } case 2: { //movzx reg, WORD PTR [ebx+] if (!offset) X64_Movzx_Reg64_Rm16(jit, reg, kREG_RBX, MOD_MEM_REG); else if (offset < SCHAR_MAX) X64_Movzx_Reg64_Rm16_Disp8(jit, reg, kREG_RBX, (jit_int8_t)offset); else X64_Movzx_Reg64_Rm16_Disp32(jit, reg, kREG_RBX, offset); break; } case 4: { //mov reg, DWORD PTR [ebx+] if (!offset) X64_Mov_Reg32_Rm(jit, reg, kREG_RBX, MOD_MEM_REG); else if (offset < SCHAR_MAX) X64_Mov_Reg32_Rm_Disp8(jit, reg, kREG_EBX, (jit_int8_t)offset); else X64_Mov_Reg32_Rm_Disp32(jit, reg, kREG_RBX, offset); break; } case 8: { //mov reg, DWORD PTR [ebx+] if (!offset) X64_Mov_Reg_Rm(jit, reg, kREG_RBX, MOD_MEM_REG); else if (offset < SCHAR_MAX) X64_Mov_Reg_Rm_Disp8(jit, reg, kREG_EBX, (jit_int8_t)offset); else X64_Mov_Reg_Rm_Disp32(jit, reg, kREG_RBX, offset); break; } case 16: { //mov reg, DWORD PTR [ebx+] //mov reg2, DWORD PTR [ebx++8] reg2 = needStack ? NextScratchReg() : NextPODReg(8); if (!offset) X64_Mov_Reg_Rm(jit, reg, kREG_RBX, MOD_MEM_REG); else if (offset < SCHAR_MAX) X64_Mov_Reg_Rm_Disp8(jit, reg, kREG_RBX, (jit_int8_t)offset); else X64_Mov_Reg_Rm_Disp32(jit, reg, kREG_RBX, offset); if (offset+8 < SCHAR_MAX) X64_Mov_Reg_Rm_Disp8(jit, reg2, kREG_RBX, (jit_int8_t)(offset+8)); else X64_Mov_Reg_Rm_Disp32(jit, reg2, kREG_RBX, offset+8); break; } } } else if (info->flags & PASSFLAG_BYREF) { //lea reg, [ebx+] if (!offset) X64_Mov_Reg_Rm(jit, reg, kREG_RBX, MOD_REG); else if (offset < SCHAR_MAX) X64_Lea_DispRegImm8(jit, reg, kREG_RBX, (jit_int8_t)offset); else X64_Lea_DispRegImm32(jit, reg, kREG_RBX, offset); } if (needStack) { // Move register value onto stack //if g_StackUsage == 0 // mov [rsp], reg //else // mov [rsp+], reg //if info->size == 16 // mov [rsp++8], reg2 if (g_StackUsage == 0) X64_Mov_RmRSP_Reg(jit, reg); else if (g_StackUsage < SCHAR_MAX) X64_Mov_RmRSP_Disp8_Reg(jit, reg, (jit_int8_t)g_StackUsage); else X64_Mov_RmRSP_Disp32_Reg(jit, reg, g_StackUsage); if (info->size == 16) { if (g_StackUsage + 8 < SCHAR_MAX) X64_Mov_RmRSP_Disp8_Reg(jit, reg2, (jit_int8_t)g_StackUsage + 8); else X64_Mov_RmRSP_Disp32_Reg(jit, reg2, (jit_int8_t)g_StackUsage + 8); g_StackUsage += 16; } else { g_StackUsage += 8; } } return reg; } inline void Write_PushFloat(JitWriter *jit, const SourceHook::PassInfo *info, unsigned int offset, uint8_t *floatRegs) { bool needStack = false; jit_uint8_t floatReg = NextFloatReg(info->size); jit_uint8_t floatReg2; if (floatReg == STACK_PARAM) { floatReg = NextScratchFloatReg(); needStack = true; } if (info->flags & PASSFLAG_BYVAL) { switch (info->size) { case 4: { //if offset % 16 == 0 // movaps floatReg, [ebx+] //else // movups floatReg, [ebx+] if (!offset) { X64_Movaps_Rm(jit, floatReg, kREG_EBX); } else if (offset < SCHAR_MAX) { if (offset % 16 == 0) X64_Movaps_Rm_Disp8(jit, floatReg, kREG_EBX, (jit_int8_t)offset); else X64_Movups_Rm_Disp8(jit, floatReg, kREG_EBX, (jit_int8_t)offset); } else { if (offset % 16 == 0) X64_Movaps_Rm_Disp32(jit, floatReg, kREG_EBX, offset); else X64_Movups_Rm_Disp32(jit, floatReg, kREG_EBX, offset); } break; } case 8: { //if offset % 16 == 0 // movapd floatReg, [ebx+] //else // movupd floatReg, [ebx+] if (!offset) { X64_Movapd_Rm(jit, floatReg, kREG_EBX); } else if (offset < SCHAR_MAX) { if (offset % 16 == 0) X64_Movapd_Rm_Disp8(jit, floatReg, kREG_EBX, (jit_int8_t)offset); else X64_Movupd_Rm_Disp8(jit, floatReg, kREG_EBX, (jit_int8_t)offset); } else { if (offset % 16 == 0) X64_Movapd_Rm_Disp32(jit, floatReg, kREG_EBX, offset); else X64_Movupd_Rm_Disp32(jit, floatReg, kREG_EBX, offset); } break; } case 16: //if offset % 16 == 0 // movaps floatReg, [ebx+] //else // movads floatReg, [ebx+] //if (offset+8) % 16 == 0 // movaps floatReg2, [ebx++8] //else // movads floatReg2, [ebx++8] floatReg2 = needStack ? NextScratchFloatReg() : NextFloatReg(8); if (!offset) { X64_Movaps_Rm(jit, floatReg, kREG_EBX); X64_Movups_Rm_Disp8(jit, floatReg2, kREG_EBX, offset+8); } else if (offset < SCHAR_MAX) { if (offset % 16 == 0) X64_Movaps_Rm_Disp8(jit, floatReg, kREG_EBX, (jit_int8_t)offset); else X64_Movups_Rm_Disp8(jit, floatReg, kREG_EBX, (jit_int8_t)offset); if ((offset + 8) % 16 == 0) X64_Movaps_Rm_Disp8(jit, floatReg2, kREG_EBX, (jit_int8_t)offset+8); else X64_Movups_Rm_Disp8(jit, floatReg2, kREG_EBX, (jit_int8_t)offset+8); } else { if (offset % 16 == 0) X64_Movaps_Rm_Disp32(jit, floatReg, kREG_EBX, offset); else X64_Movups_Rm_Disp32(jit, floatReg, kREG_EBX, offset); if ((offset + 8) % 16 == 0) X64_Movaps_Rm_Disp32(jit, floatReg2, kREG_EBX, offset+8); else X64_Movups_Rm_Disp32(jit, floatReg2, kREG_EBX, offset+8); } } } else if (info->flags & PASSFLAG_BYREF) { //lea reg, [ebx+] Write_PushPOD(jit, info, offset); return; } if (needStack) { // Move register value onto stack //if g_StackUsage == 0 // movaps [rsp], floatReg //else // movaps [rsp+], floatReg //if info->size == 16 // movaps [rsp++8], floatReg2 if (g_StackUsage == 0) { X64_Movaps_Rm_Reg(jit, kREG_RSP, floatReg); } else if (g_StackUsage < SCHAR_MAX) { if (g_StackUsage % 16 == 0) X64_Movaps_Rm_Disp8_Reg(jit, kREG_RSP, floatReg, (jit_int8_t)g_StackUsage); else X64_Movups_Rm_Disp8_Reg(jit, kREG_RSP, floatReg, (jit_int8_t)g_StackUsage); } else { if (g_StackUsage % 16 == 0) X64_Movaps_Rm_Disp32_Reg(jit, kREG_RSP, floatReg, g_StackUsage); else X64_Movups_Rm_Disp32_Reg(jit, kREG_RSP, floatReg, g_StackUsage); } if (info->size == 16) { if (g_StackUsage + 8 < SCHAR_MAX) { if ((g_StackUsage + 8) % 16 == 0) X64_Movaps_Rm_Disp8_Reg(jit, kREG_RSP, floatReg2, (jit_int8_t)g_StackUsage+8); else X64_Movups_Rm_Disp8_Reg(jit, kREG_RSP, floatReg2, (jit_int8_t)g_StackUsage+8); } else { if ((g_StackUsage + 8) % 16 == 0) X64_Movaps_Rm_Disp32_Reg(jit, kREG_RSP, floatReg, g_StackUsage+8); else X64_Movups_Rm_Disp32_Reg(jit, kREG_RSP, floatReg, g_StackUsage+8); } g_StackUsage += 16; } else { g_StackUsage += 8; } } if (floatRegs) { floatRegs[0] = floatReg; floatRegs[1] = info->size == 16 ? floatReg2 : INVALID_REG; } } #ifdef PLATFORM_WINDOWS inline uint8_t MapFloatToIntReg(uint8_t floatReg) { switch (floatReg) { case kREG_XMM0: return kREG_RCX; case kREG_XMM1: return kREG_RDX; case kREG_XMM2: return kREG_R8; case kREG_XMM3: return kREG_R9; default: return INVALID_REG; } } inline void Write_VarArgFloatCopy(JitWriter *jit, const SourceHook::PassInfo *info, uint8_t *floatRegs) { if (!floatRegs || floatRegs[0] == STACK_PARAM) return; uint8_t intReg1 = MapFloatToIntReg(floatRegs[0]); switch (info->size) { case 4: //movd intReg1, floatReg[0] X64_Movd_Reg32_Xmm(jit, intReg1, floatRegs[0]); break; case 8: //movq intReg1, floatReg[0] X64_Movq_Reg_Xmm(jit, intReg1, floatRegs[0]); break; case 16: //movq intReg1, floatReg[0] //movq intReg2, floatReg[1] X64_Movq_Reg_Xmm(jit, intReg1, floatRegs[0]); if (floatRegs[1] != STACK_PARAM) { int intReg2 = MapFloatToIntReg(floatRegs[1]); X64_Movq_Reg_Xmm(jit, intReg2, floatRegs[1]); } break; } } #endif // PLATFORM_WINDOWS enum class ObjectClass { None, Integer, Pointer, // Special case of Integer SSE, SSEUp, X87, // TODO? Really only applies to long doubles which Source doesn't use X87Up, // TODO? X87Complex, // TODO? Memory }; inline ObjectClass ClassifyType(ObjectField field) { switch (field) { case ObjectField::Boolean: case ObjectField::Int8: case ObjectField::Int16: case ObjectField::Int32: case ObjectField::Int64: case ObjectField::Pointer: return ObjectClass::Integer; case ObjectField::Float: case ObjectField::Double: return ObjectClass::SSE; default: return ObjectClass::None; } } inline size_t TypeSize(ObjectField field) { switch (field) { case ObjectField::Boolean: case ObjectField::Int8: return 1; case ObjectField::Int16: return 2; case ObjectField::Int32: case ObjectField::Float: return 4; case ObjectField::Int64: case ObjectField::Pointer: case ObjectField::Double: return 8; default: assert(false); return 0; } } inline ObjectClass MergeClasses(ObjectClass class1, ObjectClass class2) { if (class1 == class2) return class1; else if (class1 == ObjectClass::None) return class2; else if (class2 == ObjectClass::None) return class1; else if (class1 == ObjectClass::Integer || class2 == ObjectClass::Integer) return ObjectClass::Integer; return ObjectClass::SSE; } inline int ClassifyObject(const PassInfo *info, ObjectClass *classes) { ObjectField *fields = info->fields; unsigned int numFields = info->numFields; int numWords = 1; if (info->size > 16 || info->flags & PASSFLAG_OUNALIGN) classes[0] = ObjectClass::Memory; else if (info->flags & (PASSFLAG_ODTOR|PASSFLAG_OCOPYCTOR)) classes[0] = ObjectClass::Pointer; else if (info->size > 8) classes[0] = ObjectClass::None; else if (numFields == 1 || numFields == 2) classes[0] = ClassifyType(fields[0]); else classes[0] = ObjectClass::Integer; if (classes[0] == ObjectClass::None) { numWords = int((info->size + 7) / 8); unsigned int j = 0; for (int i = 0; i < numWords; i++) { classes[i] = ObjectClass::None; size_t sizeSoFar = 0; for (int k = 0; j < numFields; j++, k++) { size_t sz = TypeSize(fields[j]); if (sizeSoFar + sz > 8) break; else sizeSoFar += sz; if (k == 0 && sizeSoFar == 8) { classes[i] = ClassifyType(fields[j++]); break; } else if (j + 1 >= numFields) { break; } const ObjectField &field1 = fields[j]; const ObjectField &field2 = fields[j + 1]; classes[i] = MergeClasses(ClassifyType(field1), ClassifyType(field2)); } } } return numWords; } inline void Write_PushObject(JitWriter *jit, const SourceHook::PassInfo *info, unsigned int offset, const PassInfo *smInfo) { if (info->flags & PASSFLAG_BYVAL) { #ifdef PLATFORM_POSIX ObjectClass classes[MAX_CLASSES]; int numWords = ClassifyObject(smInfo, classes); if (classes[0] == ObjectClass::Pointer) goto push_byref; int neededIntRegs = 0; int neededFloatRegs = 0; for (int i = 0; i < numWords; i++) { switch (classes[i]) { case ObjectClass::Integer: neededIntRegs++; break; case ObjectClass::SSE: neededFloatRegs++; break; default: assert(false); break; } } if (neededIntRegs + g_PODCount > INT_REG_MAX || neededFloatRegs + g_FloatCount > FLOAT_REG_MAX) classes[0] = ObjectClass::Memory; if (classes[0] != ObjectClass::Memory) { size_t sizeLeft = info->size; for (int i = 0; i < numWords; i++) { switch (classes[i]) { case ObjectClass::Integer: { SourceHook::PassInfo podInfo; podInfo.size = (sizeLeft > 8) ? 8 : sizeLeft; podInfo.type = SourceHook::PassInfo::PassType_Basic; podInfo.flags = SourceHook::PassInfo::PassFlag_ByVal; Write_PushPOD(jit, &podInfo, offset + (i * 8)); break; } case ObjectClass::SSE: { SourceHook::PassInfo floatInfo; floatInfo.size = (sizeLeft > 8) ? 8 : sizeLeft; floatInfo.type = SourceHook::PassInfo::PassType_Float; floatInfo.flags = SourceHook::PassInfo::PassFlag_ByVal; Write_PushFloat(jit, &floatInfo, offset + (i * 8), nullptr); break; } default: assert(false); break; } if (sizeLeft > 8) sizeLeft -= 8; } } return; #elif defined PLATFORM_WINDOWS if (info->size < 64 && (info->size & (info->size - 1)) == 0) goto push_byref; else { SourceHook::PassInfo podInfo; podInfo.size = info->size; podInfo.type = SourceHook::PassInfo::PassType_Basic; podInfo.flags = SourceHook::PassInfo::PassFlag_ByVal; Write_PushPOD(jit, &podInfo, offset); } return; #endif jit_uint32_t qwords = info->size >> 3; jit_uint32_t bytes = info->size & 0x7; //sub rsp, //cld //push rdi //push rsi //lea rdi, [rsp+16] //lea rsi, [rbx+] //if dwords // mov rcx, // rep movsq //if bytes // mov rcx, // rep movsb //pop rsi //pop rdi //if (info->size < SCHAR_MAX) //{ // X64_Sub_Rm_Imm8(jit, kREG_RSP, (jit_int8_t)info->size, MOD_REG); //} else { // X64_Sub_Rm_Imm32(jit, kREG_RSP, info->size, MOD_REG); ///} X64_Cld(jit); X64_Push_Reg(jit, kREG_RDI); X64_Push_Reg(jit, kREG_RSI); if (g_StackUsage + 16 < SCHAR_MAX) X64_Lea_Reg_DispRegMultImm8(jit, kREG_RDI, kREG_NOIDX, kREG_RSP, NOSCALE, g_StackUsage+16); else X64_Lea_Reg_DispRegMultImm32(jit, kREG_RDI, kREG_NOIDX, kREG_RSP, NOSCALE, g_StackUsage+16); if (!offset) X64_Mov_Reg_Rm(jit, kREG_RSI, kREG_RBX, MOD_REG); else if (offset < SCHAR_MAX) X64_Lea_DispRegImm8(jit, kREG_RSI, kREG_RBX, (jit_int8_t)offset); else X64_Lea_DispRegImm32(jit, kREG_RSI, kREG_RBX, offset); if (qwords) { X64_Mov_Reg_Imm32(jit, kREG_RCX, qwords); X64_Rep(jit); X64_Movsq(jit); } if (bytes) { X64_Mov_Reg_Imm32(jit, kREG_RCX, bytes); X64_Rep(jit); X64_Movsb(jit); } X64_Pop_Reg(jit, kREG_RSI); X64_Pop_Reg(jit, kREG_RDI); g_StackUsage += info->size; } else if (info->flags & PASSFLAG_BYREF) { push_byref: //lea reg, [ebx+] SourceHook::PassInfo podInfo; podInfo.size = sizeof(void *); podInfo.type = SourceHook::PassInfo::PassType_Basic; podInfo.flags = SourceHook::PassInfo::PassFlag_ByRef; Write_PushPOD(jit, &podInfo, offset); } } inline void Write_PushThisPtr(JitWriter *jit) { SourceHook::PassInfo podInfo; podInfo.size = 8; podInfo.type = SourceHook::PassInfo::PassType_Basic; podInfo.flags = SourceHook::PassInfo::PassFlag_ByVal; g_ThisPtrReg = Write_PushPOD(jit, &podInfo, 0); } inline void Write_PushRetBuffer(JitWriter *jit) { jit_uint8_t reg = NextPODReg(8); //mov reg, r14 X64_Mov_Reg_Rm(jit, reg, kREG_R14, MOD_REG); } inline void Write_CallFunction(JitWriter *jit, FuncAddrMethod method, CallWrapper *pWrapper) { if (method == FuncAddr_Direct) { int64_t diff = (intptr_t)pWrapper->GetCalleeAddr() - ((intptr_t)jit->outbase + jit->get_outputpos() + 5); int32_t upperBits = (diff >> 32); if (upperBits == 0 || upperBits == -1) { //call jitoffs_t call = X64_Call_Imm32(jit, 0); X64_Write_Jump32_Abs(jit, call, pWrapper->GetCalleeAddr()); } else { //mov rax, //call rax X64_Mov_Reg_Imm64(jit, kREG_RAX, (jit_int64_t)pWrapper->GetCalleeAddr()); X64_Call_Reg(jit, kREG_RAX); } } else if (method == FuncAddr_VTable) { //*(this + thisOffs + vtblOffs)[vtblIdx] // mov r10, [g_ThisPtrReg++] // mov r11, [r10+*8] // call r11 SourceHook::MemFuncInfo *funcInfo = pWrapper->GetMemFuncInfo(); jit_uint32_t total_offs = funcInfo->thisptroffs + funcInfo->vtbloffs; jit_uint32_t vfunc_pos = funcInfo->vtblindex * 8; //X64_Mov_Reg_Rm(jit, kREG_RAX, kREG_RBX, MOD_MEM_REG); if (total_offs < SCHAR_MAX) { X64_Mov_Reg_Rm_Disp8(jit, kREG_R10, g_ThisPtrReg, (jit_int8_t)total_offs); } else if (!total_offs) { X64_Mov_Reg_Rm(jit, kREG_R10, g_ThisPtrReg, MOD_MEM_REG); } else { X64_Mov_Reg_Rm_Disp32(jit, kREG_R10, g_ThisPtrReg, total_offs); } if (vfunc_pos < SCHAR_MAX) { X64_Mov_Reg_Rm_Disp8(jit, kREG_R11, kREG_R10, (jit_int8_t)vfunc_pos); } else if (!vfunc_pos) { X64_Mov_Reg_Rm(jit, kREG_R11, kREG_R10, MOD_MEM_REG); } else { X64_Mov_Reg_Rm_Disp32(jit, kREG_R11, kREG_R10, vfunc_pos); } X64_Call_Reg(jit, kREG_R11); } } inline void Write_VarArgFloatCount(JitWriter *jit) { //mov al, g_FloatCount X64_Mov_Reg8_Imm8(jit, kREG_RAX, (jit_int8_t)g_FloatCount); } inline void Write_RectifyStack(JitWriter *jit, jit_uint32_t value) { //add rsp, if (value < SCHAR_MAX) { X64_Add_Rm_Imm8(jit, kREG_RSP, (jit_int8_t)value, MOD_REG); } else { X64_Add_Rm_Imm32(jit, kREG_RSP, value, MOD_REG); } } inline void Write_MovRet2Buf(JitWriter *jit, const PassInfo *pRet, ObjectClass *classes, int numWords) { if (pRet->type == PassType_Float) { switch (pRet->size) { case 4: { //movups xmmword ptr [r14], xmm0 X64_Movups_Rm_Reg(jit, kREG_R14, kREG_XMM0); break; } case 8: { //movupd xmmword ptr [r14], xmm0 X64_Movupd_Rm_Reg(jit, kREG_R14, kREG_XMM0); break; } } return; } else if (pRet->type == PassType_Basic) { switch (pRet->size) { case 1: { //mov BYTE PTR [r14], al X64_Mov_Rm8_Reg8(jit, kREG_R14, kREG_RAX, MOD_MEM_REG); break; } case 2: { //mov WORD PTR [r14], ax X64_Mov_Rm16_Reg16(jit, kREG_R14, kREG_RAX, MOD_MEM_REG); break; } case 4: { //mov DWORD PTR [r14], rax X64_Mov_Rm32_Reg32(jit, kREG_R14, kREG_RAX, MOD_MEM_REG); break; } case 8: { //mov QWORD PTR [r14], rax X64_Mov_Rm_Reg(jit, kREG_R14, kREG_RAX, MOD_MEM_REG); break; } } } #ifdef PLATFORM_POSIX else { // Return value registers jit_uint8_t intRetReg = kREG_RAX; jit_uint8_t floatRetReg = kREG_XMM0; jit_int8_t offset = 0; assert(numWords <= 2); for (int i = 0; i < numWords; i++) { ObjectClass &cls = classes[i]; if (cls == ObjectClass::Integer) { //mov QWORD PTR [r14+offset], intRetReg ; rax or rdx if (!offset) X64_Mov_Rm_Reg(jit, kREG_R14, intRetReg, MOD_MEM_REG); else X64_Mov_Rm_Reg_Disp8(jit, kREG_R14, intRetReg, offset); intRetReg = kREG_RDX; } else if (cls == ObjectClass::SSE) { //movupd xmmword ptr [r14+offset], floatRetReg ; xmm0 or xmm1 if (!offset) X64_Movupd_Rm_Reg(jit, kREG_R14, floatRetReg); else X64_Movupd_Rm_Disp8_Reg(jit, kREG_R14, floatRetReg, offset); floatRetReg = kREG_XMM1; } offset += 8; } } #endif } /****************************** * Assembly Compiler Function * ******************************/ void *JIT_CallCompile(CallWrapper *pWrapper, FuncAddrMethod method) { JitWriter writer; JitWriter *jit = &writer; jit_uint32_t CodeSize = 0; bool Needs_Retbuf = false; CallConvention Convention = pWrapper->GetCallConvention(); jit_uint32_t ParamCount = pWrapper->GetParamCount(); const PassInfo *pRet = pWrapper->GetReturnInfo(); bool hasParams = (ParamCount || Convention == CallConv_ThisCall); #ifdef PLATFORM_POSIX ObjectClass classes[MAX_CLASSES]; int numWords; #endif g_StackUsage = 0; writer.outbase = NULL; writer.outptr = NULL; jit_rewind: /* Write function prologue */ Write_Execution_Prologue(jit, (pRet) ? false : true, hasParams); #if defined PLATFORM_WINDOWS /* This ptr is always first on Windows */ if (Convention == CallConv_ThisCall) { Write_PushThisPtr(jit); } #endif /* Skip the return buffer stuff if this is a void function */ if (!pRet) { goto skip_retbuffer; } if ((pRet->type == PassType_Object) && (pRet->flags & PASSFLAG_BYVAL)) { #ifdef PLATFORM_POSIX numWords = ClassifyObject(pRet, classes); if (classes[0] == ObjectClass::Memory || classes[0] == ObjectClass::Pointer) Needs_Retbuf = true; #elif defined PLATFORM_WINDOWS if (pRet->size > 8 || (pRet->size & (pRet->size - 1)) != 0 || (pRet->flags & PASSFLAG_ODTOR|PASSFLAG_OCTOR|PASSFLAG_OASSIGNOP)) Needs_Retbuf = true; #endif } /* Prepare the return buffer in case we are returning objects by value. */ if (Needs_Retbuf) { Write_PushRetBuffer(jit); } skip_retbuffer: #if defined PLATFORM_POSIX /* This ptr comes after retbuf ptr on Linux/macOS */ if (Convention == CallConv_ThisCall) { Write_PushThisPtr(jit); } #endif /* Write parameter push code */ for (jit_uint32_t i = 0; i < ParamCount; i++) { unsigned int offset = pWrapper->GetParamOffset(i); const SourceHook::PassInfo *info = pWrapper->GetSHParamInfo(i); assert(info != NULL); switch (info->type) { case SourceHook::PassInfo::PassType_Basic: { Write_PushPOD(jit, info, offset); break; } case SourceHook::PassInfo::PassType_Float: { #ifdef PLATFORM_WINDOWS if ((info->flags & PASSFLAG_BYVAL) && (pWrapper->GetFunctionFlags() & FNFLAG_VARARGS)) { uint8_t floatRegs[2]; Write_PushFloat(jit, info, offset, floatRegs); Write_VarArgFloatCopy(jit, info, floatRegs); } else #endif { Write_PushFloat(jit, info, offset, nullptr); } break; } case SourceHook::PassInfo::PassType_Object: { const PassEncode *paramInfo = pWrapper->GetParamInfo(i); Write_PushObject(jit, info, offset, ¶mInfo->info); break; } } } /* Write the calling code */ Write_CallFunction(jit, method, pWrapper); #ifdef PLATFORM_POSIX if (pWrapper->GetFunctionFlags() & FNFLAG_VARARGS) Write_VarArgFloatCount(jit); #endif /* Clean up the calling stack */ if (hasParams && g_StackUsage) Write_RectifyStack(jit, g_StackAlign); /* Copy the return type to the return buffer if the function is not void */ if (pRet && !Needs_Retbuf) { #ifdef PLATFORM_POSIX Write_MovRet2Buf(jit, pRet, classes, numWords); #elif defined PLATFORM_WINDOWS Write_MovRet2Buf(jit, pRet, nullptr, 0); #endif } /* Write Function Epilogue */ Write_Function_Epilogue(jit, (pRet) ? false : true, hasParams); if (writer.outbase == NULL) { CodeSize = writer.get_outputpos(); writer.outbase = (jitcode_t)g_SPEngine->AllocatePageMemory(CodeSize); g_SPEngine->SetReadWrite(writer.outbase); writer.outptr = writer.outbase; pWrapper->SetCodeBaseAddr(writer.outbase); g_StackAlign = (g_StackUsage) ? ((g_StackUsage & 0xFFFFFFF0) + 16) - g_StackUsage : 0; g_StackAlign = (g_StackAlign == 16) ? g_StackUsage : g_StackUsage + g_StackAlign; #ifdef PLATFORM_POSIX g_StackUsage = 0; #elif defined PLATFORM_WINDOWS g_StackUsage = 32; // Shadow space #endif g_RegDecoder = 0; g_FloatRegDecoder = 0; g_PODCount = 0; g_FloatCount = 0; g_ParamCount = 0; Needs_Retbuf = false; goto jit_rewind; } g_SPEngine->SetReadExecute(writer.outbase); return writer.outbase; }