Preallocate jump stubs for dynamic methods (dotnet/coreclr#9883)
authorKoundinya Veluri <kouvel@microsoft.com>
Thu, 2 Mar 2017 04:52:15 +0000 (20:52 -0800)
committerGitHub <noreply@github.com>
Thu, 2 Mar 2017 04:52:15 +0000 (20:52 -0800)
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
src/coreclr/src/vm/amd64/cgencpu.h
src/coreclr/src/vm/crossgencompile.cpp
src/coreclr/src/vm/i386/stublinkerx86.cpp
src/coreclr/src/vm/i386/stublinkerx86.h
src/coreclr/src/vm/precode.cpp
src/coreclr/src/vm/precode.h
src/coreclr/tests/src/CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests.cs [new file with mode: 0644]
src/coreclr/tests/src/CoreMangLib/cti/system/reflection/emit/DynMethodJumpStubTests/DynMethodJumpStubTests.csproj [new file with mode: 0644]
src/coreclr/tests/testsUnsupportedOnARM32.txt
src/coreclr/tests/testsUnsupportedOutsideWindows.txt

index 51aac1e..497abcd 100644 (file)
@@ -727,6 +727,35 @@ INT32 rel32UsingJumpStub(INT32 UNALIGNED * pRel32, PCODE target, MethodDesc *pMe
     return static_cast<INT32>(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<INT32>(offset);
+}
+
 BOOL DoesSlotCallPrestub(PCODE pCode)
 {
     CONTRACTL {
index 769f402..2d4dce0 100644 (file)
@@ -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);
index 09e5cb5..c29d3cc 100644 (file)
@@ -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
 
 
index cfb5854..d9c6130 100644 (file)
@@ -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));
index e361833..9fbc17d 100644 (file)
@@ -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;
index 1934eb1..180e170 100644 (file)
@@ -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,
index 430da66..0afa762 100644 (file)
@@ -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 (file)
index 0000000..1dc717d
--- /dev/null
@@ -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 (file)
index 0000000..65b8359
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x64</Platform>
+    <AssemblyName>DynMethodJumpStubTests</AssemblyName>
+    <ProjectGuid>{742D9E05-668D-4B90-97F9-717A7572FE6C}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="DynMethodJumpStubTests.cs" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
index e827550..d889b57 100644 (file)
@@ -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
index 0f8f194..ef43049 100644 (file)
@@ -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