[browser][coreclr] WASM-specific GC OS layer; no mmap/decommit#127328
[browser][coreclr] WASM-specific GC OS layer; no mmap/decommit#127328pavelsavara wants to merge 38 commits intodotnet:mainfrom
Conversation
|
Tagging subscribers to this area: @JulieLeeMSFT, @dotnet/gc |
There was a problem hiding this comment.
Pull request overview
This PR separates WebAssembly-specific GC OS interface behavior from the shared Unix implementation by introducing a dedicated gcenv.wasm.cpp, and adjusts the PAL virtual memory implementation on WASM to avoid relying on Emscripten’s incomplete mmap/munmap support.
Changes:
- Added a dedicated WASM
GCToOSInterfaceimplementation (gcenv.wasm.cpp) and CMake wiring for building it. - Routed WASM GC builds to the new
gc/wasmdirectory and removed WASM-specific#ifdefpaths fromgcenv.unix.cpp. - Updated PAL virtual memory reserve/release on WASM to use
posix_memalign/freeinstead ofmmap/munmap.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/pal/src/map/virtual.cpp | Switches WASM reserve/release behavior to posix_memalign/free and adjusts related error/cleanup paths. |
| src/coreclr/gc/wasm/gcenv.wasm.cpp | New WASM-specific GC OS interface implementation (virtual memory, CPU/NUMA stubs, memory stats). |
| src/coreclr/gc/wasm/CMakeLists.txt | Adds build definition for the WASM GC PAL object library. |
| src/coreclr/gc/unix/gcenv.unix.cpp | Removes WASM-specific branches and fixes nanosleep EINTR retry logic. |
| src/coreclr/gc/CMakeLists.txt | Routes WASM builds to gc/wasm instead of gc/unix. |
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com>
| { | ||
| (void)configAffinityMask; | ||
| (void)configAffinitySet; | ||
| return nullptr; // only for multiple heaps |
| uint32_t GCToOSInterface::GetTotalProcessorCount() | ||
| { | ||
| return 1; | ||
| } | ||
|
|
| #else // TARGET_WASI | ||
| // WASI doesn't have an API to query max memory. | ||
| g_totalPhysicalMemSize = 2LL * 1024 * 1024 * 1024; // 2GB |
| // 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 : |
|
|
||
| while (nanosleep(&requested, &requested) != 0 && errno == EINTR) | ||
| { |
| // | ||
| // On other platforms the value is queried from the OS once and cached; the | ||
| // definition lives in ospagesize.c so there is exactly one cache per process. | ||
| #if defined(TARGET_WASM) |
There was a problem hiding this comment.
| #if defined(TARGET_WASM) | |
| #if defined(HOST_WASM) |
minipal should use HOST_... everywhere. It is not concerned about the target platform. Ifdefs that use TARGET_... in minipal are not quite right.
For example, if you use TARGET_WASM here, RyuJIT running on Windows or Linux that produces code for wasm may pickup the custom wasm page size for its allocators. It can lead to interesting bugs.
| // The OS page size used by CoreCLR on WASM (16KB). | ||
| // WASM has no hardware pages; getpagesize() returns the 64KB memory.grow granularity, | ||
| // which is too coarse for GC alignment and thresholds. |
There was a problem hiding this comment.
| // The OS page size used by CoreCLR on WASM (16KB). | |
| // WASM has no hardware pages; getpagesize() returns the 64KB memory.grow granularity, | |
| // which is too coarse for GC alignment and thresholds. | |
| // WASM has no hardware pages; getpagesize() returns the 64KB memory.grow granularity, | |
| // which is too coarse for GC alignment and thresholds. Reduce the OS page size used | |
| // by the runtime on WASM to 16KB. |
Fixes #121036
Fixes #117813
Fixes #118943
Summary
This PR adds a dedicated WASM implementation of the GC's OS abstraction layer
(
GCToOSInterface) and updates the GC and PAL to handle the fact that on WASMthere is no virtual memory: pages cannot be reserved without committing,
partial unmaps don't return memory to the engine, and
MAP_FIXED/madviseare not supported.
The previous approach reused
gcenv.unix.cppwith#ifdef TARGET_WASMpatchesand overloaded
use_large_pages_pto mean "decommit is a no-op". This changesplits those concerns:
gc/wasm/gcenv.cppreplacesgc/unix/gcenv.unix.cppon WASM andimplements
VirtualReserve/VirtualCommit/VirtualDecommit/VirtualReleaseon top ofposix_memalign/free. WASM-specific patchesin
gcenv.unix.cppare removed.never_decommit_preplaces every "is decommit a no-op" checkthat was previously expressed as
use_large_pages_p. It is set on WASMunconditionally and on other platforms whenever
use_large_pages_pis set.gc/CMakeLists.txtnow selectsgc/wasmfor WASM targets while stillincluding
unix/configure.cmaketo generateconfig.gc.h.virtual.cppis similarly cleaned up to useposix_memalign/freeon WASM and to zero memory onMEM_DECOMMIT, so the nextVirtualCommit(a no-op on WASM) sees zeroed memory.minipal_getpagesizeabstraction is introduced. On WASM it returns acompile-time constant of 16 KB (the GC's page granularity, distinct from the
64 KB
memory.growgranularity thatgetpagesize()reports). On Windows itreturns the 4 KB constant inline. On other POSIX systems it caches
getpagesize()once per process. CoreCLR PAL callers are updated to use it.Why a separate
never_decommit_pOn large pages, decommit is a no-op because the OS pre-commits the entire
range. On WASM, decommit is a no-op because there is no way to give linear
memory back to the engine. The two cases share the same set of GC code paths
that must be skipped or modified (
decommit_ephemeral_segment_pages,decommit_step,decommit_region,decommit_heap_segment_pages,decommit_heap_segment,distribute_free_regionstail-decommit,reset_memory,virtual_commit,virtual_decommitassert), but they havenothing else in common. Reusing
use_large_pages_pon WASM was misleading andmade it easy to forget large-pages-only behavior (e.g., pre-touch). The new
flag captures only the "decommit is a no-op" semantics; non-WASM behavior is
unchanged because
never_decommit_p == use_large_pages_peverywhere exceptWASM.
Why
minipal_getpagesizeThe GC needs the OS page size to be a compile-time constant on WASM (16 KB)
so that alignment math folds.
getpagesize()on emscripten returns 64 KB(the
memory.growgranularity), which is too coarse for GC alignment andthresholds. Centralizing this in minipal lets PAL and GC agree on a single
value and avoids
#ifdefs at every call site.Notes
VirtualReserveAndCommitLargePageson WASM falls through to the regularreserve/commit path; the GC does not request large pages on WASM.
posix_memalignmay return either freshly grown linear memory (zeroed bythe WASM spec) or a recycled block from emscripten's allocator free list.
We always
memsetto zero on reserve and on decommit so callers can relyon the standard "memory starts zeroed" contract.
VirtualResetreturnsfalseon WASM, forcing the GC to use thedecommit+commit fallback (
memsetto zero) rather than relying onmadvise, which is a no-op on emscripten.