Skip to content

Coreclr wasi interpreter#126959

Closed
lewing wants to merge 18 commits intodotnet:mainfrom
lewing:coreclr-wasi-interpreter
Closed

Coreclr wasi interpreter#126959
lewing wants to merge 18 commits intodotnet:mainfrom
lewing:coreclr-wasi-interpreter

Conversation

@lewing
Copy link
Copy Markdown
Member

@lewing lewing commented Apr 15, 2026

No description provided.

lewing and others added 17 commits April 9, 2026 14:33
Enable building the CoreCLR WASM interpreter as a WASI component.
The resulting corerun.wasm (4.8MB stripped) runs on wasmtime and nesm,
with help output in ~150ms. GC initialization succeeds; runtime startup
proceeds to assembly loading.

Build system:
- Add TargetsWasi to CoreCLR supported OS (eng/Subsets.props)
- Set CLR_CMAKE_TARGET_UNIX and CLR_CMAKE_HOST_UNIX for WASI
- Add TARGET_WASI compile definition inside UNIX block
- Add WASI SDK acquisition to runtime.proj
- Import AcquireWasiSdk.targets in native.wasm.targets
- Set WASI emulation flags (_WASI_EMULATED_SIGNAL, _MMAN, etc.)
- Add CI pipeline (eng/pipelines/common/templates/wasi-coreclr-build.yml)
- Disable FEATURE_EVENT_TRACE, watchdog, superpmi, jitinterface for WASI
- Fix libunwind fallthrough for WASM in PAL CMakeLists
- Guard empty PTHREAD_LIBRARY cmake checks

WASI stub headers (pal/src/include/pal/wasi/):
- signal.h: Full POSIX signal types (siginfo_t, sigaction, SA_*, FPE_*)
- pthread.h: Unlock pthread_exit, scheduling declarations
- pwd.h, link.h, dlfcn.h, unistd.h: Missing POSIX stubs
- sys/wait.h, sys/vfs.h, sys/resource.h, ucontext.h: Missing system stubs

PAL stubs (pal/src/arch/wasm/stubs.cpp):
- DebugBreak, OutputDebugString, RaiseException
- PAL_VirtualUnwind, PAL_ProbeMemory, PAL_FreeExceptionRecords
- SEHInitializeSignals, SEHCleanupSignals, UnmaskActivationSignal
- FlushInstructionCache, GetThreadContext
- __cxa_allocate_exception, __cxa_throw, __cxa_thread_atexit
- shm_open, shm_unlink, pthread_getschedparam

WASI mmap (pal/src/arch/wasm/mmap-wasi.c):
- Custom mmap/munmap/mprotect using malloc with allocation tracking
- Replaces wasi-emulated-mman which can't handle PROT_NONE reservations
- Enables GC to allocate segments via linear memory growth

Minimal source changes:
- cgencpu.h: __builtin_frame_address fallback for WASI
- PAL stubs.cpp: Guard emscripten APIs with __EMSCRIPTEN__
- PAL thread.cpp: WASI fallback for GetStackBase/GetStackLimit
- PAL context.h: native_context_t stub for WASI (no ucontext)
- PAL file.cpp: Skip fcntl F_DUPFD_CLOEXEC on WASI
- GC gcenv.unix.cpp: __wasi__ path for memory sizing
- GC events.cpp: _WASI_EMULATED_PROCESS_CLOCKS for timed wait
- finalizerthread.cpp: Synchronous WASI finalizer path
- minipal/mutex.c: Skip recursive mutex attribute on WASI
- Minimal WASI corerun host (corerun-wasi.cpp)

Build: ./build.sh clr.native+clr.corelib+clr.nativecorelib -os wasi -c Release
Test:  wasmtime run --dir /::/ corerun.wasm --help

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Enable CoreLib loading on WASI by fixing several PAL and host issues:

- eng/native/functions.cmake: Fix install_clr for WASI executables.
  WASI SDK produces a single binary, not Emscripten's .js+.wasm pair.

