Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2010bf5
rebase + feedback
pavelsavara Apr 27, 2026
26c3446
fix
pavelsavara Apr 27, 2026
5d6d6b5
dash
pavelsavara Apr 27, 2026
1b550ff
feedback
pavelsavara Apr 27, 2026
574c3eb
feedback
pavelsavara Apr 27, 2026
0275ab2
feedback
pavelsavara Apr 27, 2026
2ef6b4b
Fix WASM GC build: include configure.cmake to generate config.gc.h
pavelsavara Apr 27, 2026
fcec8d3
Merge branch 'main' into browser_no_mmap
pavelsavara Apr 29, 2026
32ddc90
feedback
pavelsavara Apr 29, 2026
adb3817
too verbose
pavelsavara Apr 29, 2026
435518a
cleanup
pavelsavara Apr 29, 2026
fe828ff
Update src/coreclr/gc/wasm/gcenv.cpp
pavelsavara Apr 29, 2026
f05b837
feedback
pavelsavara Apr 29, 2026
47b5e07
feedback
pavelsavara Apr 29, 2026
9b98c13
feedback
pavelsavara Apr 29, 2026
6555b06
Update src/coreclr/gc/wasm/gcenv.cpp
pavelsavara Apr 29, 2026
d1f1f26
feedback
pavelsavara Apr 29, 2026
96a9d4d
feedback
pavelsavara Apr 29, 2026
d70f152
feedback
pavelsavara Apr 30, 2026
c91ff1d
drop file
pavelsavara Apr 30, 2026
b5dc456
remove sbrk trick
pavelsavara Apr 30, 2026
bd7a147
never_decommit_p
pavelsavara Apr 30, 2026
66651e5
Merge branch 'main' into browser_no_mmap
pavelsavara Apr 30, 2026
fba3ee0
cleanup
pavelsavara Apr 30, 2026
c446f3b
feedback + cleanup
pavelsavara Apr 30, 2026
8c5f23c
feedback
pavelsavara Apr 30, 2026
7b913c2
fix
pavelsavara Apr 30, 2026
b31232b
Update src/coreclr/gc/wasm/gcenv.cpp
pavelsavara Apr 30, 2026
57d0e5c
Update src/coreclr/gc/wasm/gcenv.cpp
pavelsavara May 1, 2026
cb2c789
Update src/coreclr/gc/wasm/gcenv.cpp
pavelsavara May 1, 2026
f716254
Update src/coreclr/gc/wasm/gcenv.cpp
pavelsavara May 1, 2026
07c2288
Update src/coreclr/gc/wasm/gcenv.cpp
pavelsavara May 1, 2026
fd1ddd2
feedback
pavelsavara May 1, 2026
e2eb693
fix
pavelsavara May 1, 2026
d7f4c50
inline constant fold
pavelsavara May 1, 2026
b752041
Update src/coreclr/gc/wasm/gcenv.cpp
pavelsavara May 1, 2026
4616dcc
Update src/coreclr/gc/wasm/gcenv.cpp
pavelsavara May 1, 2026
d60a5bb
Update src/native/minipal/ospagesize.h
pavelsavara May 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/coreclr/gc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@ set(GC_SOURCES
gcbridge.cpp
handletablecache.cpp)

if(CLR_CMAKE_HOST_UNIX)
if(CLR_CMAKE_TARGET_ARCH_WASM)
add_subdirectory(wasm)
include(unix/configure.cmake)
elseif(CLR_CMAKE_HOST_UNIX)
add_subdirectory(unix)
include(unix/configure.cmake)
else()
Comment thread
pavelsavara marked this conversation as resolved.
add_subdirectory(windows)
set (GC_SOURCES
${GC_SOURCES}
windows/Native.rc)
endif(CLR_CMAKE_HOST_UNIX)
endif()

if (CLR_CMAKE_TARGET_ARCH_ARM64 OR CLR_CMAKE_TARGET_ARCH_AMD64)
add_subdirectory(vxsort)
Expand Down
7 changes: 6 additions & 1 deletion src/coreclr/gc/env/gcenv.unix.inl
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@

