From 3054c14acaaeef15110cf56c8a010ddb7afa3b7e Mon Sep 17 00:00:00 2001
From: Andrew Au <3410332+cshung@users.noreply.github.com>
Date: Tue, 5 May 2026 09:17:35 -0700
Subject: [PATCH 1/3] Fix GC standalone large pages emulation in segments path
Change virtual_alloc to accept an integer large_pages_config parameter
(0=none, 1=real large pages, 2=emulation) instead of a boolean. When
config=2, use VirtualReserve followed by VirtualCommit to emulate large
page behavior without requiring OS large page privileges.
Update the test to use per-object-heap hard limits which are required
for the segments path with large pages.
---
src/coreclr/gc/gc.cpp | 27 ++++++++++++++-----
src/coreclr/gc/gcpriv.h | 2 +-
src/coreclr/gc/init.cpp | 16 +++--------
src/coreclr/gc/regions_segments.cpp | 16 +++++------
.../GC/Collect_Aggressive_LargePages.csproj | 7 ++---
5 files changed, 37 insertions(+), 31 deletions(-)
diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp
index 68ab975c8d45b3..a45aba659021e4 100644
--- a/src/coreclr/gc/gc.cpp
+++ b/src/coreclr/gc/gc.cpp
@@ -2020,7 +2020,7 @@ void qsort1(uint8_t** low, uint8_t** high, unsigned int depth);
#endif //USE_INTROSORT
void* virtual_alloc (size_t size);
-void* virtual_alloc (size_t size, bool use_large_pages_p, uint16_t numa_node = NUMA_NODE_UNDEFINED);
+void* virtual_alloc (size_t size, int large_page_config, uint16_t numa_node = NUMA_NODE_UNDEFINED);
/* per heap static initialization */
#if defined(BACKGROUND_GC) && !defined(MULTIPLE_HEAPS)
@@ -4014,10 +4014,10 @@ heap_segment* make_initial_segment (int gen, int h_number, gc_heap* hp)
void* virtual_alloc (size_t size)
{
- return virtual_alloc(size, false);
+ return virtual_alloc(size, 0);
}
-void* virtual_alloc (size_t size, bool use_large_pages_p, uint16_t numa_node)
+void* virtual_alloc (size_t size, int large_page_config, uint16_t numa_node)
{
size_t requested_size = size;
@@ -4038,9 +4038,24 @@ void* virtual_alloc (size_t size, bool use_large_pages_p, uint16_t numa_node)
}
#endif // !FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
- void* prgmem = use_large_pages_p ?
- GCToOSInterface::VirtualReserveAndCommitLargePages(requested_size, numa_node) :
- GCToOSInterface::VirtualReserve(requested_size, card_size * card_word_width, flags, numa_node);
+ void* prgmem;
+ if (large_page_config == 2)
+ {
+ prgmem = GCToOSInterface::VirtualReserve (requested_size, card_size * card_word_width, flags, numa_node);
+ if (prgmem && !GCToOSInterface::VirtualCommit (prgmem, requested_size))
+ {
+ GCToOSInterface::VirtualRelease (prgmem, requested_size);
+ prgmem = 0;
+ }
+ }
+ else if (large_page_config == 1)
+ {
+ prgmem = GCToOSInterface::VirtualReserveAndCommitLargePages (requested_size, numa_node);
+ }
+ else
+ {
+ prgmem = GCToOSInterface::VirtualReserve (requested_size, card_size * card_word_width, flags, numa_node);
+ }
void *aligned_mem = prgmem;
// We don't want (prgmem + size) to be right at the end of the address space
diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h
index a9b2ac287494f1..133ee921c43dcd 100644
--- a/src/coreclr/gc/gcpriv.h
+++ b/src/coreclr/gc/gcpriv.h
@@ -2154,7 +2154,7 @@ class gc_heap
PER_HEAP_ISOLATED_METHOD BOOL is_mark_set (uint8_t* o);
- PER_HEAP_ISOLATED_METHOD BOOL reserve_initial_memory (size_t normal_size, size_t large_size, size_t pinned_size, int num_heaps, bool use_large_pages_p, bool separated_poh_p, uint16_t* heap_no_to_numa_node);
+ PER_HEAP_ISOLATED_METHOD BOOL reserve_initial_memory (size_t normal_size, size_t large_size, size_t pinned_size, int num_heaps, int large_page_config, bool separated_poh_p, uint16_t* heap_no_to_numa_node);
PER_HEAP_ISOLATED_METHOD void destroy_initial_memory();
diff --git a/src/coreclr/gc/init.cpp b/src/coreclr/gc/init.cpp
index 113415c2e48636..82ec4d695deffc 100644
--- a/src/coreclr/gc/init.cpp
+++ b/src/coreclr/gc/init.cpp
@@ -882,23 +882,13 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
// Right now all the non mark array portions are commmitted since I'm calling make_card_table
// on the whole range. This can be committed as needed.
size_t reserve_size = regions_range;
- // In large pages emulation mode, use normal reserve (not real large pages) then
- // commit all upfront to simulate the "always committed" property.
- bool use_real_large_pages = use_large_pages_p && !large_pages_emulation_mode_p;
- uint8_t* reserve_range = (uint8_t*)virtual_alloc (reserve_size, use_real_large_pages);
+ uint8_t* reserve_range = (uint8_t*)virtual_alloc (reserve_size, large_pages_emulation_mode_p ? 2 : (use_large_pages_p ? 1 : 0));
if (!reserve_range)
{
log_init_error_to_host ("Reserving %zd bytes (%zd GiB) for the regions range failed, do you have a virtual memory limit set on this process?",
reserve_size, gib (reserve_size));
return E_OUTOFMEMORY;
}
- if (large_pages_emulation_mode_p)
- {
- if (!GCToOSInterface::VirtualCommit (reserve_range, reserve_size))
- {
- return E_OUTOFMEMORY;
- }
- }
if (!global_region_allocator.init (reserve_range, (reserve_range + reserve_size),
((size_t)1 << min_segment_size_shr),
@@ -919,9 +909,9 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
heap_hard_limit_oh[soh] &&
(GCConfig::GetGCHeapHardLimitPOH() == 0) &&
(GCConfig::GetGCHeapHardLimitPOHPercent() == 0);
- bool use_real_large_pages = use_large_pages_p && !large_pages_emulation_mode_p;
+ int large_page_config = large_pages_emulation_mode_p ? 2 : (use_large_pages_p ? 1 : 0);
if (!reserve_initial_memory (soh_segment_size, loh_segment_size, poh_segment_size, number_of_heaps,
- use_real_large_pages, separated_poh_p, heap_no_to_numa_node))
+ large_page_config, separated_poh_p, heap_no_to_numa_node))
return E_OUTOFMEMORY;
if (use_large_pages_p)
{
diff --git a/src/coreclr/gc/regions_segments.cpp b/src/coreclr/gc/regions_segments.cpp
index c1e4984e38d69c..3e7f8e5ce1357c 100644
--- a/src/coreclr/gc/regions_segments.cpp
+++ b/src/coreclr/gc/regions_segments.cpp
@@ -159,7 +159,7 @@ void gc_heap::seg_mapping_table_remove_segment (heap_segment* seg)
#endif //!USE_REGIONS
BOOL gc_heap::reserve_initial_memory (size_t normal_size, size_t large_size, size_t pinned_size,
- int num_heaps, bool use_large_pages_p, bool separated_poh_p, uint16_t* heap_no_to_numa_node)
+ int num_heaps, int large_page_config, bool separated_poh_p, uint16_t* heap_no_to_numa_node)
{
BOOL reserve_success = FALSE;
@@ -267,7 +267,7 @@ BOOL gc_heap::reserve_initial_memory (size_t normal_size, size_t large_size, siz
int numa_node = block_index % numa_node_count;
bool pinned_block = block_index >= numa_node_count;
- block->memory_base = (uint8_t*)virtual_alloc (block->block_size, use_large_pages_p && !pinned_block, (uint16_t)numa_node);
+ block->memory_base = (uint8_t*)virtual_alloc (block->block_size, pinned_block ? 0 : large_page_config, (uint16_t)numa_node);
if (block->memory_base == nullptr)
{
dprintf(2, ("failed to reserve %zd bytes for on NUMA node %u", block->block_size, numa_node));
@@ -361,11 +361,11 @@ BOOL gc_heap::reserve_initial_memory (size_t normal_size, size_t large_size, siz
size_t separate_pinned_size = memory_details.block_count * pinned_size;
size_t requestedMemory = memory_details.block_count * (normal_size + large_size + temp_pinned_size);
- uint8_t* allatonce_block = (uint8_t*)virtual_alloc(requestedMemory, use_large_pages_p);
+ uint8_t* allatonce_block = (uint8_t*)virtual_alloc(requestedMemory, large_page_config);
uint8_t* separated_poh_block = nullptr;
if (allatonce_block && separated_poh_p)
{
- separated_poh_block = (uint8_t*)virtual_alloc(separate_pinned_size, false);
+ separated_poh_block = (uint8_t*)virtual_alloc(separate_pinned_size, 0);
if (!separated_poh_block)
{
virtual_free(allatonce_block, requestedMemory);
@@ -410,9 +410,9 @@ BOOL gc_heap::reserve_initial_memory (size_t normal_size, size_t large_size, siz
else
{
// try to allocate 3 blocks
- uint8_t* b1 = (uint8_t*)virtual_alloc(memory_details.block_count * normal_size, use_large_pages_p);
- uint8_t* b2 = (uint8_t*)virtual_alloc(memory_details.block_count * large_size, use_large_pages_p);
- uint8_t* b3 = (uint8_t*)virtual_alloc(memory_details.block_count * pinned_size, use_large_pages_p && !separated_poh_p);
+ uint8_t* b1 = (uint8_t*)virtual_alloc(memory_details.block_count * normal_size, large_page_config);
+ uint8_t* b2 = (uint8_t*)virtual_alloc(memory_details.block_count * large_size, large_page_config);
+ uint8_t* b3 = (uint8_t*)virtual_alloc(memory_details.block_count * pinned_size, separated_poh_p ? 0 : large_page_config);
if (b1 && b2 && b3)
{
@@ -458,7 +458,7 @@ BOOL gc_heap::reserve_initial_memory (size_t normal_size, size_t large_size, siz
numa_node = heap_no_to_numa_node[heap_no];
}
current_block->memory_base =
- (uint8_t*)virtual_alloc(block_size, use_large_pages_p, numa_node);
+ (uint8_t*)virtual_alloc(block_size, large_page_config, numa_node);
if (current_block->memory_base == 0)
{
// Free the blocks that we've allocated so far
diff --git a/src/tests/GC/API/GC/Collect_Aggressive_LargePages.csproj b/src/tests/GC/API/GC/Collect_Aggressive_LargePages.csproj
index be1c0d877cedff..b7e55f0095fa84 100644
--- a/src/tests/GC/API/GC/Collect_Aggressive_LargePages.csproj
+++ b/src/tests/GC/API/GC/Collect_Aggressive_LargePages.csproj
@@ -3,9 +3,8 @@
true
true
-
+
true
-
false
0
@@ -17,6 +16,8 @@
-
+
+
+
From 3884ff3f0d00a52b1ceff8112687b4cd36f800c6 Mon Sep 17 00:00:00 2001
From: Andrew Au <3410332+cshung@users.noreply.github.com>
Date: Sun, 14 Jun 2026 19:49:25 +0000
Subject: [PATCH 2/3] Revert "Fix GC standalone large pages emulation in
segments path"
This reverts commit 0aaa427d8649e860c6062c63a87b8330864a1e38.
---
src/coreclr/gc/gc.cpp | 27 +++++--------------
src/coreclr/gc/gcpriv.h | 2 +-
src/coreclr/gc/init.cpp | 16 ++++++++---
src/coreclr/gc/regions_segments.cpp | 16 +++++------
.../GC/Collect_Aggressive_LargePages.csproj | 7 +++--
5 files changed, 31 insertions(+), 37 deletions(-)
diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp
index a45aba659021e4..68ab975c8d45b3 100644
--- a/src/coreclr/gc/gc.cpp
+++ b/src/coreclr/gc/gc.cpp
@@ -2020,7 +2020,7 @@ void qsort1(uint8_t** low, uint8_t** high, unsigned int depth);
#endif //USE_INTROSORT
void* virtual_alloc (size_t size);
-void* virtual_alloc (size_t size, int large_page_config, uint16_t numa_node = NUMA_NODE_UNDEFINED);
+void* virtual_alloc (size_t size, bool use_large_pages_p, uint16_t numa_node = NUMA_NODE_UNDEFINED);
/* per heap static initialization */
#if defined(BACKGROUND_GC) && !defined(MULTIPLE_HEAPS)
@@ -4014,10 +4014,10 @@ heap_segment* make_initial_segment (int gen, int h_number, gc_heap* hp)
void* virtual_alloc (size_t size)
{
- return virtual_alloc(size, 0);
+ return virtual_alloc(size, false);
}
-void* virtual_alloc (size_t size, int large_page_config, uint16_t numa_node)
+void* virtual_alloc (size_t size, bool use_large_pages_p, uint16_t numa_node)
{
size_t requested_size = size;
@@ -4038,24 +4038,9 @@ void* virtual_alloc (size_t size, int large_page_config, uint16_t numa_node)
}
#endif // !FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
- void* prgmem;
- if (large_page_config == 2)
- {
- prgmem = GCToOSInterface::VirtualReserve (requested_size, card_size * card_word_width, flags, numa_node);
- if (prgmem && !GCToOSInterface::VirtualCommit (prgmem, requested_size))
- {
- GCToOSInterface::VirtualRelease (prgmem, requested_size);
- prgmem = 0;
- }
- }
- else if (large_page_config == 1)
- {
- prgmem = GCToOSInterface::VirtualReserveAndCommitLargePages (requested_size, numa_node);
- }
- else
- {
- prgmem = GCToOSInterface::VirtualReserve (requested_size, card_size * card_word_width, flags, numa_node);
- }
+ void* prgmem = use_large_pages_p ?
+ GCToOSInterface::VirtualReserveAndCommitLargePages(requested_size, numa_node) :
+ GCToOSInterface::VirtualReserve(requested_size, card_size * card_word_width, flags, numa_node);
void *aligned_mem = prgmem;
// We don't want (prgmem + size) to be right at the end of the address space
diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h
index 133ee921c43dcd..a9b2ac287494f1 100644
--- a/src/coreclr/gc/gcpriv.h
+++ b/src/coreclr/gc/gcpriv.h
@@ -2154,7 +2154,7 @@ class gc_heap
PER_HEAP_ISOLATED_METHOD BOOL is_mark_set (uint8_t* o);
- PER_HEAP_ISOLATED_METHOD BOOL reserve_initial_memory (size_t normal_size, size_t large_size, size_t pinned_size, int num_heaps, int large_page_config, bool separated_poh_p, uint16_t* heap_no_to_numa_node);
+ PER_HEAP_ISOLATED_METHOD BOOL reserve_initial_memory (size_t normal_size, size_t large_size, size_t pinned_size, int num_heaps, bool use_large_pages_p, bool separated_poh_p, uint16_t* heap_no_to_numa_node);
PER_HEAP_ISOLATED_METHOD void destroy_initial_memory();
diff --git a/src/coreclr/gc/init.cpp b/src/coreclr/gc/init.cpp
index 82ec4d695deffc..113415c2e48636 100644
--- a/src/coreclr/gc/init.cpp
+++ b/src/coreclr/gc/init.cpp
@@ -882,13 +882,23 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
// Right now all the non mark array portions are commmitted since I'm calling make_card_table
// on the whole range. This can be committed as needed.
size_t reserve_size = regions_range;
- uint8_t* reserve_range = (uint8_t*)virtual_alloc (reserve_size, large_pages_emulation_mode_p ? 2 : (use_large_pages_p ? 1 : 0));
+ // In large pages emulation mode, use normal reserve (not real large pages) then
+ // commit all upfront to simulate the "always committed" property.
+ bool use_real_large_pages = use_large_pages_p && !large_pages_emulation_mode_p;
+ uint8_t* reserve_range = (uint8_t*)virtual_alloc (reserve_size, use_real_large_pages);
if (!reserve_range)
{
log_init_error_to_host ("Reserving %zd bytes (%zd GiB) for the regions range failed, do you have a virtual memory limit set on this process?",
reserve_size, gib (reserve_size));
return E_OUTOFMEMORY;
}
+ if (large_pages_emulation_mode_p)
+ {
+ if (!GCToOSInterface::VirtualCommit (reserve_range, reserve_size))
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
if (!global_region_allocator.init (reserve_range, (reserve_range + reserve_size),
((size_t)1 << min_segment_size_shr),
@@ -909,9 +919,9 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
heap_hard_limit_oh[soh] &&
(GCConfig::GetGCHeapHardLimitPOH() == 0) &&
(GCConfig::GetGCHeapHardLimitPOHPercent() == 0);
- int large_page_config = large_pages_emulation_mode_p ? 2 : (use_large_pages_p ? 1 : 0);
+ bool use_real_large_pages = use_large_pages_p && !large_pages_emulation_mode_p;
if (!reserve_initial_memory (soh_segment_size, loh_segment_size, poh_segment_size, number_of_heaps,
- large_page_config, separated_poh_p, heap_no_to_numa_node))
+ use_real_large_pages, separated_poh_p, heap_no_to_numa_node))
return E_OUTOFMEMORY;
if (use_large_pages_p)
{
diff --git a/src/coreclr/gc/regions_segments.cpp b/src/coreclr/gc/regions_segments.cpp
index 3e7f8e5ce1357c..c1e4984e38d69c 100644
--- a/src/coreclr/gc/regions_segments.cpp
+++ b/src/coreclr/gc/regions_segments.cpp
@@ -159,7 +159,7 @@ void gc_heap::seg_mapping_table_remove_segment (heap_segment* seg)
#endif //!USE_REGIONS
BOOL gc_heap::reserve_initial_memory (size_t normal_size, size_t large_size, size_t pinned_size,
- int num_heaps, int large_page_config, bool separated_poh_p, uint16_t* heap_no_to_numa_node)
+ int num_heaps, bool use_large_pages_p, bool separated_poh_p, uint16_t* heap_no_to_numa_node)
{
BOOL reserve_success = FALSE;
@@ -267,7 +267,7 @@ BOOL gc_heap::reserve_initial_memory (size_t normal_size, size_t large_size, siz
int numa_node = block_index % numa_node_count;
bool pinned_block = block_index >= numa_node_count;
- block->memory_base = (uint8_t*)virtual_alloc (block->block_size, pinned_block ? 0 : large_page_config, (uint16_t)numa_node);
+ block->memory_base = (uint8_t*)virtual_alloc (block->block_size, use_large_pages_p && !pinned_block, (uint16_t)numa_node);
if (block->memory_base == nullptr)
{
dprintf(2, ("failed to reserve %zd bytes for on NUMA node %u", block->block_size, numa_node));
@@ -361,11 +361,11 @@ BOOL gc_heap::reserve_initial_memory (size_t normal_size, size_t large_size, siz
size_t separate_pinned_size = memory_details.block_count * pinned_size;
size_t requestedMemory = memory_details.block_count * (normal_size + large_size + temp_pinned_size);
- uint8_t* allatonce_block = (uint8_t*)virtual_alloc(requestedMemory, large_page_config);
+ uint8_t* allatonce_block = (uint8_t*)virtual_alloc(requestedMemory, use_large_pages_p);
uint8_t* separated_poh_block = nullptr;
if (allatonce_block && separated_poh_p)
{
- separated_poh_block = (uint8_t*)virtual_alloc(separate_pinned_size, 0);
+ separated_poh_block = (uint8_t*)virtual_alloc(separate_pinned_size, false);
if (!separated_poh_block)
{
virtual_free(allatonce_block, requestedMemory);
@@ -410,9 +410,9 @@ BOOL gc_heap::reserve_initial_memory (size_t normal_size, size_t large_size, siz
else
{
// try to allocate 3 blocks
- uint8_t* b1 = (uint8_t*)virtual_alloc(memory_details.block_count * normal_size, large_page_config);
- uint8_t* b2 = (uint8_t*)virtual_alloc(memory_details.block_count * large_size, large_page_config);
- uint8_t* b3 = (uint8_t*)virtual_alloc(memory_details.block_count * pinned_size, separated_poh_p ? 0 : large_page_config);
+ uint8_t* b1 = (uint8_t*)virtual_alloc(memory_details.block_count * normal_size, use_large_pages_p);
+ uint8_t* b2 = (uint8_t*)virtual_alloc(memory_details.block_count * large_size, use_large_pages_p);
+ uint8_t* b3 = (uint8_t*)virtual_alloc(memory_details.block_count * pinned_size, use_large_pages_p && !separated_poh_p);
if (b1 && b2 && b3)
{
@@ -458,7 +458,7 @@ BOOL gc_heap::reserve_initial_memory (size_t normal_size, size_t large_size, siz
numa_node = heap_no_to_numa_node[heap_no];
}
current_block->memory_base =
- (uint8_t*)virtual_alloc(block_size, large_page_config, numa_node);
+ (uint8_t*)virtual_alloc(block_size, use_large_pages_p, numa_node);
if (current_block->memory_base == 0)
{
// Free the blocks that we've allocated so far
diff --git a/src/tests/GC/API/GC/Collect_Aggressive_LargePages.csproj b/src/tests/GC/API/GC/Collect_Aggressive_LargePages.csproj
index b7e55f0095fa84..be1c0d877cedff 100644
--- a/src/tests/GC/API/GC/Collect_Aggressive_LargePages.csproj
+++ b/src/tests/GC/API/GC/Collect_Aggressive_LargePages.csproj
@@ -3,8 +3,9 @@
true
true
-
+
true
+
false
0
@@ -16,8 +17,6 @@
-
-
-
+
From 973587c3101fba8777eaaaf3a77b5b798dfe3aa6 Mon Sep 17 00:00:00 2001
From: Andrew Au <3410332+cshung@users.noreply.github.com>
Date: Sun, 14 Jun 2026 19:50:09 +0000
Subject: [PATCH 3/3] Disable large page emulation on segments path
Large page emulation mode (GCLargePages=2) is not supported on the
segments path. When emulation is requested on segments, silently
disable large pages instead of crashing with a segfault due to
memory being reserved but never committed.
Real large pages (GCLargePages=1) continue to work on segments as
before.
Fixes #127668
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/gc/init.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/coreclr/gc/init.cpp b/src/coreclr/gc/init.cpp
index 113415c2e48636..2828ee4ee49ce1 100644
--- a/src/coreclr/gc/init.cpp
+++ b/src/coreclr/gc/init.cpp
@@ -915,6 +915,13 @@ HRESULT gc_heap::initialize_gc (size_t soh_segment_size,
return E_FAIL;
}
#else //USE_REGIONS
+ // Large page emulation mode is not supported on the segments path.
+ // Silently disable large pages when emulation is requested.
+ if (large_pages_emulation_mode_p)
+ {
+ use_large_pages_p = false;
+ large_pages_emulation_mode_p = false;
+ }
bool separated_poh_p = use_large_pages_p &&
heap_hard_limit_oh[soh] &&
(GCConfig::GetGCHeapHardLimitPOH() == 0) &&