- corerun-wasi.cpp: Resolve CORE_ROOT to absolute path using getcwd
  (not realpath which returns '/' on WASI). The assembly binder rejects
  relative TPA paths, so corerun must provide absolute paths.

- clrhost.cpp: On WASI (TARGET_WASI), use CORE_ROOT env var to
  determine the system directory. The runtime is statically linked
  so GetClrModulePathName can't discover the module path normally.

- file.cpp: Bypass realpath() in InternalCanonicalizeRealPath on WASI.
  WASI preopen paths are virtual directories that realpath cannot
  resolve. Pass paths through directly instead.

- map.cpp: Skip fcntl(F_DUPFD_CLOEXEC) in CreateFileMapping on WASI.
  Reuse the fd directly since WASI has no fork/exec (CLOEXEC is moot).

With these fixes, corerun.wasm successfully:
- Opens System.Private.CoreLib.dll (5MB) via WASI preopened dirs
- Maps it into memory via the malloc-based mmap
- Completes coreclr_initialize (host->Start() returns S_OK)
- Begins managed code execution in CreateAppDomainWithManager

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The malloc-based mmap caused memory corruption: the GC reserves and
releases large anonymous regions via mmap/munmap, and malloc reuses
freed addresses. File-backed mappings (assembly loading) then get
addresses within previously-freed GC regions. When the GC later
initializes those pages, it zeroes out the assembly data.

Fix: use a bump allocator backed by a 256MB arena for anonymous
mappings. This prevents address reuse, matching real mmap semantics
where munmap'd pages aren't returned to other callers. File-backed
mappings continue using malloc since they're long-lived and isolated.

With this fix, coreclr_initialize completes successfully on WASI:
- GC heap initializes in the arena
- CoreLib (5MB) loads via malloc into non-overlapping memory
- AppContext.Setup compiles and executes through the interpreter
- CreateAppDomainWithManager returns S_OK
- coreclr_execute_assembly is reached

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The 64KB TPA buffer overflowed when scanning directories with many
framework assemblies. Increase to 256KB.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove redundant AcquireWasiSdk.targets import from build-native.proj.
It's already imported through native.wasm.targets at line 3, causing
MSB4011 when building libs for WASI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WASI SDK 32 ships LLVM 22.1.0 which is within the supported version
range (8-23), but init-compiler.sh fails to find it because the WASI
SDK clang is not on PATH. The WASI SDK cmake toolchain file
(wasi-sdk-p2.cmake) already sets CMAKE_C_COMPILER and
CMAKE_CXX_COMPILER to the SDK's clang, so init-compiler.sh is
unnecessary.

Follow the same pattern used for Android builds: set __Compiler to
'default' for WASI targets so gen-buildsys.sh skips init-compiler.sh
and lets the toolchain file handle compiler selection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bump WASI SDK from 25 (LLVM 19) to 32 (LLVM 22). This gives us:
- Native WASI P2 component output from wasm-ld (no wasm-tools wrapper needed)
- Path toward -fwasm-exceptions (requires wasi-sdk-33+ with exceptions sysroot)
- LLVM 22 with better codegen

Fix LLVM 22 strictness warnings promoted to errors:
- pal_icushim_static.c: explicit UErrorCode casts, char* cast, declaration reorder
- pal_networking.c: move declaration before goto label
- stubs.cpp: remove __cxa_thread_atexit stub (now provided by SDK 32 libc++abi)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Switch from sjlj (-fexceptions) to native WASM exceptions
(-fwasm-exceptions -mllvm -wasm-use-legacy-eh=false) for WASI.

sjlj exceptions cannot handle throw-from-catch patterns needed by
the interpreter's ResumeAfterCatch mechanism. Native WASM exceptions
resolve this — throw/catch now connects properly.

Changes:
- configureplatform.cmake: -fwasm-exceptions + -wasm-use-legacy-eh=false
- CMakeLists.txt: disable -mllvm -wasm-enable-sjlj for WASI
- corerun CMakeLists.txt: link -lunwind instead of -lsetjmp,
  pass -fwasm-exceptions at link time