#include "gcenv.os.h"

extern uint32_t g_pageSizeUnixInl;
#include <minipal/ospagesize.h>

#define OS_PAGE_SIZE GCToOSInterface::GetPageSize()

#ifndef DACCESS_COMPILE
FORCEINLINE size_t GCToOSInterface::GetPageSize()
{
#if defined(__wasm__)
return minipal_getpagesize();
#else
extern uint32_t g_pageSizeUnixInl;
return g_pageSizeUnixInl;
#endif // defined(__wasm__)
}
#endif // DACCESS_COMPILE

Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/gc/env/gcenv.windows.inl
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@

#include "gcenv.os.h"

#include <minipal/ospagesize.h>

#define OS_PAGE_SIZE GCToOSInterface::GetPageSize()

FORCEINLINE size_t GCToOSInterface::GetPageSize()
{
return 0x1000;
return minipal_getpagesize();
}

#endif // __GCENV_WINDOWS_INL__
1 change: 1 addition & 0 deletions src/coreclr/gc/gc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2620,6 +2620,7 @@ heap_segment* gc_heap::segment_standby_list;
#endif //USE_REGIONS
bool gc_heap::use_large_pages_p = 0;
bool gc_heap::large_pages_emulation_mode_p = 0;
bool gc_heap::never_decommit_p = 0;
#ifdef HEAP_BALANCE_INSTRUMENTATION
size_t gc_heap::last_gc_end_time_us = 0;
#endif //HEAP_BALANCE_INSTRUMENTATION
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/gc/gcpriv.h
Original file line number Diff line number Diff line change
Expand Up @@ -5373,6 +5373,14 @@ class gc_heap
PER_HEAP_ISOLATED_FIELD_INIT_ONLY bool use_large_pages_p;
PER_HEAP_ISOLATED_FIELD_INIT_ONLY bool large_pages_emulation_mode_p;

// Indicates that the underlying OS does not support decommitting memory.
// Implies that VirtualCommit/VirtualDecommit are no-ops on heap memory and
// that GC code paths that rely on returning memory to the OS must be skipped.
// Set unconditionally on WASM and whenever use_large_pages_p is set (large pages
// are pre-committed and cannot be decommitted). Code that wants to skip a
// decommit-related path should test this flag rather than use_large_pages_p.
PER_HEAP_ISOLATED_FIELD_INIT_ONLY bool never_decommit_p;

#ifdef MULTIPLE_HEAPS
// Init-ed in gc_heap::initialize_gc
PER_HEAP_ISOLATED_FIELD_INIT_ONLY gc_heap** g_heaps;
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/gc/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,15 @@ bool gc_heap::compute_hard_limit()
large_pages_emulation_mode_p = (large_pages_config == 2);
#endif //HOST_64BIT

// Large pages are pre-committed and cannot be decommitted, so they imply
// never_decommit_p. On WASM, reserve == commit (posix_memalign allocates real
// memory) and there is no way to give memory back to the engine.
#if defined(HOST_WASM) || defined(__wasm__)
never_decommit_p = true;
#else
never_decommit_p = use_large_pages_p;
#endif // defined(HOST_WASM) || defined(__wasm__)

