From: Aleksandr Shaurtaev <38426614+ashaurtaev@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:24:21 +0000 (+0300) Subject: [RISC-V] Add DynamicHelpers to riscv64 stubs (#94735) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055536~12 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=79b54260d06a7ce6d1b9b9b9807ed012c51a735b;p=platform%2Fupstream%2Fdotnet%2Fruntime.git [RISC-V] Add DynamicHelpers to riscv64 stubs (#94735) * Add DynamicHelpers to riscv64 stubs * Code review feedback * Apply suggestions from code review --------- Co-authored-by: Jan Kotas --- diff --git a/src/coreclr/jit/emitriscv64.h b/src/coreclr/jit/emitriscv64.h index ce207755..ea9149e 100644 --- a/src/coreclr/jit/emitriscv64.h +++ b/src/coreclr/jit/emitriscv64.h @@ -112,6 +112,12 @@ static bool isValidSimm20(ssize_t value) return -(((int)1) << 19) <= value && value < (((int)1) << 19); }; +// Returns true if 'value' is a legal unsigned immediate 20 bit encoding. +static bool isValidUimm20(ssize_t value) +{ + return (0 == (value >> 20)); +}; + // Returns true if 'value' is a legal signed immediate 21 bit encoding. static bool isValidSimm21(ssize_t value) { diff --git a/src/coreclr/vm/riscv64/cgencpu.h b/src/coreclr/vm/riscv64/cgencpu.h index 13b20a5..1a33214 100644 --- a/src/coreclr/vm/riscv64/cgencpu.h +++ b/src/coreclr/vm/riscv64/cgencpu.h @@ -355,7 +355,12 @@ public: static bool isValidSimm12(int value) { return -( ((int)1) << 11 ) <= value && value < ( ((int)1) << 11 ); } - + static bool isValidSimm13(int value) { + return -(((int)1) << 12) <= value && value < (((int)1) << 12); + } + static bool isValidUimm20(int value) { + return (0 == (value >> 20)); + } void EmitCallManagedMethod(MethodDesc *pMD, BOOL fTailCall); void EmitCallLabel(CodeLabel *target, BOOL fTailCall, BOOL fIndirect); diff --git a/src/coreclr/vm/riscv64/stubs.cpp b/src/coreclr/vm/riscv64/stubs.cpp index a308af6..af42aa8 100644 --- a/src/coreclr/vm/riscv64/stubs.cpp +++ b/src/coreclr/vm/riscv64/stubs.cpp @@ -1248,6 +1248,29 @@ static unsigned RTypeInstr(unsigned opcode, unsigned funct3, unsigned funct7, un return opcode | (rd << 7) | (funct3 << 12) | (rs1 << 15) | (rs2 << 20) | (funct7 << 25); } +static unsigned UTypeInstr(unsigned opcode, unsigned rd, int imm20) +{ + _ASSERTE(!(opcode >> 7)); + _ASSERTE(!(rd >> 5)); + _ASSERTE(StubLinkerCPU::isValidUimm20(imm20)); + return opcode | (rd << 7) | (imm20 << 12); +} + +static unsigned BTypeInstr(unsigned opcode, unsigned funct3, unsigned rs1, unsigned rs2, int imm13) +{ + _ASSERTE(!(opcode >> 7)); + _ASSERTE(!(funct3 >> 3)); + _ASSERTE(!(rs1 >> 5)); + _ASSERTE(!(rs2 >> 5)); + _ASSERTE(StubLinkerCPU::isValidSimm13(imm13)); + _ASSERTE(IS_ALIGNED(imm13, 2)); + int immLo1 = (imm13 >> 11) & 0x1; + int immLo4 = (imm13 >> 1) & 0xf; + int immHi6 = (imm13 >> 5) & 0x3f; + int immHi1 = (imm13 >> 12) & 0x1; + return opcode | (immLo4 << 8) | (funct3 << 12) | (rs1 << 15) | (rs2 << 20) | (immHi6 << 25) | (immLo1 << 7) | (immHi1 << 31); +} + void StubLinkerCPU::EmitLoad(IntReg dest, IntReg srcAddr, int offset) { Emit32(ITypeInstr(0x3, 0x3, dest, srcAddr, offset)); // ld @@ -1507,14 +1530,22 @@ void StubLinkerCPU::EmitCallManagedMethod(MethodDesc *pMD, BOOL fTailCall) // // Allocation of dynamic helpers // - #define DYNAMIC_HELPER_ALIGNMENT sizeof(TADDR) #define BEGIN_DYNAMIC_HELPER_EMIT(size) \ - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); -#define END_DYNAMIC_HELPER_EMIT() \ - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); + SIZE_T cb = size; \ + SIZE_T cbAligned = ALIGN_UP(cb, DYNAMIC_HELPER_ALIGNMENT); \ + BYTE * pStartRX = (BYTE *)(void*)pAllocator->GetDynamicHelpersHeap()->AllocAlignedMem(cbAligned, DYNAMIC_HELPER_ALIGNMENT); \ + ExecutableWriterHolder startWriterHolder(pStartRX, cbAligned); \ + BYTE * pStart = startWriterHolder.GetRW(); \ + size_t rxOffset = pStartRX - pStart; \ + BYTE * p = pStart; +#define END_DYNAMIC_HELPER_EMIT() \ + _ASSERTE(pStart + cb == p); \ + while (p < pStart + cbAligned) { *(DWORD*)p = 0xffffff0f/*badcode*/; p += 4; }\ + ClrFlushInstructionCache(pStart, cbAligned); \ + return (PCODE)pStart // Uses x8 as scratch register to store address of data label // After load x8 is increment to point to next data // only accepts positive offsets @@ -1525,68 +1556,450 @@ static void LoadRegPair(BYTE* p, int reg1, int reg2, UINT32 offset) PCODE DynamicHelpers::CreateHelper(LoaderAllocator * pAllocator, TADDR arg, PCODE target) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); - return NULL; + STANDARD_VM_CONTRACT; + + BEGIN_DYNAMIC_HELPER_EMIT(32); + + EmitHelperWithArg(p, rxOffset, pAllocator, arg, target); + + END_DYNAMIC_HELPER_EMIT(); } // Caller must ensure sufficient byte are allocated including padding (if applicable) void DynamicHelpers::EmitHelperWithArg(BYTE*& p, size_t rxOffset, LoaderAllocator * pAllocator, TADDR arg, PCODE target) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); + STANDARD_VM_CONTRACT; + + const IntReg RegR0 = 0, RegT0 = 5, RegA0 = 10; + + *(DWORD*)p = UTypeInstr(0x17, RegT0, 0);// auipc t0, 0 + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA0, RegT0, 16);// ld a0, 16(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegT0, RegT0, 24);;// ld t0, 24(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x67, 0, RegR0, RegT0, 0);// jalr zero, 0(t0) + p += 4; + + // label: + // arg + *(TADDR*)p = arg; + p += 8; + // target + *(PCODE*)p = target; + p += 8; } PCODE DynamicHelpers::CreateHelperWithArg(LoaderAllocator * pAllocator, TADDR arg, PCODE target) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); - return NULL; + STANDARD_VM_CONTRACT; + + BEGIN_DYNAMIC_HELPER_EMIT(32); + + EmitHelperWithArg(p, rxOffset, pAllocator, arg, target); + + END_DYNAMIC_HELPER_EMIT(); } PCODE DynamicHelpers::CreateHelper(LoaderAllocator * pAllocator, TADDR arg, TADDR arg2, PCODE target) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); - return NULL; + STANDARD_VM_CONTRACT; + + BEGIN_DYNAMIC_HELPER_EMIT(48); + + const IntReg RegR0 = 0, RegT0 = 5, RegA0 = 10, RegA1 = 11; + + *(DWORD*)p = UTypeInstr(0x17, RegT0, 0);// auipc t0, 0 + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA0, RegT0, 24);// ld a0, 24(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA1, RegT0, 32);// ld a1, 32(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegT0, RegT0, 40);// ld t0, 40(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x67, 0, RegR0, RegT0, 0);// jalr x0, 0(t0) + p += 4; + + *(DWORD*)p = ITypeInstr(0x13, 0, RegR0, RegR0, 0);// nop, padding to make 8 byte aligned + p += 4; + + // label: + // arg + *(TADDR*)p = arg; + p += 8; + // arg2 + *(TADDR*)p = arg2; + p += 8; + // target + *(TADDR*)p = target; + p += 8; + + END_DYNAMIC_HELPER_EMIT(); } PCODE DynamicHelpers::CreateHelperArgMove(LoaderAllocator * pAllocator, TADDR arg, PCODE target) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); - return NULL; + STANDARD_VM_CONTRACT; + + BEGIN_DYNAMIC_HELPER_EMIT(40); + + const IntReg RegR0 = 0, RegT0 = 5, RegA0 = 10, RegA1 = 11; + + *(DWORD*)p = UTypeInstr(0x17, RegT0, 0);// auipc t0, 0 + p += 4; + *(DWORD*)p = ITypeInstr(0x13, 0, RegA1, RegA0, 0);// addi a1, a0, 0 + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA0, RegT0, 24);// ld a0, 24(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegT0, RegT0, 32);// ld t0, 32(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x67, 0, RegR0, RegT0, 0);// jalr x0, 0(t0) + p += 4; + + *(DWORD*)p = ITypeInstr(0x13, 0, RegR0, RegR0, 0);// nop, padding to make 8 byte aligned + p += 4; + + // label: + // arg + *(TADDR*)p = arg; + p += 8; + // target + *(TADDR*)p = target; + p += 8; + + END_DYNAMIC_HELPER_EMIT(); } PCODE DynamicHelpers::CreateReturn(LoaderAllocator * pAllocator) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); - return NULL; + STANDARD_VM_CONTRACT; + + BEGIN_DYNAMIC_HELPER_EMIT(4); + + const IntReg RegR0 = 0, RegRa = 1; + + *(DWORD*)p = ITypeInstr(0x67, 0, RegR0, RegRa, 0);// ret + p += 4; + + END_DYNAMIC_HELPER_EMIT(); } PCODE DynamicHelpers::CreateReturnConst(LoaderAllocator * pAllocator, TADDR arg) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); - return NULL; + STANDARD_VM_CONTRACT; + + BEGIN_DYNAMIC_HELPER_EMIT(24); + + const IntReg RegR0 = 0, RegRa = 1, RegT0 = 5, RegA0 = 10; + + *(DWORD*)p = UTypeInstr(0x17, RegT0, 0);// auipc t0, 0 + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA0, RegT0, 16);// ld a0, 16(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x67, 0, RegR0, RegRa, 0);// ret + p += 4; + *(DWORD*)p = ITypeInstr(0x13, 0, RegR0, RegR0, 0);// nop, padding to make 8 byte aligned + p += 4; + + // label: + // arg + *(TADDR*)p = arg; + p += 8; + + END_DYNAMIC_HELPER_EMIT(); } PCODE DynamicHelpers::CreateReturnIndirConst(LoaderAllocator * pAllocator, TADDR arg, INT8 offset) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); - return NULL; + STANDARD_VM_CONTRACT; + + BEGIN_DYNAMIC_HELPER_EMIT(32); + + const IntReg RegR0 = 0, RegRa = 1, RegT0 = 5, RegA0 = 10; + + *(DWORD*)p = UTypeInstr(0x17, RegT0, 0);// auipc t0, 0 + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA0, RegT0, 24);// ld a0, 24(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA0, RegA0, 0);// ld a0,0(a0) + p += 4; + *(DWORD*)p = ITypeInstr(0x13, 0, RegA0, RegA0, offset & 0xfff);// addi a0, a0, offset + p += 4; + *(DWORD*)p = ITypeInstr(0x67, 0, RegR0, RegRa, 0);// ret + p += 4; + *(DWORD*)p = ITypeInstr(0x13, 0, RegR0, RegR0, 0);// nop, padding to make 8 byte aligned + p += 4; + + // label: + // arg + *(TADDR*)p = arg; + p += 8; + + END_DYNAMIC_HELPER_EMIT(); } PCODE DynamicHelpers::CreateHelperWithTwoArgs(LoaderAllocator * pAllocator, TADDR arg, PCODE target) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); - return NULL; + STANDARD_VM_CONTRACT; + + BEGIN_DYNAMIC_HELPER_EMIT(32); + + const IntReg RegR0 = 0, RegT0 = 5, RegA2 = 12; + + *(DWORD*)p = UTypeInstr(0x17, RegT0, 0);// auipc t0, 0 + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA2, RegT0, 16);// ld a2,16(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegT0, RegT0, 24);// ld t0,24(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x67, 0, RegR0, RegT0, 0);// jalr x0, 0(t0) + p += 4; + + // label: + // arg + *(TADDR*)p = arg; + p += 8; + + // target + *(TADDR*)p = target; + p += 8; + END_DYNAMIC_HELPER_EMIT(); } PCODE DynamicHelpers::CreateHelperWithTwoArgs(LoaderAllocator * pAllocator, TADDR arg, TADDR arg2, PCODE target) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); - return NULL; + STANDARD_VM_CONTRACT; + + BEGIN_DYNAMIC_HELPER_EMIT(48); + + const IntReg RegR0 = 0, RegT0 = 5, RegA2 = 12, RegA3 = 1; + + *(DWORD*)p = UTypeInstr(0x17, RegT0, 0);// auipc t0, 0 + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA2, RegT0, 24);// ld a2,24(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA3, RegT0, 32);// ld a3,32(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegT0, RegT0, 40);;// ld t0,40(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x67, 0, RegR0, RegT0, 0);// jalr x0, 0(t0) + p += 4; + *(DWORD*)p = ITypeInstr(0x13, 0, RegR0, RegR0, 0);// nop, padding to make 8 byte aligned + p += 4; + + // label: + // arg + *(TADDR*)p = arg; + p += 8; + // arg2 + *(TADDR*)p = arg2; + p += 8; + // target + *(TADDR*)p = target; + p += 8; + END_DYNAMIC_HELPER_EMIT(); } PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, CORINFO_RUNTIME_LOOKUP * pLookup, DWORD dictionaryIndexAndSlot, Module * pModule) { - _ASSERTE(!"RISCV64: not implementation on riscv64!!!"); - return NULL; + STANDARD_VM_CONTRACT; + const IntReg RegR0 = 0, RegA0 = 10, RegT2 = 7, RegT4 = 29, RegT5 = 30; + + PCODE helperAddress = (pLookup->helper == CORINFO_HELP_RUNTIMEHANDLE_METHOD ? + GetEEFuncEntryPoint(JIT_GenericHandleMethodWithSlotAndModule) : + GetEEFuncEntryPoint(JIT_GenericHandleClassWithSlotAndModule)); + + GenericHandleArgs * pArgs = (GenericHandleArgs *)(void *)pAllocator->GetDynamicHelpersHeap()->AllocAlignedMem(sizeof(GenericHandleArgs), DYNAMIC_HELPER_ALIGNMENT); + ExecutableWriterHolder argsWriterHolder(pArgs, sizeof(GenericHandleArgs)); + argsWriterHolder.GetRW()->dictionaryIndexAndSlot = dictionaryIndexAndSlot; + argsWriterHolder.GetRW()->signature = pLookup->signature; + argsWriterHolder.GetRW()->module = (CORINFO_MODULE_HANDLE)pModule; + + WORD slotOffset = (WORD)(dictionaryIndexAndSlot & 0xFFFF) * sizeof(Dictionary*); + + // It's available only via the run-time helper function + if (pLookup->indirections == CORINFO_USEHELPER) + { + BEGIN_DYNAMIC_HELPER_EMIT(32); + + // a0 already contains generic context parameter + // reuse EmitHelperWithArg for below two operations + // a1 <- pArgs + // branch to helperAddress + EmitHelperWithArg(p, rxOffset, pAllocator, (TADDR)pArgs, helperAddress); + + END_DYNAMIC_HELPER_EMIT(); + } + else + { + int codeSize = 0; + int indirectionsDataSize = 0; + if (pLookup->testForNull || pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) + { + //mv t2, a0 + codeSize += 4; + } + + for (WORD i = 0; i < pLookup->indirections; i++) { + _ASSERTE(pLookup->offsets[i] >= 0); + if (i == pLookup->indirections - 1 && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) + { + codeSize += (pLookup->sizeOffset > 2047 ? 24 : 16); + indirectionsDataSize += (pLookup->sizeOffset > 2047 ? 4 : 0); + } + + codeSize += (pLookup->offsets[i] > 2047 ? 12 : 4); // if( > 2047) (12 bytes) else 4 bytes for instructions. + indirectionsDataSize += (pLookup->offsets[i] > 2047 ? 4 : 0); // 4 bytes for storing indirection offset values + } + + codeSize += indirectionsDataSize ? 4 : 0; // auipc + + if (pLookup->testForNull) + { + codeSize += 12; // beq-ret-addi + + //padding for 8-byte align (required by EmitHelperWithArg) + codeSize = ALIGN_UP(codeSize, 8); + + codeSize += 32; // size of EmitHelperWithArg + } + else + { + codeSize += 4; /* jalr */ + } + + // the offset value of data_label. + uint dataOffset = codeSize; + + codeSize += indirectionsDataSize; + + BEGIN_DYNAMIC_HELPER_EMIT(codeSize); + + BYTE * old_p = p; + + if (indirectionsDataSize) + { + _ASSERTE(codeSize < 2047); + + //auipc t4, 0 + *(DWORD*)p = UTypeInstr(0x17, RegT4, 0); + p += 4; + } + + if (pLookup->testForNull || pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) + { + *(DWORD*)p = ITypeInstr(0x13, 0, RegT2, RegA0, 0);// addi t2, a0, 0 + p += 4; + } + + BYTE* pBLTCall = NULL; + + for (WORD i = 0; i < pLookup->indirections; i++) + { + if (i == pLookup->indirections - 1 && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) + { + _ASSERTE(pLookup->testForNull && i > 0); + + if (pLookup->sizeOffset > 2047) + { + *(DWORD*)p = ITypeInstr(0x3, 0x2, RegT4, RegT4, dataOffset);// lw t4, dataOffset(t4) + p += 4; + *(DWORD*)p = RTypeInstr(0x33, 0, 0, RegT5, RegA0, RegT4);// add t5, a0, t4 + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegT5, RegT5, 0);// ld t5, 0(t5) + p += 4; + + // move to next indirection offset data + dataOffset += 4; + } + else + { + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegT5, RegA0, (UINT32)pLookup->sizeOffset);// ld t5, #(pLookup->sizeOffset)(a0) + p += 4; + } + // lui t4, (slotOffset&0xfffff000)>>12 + *(DWORD*)p = UTypeInstr(0x37, RegT4, (((UINT32)slotOffset & 0xfffff000) >> 12)); + p += 4; + *(DWORD*)p = ITypeInstr(0x13, 0, RegT4, RegT4, slotOffset & 0xfff);// addi t4, t4, (slotOffset&0xfff) + p += 4; + // blt t4, t5, CALL HELPER + pBLTCall = p; // Offset filled later + p += 4; + } + + if (pLookup->offsets[i] > 2047) + { + _ASSERTE(dataOffset < 2047); + *(DWORD*)p = ITypeInstr(0x3, 0x2, RegT4, RegT4, dataOffset & 0xfff);// lw t4, dataOffset(t4) + p += 4; + *(DWORD*)p = RTypeInstr(0x33, 0, 0, RegA0, RegA0, RegT4);// add a0, a0, t4 + p += 4; + *(DWORD*)p = ITypeInstr(0x3, 0x2, RegA0, RegA0, 0);// lw a0, 0(a0) + p += 4; + // move to next indirection offset data + dataOffset += 4; // add 4 as next data is at 4 bytes from previous data + } + else + { + // offset must be 8 byte aligned + _ASSERTE((pLookup->offsets[i] & 0x7) == 0); + *(DWORD*)p = ITypeInstr(0x3, 0x3, RegA0, RegA0, (UINT32)pLookup->offsets[i]);// ld a0, #(pLookup->offsets[i])(a0) + p += 4; + } + } + + // No null test required + if (!pLookup->testForNull) + { + _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK); + *(DWORD*)p = ITypeInstr(0x67, 0, RegR0, RegRa, 0);// ret + p += 4; + } + else + { + // beq a0, x0, CALL HELPER: + *(DWORD*)p = BTypeInstr(0x63, 0, RegA0, RegR0, 8); + p += 4; + + *(DWORD*)p = ITypeInstr(0x67, 0, RegR0, RegRa, 0);// ret + p += 4; + + // CALL HELPER: + if (pBLTCall != NULL) + *(DWORD*)pBLTCall = BTypeInstr(0x63, 0x4, RegT4, RegT5, (UINT32)(p - pBLTCall)); + + *(DWORD*)p = ITypeInstr(0x13, 0, RegA0, RegT2, 0);// addi a0, t2, 0 + p += 4; + if ((uintptr_t)(p - old_p) & 0x7) + { + // nop, padding for 8-byte align (required by EmitHelperWithArg) + *(DWORD*)p = ITypeInstr(0x13, 0, RegR0, RegR0, 0); + p += 4; + } + + // reuse EmitHelperWithArg for below two operations + // a1 <- pArgs + // branch to helperAddress + EmitHelperWithArg(p, rxOffset, pAllocator, (TADDR)pArgs, helperAddress); + } + + // data_label: + for (WORD i = 0; i < pLookup->indirections; i++) + { + if (i == pLookup->indirections - 1 && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK && pLookup->sizeOffset > 2047) + { + *(UINT32*)p = (UINT32)pLookup->sizeOffset; + p += 4; + } + if (pLookup->offsets[i] > 2047) + { + *(UINT32*)p = (UINT32)pLookup->offsets[i]; + p += 4; + } + } + + END_DYNAMIC_HELPER_EMIT(); + } } #endif // FEATURE_READYTORUN