- stubs.cpp: remove __cxa_throw/__cxa_allocate_exception stubs
  (now provided by EH-enabled libc++abi/libunwind)

Requires EH-enabled sysroot libraries (libc++.a, libc++abi.a,
libunwind.a) built from wasi-sdk main with -DWASI_SDK_EXCEPTIONS=ON.
These will ship in wasi-sdk-33+. For now, build from source:
  cd wasi-sdk && cmake -G Ninja -B build -DWASI_SDK_EXCEPTIONS=ON
  ninja -C build

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On WASM, RtlCaptureContext/ClrCaptureContext zeros the CONTEXT since
there are no CPU registers to capture. This leaves the exception
dispatch with IP=0, SP=0 which breaks the managed stack frame iterator.

Fix: In DispatchManagedException, populate the CONTEXT from the current
interpreter frame (InterpreterFrame::SetContextToInterpMethodContextFrame)
before passing it to the EH dispatch. Also record the current interpreter
IP in the frame before throwing (INTOP_THROW sets pFrame->ip).

This provides valid IP/SP to the EH dispatch, but the managed exception
handling still needs additional work in SfiInitWorker to properly walk
interpreter frames on WASM (tracked in dotnet#113694).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The arena bump allocator in mmap-wasi.c aligned the offset within the
arena rather than the absolute address. When malloc returned a base
address that was not 64K-aligned, subsequent 64K-aligned allocations
(e.g., GC handle table segments) were misaligned. The handle table uses
pointer masking (handle & ~0xFFFF) to locate segment headers, so
misaligned segments caused NULL pHandleTable lookups and crashes during
exception handling cleanup (DestroyHandle in CallCatchFunclet).

Also enable recursive mutexes on WASI to match other platforms, since
the runtime may re-enter locks during exception handling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The stack walker finds interpreter frames through the explicit frame
chain (Thread::GetFrame()), not through the CONTEXT IP/SP. The zeroed
context from RtlCaptureContext on WASM is fine because the EH dispatch
starts on an explicit frame. Browser WASM works the same way without
this patch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Report available memory as max (4GB wasm32 limit) minus current usage,
matching Emscripten's approach. The previous code reported current memory
size as available, which is incorrect — it shrinks as memory grows and
doesn't reflect room to grow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The main corerun.cpp already has TARGET_WASM and TARGET_WASI ifdefs
that handle the WASI differences (no dlopen, static exports, etc.).
The separate corerun-wasi.cpp reimplemented TPA building, arg parsing,
and the main loop unnecessarily.

Use the unified corerun.cpp for all platforms. The only WASI-specific
addition is providing HAVE_GETAUXVAL/HAVE_DIRENT_D_TYPE defines directly
since configure.cmake can't run for cross-compilation targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The inner `if(NOT CLR_CMAKE_TARGET_WASI)` could never execute since
it was nested inside `if(CLR_CMAKE_TARGET_WASI)`. The sjlj flag is
not needed — WASI uses -fwasm-exceptions instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 15, 2026 17:51
@lewing lewing closed this Apr 15, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enable building/running CoreCLR for the WASI target by wiring up WASI SDK acquisition, adjusting CoreCLR/PAL platform conditionals, and adding WASI-specific stubs and build/pipeline support.

Changes:

  • Add/extend WASI build plumbing (SDK acquisition, cmake/msbuild targets, subsets, pipelines).
  • Introduce WASI-specific PAL headers/stubs and WASI mmap implementation, plus various WASI feature guards across CoreCLR/PAL/GC/corerun.
  • Adjust interpreter/browser/wasm conditionals to better separate Emscripten vs WASI behavior.

Reviewed changes

Copilot reviewed 50 out of 50 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/native/libs/build-native.proj Removes standalone WASI SDK import; relies on shared wasm targets.
src/native/libs/System.Native/pal_networking.c Minor variable initialization tweak.
src/native/libs/System.Globalization.Native/pal_icushim_static.c ICU status init and malloc cast cleanup.
src/coreclr/vm/wasm/cgencpu.h Guard Emscripten stack include; WASI fallback SP logic.
src/coreclr/vm/prestub.cpp Refine interpreter ETW handling with portable entrypoints.
src/coreclr/vm/finalizerthread.cpp WASI synchronous finalization path under wasm.
src/coreclr/utilcode/clrhost.cpp WASI module path derived from CORE_ROOT.
src/coreclr/tools/CMakeLists.txt Skip superpmi for WASI/browser builds.
src/coreclr/runtime.proj Add AcquireWasiSdk dependency for WASI runtime build.
src/coreclr/pal/src/thread/thread.cpp WASI stack base/limit handling adjustments.
src/coreclr/pal/src/thread/context.cpp Disable remote register capture on WASI.
src/coreclr/pal/src/map/map.cpp WASI fcntl/dup workaround for file mappings.
src/coreclr/pal/src/include/pal/wasi/unistd.h WASI unistd wrapper with stubbed POSIX APIs.
src/coreclr/pal/src/include/pal/wasi/ucontext.h WASI ucontext stub header.
src/coreclr/pal/src/include/pal/wasi/sys/wait.h WASI sys/wait stub header.
src/coreclr/pal/src/include/pal/wasi/sys/vfs.h WASI sys/vfs stub header.
src/coreclr/pal/src/include/pal/wasi/sys/resource.h WASI sys/resource wrapper for rlimit.
src/coreclr/pal/src/include/pal/wasi/signal.h WASI signal wrapper defining missing POSIX types/APIs.
src/coreclr/pal/src/include/pal/wasi/pwd.h WASI pwd stub header.
src/coreclr/pal/src/include/pal/wasi/pthread.h WASI pthread wrapper for hidden decls.
src/coreclr/pal/src/include/pal/wasi/link.h WASI link stub for dl_iterate_phdr types.
src/coreclr/pal/src/include/pal/wasi/dlfcn.h WASI dlfcn wrapper for Dl_info/dladdr.
src/coreclr/pal/src/include/pal/context.h Add WASI native_context_t fallback.
src/coreclr/pal/src/file/file.cpp WASI fd duplication workaround for std handles/realpath.
src/coreclr/pal/src/exception/seh.cpp Disable seh-unwind.cpp inclusion on WASI.
src/coreclr/pal/src/configure.cmake Make pthread feature checks conditional; add WASI sched flags.
src/coreclr/pal/src/arch/wasm/stubs.cpp Emscripten guards + WASI stubs for missing APIs.
src/coreclr/pal/src/arch/wasm/mmap-wasi.c New WASI mmap/munmap implementation.
src/coreclr/pal/src/CMakeLists.txt WASI source selection + avoid unwind/dac pieces on wasm.
src/coreclr/hosts/corerun/dotenv.cpp Disable corerun dotenv self-test on WASI.
src/coreclr/hosts/corerun/corerun.hpp Avoid config.h for wasm; define minimal feature macros.
src/coreclr/hosts/corerun/corerun.cpp Disable corerun self-test path on WASI.
src/coreclr/hosts/corerun/CMakeLists.txt WASI link options/libs; avoid dl/rdynamic on wasm.
src/coreclr/gc/unix/gcenv.unix.cpp WASI mman define handling + available memory logic.
src/coreclr/gc/unix/events.cpp Allow process-clock emulation define for waits.
src/coreclr/gc/unix/configure.cmake Guard pthread checks on presence of pthread library.
src/coreclr/debug/debug-pal/unix/twowaypipe.cpp WASI mkfifo stub for debug transport.
src/coreclr/debug/debug-pal/CMakeLists.txt Disable diagnostic server reference on WASI.
src/coreclr/clrfeatures.cmake Disable event trace default for WASI.
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj Include Unix/browser interop stubs for WASI too.
src/coreclr/CMakeLists.txt WASI compile defs + include stub headers; skip tools/watchdog.
eng/pipelines/runtime.yml Add WASI CoreCLR build job template.
eng/pipelines/common/templates/wasi-coreclr-build.yml New pipeline template for WASI CoreCLR build-only job.
eng/native/functions.cmake Install rule adjustment for WASI wasm executables.
eng/native/configureplatform.cmake Treat WASI as UNIX; set WASI target flags.
eng/native/configurecompiler.cmake Emit TARGET_WASI compile definition.
eng/native/build-commons.sh Avoid overriding CC/CXX for WASI builds.
eng/native.wasm.targets Import AcquireWasiSdk + add WASI CoreCLR cmake args.
eng/Subsets.props Enable CoreCLR subsets and defaults for WASI.
eng/AcquireWasiSdk.targets Update runtime-local WASI SDK version.

Comment on lines +2360 to +2364
#elif defined(__EMSCRIPTEN__)
stackBase = (void*)emscripten_stack_get_base();
#endif // TARGET_BROWSER
#else // WASI
stackBase = __builtin_frame_address(0);
#endif // TARGET_WASM
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the WASI path, GetStackBase returns __builtin_frame_address(0), which is the current frame address, not the thread’s stack base. Components that rely on PAL_GetStackBase() for stack scanning/overflow checks will get an incorrect range. Consider using toolchain/linker-provided stack bound symbols (e.g., __stack_high/__stack_low or equivalent for wasi-sdk) or another reliable WASI-specific mechanism rather than the current frame address.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This response was generated with assistance from GitHub Copilot.

Good catch. __builtin_frame_address(0) is an approximation — WASI/wasm32 doesn't expose linker-provided stack bound symbols like __stack_high for wasip2. Added a comment explaining this is an approximation. The value is cached on first call and used for the lifetime of the (single) thread, so it's close to the initial stack top.

Comment on lines +469 to +477
#ifdef TARGET_WASI
// WASI's fcntl may not support F_DUPFD_CLOEXEC at runtime.
// CLOEXEC is irrelevant on WASI (no exec). Use fd_fdstat_get + path_open
// workaround: just reuse the fd directly without duplication since
// WASI doesn't support fork/exec anyway.
UnixFd = pFileLocalData->unix_fd;
#else
UnixFd = fcntl(pFileLocalData->unix_fd, F_DUPFD_CLOEXEC, 0); // dup, but with CLOEXEC
#endif
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On WASI, assigning UnixFd = pFileLocalData->unix_fd means the file mapping now owns the original file descriptor. FileMappingCleanupRoutine later close()s pLocalData->UnixFd, which will unexpectedly close the underlying handle used by hFile and any other users. Use a real dup operation on WASI (e.g., dup()/dup3() if available) or track ownership so the mapping cleanup doesn’t close a borrowed descriptor.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This response was generated with assistance from GitHub Copilot.

Valid concern. WASI's fcntl doesn't implement F_DUPFD_CLOEXEC (or F_DUPFD) at runtime despite the constant being defined at compile time, and dup() isn't available in wasip2 headers. The fd sharing is safe here because WASI is single-threaded with no fork/exec, so CLOEXEC is irrelevant and the file mapping lifetime matches the source handle. Updated the comment to explain this rationale.

Comment on lines 2438 to +2445
/* duplicate the FILE *, so that we can fclose() in FILECloseHandle without
closing the original */
#ifdef TARGET_WASI
// WASI: fcntl F_DUPFD_CLOEXEC not supported; reuse fd directly (single-threaded, no exec)
new_fd = fileno(stream);
#else
new_fd = fcntl(fileno(stream), F_DUPFD_CLOEXEC, 0); // dup, but with CLOEXEC
#endif
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WASI path sets new_fd = fileno(stream) instead of duplicating the descriptor. This makes the PAL file handle for stdin/stdout/stderr own and potentially close() the original stdio fd (e.g., on failure paths and when the handle is later closed), which can break subsequent I/O. Use an actual descriptor duplication mechanism on WASI, or ensure the PAL handle is marked non-owning so it won’t close the original fd.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This response was generated with assistance from GitHub Copilot.

Same situation as the map.cpp case — fcntl F_DUPFD* and dup() aren't available on wasip2. The stdio fds (0/1/2) are never closed by the PAL on WASI, so sharing is safe. Updated comment to explain the safety rationale.

<Project>
<PropertyGroup>
<_WasiSdkVersion>25.0</_WasiSdkVersion>
<_WasiSdkVersion>32.0</_WasiSdkVersion>
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_WasiSdkVersion was updated to 32.0, but src/mono/wasi/build/WasiApp.targets still hard-codes an expected WASI SDK version of 25.0. This will cause Mono WASI builds (and any tooling that validates WASI_SDK_PATH/VERSION) to fail when using the runtime-local SDK acquired here. Please update the other expected-version checks (or centralize the version) so all WASI build paths agree on a single SDK version.

Suggested change
<_WasiSdkVersion>32.0</_WasiSdkVersion>
<WasiSdkVersion Condition="'$(WasiSdkVersion)' == ''">32.0</WasiSdkVersion>
<_WasiSdkVersion>$(WasiSdkVersion)</_WasiSdkVersion>

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This response was generated with assistance from GitHub Copilot.

The Mono WASI build uses its own SDK acquisition path and isn't affected by this CoreCLR-specific version bump. We have a separate PR (dotnet/wasi-sdk#239) to update the dotnet fork of wasi-sdk to upstream v33 which will unify SDK versions across both runtimes.

Comment on lines +2415 to +2417
#else // WASI
stackLimit = (void*)sizeof(size_t);
#endif // TARGET_WASM
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For WASI, GetStackLimit() sets stackLimit to (void*)sizeof(size_t), which is just a small constant cast to a pointer and not an actual stack boundary. This can break stack range calculations and stack overflow detection. Please compute a real lower stack bound for WASI (or, if unavailable, adjust callers to use SP-based scanning and return a clearly defined sentinel that won’t be treated as an address).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This response was generated with assistance from GitHub Copilot.

Fixed. Now computes stack limit as frame_address - 8MB (matching the -Wl,-z,stack-size=8388608 set at link time) with a floor at sizeof(size_t) to avoid wrapping to zero.

Comment on lines 485 to 516
@@ -509,6 +510,7 @@ BOOL CONTEXT_GetRegisters(DWORD processId, LPCONTEXT lpContext)
#undef ASSIGN_REG

CONTEXTFromNativeContext(&registers, lpContext, lpContext->ContextFlags);
#endif // !TARGET_WASI
}

