diff --git a/src/coreclr/inc/executableallocator.h b/src/coreclr/inc/executableallocator.h index 101178f9a4ef0..04dfdf031b41f 100644 --- a/src/coreclr/inc/executableallocator.h +++ b/src/coreclr/inc/executableallocator.h @@ -56,14 +56,17 @@ class ExecutableAllocator // Callback to the runtime to report fatal errors static FatalErrorHandler g_fatalErrorHandler; -#if USE_UPPER_ADDRESS - // Preferred region to allocate the code in. - static BYTE* g_codeMinAddr; - static BYTE* g_codeMaxAddr; - static BYTE* g_codeAllocStart; - // Next address to try to allocate for code in the preferred region. - static BYTE* g_codeAllocHint; -#endif // USE_UPPER_ADDRESS +#if USE_LAZY_PREFERRED_RANGE + static BYTE* g_lazyPreferredRangeStart; + // Next address to try to allocate for code in the lazy preferred region. + static BYTE* g_lazyPreferredRangeHint; +#endif // USE_LAZY_PREFERRED_RANGE + + // For PAL, this region represents the area that is eagerly reserved on + // startup where executable memory and static fields are preferrably kept. + // For Windows, this is the region that we lazily reserve from. + static BYTE* g_preferredRangeMin; + static BYTE* g_preferredRangeMax; // Caches the COMPlus_EnableWXORX setting static bool g_isWXorXEnabled; @@ -154,14 +157,16 @@ class ExecutableAllocator // Return true if W^X is enabled static bool IsWXORXEnabled(); - // Use this function to initialize the g_codeAllocHint - // during startup. base is runtime .dll base address, - // size is runtime .dll virtual size. - static void InitCodeAllocHint(size_t base, size_t size, int randomPageOffset); + // Use this function to initialize g_lazyPreferredRangeHint during startup. + // base is runtime .dll base address, size is runtime .dll virtual size. + static void InitLazyPreferredRange(size_t base, size_t size, int randomPageOffset); - // Use this function to reset the g_codeAllocHint - // after unloading an AppDomain - static void ResetCodeAllocHint(); + // Use this function to reset g_lazyPreferredRangeHint after unloading code. + static void ResetLazyPreferredRangeHint(); + + // Use this function to initialize the preferred range of executable memory + // from PAL. + static void InitPreferredRange(); // Returns TRUE if p is located in near clr.dll that allows us // to use rel32 IP-relative addressing modes. diff --git a/src/coreclr/inc/switches.h b/src/coreclr/inc/switches.h index 91947135de94a..9e066527a8bde 100644 --- a/src/coreclr/inc/switches.h +++ b/src/coreclr/inc/switches.h @@ -49,19 +49,19 @@ #endif #if defined(TARGET_X86) || defined(TARGET_ARM) - #define USE_UPPER_ADDRESS 0 + #define USE_LAZY_PREFERRED_RANGE 0 #elif defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_S390X) - #define UPPER_ADDRESS_MAPPING_FACTOR 2 - #define CLR_UPPER_ADDRESS_MIN 0x64400000000 - #define CODEHEAP_START_ADDRESS 0x64480000000 - #define CLR_UPPER_ADDRESS_MAX 0x644FC000000 -#if !defined(HOST_UNIX) - #define USE_UPPER_ADDRESS 1 +#if defined(HOST_UNIX) + // In PAL we have a smechanism that reserves memory on start up that is + // close to libcoreclr and intercepts calls to VirtualAlloc to serve back + // from this area. + #define USE_LAZY_PREFERRED_RANGE 0 #else - #define USE_UPPER_ADDRESS 0 -#endif // !HOST_UNIX + // On Windows we lazily try to reserve memory close to coreclr.dll. + #define USE_LAZY_PREFERRED_RANGE 1 +#endif #else #error Please add a new #elif clause and define all portability macros for the new platform diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index cd96f5753cc8f..6e6fc8a4335ba 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -2743,6 +2743,13 @@ PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange( IN LPCVOID lpEndAddress, IN SIZE_T dwSize); +PALIMPORT +void +PALAPI +PAL_GetExecutableMemoryAllocatorPreferredRange( + OUT PVOID *start, + OUT PVOID *end); + PALIMPORT LPVOID PALAPI diff --git a/src/coreclr/pal/src/include/pal/virtual.h b/src/coreclr/pal/src/include/pal/virtual.h index b8ad1856a4a56..bac28112ba378 100644 --- a/src/coreclr/pal/src/include/pal/virtual.h +++ b/src/coreclr/pal/src/include/pal/virtual.h @@ -145,6 +145,21 @@ class ExecutableMemoryAllocator --*/ void *AllocateMemoryWithinRange(const void *beginAddress, const void *endAddress, SIZE_T allocationSize); + /*++ + Function: + GetPreferredRange + + Gets the preferred range, which is the range that the allocator will try to put code into. + When this range is close to libcoreclr, it will additionally include libcoreclr's memory + range, the purpose being that this can be used to check if we expect code to be close enough + to libcoreclr to use IP-relative addressing. + --*/ + void GetPreferredRange(void **start, void **end) + { + *start = m_preferredRangeStart; + *end = m_preferredRangeEnd; + } + private: /*++ Function: @@ -179,17 +194,21 @@ class ExecutableMemoryAllocator static const int32_t MaxExecutableMemorySizeNearCoreClr = MaxExecutableMemorySize - CoreClrLibrarySize; // Start address of the reserved virtual address space - void* m_startAddress; + void* m_startAddress = NULL; // Next available address in the reserved address space - void* m_nextFreeAddress; + void* m_nextFreeAddress = NULL; // Total size of the virtual memory that the allocator has been able to // reserve during its initialization. - int32_t m_totalSizeOfReservedMemory; + int32_t m_totalSizeOfReservedMemory = 0; // Remaining size of the reserved virtual memory that can be used to satisfy allocation requests. - int32_t m_remainingReservedMemory; + int32_t m_remainingReservedMemory = 0; + + // Preferred range to report back to EE for where the allocator will put code. + void* m_preferredRangeStart = NULL; + void* m_preferredRangeEnd = NULL; }; #endif // __cplusplus diff --git a/src/coreclr/pal/src/map/virtual.cpp b/src/coreclr/pal/src/map/virtual.cpp index 645cf10aec8d6..dc15c3a7204c6 100644 --- a/src/coreclr/pal/src/map/virtual.cpp +++ b/src/coreclr/pal/src/map/virtual.cpp @@ -1319,6 +1319,27 @@ PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange( #endif // HOST_64BIT } +/*++ +Function: + PAL_GetExecutableMemoryAllocatorPreferredRange + + This function gets the preferred range used by the executable memory allocator. + This is the range that the memory allocator will prefer to allocate memory in, + including (if nearby) the libcoreclr memory range. + + lpBeginAddress - Inclusive beginning of range + lpEndAddress - Exclusive end of range + dwSize - Number of bytes to allocate +--*/ +void +PALAPI +PAL_GetExecutableMemoryAllocatorPreferredRange( + OUT LPVOID *start, + OUT LPVOID *end) +{ + g_executableMemoryAllocator.GetPreferredRange(start, end); +} + /*++ Function: VirtualAlloc @@ -2093,11 +2114,6 @@ void* ReserveMemoryFromExecutableAllocator(CPalThread* pThread, SIZE_T allocatio --*/ void ExecutableMemoryAllocator::Initialize() { - m_startAddress = NULL; - m_nextFreeAddress = NULL; - m_totalSizeOfReservedMemory = 0; - m_remainingReservedMemory = 0; - // Enable the executable memory allocator on 64-bit platforms only // because 32-bit platforms have limited amount of virtual address space. #ifdef HOST_64BIT @@ -2189,6 +2205,26 @@ void ExecutableMemoryAllocator::TryReserveInitialMemory() { return; } + + m_preferredRangeStart = m_startAddress; + m_preferredRangeEnd = (char*)m_startAddress + sizeOfAllocation; + } + else + { + // We managed to allocate memory close to libcoreclr, so include its memory address in the preferred range to allow + // generated code to use IP-relative addressing. + if ((char*)m_startAddress < (char*)coreclrLoadAddress) + { + m_preferredRangeStart = (void*)m_startAddress; + m_preferredRangeEnd = (char*)coreclrLoadAddress + CoreClrLibrarySize; + } + else + { + m_preferredRangeStart = (void*)coreclrLoadAddress; + m_preferredRangeEnd = (char*)m_startAddress + sizeOfAllocation; + } + + _ASSERTE((char*)m_preferredRangeEnd - (char*)m_preferredRangeStart <= INT_MAX); } // Memory has been successfully reserved. diff --git a/src/coreclr/utilcode/executableallocator.cpp b/src/coreclr/utilcode/executableallocator.cpp index ac4c326c83784..49431b6ecce74 100644 --- a/src/coreclr/utilcode/executableallocator.cpp +++ b/src/coreclr/utilcode/executableallocator.cpp @@ -4,14 +4,15 @@ #include "pedecoder.h" #include "executableallocator.h" -#if USE_UPPER_ADDRESS +#if USE_LAZY_PREFERRED_RANGE // Preferred region to allocate the code in. -BYTE * ExecutableAllocator::g_codeMinAddr; -BYTE * ExecutableAllocator::g_codeMaxAddr; -BYTE * ExecutableAllocator::g_codeAllocStart; +BYTE * ExecutableAllocator::g_lazyPreferredRangeStart; // Next address to try to allocate for code in the preferred region. -BYTE * ExecutableAllocator::g_codeAllocHint; -#endif // USE_UPPER_ADDRESS +BYTE * ExecutableAllocator::g_lazyPreferredRangeHint; +#endif // USE_LAZY_PREFERRED_RANGE + +BYTE * ExecutableAllocator::g_preferredRangeMin; +BYTE * ExecutableAllocator::g_preferredRangeMax; bool ExecutableAllocator::g_isWXorXEnabled = false; @@ -50,12 +51,9 @@ size_t ExecutableAllocator::Granularity() return g_SystemInfo.dwAllocationGranularity; } -// Use this function to initialize the g_codeAllocHint -// during startup. base is runtime .dll base address, -// size is runtime .dll virtual size. -void ExecutableAllocator::InitCodeAllocHint(size_t base, size_t size, int randomPageOffset) +void ExecutableAllocator::InitLazyPreferredRange(size_t base, size_t size, int randomPageOffset) { -#if USE_UPPER_ADDRESS +#if USE_LAZY_PREFERRED_RANGE #ifdef _DEBUG // If GetForceRelocs is enabled we don't constrain the pMinAddr @@ -64,39 +62,25 @@ void ExecutableAllocator::InitCodeAllocHint(size_t base, size_t size, int random #endif // - // If we are using the UPPER_ADDRESS space (on Win64) - // then for any code heap that doesn't specify an address - // range using [pMinAddr..pMaxAddr] we place it in the - // upper address space - // This enables us to avoid having to use long JumpStubs - // to reach the code for our ngen-ed images. - // Which are also placed in the UPPER_ADDRESS space. + // If we are using USE_LAZY_PREFERRED_RANGE then we try to allocate memory close + // to coreclr.dll. This avoids having to create jump stubs for calls to + // helpers and R2R images loaded close to coreclr.dll. // SIZE_T reach = 0x7FFF0000u; - // We will choose the preferred code region based on the address of clr.dll. The JIT helpers - // in clr.dll are the most heavily called functions. - g_codeMinAddr = (base + size > reach) ? (BYTE *)(base + size - reach) : (BYTE *)0; - g_codeMaxAddr = (base + reach > base) ? (BYTE *)(base + reach) : (BYTE *)-1; + // We will choose the preferred code region based on the address of coreclr.dll. The JIT helpers + // in coreclr.dll are the most heavily called functions. + g_preferredRangeMin = (base + size > reach) ? (BYTE *)(base + size - reach) : (BYTE *)0; + g_preferredRangeMax = (base + reach > base) ? (BYTE *)(base + reach) : (BYTE *)-1; BYTE * pStart; - if (g_codeMinAddr <= (BYTE *)CODEHEAP_START_ADDRESS && - (BYTE *)CODEHEAP_START_ADDRESS < g_codeMaxAddr) - { - // clr.dll got loaded at its preferred base address? (OS without ASLR - pre-Vista) - // Use the code head start address that does not cause collisions with NGen images. - // This logic is coupled with scripts that we use to assign base addresses. - pStart = (BYTE *)CODEHEAP_START_ADDRESS; - } - else if (base > UINT32_MAX) { - // clr.dll got address assigned by ASLR? // Try to occupy the space as far as possible to minimize collisions with other ASLR assigned // addresses. Do not start at g_codeMinAddr exactly so that we can also reach common native images - // that can be placed at higher addresses than clr.dll. - pStart = g_codeMinAddr + (g_codeMaxAddr - g_codeMinAddr) / 8; + // that can be placed at higher addresses than coreclr.dll. + pStart = g_preferredRangeMin + (g_preferredRangeMax - g_preferredRangeMin) / 8; } else { @@ -108,31 +92,35 @@ void ExecutableAllocator::InitCodeAllocHint(size_t base, size_t size, int random // Randomize the address space pStart += GetOsPageSize() * randomPageOffset; - g_codeAllocStart = pStart; - g_codeAllocHint = pStart; + g_lazyPreferredRangeStart = pStart; + g_lazyPreferredRangeHint = pStart; #endif } -// Use this function to reset the g_codeAllocHint -// after unloading an AppDomain -void ExecutableAllocator::ResetCodeAllocHint() +void ExecutableAllocator::InitPreferredRange() { - LIMITED_METHOD_CONTRACT; -#if USE_UPPER_ADDRESS - g_codeAllocHint = g_codeAllocStart; +#ifdef TARGET_UNIX + void *start, *end; + PAL_GetExecutableMemoryAllocatorPreferredRange(&start, &end); + g_preferredRangeMin = (BYTE *)start; + g_preferredRangeMax = (BYTE *)end; #endif } -// Returns TRUE if p is located in near clr.dll that allows us -// to use rel32 IP-relative addressing modes. -bool ExecutableAllocator::IsPreferredExecutableRange(void * p) +void ExecutableAllocator::ResetLazyPreferredRangeHint() { LIMITED_METHOD_CONTRACT; -#if USE_UPPER_ADDRESS - if (g_codeMinAddr <= (BYTE *)p && (BYTE *)p < g_codeMaxAddr) - return true; +#if USE_LAZY_PREFERRED_RANGE + g_lazyPreferredRangeHint = g_lazyPreferredRangeStart; #endif - return false; +} +// Returns TRUE if p is is located in the memory area where we prefer to put +// executable code and static fields. This area is typically close to the +// coreclr library. +bool ExecutableAllocator::IsPreferredExecutableRange(void * p) +{ + LIMITED_METHOD_CONTRACT; + return g_preferredRangeMin <= (BYTE *)p && (BYTE *)p < g_preferredRangeMax; } ExecutableAllocator* ExecutableAllocator::Instance() @@ -553,7 +541,7 @@ void* ExecutableAllocator::Reserve(size_t size) BYTE *result = NULL; -#if USE_UPPER_ADDRESS +#if USE_LAZY_PREFERRED_RANGE // // If we are using the UPPER_ADDRESS space (on Win64) // then for any heap that will contain executable code @@ -563,32 +551,32 @@ void* ExecutableAllocator::Reserve(size_t size) // to reach the code for our ngen-ed images on x64, // since they are also placed in the UPPER_ADDRESS space. // - BYTE * pHint = g_codeAllocHint; + BYTE * pHint = g_lazyPreferredRangeHint; - if (size <= (SIZE_T)(g_codeMaxAddr - g_codeMinAddr) && pHint != NULL) + if (size <= (SIZE_T)(g_preferredRangeMax - g_preferredRangeMin) && pHint != NULL) { // Try to allocate in the preferred region after the hint - result = (BYTE*)ReserveWithinRange(size, pHint, g_codeMaxAddr); + result = (BYTE*)ReserveWithinRange(size, pHint, g_preferredRangeMax); if (result != NULL) { - g_codeAllocHint = result + size; + g_lazyPreferredRangeHint = result + size; } else { // Try to allocate in the preferred region before the hint - result = (BYTE*)ReserveWithinRange(size, g_codeMinAddr, pHint + size); + result = (BYTE*)ReserveWithinRange(size, g_preferredRangeMin, pHint + size); if (result != NULL) { - g_codeAllocHint = result + size; + g_lazyPreferredRangeHint = result + size; } - g_codeAllocHint = NULL; + g_lazyPreferredRangeHint = NULL; } } // Fall through to -#endif // USE_UPPER_ADDRESS +#endif // USE_LAZY_PREFERRED_RANGE if (result == NULL) { diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 8f347255d4e0d..7ae4605e95adc 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -808,14 +808,16 @@ void EEStartupHelper() StubManager::InitializeStubManagers(); -#ifndef TARGET_UNIX +#ifdef TARGET_UNIX + ExecutableAllocator::InitPreferredRange(); +#else { - // Record mscorwks geometry + // Record coreclr.dll geometry PEDecoder pe(GetClrModuleBase()); g_runtimeLoadedBaseAddress = (SIZE_T)pe.GetBase(); g_runtimeVirtualSize = (SIZE_T)pe.GetVirtualSize(); - ExecutableAllocator::InitCodeAllocHint(g_runtimeLoadedBaseAddress, g_runtimeVirtualSize, GetRandomInt(64)); + ExecutableAllocator::InitLazyPreferredRange(g_runtimeLoadedBaseAddress, g_runtimeVirtualSize, GetRandomInt(64)); } #endif // !TARGET_UNIX diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index ba8460332ed05..56805d6a3534f 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -3343,7 +3343,7 @@ void EEJitManager::Unload(LoaderAllocator *pAllocator) } } - ExecutableAllocator::ResetCodeAllocHint(); + ExecutableAllocator::ResetLazyPreferredRangeHint(); } EEJitManager::DomainCodeHeapList::DomainCodeHeapList() diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 3ffb87896fa96..a2d9a31898897 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -11399,6 +11399,25 @@ void CEEJitInfo::recordRelocation(void * location, #endif // HOST_64BIT } +// Get a hint for whether the relocation kind to use for the target address. +// Note that this is currently a best-guess effort as we do not know exactly +// where the jitted code will end up at. Instead we try to keep executable code +// and static fields in a preferred memory region and base the decision on this +// region. +// +// If we guess wrong we will recover in recordRelocation if we notice that we +// cannot actually use the kind of reloc: in that case we will rejit the +// function and turn off the use of those relocs in the future. This scheme +// works based on two assumptions: +// +// 1) The JIT will ask about relocs only for memory that was allocated by the +// loader heap in the preferred region. +// 2) The loader heap allocates memory in the preferred region in a circular fashion; +// the region itself might be larger than 2 GB, but the current compilation should +// only be hitting the preferred region within 2 GB. +// +// Under these assumptions we should only hit the "recovery" case once the +// preferred range is actually full. WORD CEEJitInfo::getRelocTypeHint(void * target) { CONTRACTL { @@ -11410,7 +11429,6 @@ WORD CEEJitInfo::getRelocTypeHint(void * target) #ifdef TARGET_AMD64 if (m_fAllowRel32) { - // The JIT calls this method for data addresses only. It always uses REL32s for direct code targets. if (ExecutableAllocator::IsPreferredExecutableRange(target)) return IMAGE_REL_BASED_REL32; }