Skip to content

Commit

Permalink
Use preferred region from PAL for JIT reloc hints (#60747)
Browse files Browse the repository at this point in the history
We currently have two schemes that try to ensure jitted code and statics
end up close to each other and close to coreclr. In Windows, we have
USE_UPPER_ADDRESS that lazily will reserve memory for loader heaps that
is close to coreclr. For PAL, we eagerly reserve a large chunk of memory
nearby during start up. However for PAL we were not using this region to
report back to the JIT that addresses in this range can use rip-relative
addressing. Add this support.

I have also cleaned up some of the code around USE_UPPER_ADDRESS: I have
renamed it to USE_LAZY_PREFERRED_RANGE and removed some dead code.
  • Loading branch information
jakobbotsch committed Oct 26, 2021
1 parent 23d734e commit 7084d44
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 96 deletions.
35 changes: 20 additions & 15 deletions src/coreclr/inc/executableallocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
18 changes: 9 additions & 9 deletions src/coreclr/inc/switches.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/pal/inc/pal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 23 additions & 4 deletions src/coreclr/pal/src/include/pal/virtual.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
46 changes: 41 additions & 5 deletions src/coreclr/pal/src/map/virtual.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
104 changes: 46 additions & 58 deletions src/coreclr/utilcode/executableallocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -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
{
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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)
{
Expand Down
Loading

0 comments on commit 7084d44

Please sign in to comment.