bRet = TRUE;
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When processId != GetCurrentProcessId() on WASI, the ucontext_t registers block is compiled out, but the function still falls through and returns TRUE without populating lpContext. This results in callers observing success with uninitialized/incorrect register state. Return FALSE (and set an appropriate last error) or explicitly zero/initialize lpContext similarly to the CONTEXT_GetThreadContext fallback path.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This response was generated with assistance from GitHub Copilot.

Fixed. Now returns FALSE immediately for non-current process on WASI.

Comment on lines +31 to +34
};

static inline int dl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data) { (void)callback; (void)data; return 0; }

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This header uses size_t in the dl_iterate_phdr callback signature but doesn’t include a header that defines it. Add an include like <stddef.h> (or <sys/types.h>) to ensure size_t is available for both C and C++ compilation.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This response was generated with assistance from GitHub Copilot.

Fixed. Added #include <stddef.h>.

#ifdef TARGET_WASI
extern "C" int pthread_getschedparam(pthread_t, int *policy, struct sched_param *param)
{
if (policy) *policy = 0;
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pthread_getschedparam returns success but leaves *param uninitialized. Callers expecting sched_param fields to be set may read garbage. Either populate *param with sensible defaults (e.g., zero priority) or return an error code (e.g., ENOTSUP) to signal the API isn’t implemented on WASI.

Suggested change
if (policy) *policy = 0;
if (policy) *policy = 0;
if (param) *param = sched_param{};

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This response was generated with assistance from GitHub Copilot.

Fixed. Now zero-initializes *param with memset.

Comment on lines +21 to +58
// 256MB arena — sufficient for GC heap + runtime structures.
// On WASM this is virtual (backed by memory.grow on demand).
#define ANON_ARENA_SIZE (256 * 1024 * 1024)

static char *s_arena_base;
static size_t s_arena_offset;

static void ensure_arena(void) {
if (s_arena_base == NULL) {
s_arena_base = (char *)malloc(ANON_ARENA_SIZE);
if (s_arena_base == NULL) {
fprintf(stderr, "WASI mmap: failed to allocate %d byte arena\n", ANON_ARENA_SIZE);
abort();
}
s_arena_offset = 0;
}
}

// Allocate from the bump arena with alignment. Never reuses freed addresses.
static void *arena_alloc(size_t length, size_t align) {
ensure_arena();
// Align the absolute address, not just the offset
uintptr_t base = (uintptr_t)s_arena_base;
uintptr_t current = base + s_arena_offset;
uintptr_t aligned_addr = (current + align - 1) & ~(align - 1);
size_t aligned_offset = aligned_addr - base;
if (aligned_offset + length > ANON_ARENA_SIZE) {
return NULL;
}
void *ptr = (void *)aligned_addr;
s_arena_offset = aligned_offset + length;
return ptr;
}

static int is_arena_ptr(void *addr) {
if (s_arena_base == NULL) return 0;
return (char *)addr >= s_arena_base &&
(char *)addr < s_arena_base + ANON_ARENA_SIZE;
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensure_arena() allocates a fixed 256MB arena eagerly via malloc(ANON_ARENA_SIZE). On wasm32/WASI this directly consumes linear memory and can cause large, immediate memory growth or OOM even for small apps. Consider allocating the arena lazily in smaller chunks (or growing it) and/or sizing it based on configured limits rather than a hard-coded 256MB constant.

Suggested change
// 256MB arena — sufficient for GC heap + runtime structures.
// On WASM this is virtual (backed by memory.grow on demand).
#define ANON_ARENA_SIZE (256 * 1024 * 1024)
static char *s_arena_base;
static size_t s_arena_offset;
static void ensure_arena(void) {
if (s_arena_base == NULL) {
s_arena_base = (char *)malloc(ANON_ARENA_SIZE);
if (s_arena_base == NULL) {
fprintf(stderr, "WASI mmap: failed to allocate %d byte arena\n", ANON_ARENA_SIZE);
abort();
}
s_arena_offset = 0;
}
}
// Allocate from the bump arena with alignment. Never reuses freed addresses.
static void *arena_alloc(size_t length, size_t align) {
ensure_arena();
// Align the absolute address, not just the offset
uintptr_t base = (uintptr_t)s_arena_base;
uintptr_t current = base + s_arena_offset;
uintptr_t aligned_addr = (current + align - 1) & ~(align - 1);
size_t aligned_offset = aligned_addr - base;
if (aligned_offset + length > ANON_ARENA_SIZE) {
return NULL;
}
void *ptr = (void *)aligned_addr;
s_arena_offset = aligned_offset + length;
return ptr;
}
static int is_arena_ptr(void *addr) {
if (s_arena_base == NULL) return 0;
return (char *)addr >= s_arena_base &&
(char *)addr < s_arena_base + ANON_ARENA_SIZE;
// Cap anonymous mappings at 256MB total, but allocate backing storage lazily
// in small chunks so small WASI apps do not eagerly consume large amounts of
// linear memory on the first anonymous mapping.
#define ANON_ARENA_SIZE (256 * 1024 * 1024)
#define ANON_ARENA_CHUNK_SIZE (64 * 1024)
#define ANON_ARENA_MAX_CHUNKS (ANON_ARENA_SIZE / ANON_ARENA_CHUNK_SIZE)
typedef struct
{
char *base;
size_t size;
size_t offset;
} anon_arena_chunk;
static anon_arena_chunk s_arena_chunks[ANON_ARENA_MAX_CHUNKS];
static size_t s_arena_chunk_count;
static size_t s_arena_total_size;
static void ensure_arena(void) {
// Chunks are allocated lazily by arena_alloc().
}
static anon_arena_chunk *arena_alloc_chunk(size_t min_length, size_t align) {
size_t chunk_size;
anon_arena_chunk *chunk;
if (s_arena_chunk_count >= ANON_ARENA_MAX_CHUNKS) {
return NULL;
}
chunk_size = ANON_ARENA_CHUNK_SIZE;
if (min_length > chunk_size || align > 1) {
size_t required = min_length + align - 1;
if (required > chunk_size) {
chunk_size = required;
}
}
if (chunk_size > ANON_ARENA_SIZE - s_arena_total_size) {
return NULL;
}
chunk = &s_arena_chunks[s_arena_chunk_count];
chunk->base = (char *)malloc(chunk_size);
if (chunk->base == NULL) {
fprintf(stderr, "WASI mmap: failed to allocate %zu byte arena chunk\n", chunk_size);
abort();
}
chunk->size = chunk_size;
chunk->offset = 0;
s_arena_chunk_count++;
s_arena_total_size += chunk_size;
return chunk;
}
// Allocate from lazily created bump arena chunks with alignment. Never reuses
// freed addresses.
static void *arena_alloc(size_t length, size_t align) {
anon_arena_chunk *chunk;
uintptr_t base;
uintptr_t current;
uintptr_t aligned_addr;
size_t aligned_offset;
ensure_arena();
if (align == 0) {
align = 1;
}
if (s_arena_chunk_count != 0) {
chunk = &s_arena_chunks[s_arena_chunk_count - 1];
base = (uintptr_t)chunk->base;
current = base + chunk->offset;
aligned_addr = (current + align - 1) & ~(align - 1);
aligned_offset = aligned_addr - base;
if (aligned_offset + length <= chunk->size) {
chunk->offset = aligned_offset + length;
return (void *)aligned_addr;
}
}
chunk = arena_alloc_chunk(length, align);
if (chunk == NULL) {
return NULL;
}
base = (uintptr_t)chunk->base;
aligned_addr = (base + align - 1) & ~(align - 1);
aligned_offset = aligned_addr - base;
if (aligned_offset + length > chunk->size) {
return NULL;
}
chunk->offset = aligned_offset + length;
return (void *)aligned_addr;
}
static int is_arena_ptr(void *addr) {
size_t i;
for (i = 0; i < s_arena_chunk_count; i++) {
char *base = s_arena_chunks[i].base;
if ((char *)addr >= base && (char *)addr < base + s_arena_chunks[i].size) {
return 1;
}
}
return 0;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This response was generated with assistance from GitHub Copilot.

Good suggestion. On wasm32, malloc of 256MB doesn't immediately consume physical memory — it triggers memory.grow lazily as pages are touched. The arena is bumped incrementally so only the used portion is backed by real pages. That said, a lazy chunked approach would be cleaner and we'll consider it as a follow-up optimization.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This response was generated with assistance from GitHub Copilot.

Correction to my previous reply: wasm memory.grow commits pages immediately — there's no lazy page fault mechanism in wasm linear memory. So the 256MB malloc does grow linear memory upfront. A chunked approach that grows the arena in smaller increments as needed would save real memory for smaller apps. Filed as a follow-up improvement.

- context.cpp: Return FALSE for non-current process on WASI instead of
  falling through with uninitialized context
- thread.cpp: Improve GetStackBase/GetStackLimit comments; compute stack
  limit from frame address minus configured stack size (8MB)
- file.cpp, map.cpp: Improve fd-sharing comments explaining why reuse is
  safe on WASI (no fork/exec, no CLOEXEC needed)
- link.h: Add missing stddef.h include for size_t
- stubs.cpp: Zero-initialize sched_param in pthread_getschedparam stub

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lewing lewing reopened this Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants