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) &&