From 3d2ae11d7744fe623f9f3b93822748bd78c61843 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Wed, 1 Mar 2017 20:52:15 -0800 Subject: [PATCH] Preallocate jump stubs for dynamic methods (dotnet/coreclr#9883) Preallocate jump stubs for dynamic methods - This eliminates the possibility of running into an out-of-memory situation after compiling the method - The temporary entry points block containing FixupPrecodes is extended for dynamic methods to include sufficient space for jump stubs - When the target is too far for the FixupPrecode to encode a short relative jump, it instead does a short relative call or jump to the corresponding jump stub, which does an absolute jump to the target Commit migrated from https://github.com/dotnet/coreclr/commit/4bafc1004b99013eaa58450e4f974dc7169b5af1 --- src/coreclr/src/vm/amd64/cgenamd64.cpp | 29 +++++ src/coreclr/src/vm/amd64/cgencpu.h | 4 + src/coreclr/src/vm/crossgencompile.cpp | 6 + src/coreclr/src/vm/i386/stublinkerx86.cpp | 34 +++++- src/coreclr/src/vm/i386/stublinkerx86.h | 4 + src/coreclr/src/vm/precode.cpp | 52 +++++++- src/coreclr/src/vm/precode.h | 9 +- .../DynMethodJumpStubTests.cs | 136 +++++++++++++++++++++ .../DynMethodJumpStubTests.csproj | 21 ++++ src/coreclr/tests/testsUnsupportedOnARM32.txt | 1 + .../tests/testsUnsupportedOutsideWindows.txt | 1 + 11 files changed, 284 insertions(+), 13 deletions(-) create mode 100644 src/coreclr/tests/src/CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests.cs create mode 100644 src/coreclr/tests/src/CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests.csproj diff --git a/src/coreclr/src/vm/amd64/cgenamd64.cpp b/src/coreclr/src/vm/amd64/cgenamd64.cpp index 51aac1e..497abcd 100644 --- a/src/coreclr/src/vm/amd64/cgenamd64.cpp +++ b/src/coreclr/src/vm/amd64/cgenamd64.cpp @@ -727,6 +727,35 @@ INT32 rel32UsingJumpStub(INT32 UNALIGNED * pRel32, PCODE target, MethodDesc *pMe return static_cast(offset); } +INT32 rel32UsingPreallocatedJumpStub(INT32 UNALIGNED * pRel32, PCODE target, PCODE jumpStubAddr) +{ + CONTRACTL + { + THROWS; // emitBackToBackJump may throw (see emitJump) + GC_NOTRIGGER; + } + CONTRACTL_END; + + TADDR baseAddr = (TADDR)pRel32 + 4; + _ASSERTE(FitsInI4(jumpStubAddr - baseAddr)); + + INT_PTR offset = target - baseAddr; + if (!FitsInI4(offset) INDEBUG(|| PEDecoder::GetForceRelocs())) + { + offset = jumpStubAddr - baseAddr; + if (!FitsInI4(offset)) + { + _ASSERTE(!"jump stub was not in expected range"); + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); + } + + emitBackToBackJump((LPBYTE)jumpStubAddr, (LPVOID)target); + } + + _ASSERTE(FitsInI4(offset)); + return static_cast(offset); +} + BOOL DoesSlotCallPrestub(PCODE pCode) { CONTRACTL { diff --git a/src/coreclr/src/vm/amd64/cgencpu.h b/src/coreclr/src/vm/amd64/cgencpu.h index 769f402..2d4dce0 100644 --- a/src/coreclr/src/vm/amd64/cgencpu.h +++ b/src/coreclr/src/vm/amd64/cgencpu.h @@ -57,6 +57,7 @@ EXTERN_C void FastCallFinalizeWorker(Object *obj, PCODE funcPtr); //#define HAS_REMOTING_PRECODE 1 // TODO: Implement #define HAS_FIXUP_PRECODE 1 #define HAS_FIXUP_PRECODE_CHUNKS 1 +#define FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS 1 // ThisPtrRetBufPrecode one is necessary for closed delegates over static methods with return buffer #define HAS_THISPTR_RETBUF_PRECODE 1 @@ -381,6 +382,9 @@ void EncodeLoadAndJumpThunk (LPBYTE pBuffer, LPVOID pv, LPVOID pTarget); // Get Rel32 destination, emit jumpStub if necessary INT32 rel32UsingJumpStub(INT32 UNALIGNED * pRel32, PCODE target, MethodDesc *pMethod, LoaderAllocator *pLoaderAllocator = NULL); +// Get Rel32 destination, emit jumpStub if necessary into a preallocated location +INT32 rel32UsingPreallocatedJumpStub(INT32 UNALIGNED * pRel32, PCODE target, PCODE jumpStubAddr); + void emitCOMStubCall (ComCallMethodDesc *pCOMMethod, PCODE target); void emitJump(LPBYTE pBuffer, LPVOID target); diff --git a/src/coreclr/src/vm/crossgencompile.cpp b/src/coreclr/src/vm/crossgencompile.cpp index 09e5cb5..c29d3cc 100644 --- a/src/coreclr/src/vm/crossgencompile.cpp +++ b/src/coreclr/src/vm/crossgencompile.cpp @@ -292,6 +292,12 @@ INT32 rel32UsingJumpStub(INT32 UNALIGNED * pRel32, PCODE target, MethodDesc *pMe // crossgen does not have jump stubs return 0; } + +INT32 rel32UsingPreallocatedJumpStub(INT32 UNALIGNED * pRel32, PCODE target, PCODE jumpStubAddr) +{ + // crossgen does not have jump stubs + return 0; +} #endif diff --git a/src/coreclr/src/vm/i386/stublinkerx86.cpp b/src/coreclr/src/vm/i386/stublinkerx86.cpp index cfb5854..d9c6130 100644 --- a/src/coreclr/src/vm/i386/stublinkerx86.cpp +++ b/src/coreclr/src/vm/i386/stublinkerx86.cpp @@ -6537,6 +6537,22 @@ TADDR FixupPrecode::GetMethodDesc() } #endif +#ifdef FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS +PCODE FixupPrecode::GetDynamicMethodEntryJumpStub() +{ + _ASSERTE(((PTR_MethodDesc)GetMethodDesc())->IsLCGMethod()); + + // m_PrecodeChunkIndex has a value inverted to the order of precodes in memory (the precode at the lowest address has the + // highest index, and the precode at the highest address has the lowest index). To map a precode to its jump stub by memory + // order, invert the precode index to get the jump stub index. + UINT32 count = ((PTR_MethodDesc)GetMethodDesc())->GetMethodDescChunk()->GetCount(); + _ASSERTE(m_PrecodeChunkIndex < count); + SIZE_T jumpStubIndex = count - 1 - m_PrecodeChunkIndex; + + return GetBase() + sizeof(PTR_MethodDesc) + jumpStubIndex * BACK_TO_BACK_JUMP_ALLOCATE_SIZE; +} +#endif // FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + #ifdef DACCESS_COMPILE void FixupPrecode::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { @@ -6656,10 +6672,17 @@ void FixupPrecode::Init(MethodDesc* pMD, LoaderAllocator *pLoaderAllocator, int _ASSERTE(GetMethodDesc() == (TADDR)pMD); + PCODE target = (PCODE)GetEEFuncEntryPoint(PrecodeFixupThunk); +#ifdef FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + if (pMD->IsLCGMethod()) + { + m_rel32 = rel32UsingPreallocatedJumpStub(&m_rel32, target, GetDynamicMethodEntryJumpStub()); + return; + } +#endif // FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS if (pLoaderAllocator != NULL) { - m_rel32 = rel32UsingJumpStub(&m_rel32, - GetEEFuncEntryPoint(PrecodeFixupThunk), NULL /* pMD */, pLoaderAllocator); + m_rel32 = rel32UsingJumpStub(&m_rel32, target, NULL /* pMD */, pLoaderAllocator); } } @@ -6689,7 +6712,12 @@ BOOL FixupPrecode::SetTargetInterlocked(TADDR target, TADDR expected) pOldValue[offsetof(FixupPrecode,m_op)] = X86_INSTR_CALL_REL32; pNewValue[offsetof(FixupPrecode,m_op)] = X86_INSTR_JMP_REL32; - *(INT32*)(&pNewValue[offsetof(FixupPrecode,m_rel32)]) = rel32UsingJumpStub(&m_rel32, target, pMD); + *(INT32*)(&pNewValue[offsetof(FixupPrecode, m_rel32)]) = +#ifdef FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + pMD->IsLCGMethod() ? + rel32UsingPreallocatedJumpStub(&m_rel32, target, GetDynamicMethodEntryJumpStub()) : +#endif // FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + rel32UsingJumpStub(&m_rel32, target, pMD); _ASSERTE(IS_ALIGNED(this, sizeof(INT64))); EnsureWritableExecutablePages(this, sizeof(INT64)); diff --git a/src/coreclr/src/vm/i386/stublinkerx86.h b/src/coreclr/src/vm/i386/stublinkerx86.h index e361833..9fbc17d 100644 --- a/src/coreclr/src/vm/i386/stublinkerx86.h +++ b/src/coreclr/src/vm/i386/stublinkerx86.h @@ -703,6 +703,10 @@ struct FixupPrecode { } #endif // HAS_FIXUP_PRECODE_CHUNKS +#ifdef FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + PCODE GetDynamicMethodEntryJumpStub(); +#endif // FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + PCODE GetTarget() { LIMITED_METHOD_DAC_CONTRACT; diff --git a/src/coreclr/src/vm/precode.cpp b/src/coreclr/src/vm/precode.cpp index 1934eb1..180e170 100644 --- a/src/coreclr/src/vm/precode.cpp +++ b/src/coreclr/src/vm/precode.cpp @@ -300,20 +300,54 @@ Precode* Precode::GetPrecodeForTemporaryEntryPoint(TADDR temporaryEntryPoints, i return PTR_Precode(temporaryEntryPoints + index * oneSize); } -SIZE_T Precode::SizeOfTemporaryEntryPoints(PrecodeType t, int count) +SIZE_T Precode::SizeOfTemporaryEntryPoints(PrecodeType t, bool preallocateJumpStubs, int count) { WRAPPER_NO_CONTRACT; SUPPORTS_DAC; + #ifdef HAS_FIXUP_PRECODE_CHUNKS if (t == PRECODE_FIXUP) { - return count * (sizeof(FixupPrecode) + sizeof(PCODE)) + sizeof(PTR_MethodDesc); + SIZE_T size = count * sizeof(FixupPrecode) + sizeof(PTR_MethodDesc); + +#ifdef FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + if (preallocateJumpStubs) + { + // For dynamic methods, space for jump stubs is allocated along with the precodes as part of the temporary entry + // points block. The first jump stub begins immediately after the PTR_MethodDesc. + size += count * BACK_TO_BACK_JUMP_ALLOCATE_SIZE; + } +#else // !FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + _ASSERTE(!preallocateJumpStubs); +#endif // FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + + return size; + } + else + { + _ASSERTE(!preallocateJumpStubs); } #endif SIZE_T oneSize = SizeOfTemporaryEntryPoint(t); return count * oneSize; } +SIZE_T Precode::SizeOfTemporaryEntryPoints(TADDR temporaryEntryPoints, int count) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + PrecodeType precodeType = PTR_Precode(temporaryEntryPoints)->GetType(); +#ifdef FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + bool preallocateJumpStubs = + precodeType == PRECODE_FIXUP && + ((PTR_MethodDesc)((PTR_FixupPrecode)temporaryEntryPoints)->GetMethodDesc())->IsLCGMethod(); +#else // !FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + bool preallocateJumpStubs = false; +#endif // FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + return SizeOfTemporaryEntryPoints(precodeType, preallocateJumpStubs, count); +} + #ifndef DACCESS_COMPILE Precode* Precode::Allocate(PrecodeType t, MethodDesc* pMD, @@ -464,16 +498,28 @@ TADDR Precode::AllocateTemporaryEntryPoints(MethodDescChunk * pChunk, int count = pChunk->GetCount(); PrecodeType t = PRECODE_STUB; + bool preallocateJumpStubs = false; #ifdef HAS_FIXUP_PRECODE // Default to faster fixup precode if possible if (!pFirstMD->RequiresMethodDescCallingConvention(count > 1)) { t = PRECODE_FIXUP; + +#ifdef FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + if (pFirstMD->IsLCGMethod()) + { + preallocateJumpStubs = true; + } +#endif // FIXUP_PRECODE_PREALLOCATE_DYNAMIC_METHOD_JUMP_STUBS + } + else + { + _ASSERTE(!pFirstMD->IsLCGMethod()); } #endif // HAS_FIXUP_PRECODE - SIZE_T totalSize = SizeOfTemporaryEntryPoints(t, count); + SIZE_T totalSize = SizeOfTemporaryEntryPoints(t, preallocateJumpStubs, count); #ifdef HAS_COMPACT_ENTRYPOINTS // Note that these are just best guesses to save memory. If we guessed wrong, diff --git a/src/coreclr/src/vm/precode.h b/src/coreclr/src/vm/precode.h index 430da66..0afa762 100644 --- a/src/coreclr/src/vm/precode.h +++ b/src/coreclr/src/vm/precode.h @@ -311,13 +311,8 @@ public: static Precode * GetPrecodeForTemporaryEntryPoint(TADDR temporaryEntryPoints, int index); - static SIZE_T SizeOfTemporaryEntryPoints(PrecodeType t, int count); - static SIZE_T SizeOfTemporaryEntryPoints(TADDR temporaryEntryPoints, int count) - { - WRAPPER_NO_CONTRACT; - SUPPORTS_DAC; - return SizeOfTemporaryEntryPoints(PTR_Precode(temporaryEntryPoints)->GetType(), count); - } + static SIZE_T SizeOfTemporaryEntryPoints(PrecodeType t, bool preallocateJumpStubs, int count); + static SIZE_T SizeOfTemporaryEntryPoints(TADDR temporaryEntryPoints, int count); static TADDR AllocateTemporaryEntryPoints(MethodDescChunk* pChunk, LoaderAllocator *pLoaderAllocator, AllocMemTracker *pamTracker); diff --git a/src/coreclr/tests/src/CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests.cs b/src/coreclr/tests/src/CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests.cs new file mode 100644 index 0000000..1dc717d --- /dev/null +++ b/src/coreclr/tests/src/CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using System.Threading; + +public static class DynamicMethodJumpStubTests +{ + private static int Main() + { + DynamicMethodJumpStubTest(); + return 100; + } + + public static void DynamicMethodJumpStubTest() + { + if (!Environment.Is64BitProcess) + { + return; + } + + // Reserve memory around framework libraries. This is just a best attempt, it typically doesn't help since the + // precode allocator may have already committed pages it can allocate from, or it may commit reserved pages close to + // framework libraries. + ReserveMemoryAround(new Action(ExecutionContext.RestoreFlow).Method.MethodHandle); + + for (int i = 0; i < 64; ++i) + { + DynamicMethod dynamicMethod = CreateDynamicMethod("DynMethod" + i); + Action dynamicMethodDelegate = (Action)dynamicMethod.CreateDelegate(typeof(Action)); + + // Before compiling the dynamic method, reserve memory around its current entry point, which should be its + // precode. Then, when compiling the method, there would be a good chance that the code will be located far from + // the precode, forcing the use of a jump stub. + ReserveMemoryAround( + (RuntimeMethodHandle) + typeof(DynamicMethod).InvokeMember( + "GetMethodDescriptor", + BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, + null, + dynamicMethod, + null)); + + dynamicMethodDelegate(); + } + + // This test does not release reserved pages because they may have been committed by other components on the system + } + + private static DynamicMethod CreateDynamicMethod(string name) + { + var dynamicMethod = new DynamicMethod(name, null, null); + ILGenerator ilGenerator = dynamicMethod.GetILGenerator(); + ilGenerator.Emit(OpCodes.Ret); + return dynamicMethod; + } + + private const uint AllocationGranularity = (uint)64 << 10; + private const ulong ReserveRangeRadius = (ulong)4 << 30; // reserve 4 GB before and after the base address + + private static void ReserveMemoryAround(RuntimeMethodHandle methodHandle) + { + ulong baseAddress = (ulong)methodHandle.Value.ToInt64(); + + ulong low = baseAddress - ReserveRangeRadius; + if (low > baseAddress) + { + low = ulong.MinValue; + } + else + { + low &= ~((ulong)AllocationGranularity - 1); + } + + ulong high = baseAddress + ReserveRangeRadius; + if (high < baseAddress) + { + high = ulong.MaxValue; + } + + ulong address = low; + while (address <= high) + { + VirtualAlloc( + new UIntPtr(address), + new UIntPtr(AllocationGranularity), + AllocationType.RESERVE, + MemoryProtection.NOACCESS); + + if (address + AllocationGranularity < address) + { + break; + } + address += AllocationGranularity; + } + } + + [Flags] + private enum AllocationType : uint + { + COMMIT = 0x1000, + RESERVE = 0x2000, + RESET = 0x80000, + LARGE_PAGES = 0x20000000, + PHYSICAL = 0x400000, + TOP_DOWN = 0x100000, + WRITE_WATCH = 0x200000 + } + + [Flags] + private enum MemoryProtection : uint + { + EXECUTE = 0x10, + EXECUTE_READ = 0x20, + EXECUTE_READWRITE = 0x40, + EXECUTE_WRITECOPY = 0x80, + NOACCESS = 0x01, + READONLY = 0x02, + READWRITE = 0x04, + WRITECOPY = 0x08, + GUARD_Modifierflag = 0x100, + NOCACHE_Modifierflag = 0x200, + WRITECOMBINE_Modifierflag = 0x400 + } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern UIntPtr VirtualAlloc( + UIntPtr lpAddress, + UIntPtr dwSize, + AllocationType flAllocationType, + MemoryProtection flProtect); +} diff --git a/src/coreclr/tests/src/CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests.csproj b/src/coreclr/tests/src/CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests.csproj new file mode 100644 index 0000000..65b8359 --- /dev/null +++ b/src/coreclr/tests/src/CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests.csproj @@ -0,0 +1,21 @@ + + + + + Debug + x64 + DynMethodJumpStubTests + {742D9E05-668D-4B90-97F9-717A7572FE6C} + Exe + 512 + + + + + + + + + + + diff --git a/src/coreclr/tests/testsUnsupportedOnARM32.txt b/src/coreclr/tests/testsUnsupportedOnARM32.txt index e827550..d889b57 100644 --- a/src/coreclr/tests/testsUnsupportedOnARM32.txt +++ b/src/coreclr/tests/testsUnsupportedOnARM32.txt @@ -1,3 +1,4 @@ +CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests/DynMethodJumpStubTests.sh JIT/Directed/tailcall/tailcall/tailcall.sh JIT/Methodical/tailcall_v4/hijacking/hijacking.sh JIT/Methodical/tailcall_v4/smallFrame/smallFrame.sh diff --git a/src/coreclr/tests/testsUnsupportedOutsideWindows.txt b/src/coreclr/tests/testsUnsupportedOutsideWindows.txt index 0f8f194..ef43049 100644 --- a/src/coreclr/tests/testsUnsupportedOutsideWindows.txt +++ b/src/coreclr/tests/testsUnsupportedOutsideWindows.txt @@ -124,6 +124,7 @@ CoreMangLib/cti/system/double/DoubleToString3/DoubleToString3.sh CoreMangLib/cti/system/double/DoubleToString4/DoubleToString4.sh CoreMangLib/cti/system/int/Int32ToString3/Int32ToString3.sh CoreMangLib/cti/system/int64/Int64ToString3/Int64ToString3.sh +CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests/DynMethodJumpStubTests.sh CoreMangLib/cti/system/string/StringCompare1/StringCompare1.sh CoreMangLib/cti/system/string/StringCompare15/StringCompare15.sh CoreMangLib/cti/system/string/StringCompare2/StringCompare2.sh -- 2.7.4