if (heap_hard_limit_oh[soh] || heap_hard_limit_oh[loh] || heap_hard_limit_oh[poh])
{
if (!heap_hard_limit_oh[soh])
Expand Down
37 changes: 20 additions & 17 deletions src/coreclr/gc/memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ bool gc_heap::virtual_commit (void* address, size_t size, int bucket, int h_numb
}

// If it's a valid heap number it means it's commiting for memory on the GC heap.
// In addition if large pages is enabled, we set commit_succeeded_p to true because memory is already committed.
bool commit_succeeded_p = ((h_number >= 0) ? (use_large_pages_p ? true :
// In addition if never-decommit is enabled (which is implied by large pages), we
// set commit_succeeded_p to true because memory is already committed (and
// VirtualCommit would be a no-op).
bool commit_succeeded_p = ((h_number >= 0) ? (never_decommit_p ? true :
Comment on lines +101 to +104
virtual_alloc_commit_for_heap (address, size, h_number)) :
GCToOSInterface::VirtualCommit(address, size));

Expand Down Expand Up @@ -171,10 +173,11 @@ bool gc_heap::virtual_decommit (void* address, size_t size, int bucket, int h_nu
* Case 3: This is for free - the bucket will be recorded_committed_free_bucket, and the h_number will be -1
*/

// With large pages, VirtualDecommit on heap memory is a no-op. All such callers
// should either skip the decommit or handle stale data themselves (decommit_region
// does the latter by calling reduce_committed_bytes directly and clearing memory).
assert (!use_large_pages_p || bucket == recorded_committed_bookkeeping_bucket);
// With never-decommit (implied by large pages), VirtualDecommit on heap memory is
// a no-op. All such callers should either skip the decommit or handle stale data
// themselves (decommit_region does the latter by calling reduce_committed_bytes
// directly and clearing memory).
assert (!never_decommit_p || bucket == recorded_committed_bookkeeping_bucket);

bool decommit_succeeded_p = GCToOSInterface::VirtualDecommit (address, size);

Expand Down Expand Up @@ -202,7 +205,7 @@ void gc_heap::virtual_free (void* add, size_t allocated_size, heap_segment* sg)
// distribute_free_regions where we are calling estimate_gen_growth.
void gc_heap::decommit_ephemeral_segment_pages()
{
if (settings.concurrent || use_large_pages_p || (settings.pause_mode == pause_no_gc))
if (settings.concurrent || never_decommit_p || (settings.pause_mode == pause_no_gc))
{
return;
}
Expand Down Expand Up @@ -315,15 +318,15 @@ bool gc_heap::decommit_step (uint64_t step_milliseconds)
}
}
}
if (use_large_pages_p)
if (never_decommit_p)
{
return (decommit_size != 0);
}
#endif //USE_REGIONS
#ifdef MULTIPLE_HEAPS
// should never get here for large pages because decommit_ephemeral_segment_pages
// will not do anything if use_large_pages_p is true
assert(!use_large_pages_p);
// should never get here for never-decommit because decommit_ephemeral_segment_pages
// will not do anything if never_decommit_p is true
assert(!never_decommit_p);

for (int i = 0; i < n_heaps; i++)
{
Expand All @@ -345,18 +348,18 @@ size_t gc_heap::decommit_region (heap_segment* region, int bucket, int h_number)
uint8_t* decommit_end = heap_segment_committed (region);
size_t decommit_size = decommit_end - page_start;
bool decommit_succeeded_p;
if (use_large_pages_p)
if (never_decommit_p)
{
// VirtualDecommit is a no-op for large pages so skip it and update
// committed bookkeeping directly. Memory clearing is handled below.
// VirtualDecommit is a no-op when never_decommit_p is set, so skip it and
// update committed bookkeeping directly. Memory clearing is handled below.
decommit_succeeded_p = true;
reduce_committed_bytes (page_start, decommit_size, bucket, h_number, true);
}
else
{
decommit_succeeded_p = virtual_decommit (page_start, decommit_size, bucket, h_number);
}
bool require_clearing_memory_p = !decommit_succeeded_p || use_large_pages_p;
bool require_clearing_memory_p = !decommit_succeeded_p || never_decommit_p;
dprintf (REGIONS_LOG, ("decommitted region %p(%p-%p) (%zu bytes) - success: %d",
region,
page_start,
Expand All @@ -365,7 +368,7 @@ size_t gc_heap::decommit_region (heap_segment* region, int bucket, int h_number)
decommit_succeeded_p));
if (require_clearing_memory_p)
{
uint8_t* clear_end = use_large_pages_p ? heap_segment_used (region) : heap_segment_committed (region);
uint8_t* clear_end = never_decommit_p ? heap_segment_used (region) : heap_segment_committed (region);
size_t clear_size = clear_end - page_start;
memclr (page_start, clear_size);
heap_segment_used (region) = heap_segment_mem (region);
Expand Down Expand Up @@ -397,7 +400,7 @@ size_t gc_heap::decommit_region (heap_segment* region, int bucket, int h_number)
}
#endif //BACKGROUND_GC

if (use_large_pages_p)
if (never_decommit_p)
{
assert (heap_segment_used (region) == heap_segment_mem (region));
}
Expand Down
20 changes: 11 additions & 9 deletions src/coreclr/gc/regions_segments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1475,7 +1475,7 @@ void gc_heap::reset_heap_segment_pages (heap_segment* seg)
void gc_heap::decommit_heap_segment_pages (heap_segment* seg,
size_t extra_space)
{
if (use_large_pages_p)
if (never_decommit_p)
return;

uint8_t* page_start = align_on_page (heap_segment_allocated(seg));
Expand All @@ -1493,7 +1493,7 @@ void gc_heap::decommit_heap_segment_pages (heap_segment* seg,
size_t gc_heap::decommit_heap_segment_pages_worker (heap_segment* seg,
uint8_t* new_committed)
{
assert (!use_large_pages_p);
assert (!never_decommit_p);
uint8_t* page_start = align_on_page (new_committed);
ptrdiff_t size = heap_segment_committed (seg) - page_start;
if (size > 0)
Expand Down Expand Up @@ -1522,9 +1522,10 @@ size_t gc_heap::decommit_heap_segment_pages_worker (heap_segment* seg,
//decommit all pages except one or 2
void gc_heap::decommit_heap_segment (heap_segment* seg)
{
// For large pages, VirtualDecommit is a no-op so skip the decommit entirely
// to avoid lowering committed/used bookkeeping while memory retains stale data.
if (use_large_pages_p)
// For never-decommit (implied by large pages), VirtualDecommit is a no-op so
// skip the decommit entirely to avoid lowering committed/used bookkeeping while
// memory retains stale data.
if (never_decommit_p)
{
return;
}
Expand Down Expand Up @@ -1820,10 +1821,11 @@ void gc_heap::distribute_free_regions()
while (decommit_step(DECOMMIT_TIME_STEP_MILLISECONDS))
{
}
// For large pages, VirtualDecommit on in-use regions is a no-op so the
// memory is never actually returned to the OS. Skip the tail decommit
// entirely to avoid misleading bookkeeping and unnecessary memclr overhead.
if (!use_large_pages_p)
// For never-decommit (implied by large pages), VirtualDecommit on in-use
// regions is a no-op so the memory is never actually returned to the OS.
// Skip the tail decommit entirely to avoid misleading bookkeeping and
// unnecessary memclr overhead.
if (!never_decommit_p)
{
#ifdef MULTIPLE_HEAPS
for (int i = 0; i < n_heaps; i++)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/gc/sweep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ void gc_heap::clear_unused_array (uint8_t* x, size_t size)

void gc_heap::reset_memory (uint8_t* o, size_t sizeo)
{
if (gc_heap::use_large_pages_p)
if (gc_heap::never_decommit_p)
return;

if (sizeo > 128 * 1024)
Expand Down
27 changes: 6 additions & 21 deletions src/coreclr/gc/unix/gcenv.unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,6 @@ typedef cpuset_t cpu_set_t;
#define SYSCONF_GET_NUMPROCS _SC_NPROCESSORS_ONLN
#endif

#ifdef __EMSCRIPTEN__
#include <emscripten/heap.h>
#endif // __EMSCRIPTEN__


// The cached total number of CPUs that can be used in the OS.
uint32_t g_totalCpuCount = 0;

Expand Down Expand Up @@ -347,7 +342,7 @@ void GCToOSInterface::Sleep(uint32_t sleepMSec)
requested.tv_nsec = (sleepMSec - requested.tv_sec * tccSecondsToMilliSeconds) * tccMilliSecondsToNanoSeconds;

timespec remaining;
while (nanosleep(&requested, &remaining) == EINTR)
while (nanosleep(&requested, &remaining) == -1 && errno == EINTR)
{
requested = remaining;
}
Expand Down Expand Up @@ -405,7 +400,7 @@ static void* VirtualReserveInner(size_t size, size_t alignment, uint32_t flags,
}

pRetVal = pAlignedRetVal;
#if defined(MADV_DONTDUMP) && !defined(TARGET_WASM)
#if defined(MADV_DONTDUMP)
// Do not include reserved uncommitted memory in coredump.
if (!committing)
{
Expand Down Expand Up @@ -453,13 +448,9 @@ bool GCToOSInterface::VirtualRelease(void* address, size_t size)
// true if it has succeeded, false if it has failed
static bool VirtualCommitInner(void* address, size_t size, uint16_t node, bool newMemory)
{
#ifndef TARGET_WASM
bool success = mprotect(address, size, PROT_WRITE | PROT_READ) == 0;
#else
bool success = true;
#endif // !TARGET_WASM

#if defined(MADV_DONTDUMP) && !defined(TARGET_WASM)
#if defined(MADV_DONTDUMP)
if (success && !newMemory)
{
// Include committed memory in coredump. New memory is included by default.
Expand Down Expand Up @@ -544,13 +535,13 @@ bool GCToOSInterface::VirtualDecommit(void* address, size_t size)
#endif
bool bRetVal = mmap(address, size, PROT_NONE, mmapFlags, -1, 0) != MAP_FAILED;

#if defined(MADV_DONTDUMP) && !defined(TARGET_WASM)
#if defined(MADV_DONTDUMP)
if (bRetVal)
{
// Do not include freed memory in coredump.
madvise(address, size, MADV_DONTDUMP);
}
#endif // defined(MADV_DONTDUMP) && !defined(TARGET_WASM)
#endif // defined(MADV_DONTDUMP)

return bRetVal;
}
Expand All @@ -565,9 +556,6 @@ bool GCToOSInterface::VirtualDecommit(void* address, size_t size)
// true if it has succeeded, false if it has failed
bool GCToOSInterface::VirtualReset(void * address, size_t size, bool unlock)
{
#ifdef TARGET_WASM
return true;
#else // !TARGET_WASM
int st = EINVAL;

#ifdef MADV_DONTDUMP
Expand All @@ -586,7 +574,6 @@ bool GCToOSInterface::VirtualReset(void * address, size_t size, bool unlock)
#endif // MADV_FREE

return (st == 0);
#endif // !TARGET_WASM
}

// Check if the OS supports write watching
Expand Down Expand Up @@ -836,7 +823,7 @@ static uint64_t GetMemorySizeMultiplier(char units)
return 1;
}

#if !defined(__APPLE__) && !defined(__HAIKU__) && !defined(__EMSCRIPTEN__)
#if !defined(__APPLE__) && !defined(__HAIKU__)
// Try to read the MemAvailable entry from /proc/meminfo.
// Return true if the /proc/meminfo existed, the entry was present and we were able to parse it.
static bool ReadMemAvailable(uint64_t* memAvailable)
Expand Down Expand Up @@ -1104,8 +1091,6 @@ uint64_t GetAvailablePhysicalMemory()
{
available = info.free_memory;
}
#elif defined(__EMSCRIPTEN__)
available = emscripten_get_heap_max() - emscripten_get_heap_size();
#else // Linux
static volatile bool tryReadMemInfo = true;

Expand Down
14 changes: 14 additions & 0 deletions src/coreclr/gc/wasm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON)
include_directories("../env")
include_directories("..")
include_directories("../unix")

# Generates config.gc.h in this subdirectory's binary dir (picked up via
# CMAKE_INCLUDE_CURRENT_DIR). Mirrors how gc/unix/CMakeLists.txt does it.
include(../unix/configure.cmake)

set(GC_PAL_SOURCES
gcenv.cpp
../unix/events.cpp)

Comment thread
pavelsavara marked this conversation as resolved.
add_library(gc_pal OBJECT ${GC_PAL_SOURCES} ${VERSION_FILE_PATH})
Loading
Loading