diff --git a/eng/Subsets.props b/eng/Subsets.props
index def130086d3543..3f8bf80d4b9da8 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -33,6 +33,7 @@
<_CoreCLRSupportedOS Condition="'$(TargetsAndroid)' == 'true' and '$(TargetArchitecture)' != 'x86'">true
<_CoreCLRSupportedOS Condition="'$(TargetsBrowser)' == 'true'">true
+ <_CoreCLRSupportedOS Condition="'$(TargetsWasi)' == 'true'">true
<_CoreCLRSupportedOS Condition="'$(TargetsAppleMobile)' == 'true'">true
<_CoreCLRSupportedArch Condition="'$(TargetArchitecture)' != 'ppc64le' and '$(TargetArchitecture)' != 's390x'">true
@@ -61,6 +62,7 @@
CoreCLR
Mono
Mono
+ Mono
$(DefaultPrimaryRuntimeFlavor)
@@ -98,6 +100,7 @@
CoreCLR
CoreCLR
CoreCLR
+ CoreCLR
Mono
Mono
$(PrimaryRuntimeFlavor)
@@ -109,6 +112,7 @@
clr.native+clr.corelib+clr.tools+clr.nativecorelib+clr.packages+clr.nativeaotlibs+clr.crossarchtools
clr.native+clr.corelib+clr.tools+clr.nativecorelib+clr.packages+clr.nativeaotlibs+clr.crossarchtools
provision.emsdk+clr.native+clr.corelib+clr.nativecorelib+clr.tools+clr.packages+clr.crossarchtools+libs.native+host.native
+ clr.native+clr.corelib+clr.nativecorelib+clr.tools+clr.packages+clr.crossarchtools+libs.native
clr.iltools+clr.packages
diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets
index 6dd6ce0407489d..5f71357d1e165e 100644
--- a/eng/liveBuilds.targets
+++ b/eng/liveBuilds.targets
@@ -214,6 +214,14 @@
$(LibrariesNativeArtifactsPath)libbrotlicommon.a;
$(LibrariesNativeArtifactsPath)libbrotlidec.a;
$(LibrariesNativeArtifactsPath)libbrotlienc.a" />
+
+
+
-
+
+
+
+
diff --git a/eng/native.wasm.targets b/eng/native.wasm.targets
index 42f9c581114313..74beb2014d1578 100644
--- a/eng/native.wasm.targets
+++ b/eng/native.wasm.targets
@@ -1,5 +1,7 @@
+
+
+
+
diff --git a/eng/native/configurecompiler.cmake b/eng/native/configurecompiler.cmake
index 54353bef735127..cee494e67091d1 100644
--- a/eng/native/configurecompiler.cmake
+++ b/eng/native/configurecompiler.cmake
@@ -856,6 +856,9 @@ if(CLR_CMAKE_TARGET_UNIX)
if(CLR_CMAKE_TARGET_BROWSER)
add_compile_definitions($<$>>:TARGET_BROWSER>)
endif()
+ if(CLR_CMAKE_TARGET_WASI)
+ add_compile_definitions($<$>>:TARGET_WASI>)
+ endif()
elseif(CLR_CMAKE_TARGET_WASI)
add_compile_definitions($<$>>:TARGET_WASI>)
else(CLR_CMAKE_TARGET_UNIX)
diff --git a/eng/native/configureplatform.cmake b/eng/native/configureplatform.cmake
index bdb9837b09d53a..c6fa5b83e001a0 100644
--- a/eng/native/configureplatform.cmake
+++ b/eng/native/configureplatform.cmake
@@ -221,6 +221,7 @@ endif(CLR_CMAKE_HOST_OS STREQUAL emscripten)
if(CLR_CMAKE_TARGET_OS STREQUAL wasi)
set(CLR_CMAKE_HOST_WASI 1)
+ set(CLR_CMAKE_HOST_UNIX 1)
endif(CLR_CMAKE_TARGET_OS STREQUAL wasi)
#--------------------------------------------
@@ -436,6 +437,7 @@ if(CLR_CMAKE_TARGET_OS STREQUAL emscripten OR CLR_CMAKE_TARGET_OS STREQUAL brows
endif(CLR_CMAKE_TARGET_OS STREQUAL emscripten OR CLR_CMAKE_TARGET_OS STREQUAL browser)
if(CLR_CMAKE_TARGET_OS STREQUAL wasi)
+ set(CLR_CMAKE_TARGET_UNIX 1)
set(CLR_CMAKE_TARGET_WASI 1)
endif(CLR_CMAKE_TARGET_OS STREQUAL wasi)
@@ -522,7 +524,36 @@ else()
add_compile_options(-msimd128)
endif()
if(CLR_CMAKE_TARGET_WASI)
- add_compile_options(-fexceptions)
+ # Native wasm exceptions: sjlj cannot handle the interpreter's
+ # ResumeAfterCatch (throw-from-catch). -wasm-use-legacy-eh=false
+ # selects the new try_table/throw_ref proposal that wasmtime 45+
+ # supports natively; legacy would require --legacy-exceptions.
+ add_compile_options(-fwasm-exceptions)
+ add_compile_options(-mllvm -wasm-use-legacy-eh=false)
+
+ # Suppress CMake's default -lstdc++; wasi-sdk has libc++ only and
+ # we link the eh-flavored variant explicitly below.
+ set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "" CACHE STRING "" FORCE)
+ set(CMAKE_CXX_STANDARD_LIBRARIES "" CACHE STRING "" FORCE)
+ set(CMAKE_C_IMPLICIT_LINK_LIBRARIES "" CACHE STRING "" FORCE)
+ set(CMAKE_C_STANDARD_LIBRARIES "" CACHE STRING "" FORCE)
+
+ add_link_options(-fwasm-exceptions)
+ add_link_options(-mllvm -wasm-use-legacy-eh=false)
+ add_link_options(-Wno-unused-command-line-argument)
+ add_link_options(-Wl,--error-limit=0)
+
+ add_link_options(-lc)
+ add_link_options(-lc++)
+ add_link_options(-lc++abi)
+ add_link_options(-lunwind)
+ add_link_options(-ldl)
+
+ # WASI emulated libc helpers the CoreCLR PAL pulls in.
+ add_link_options(-lwasi-emulated-process-clocks)
+ add_link_options(-lwasi-emulated-signal)
+ add_link_options(-lwasi-emulated-mman)
+ add_link_options(-lwasi-emulated-getpid)
endif()
endif()
endif()
diff --git a/eng/native/functions.cmake b/eng/native/functions.cmake
index 5064f51b101220..f7b11dcab7cb4c 100644
--- a/eng/native/functions.cmake
+++ b/eng/native/functions.cmake
@@ -565,8 +565,8 @@ function(install_clr)
foreach(destination ${destinations})
# CMake bug with executable WASM outputs - https://gitlab.kitware.com/cmake/cmake/-/issues/20745
- if (CLR_CMAKE_TARGET_ARCH_WASM AND "${targetType}" STREQUAL "EXECUTABLE")
- # Use install FILES since these are WASM assets that aren't executable.
+ if (CLR_CMAKE_TARGET_BROWSER AND "${targetType}" STREQUAL "EXECUTABLE")
+ # Emscripten produces a .js + .wasm pair; install both as data files.
install(FILES
"$/${targetName}.js"
"$/${targetName}.wasm"
@@ -580,6 +580,13 @@ function(install_clr)
endif()
"
COMPONENT ${INSTALL_CLR_COMPONENT} ${INSTALL_CLR_OPTIONAL})
+ elseif (CLR_CMAKE_TARGET_WASI AND "${targetType}" STREQUAL "EXECUTABLE")
+ # wasi-sdk produces a single .wasm binary (the executable itself, no
+ # extension). Install it as a data file — making it PROGRAMS would
+ # require execute permission on a wasm file which the host runtime
+ # (wasmtime) doesn't need.
+ install(FILES $
+ DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT} ${INSTALL_CLR_OPTIONAL})
else()
# We don't need to install the export libraries for our DLLs
# since they won't be directly linked against.
diff --git a/eng/pipelines/common/templates/wasi-wasm-coreclr-build-only.yml b/eng/pipelines/common/templates/wasi-wasm-coreclr-build-only.yml
new file mode 100644
index 00000000000000..e8593a9986cee3
--- /dev/null
+++ b/eng/pipelines/common/templates/wasi-wasm-coreclr-build-only.yml
@@ -0,0 +1,38 @@
+parameters:
+ condition: false
+ extraBuildArgs: ''
+ isExtraPlatformsBuild: false
+ nameSuffix: ''
+ platforms: []
+ publishArtifactsForWorkload: false
+
+jobs:
+
+#
+# Build CoreCLR + libraries for WASI (wasm32-wasi).
+#
+# Produces a runtime pack + corerun.wasm artifact under
+# wasi.wasm.Release. The companion pipeline that actually runs the
+# src/tests CoreCLR runtime tests on Helix under wasmtime lives in
+# wasi-wasm-coreclr-runtime-tests.yml (the 'wasi_wasm' Helix queue is
+# already mapped to the ubuntu-26.04-helix-webassembly-amd64 image,
+# which ships wasmtime — see eng/pipelines/libraries/helix-queues-setup.yml).
+#
+- template: /eng/pipelines/common/platform-matrix.yml
+ parameters:
+ jobTemplate: /eng/pipelines/common/global-build-job.yml
+ buildConfig: Release
+ runtimeFlavor: CoreCLR
+ platforms: ${{ parameters.platforms }}
+ jobParameters:
+ isExtraPlatforms: ${{ parameters.isExtraPlatformsBuild }}
+ testGroup: innerloop
+ nameSuffix: CoreCLR${{ parameters.nameSuffix }}_BuildOnly
+ buildArgs: -s clr+libs+packs -c $(_BuildConfig) ${{ parameters.extraBuildArgs }} /p:TestAssemblies=false
+ timeoutInMinutes: 120
+ condition: ${{ parameters.condition }}
+ postBuildSteps:
+ - template: /eng/pipelines/common/wasm-post-build-steps.yml
+ parameters:
+ publishArtifactsForWorkload: ${{ parameters.publishArtifactsForWorkload }}
+ publishWBT: false
diff --git a/eng/pipelines/common/templates/wasi-wasm-coreclr-runtime-tests.yml b/eng/pipelines/common/templates/wasi-wasm-coreclr-runtime-tests.yml
new file mode 100644
index 00000000000000..10753d02dca48e
--- /dev/null
+++ b/eng/pipelines/common/templates/wasi-wasm-coreclr-runtime-tests.yml
@@ -0,0 +1,68 @@
+parameters:
+ alwaysRun: false
+ isExtraPlatformsBuild: false
+ isWasmOnlyBuild: false
+ platforms: []
+ extraBuildArgs: ''
+
+jobs:
+
+#
+# Build CoreCLR + libraries for WASI and run a subset of src/tests on
+# Helix under wasmtime. Mirrors wasm-runtime-tests.yml (Mono variant),
+# with runtimeFlavor=coreclr and a curated -tree: subset (see the
+# testBuildArgs below).
+#
+# Merged-runner mode; xharness dispatches via helixpublishwitharcade.proj.
+# Reuses the wasi_wasm helix queue (ubuntu-26.04-helix-webassembly-amd64).
+#
+
+- template: /eng/pipelines/common/platform-matrix.yml
+ parameters:
+ jobTemplate: /eng/pipelines/common/global-build-job.yml
+ helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml
+ buildConfig: Release
+ runtimeFlavor: coreclr
+ platforms: ${{ parameters.platforms }}
+ variables:
+ - name: alwaysRunVar
+ value: ${{ parameters.alwaysRun }}
+ - name: timeoutPerTestInMinutes
+ value: 10
+ - name: timeoutPerTestCollectionInMinutes
+ value: 200
+ - name: shouldRunOnDefaultPipelines
+ value: $[
+ or(
+ eq(variables['wasmDarcDependenciesChanged'], true),
+ eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_illink.containsChange'], true),
+ eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_wasm_coreclr_runtimetests.containsChange'], true))
+ ]
+ jobParameters:
+ testGroup: innerloop
+ isExtraPlatforms: ${{ parameters.isExtraPlatformsBuild }}
+ nameSuffix: CoreCLR_WASI_RuntimeTests
+ buildArgs: -s clr+libs+packs -c $(_BuildConfig) ${{ parameters.extraBuildArgs }} /p:TestAssemblies=false
+ timeoutInMinutes: 180
+ condition: >-
+ or(
+ eq(variables['alwaysRunVar'], true),
+ eq(variables['isDefaultPipeline'], variables['shouldRunOnDefaultPipelines']))
+ postBuildSteps:
+ - template: /eng/pipelines/common/templates/runtimes/build-runtime-tests-and-send-to-helix.yml
+ parameters:
+ creator: dotnet-bot
+ testRunNamePrefixSuffix: CoreCLR_WASI_$(_BuildConfig)
+ # Curated test-tree subset -- expand as gates stabilize.
+ testBuildArgs: >-
+ -priority1
+ -tree:JIT/CodeGenBringUpTests
+ -tree:JIT/Generics
+ -tree:JIT/Directed
+ -tree:JIT/opt
+ -tree:JIT/jit64
+ -tree:Loader
+ -tree:Exceptions
+ -tree:reflection
+ extraVariablesTemplates:
+ - template: /eng/pipelines/common/templates/runtimes/test-variables.yml
diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml
index 231972e1b90084..3dc0b8923fa6fa 100644
--- a/eng/pipelines/runtime.yml
+++ b/eng/pipelines/runtime.yml
@@ -142,6 +142,29 @@ extends:
eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_wasmbuildtests.containsChange'], true),
eq(variables['isRollingBuild'], true))
+ # Release build of wasi wasm for CoreCLR (build-only — the
+ # runtime tests run in a separate job dispatched to Helix from
+ # wasi-wasm-coreclr-runtime-tests.yml below).
+ - template: /eng/pipelines/common/templates/wasi-wasm-coreclr-build-only.yml
+ parameters:
+ platforms:
+ - wasi_wasm
+ publishArtifactsForWorkload: true
+ condition: >-
+ or(
+ eq(variables['wasmDarcDependenciesChanged'], true),
+ eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_illink.containsChange'], true),
+ eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_wasm_coreclr_runtimetests.containsChange'], true),
+ eq(variables['isRollingBuild'], true))
+
+ # CoreCLR src/tests subset run on wasmtime via Helix
+ # (wasi_wasm queue -> ubuntu-26.04-helix-webassembly-amd64 image).
+ - template: /eng/pipelines/common/templates/wasi-wasm-coreclr-runtime-tests.yml
+ parameters:
+ platforms:
+ - wasi_wasm
+ alwaysRun: ${{ variables.isRollingBuild }}
+
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt
index 60fe8cee3fcac4..968705461f4e9d 100644
--- a/src/coreclr/CMakeLists.txt
+++ b/src/coreclr/CMakeLists.txt
@@ -30,10 +30,28 @@ if(CORECLR_SET_RPATH)
set(MACOSX_RPATH ON)
endif(CORECLR_SET_RPATH)
-if(CLR_CMAKE_HOST_MACCATALYST OR CLR_CMAKE_HOST_IOS OR CLR_CMAKE_HOST_TVOS OR CLR_CMAKE_HOST_BROWSER OR CLR_CMAKE_HOST_ANDROID)
+if(CLR_CMAKE_HOST_MACCATALYST OR CLR_CMAKE_HOST_IOS OR CLR_CMAKE_HOST_TVOS OR CLR_CMAKE_HOST_BROWSER OR CLR_CMAKE_HOST_WASI OR CLR_CMAKE_HOST_ANDROID)
set(FEATURE_STANDALONE_GC 0)
endif()
+if(CLR_CMAKE_TARGET_WASI)
+ add_compile_definitions(_WASI_EMULATED_SIGNAL)
+ add_compile_definitions(_WASI_EMULATED_PROCESS_CLOCKS)
+ add_compile_definitions(_WASI_EMULATED_MMAN)
+ add_compile_definitions(_WASI_EMULATED_GETPID)
+ # EventPipe / diag-server PAL require sockets we have not validated
+ # for the WASI bring-up; revisit once corerun.wasm runs.
+ set(FEATURE_EVENT_TRACE 0)
+ set(FEATURE_PERFTRACING 0)
+ include_directories(BEFORE SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/pal/src/include/pal/wasi)
+ # Cross-compile try_run() answers — wasm32 binaries cannot execute on
+ # the build host, so pre-seed the cache.
+ set(REALPATH_SUPPORTS_NONEXISTENT_FILES_EXITCODE 1 CACHE STRING "" FORCE)
+ set(HAVE_WORKING_GETTIMEOFDAY_EXITCODE 0 CACHE STRING "" FORCE)
+ set(HAVE_WORKING_CLOCK_GETTIME_EXITCODE 0 CACHE STRING "" FORCE)
+ set(HAVE_PROCFS_STATM_EXITCODE 1 CACHE STRING "" FORCE)
+endif()
+
OPTION(CLR_CMAKE_ENABLE_CODE_COVERAGE "Enable code coverage" OFF)
#----------------------------------------------------
@@ -84,6 +102,11 @@ if(NOT CLR_CROSS_COMPONENTS_BUILD)
set(STATIC_LIB_DESTINATION ${CMAKE_BINARY_DIR}/libs-native)
set(CORERUN_LIBS_ONLY 1)
endif()
+ if(CLR_CMAKE_TARGET_WASI)
+ # Side-build libs to avoid collision with the published libs.native artifact.
+ set(STATIC_LIB_DESTINATION ${CMAKE_BINARY_DIR}/libs-native)
+ set(CORERUN_LIBS_ONLY 1)
+ endif()
add_subdirectory(${CLR_SRC_NATIVE_DIR}/libs libs-native)
endif()
@@ -119,7 +142,7 @@ endif()
#----------------------------------------------------
# Build the test watchdog alongside the CLR
#----------------------------------------------------
-if(NOT CLR_CMAKE_HOST_MACCATALYST AND NOT CLR_CMAKE_HOST_IOS AND NOT CLR_CMAKE_HOST_TVOS AND NOT CLR_CMAKE_TARGET_BROWSER)
+if(NOT CLR_CMAKE_HOST_MACCATALYST AND NOT CLR_CMAKE_HOST_IOS AND NOT CLR_CMAKE_HOST_TVOS AND NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI)
add_subdirectory("${CLR_SRC_NATIVE_DIR}/watchdog" test-watchdog)
endif()
@@ -149,7 +172,7 @@ endif()
include_directories("pal/prebuilt/inc")
include_directories(${CLR_ARTIFACTS_OBJ_DIR})
-if (NOT CLR_CMAKE_TARGET_BROWSER OR CLR_CROSS_COMPONENTS_BUILD)
+if ((NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI) OR CLR_CROSS_COMPONENTS_BUILD)
add_subdirectory(tools/aot/jitinterface)
endif ()
diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
index 3633952be42ca6..49dcc05f084b69 100644
--- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -291,7 +291,7 @@
-
+
diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props
index bb0135898ad02c..af8673df1c3b0e 100644
--- a/src/coreclr/clr.featuredefines.props
+++ b/src/coreclr/clr.featuredefines.props
@@ -25,6 +25,18 @@
false
+
+
+ false
+
+
true
true
diff --git a/src/coreclr/clrfeatures.cmake b/src/coreclr/clrfeatures.cmake
index d8f20db1d4c770..b8ad96822e9d04 100644
--- a/src/coreclr/clrfeatures.cmake
+++ b/src/coreclr/clrfeatures.cmake
@@ -74,7 +74,7 @@ if(CLR_CMAKE_TARGET_ARCH_WASM)
set(FEATURE_PORTABLE_HELPERS 1)
endif(CLR_CMAKE_TARGET_ARCH_WASM)
-if(CLR_CMAKE_TARGET_BROWSER)
+if(CLR_CMAKE_TARGET_BROWSER OR CLR_CMAKE_TARGET_WASI)
set(FEATURE_WEBCIL 1)
endif()
diff --git a/src/coreclr/crossgen-corelib.proj b/src/coreclr/crossgen-corelib.proj
index 54489a43e6296d..ee6290d6c689b2 100644
--- a/src/coreclr/crossgen-corelib.proj
+++ b/src/coreclr/crossgen-corelib.proj
@@ -22,7 +22,6 @@
true
false
- false
CopyILCoreLib
@@ -41,7 +40,7 @@
$(CrossgenTargetName);LinkCoreLibMachO
-
+
wasm
CopyILCoreLib;$(CrossgenTargetName)
diff --git a/src/coreclr/debug/debug-pal/CMakeLists.txt b/src/coreclr/debug/debug-pal/CMakeLists.txt
index f1545c9902a070..475fa62d2c0f1e 100644
--- a/src/coreclr/debug/debug-pal/CMakeLists.txt
+++ b/src/coreclr/debug/debug-pal/CMakeLists.txt
@@ -24,7 +24,12 @@ if(CLR_CMAKE_HOST_WIN32)
endif(CLR_CMAKE_HOST_WIN32)
if(CLR_CMAKE_HOST_UNIX)
- set(DEBUG_PAL_REFEREENCE_DIAGNOSTICSERVER ON)
+ if (NOT CLR_CMAKE_TARGET_WASI)
+ # ds-ipc-pal-socket.c needs UNIX/TCP sockets; re-enable once
+ # WASI PERFTRACING is wired. Tracked by https://github.com/dotnet/runtime/issues/130383
+
+ set(DEBUG_PAL_REFEREENCE_DIAGNOSTICSERVER ON)
+ endif()
add_definitions(-DPAL_IMPLEMENTATION)
add_definitions(-D_POSIX_C_SOURCE=200809L)
diff --git a/src/coreclr/debug/debug-pal/unix/twowaypipe.cpp b/src/coreclr/debug/debug-pal/unix/twowaypipe.cpp
index 4d6805fad3404a..7d8e9c9071a5a6 100644
--- a/src/coreclr/debug/debug-pal/unix/twowaypipe.cpp
+++ b/src/coreclr/debug/debug-pal/unix/twowaypipe.cpp
@@ -11,6 +11,10 @@
#include
#include "twowaypipe.h"
+#ifdef TARGET_WASI
+static inline int mkfifo(const char *path, mode_t mode) { (void)path; (void)mode; errno = ENOTSUP; return -1; }
+#endif
+
// Pipe names stored for use in AbortPipeServerImpl().
static char s_serverInPipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH];
static char s_serverOutPipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH];
diff --git a/src/coreclr/hosts/corerun/CMakeLists.txt b/src/coreclr/hosts/corerun/CMakeLists.txt
index 00ca431dd31249..cb52b3746237e8 100644
--- a/src/coreclr/hosts/corerun/CMakeLists.txt
+++ b/src/coreclr/hosts/corerun/CMakeLists.txt
@@ -36,9 +36,11 @@ if(CLR_CMAKE_HOST_WIN32)
target_link_options(corerun PRIVATE "/CETCOMPAT")
endif()
else()
- target_link_libraries(corerun PRIVATE ${CMAKE_DL_LIBS})
- # Required to expose symbols for global symbol discovery
- target_link_libraries(corerun PRIVATE -rdynamic)
+ if (NOT CLR_CMAKE_TARGET_ARCH_WASM)
+ target_link_libraries(corerun PRIVATE ${CMAKE_DL_LIBS})
+ # Required to expose symbols for global symbol discovery
+ target_link_libraries(corerun PRIVATE -rdynamic)
+ endif()
# Android implements pthread natively.
# WASM, linking against pthreads indicates Node.js workers are
@@ -112,6 +114,18 @@ else()
-sENVIRONMENT=node,shell,web
-lexports.js
-Wl,--error-limit=0)
+ elseif (CLR_CMAKE_TARGET_WASI)
+ # wasm-component-ld wraps wasi:io@0.2.x imports into the WASI
+ # component-model; the default lld produces a plain core module
+ # the component-host can't instantiate. Absolute path is
+ # required because clang only recognizes well-known linker
+ # names (lld/bfd/gold/etc.) by short name.
+ get_filename_component(_wasi_sdk_bin "${CMAKE_C_COMPILER}" DIRECTORY)
+ target_link_options(corerun PRIVATE
+ "-fuse-ld=${_wasi_sdk_bin}/wasm-component-ld"
+ -Wl,-z,stack-size=8388608
+ -Wl,--initial-memory=134217728
+ -Wl,--max-memory=4294967296)
endif()
if (CORERUN_IN_BROWSER)
diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp
index a47c1508f2a010..3ab04343b8a052 100644
--- a/src/coreclr/hosts/corerun/corerun.cpp
+++ b/src/coreclr/hosts/corerun/corerun.cpp
@@ -649,7 +649,24 @@ static int run(const configuration& config)
// Or in Module.onExit handler when managed Main() is synchronous.
return exit_code;
#else // TARGET_BROWSER
- return corerun_shutdown(exit_code);
+ int final_exit_code = corerun_shutdown(exit_code);
+#ifdef TARGET_WASI
+ // wasi:cli/exit's stable exit() only signals ok/err, so wasmtime
+ // collapses any non-zero Main return to host exit 1. When
+ // DOTNET_WASI_PRINT_EXIT_CODE=1, emit a "WASM EXIT " marker on
+ // stderr matching Mono (src/mono/wasi/runtime/main.c); the WASI
+ // launcher in src/tests/Common/CLRTest.Execute.Bash.targets recovers
+ // the value from that. wasi:cli/exit already defines
+ // exit-with-code(status-code: u8), but it is gated
+ // @unstable(feature = cli-exit-with-code) in wasi-cli 0.2.x. Once that
+ // feature stabilizes and wasi-libc/wasi-sdk/wasmtime expose it, this
+ // marker (and the parser in CLRTest.Execute.Bash.targets) can be removed.
+ if (pal::getenv(W("DOTNET_WASI_PRINT_EXIT_CODE")) == W("1"))
+ {
+ pal::fprintf(stderr, W("WASM EXIT %d\n"), final_exit_code);
+ }
+#endif // TARGET_WASI
+ return final_exit_code;
#endif // TARGET_BROWSER
}
diff --git a/src/coreclr/hosts/corerun/corerun.hpp b/src/coreclr/hosts/corerun/corerun.hpp
index 03187959f88b49..e15be76720e466 100644
--- a/src/coreclr/hosts/corerun/corerun.hpp
+++ b/src/coreclr/hosts/corerun/corerun.hpp
@@ -723,6 +723,11 @@ namespace pal
inline bool try_load_library(const pal::string_t& path, pal::mod_t& hMod)
{
+#ifdef TARGET_WASI
+ // wasi-libc has no dlopen; callers treat false as "not available".
+ (void)path; hMod = nullptr;
+ return false;
+#else
hMod = (pal::mod_t)dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
if (hMod == nullptr)
{
@@ -730,6 +735,7 @@ namespace pal
return false;
}
return true;
+#endif
}
diff --git a/src/coreclr/interpreter/CMakeLists.txt b/src/coreclr/interpreter/CMakeLists.txt
index f4f88b9ed3260b..faae9e13fb2c96 100644
--- a/src/coreclr/interpreter/CMakeLists.txt
+++ b/src/coreclr/interpreter/CMakeLists.txt
@@ -47,7 +47,7 @@ add_library_clr(clrinterpreter_objects OBJECT ${INTERPRETER_SOURCES})
target_include_directories(clrinterpreter_objects PRIVATE ../jitshared)
-if (FEATURE_PERFTRACING_DISABLE_THREADS)
+if (FEATURE_PERFTRACING AND FEATURE_PERFTRACING_DISABLE_THREADS)
target_compile_definitions(clrinterpreter_objects PRIVATE PERFTRACING_DISABLE_THREADS)
endif()
diff --git a/src/coreclr/minipal/Unix/doublemapping.cpp b/src/coreclr/minipal/Unix/doublemapping.cpp
index 6c7619c55fa26e..997d7a523e81c8 100644
--- a/src/coreclr/minipal/Unix/doublemapping.cpp
+++ b/src/coreclr/minipal/Unix/doublemapping.cpp
@@ -27,8 +27,16 @@
#include "minipal/cpufeatures.h"
#ifndef TARGET_APPLE
+#if !defined(TARGET_WASI)
#include
+#endif
#include
+#if defined(TARGET_WASI)
+// pal_wasi_missing.h provides Dl_info for the struct InitializeTemplateThunkLocals
+// declaration below; the actual dladdr() call is FEATURE_MAP_THUNKS_FROM_IMAGE-only
+// (Apple) so the stub is never invoked on WASI.
+#include "../../pal/src/include/pal/wasi/pal_wasi_missing.h"
+#endif
#endif // TARGET_APPLE
#ifdef TARGET_APPLE
@@ -103,6 +111,7 @@ bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecu
// for the rest of the process.
#ifdef RLIMIT_AS
// OpenBSD has no address-space rlimit (RLIMIT_AS), so this clipping is skipped there.
+ // WASI also has no RLIMIT_AS.
struct rlimit virtualAddressSpaceLimit;
if ((getrlimit(RLIMIT_AS, &virtualAddressSpaceLimit) == 0) && (virtualAddressSpaceLimit.rlim_cur != RLIM_INFINITY))
{
@@ -115,6 +124,8 @@ bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecu
#endif // RLIMIT_AS
// Clip the maximum double mapped memory size to the file size limit
+#ifdef RLIMIT_FSIZE
+ // WASI has no RLIMIT_FSIZE, so this clipping is skipped there.
struct rlimit fileSizeLimit;
if ((getrlimit(RLIMIT_FSIZE, &fileSizeLimit) == 0) && (fileSizeLimit.rlim_cur != RLIM_INFINITY))
{
@@ -123,6 +134,7 @@ bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecu
maxDoubleMappedMemorySize = fileSizeLimit.rlim_cur;
}
}
+#endif // RLIMIT_FSIZE
if (ftruncate(fd, maxDoubleMappedMemorySize) == -1)
{
diff --git a/src/coreclr/pal/src/CMakeLists.txt b/src/coreclr/pal/src/CMakeLists.txt
index 9e75a724102757..545ba6550f4187 100644
--- a/src/coreclr/pal/src/CMakeLists.txt
+++ b/src/coreclr/pal/src/CMakeLists.txt
@@ -17,9 +17,9 @@ if(NOT CLR_CMAKE_USE_SYSTEM_LIBUNWIND AND NOT CLR_CMAKE_HOST_ARCH_WASM)
include_directories(${CLR_ARTIFACTS_OBJ_DIR}/external/libunwind/include/tdep)
add_subdirectory(${CLR_SRC_NATIVE_DIR}/external/libunwind_extras ${CLR_ARTIFACTS_OBJ_DIR}/external/libunwind)
-elseif(NOT CLR_CMAKE_TARGET_APPLE)
+elseif(NOT CLR_CMAKE_TARGET_APPLE AND NOT CLR_CMAKE_HOST_ARCH_WASM)
find_unwind_libs(UNWIND_LIBS)
-else()
+elseif(CLR_CMAKE_TARGET_APPLE)
add_subdirectory(${CLR_SRC_NATIVE_DIR}/external/libunwind_extras ${CLR_ARTIFACTS_OBJ_DIR}/external/libunwind)
endif(NOT CLR_CMAKE_USE_SYSTEM_LIBUNWIND AND NOT CLR_CMAKE_HOST_ARCH_WASM)
@@ -150,9 +150,7 @@ endif()
set(SOURCES
com/guid.cpp
cruntime/wchar.cpp
- debug/debug.cpp
exception/seh.cpp
- exception/signal.cpp
file/directory.cpp
file/file.cpp
file/path.cpp
@@ -203,12 +201,19 @@ set(SOURCES
synchmgr/synchcontrollers.cpp
synchmgr/synchmanager.cpp
synchmgr/wait.cpp
- thread/context.cpp
thread/process.cpp
thread/thread.cpp
thread/threadsusp.cpp
)
+if (NOT CLR_CMAKE_TARGET_WASI)
+ list(APPEND SOURCES
+ debug/debug.cpp
+ exception/signal.cpp
+ thread/context.cpp
+ )
+endif()
+
set_source_files_properties(
com/guid.cpp
PROPERTIES
@@ -249,11 +254,11 @@ if(CLR_CMAKE_TARGET_APPLE)
target_compile_definitions(coreclrpal_dac PUBLIC -DUNW_REMOTE_ONLY)
else()
- if(NOT FEATURE_CROSSBITNESS)
+ if(NOT FEATURE_CROSSBITNESS AND NOT CLR_CMAKE_TARGET_ARCH_WASM)
add_library(coreclrpal_dac STATIC
exception/remote-unwind.cpp
)
- endif(NOT FEATURE_CROSSBITNESS)
+ endif(NOT FEATURE_CROSSBITNESS AND NOT CLR_CMAKE_TARGET_ARCH_WASM)
endif(CLR_CMAKE_TARGET_APPLE)
# There is only one function exported in 'tracepointprovider.cpp' namely 'PAL_InitializeTracing',
diff --git a/src/coreclr/pal/src/arch/wasm/stubs.cpp b/src/coreclr/pal/src/arch/wasm/stubs.cpp
index 7405e744889fd3..667b5eea786d93 100644
--- a/src/coreclr/pal/src/arch/wasm/stubs.cpp
+++ b/src/coreclr/pal/src/arch/wasm/stubs.cpp
@@ -3,7 +3,22 @@
#include "pal/dbgmsg.h"
#include "pal/signal.hpp"
+#include "pal/context.h"
+#include "pal/seh.hpp"
+
+#if defined(TARGET_BROWSER)
#include
+#elif defined(TARGET_WASI)
+#include
+#include
+#include
+#include
+#include
+#include "pal/wasi/pal_wasi_missing.h"
+// RESERVED_SEH_BIT is defined file-locally in seh.cpp (internal linkage) and
+// isn't exposed in a header, so redefine the value here for WASI.
+namespace { const UINT WASI_RESERVED_SEH_BIT = 0x800000; }
+#endif
SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do this first
@@ -16,6 +31,7 @@ extern void DBG_PrintInterpreterStack();
extern "C" void
DBG_DebugBreak()
{
+#if defined(TARGET_BROWSER)
#ifdef _DEBUG
DBG_PrintInterpreterStack();
double start = emscripten_get_now();
@@ -31,6 +47,14 @@ DBG_DebugBreak()
#else // _DEBUG
emscripten_throw_string("Debug break called in release build.");
#endif // _DEBUG
+#elif defined(TARGET_WASI)
+ // WASI has no host-side debugger hook; use the wasm trap intrinsic.
+#ifdef _DEBUG
+ DBG_PrintInterpreterStack();
+#endif
+ __builtin_debugtrap();
+ abort();
+#endif
}
/* context */
@@ -60,6 +84,10 @@ void ExecuteHandlerOnCustomStack(int code, siginfo_t *siginfo, void *context, si
_ASSERT(!"ExecuteHandlerOnCustomStack not implemented on wasm");
}
+#if defined(TARGET_BROWSER)
+// seh-unwind.cpp is excluded from the browser-wasm build, but emscripten's
+// libunwind shim still exposes the unw_* symbols at link time. Trap stubs
+// satisfy any residual references.
extern "C" int unw_getcontext(int)
{
_ASSERT(!"unw_getcontext not implemented on wasm");
@@ -83,6 +111,7 @@ extern "C" int unw_is_signal_frame(int)
_ASSERT(!"unw_is_signal_frame not implemented on wasm");
return 0;
}
+#endif // TARGET_BROWSER
/* threading */
@@ -91,3 +120,214 @@ extern "C" int pthread_setschedparam(pthread_t, int, const struct sched_param *)
_ASSERT(!"pthread_setschedparam not implemented on wasm");
return 0;
}
+
+#if defined(TARGET_WASI)
+// WASI build skips thread/context.cpp (no native_context_t / siginfo_t /
+// FPE_*). Trap stubs satisfy linker references in headers still compiled;
+// these are never reached on WASI.
+
+extern "C" BOOL CONTEXT_GetRegisters(DWORD processId, LPCONTEXT lpContext)
+{
+ _ASSERT(!"CONTEXT_GetRegisters not implemented on wasi");
+ return FALSE;
+}
+
+void CONTEXTToNativeContext(CONST CONTEXT *lpContext, native_context_t *native)
+{
+ _ASSERT(!"CONTEXTToNativeContext not implemented on wasi");
+}
+
+void CONTEXTFromNativeContext(const native_context_t *native, LPCONTEXT lpContext,
+ ULONG contextFlags)
+{
+ _ASSERT(!"CONTEXTFromNativeContext not implemented on wasi");
+}
+
+LPVOID GetNativeContextPC(const native_context_t *context)
+{
+ _ASSERT(!"GetNativeContextPC not implemented on wasi");
+ return nullptr;
+}
+
+LPVOID GetNativeContextSP(const native_context_t *context)
+{
+ _ASSERT(!"GetNativeContextSP not implemented on wasi");
+ return nullptr;
+}
+
+DWORD CONTEXTGetExceptionCodeForSignal(const siginfo_t *siginfo,
+ const native_context_t *context)
+{
+ _ASSERT(!"CONTEXTGetExceptionCodeForSignal not implemented on wasi");
+ return 0;
+}
+
+// WIN32 SEH entry — normally in seh-unwind.cpp which we exclude on WASI.
+// Mirrors that file's RaiseException: allocate EXCEPTION_RECORD + CONTEXT,
+// fill the record, throw PAL_SEHException (what RtlpRaiseException does on
+// Unix). The CONTEXT capture / PAL_VirtualUnwind step is skipped — already
+// guarded by #ifndef TARGET_WASM in seh-unwind.cpp.
+extern "C" VOID PALAPI
+RaiseException(
+ IN DWORD dwExceptionCode,
+ IN DWORD dwExceptionFlags,
+ IN DWORD nNumberOfArguments,
+ IN CONST ULONG_PTR *lpArguments)
+{
+ if (dwExceptionCode & WASI_RESERVED_SEH_BIT)
+ {
+ dwExceptionCode ^= WASI_RESERVED_SEH_BIT;
+ }
+
+ if (nNumberOfArguments > EXCEPTION_MAXIMUM_PARAMETERS)
+ {
+ nNumberOfArguments = EXCEPTION_MAXIMUM_PARAMETERS;
+ }
+
+ EXCEPTION_RECORD *exceptionRecord;
+ CONTEXT *contextRecord;
+ AllocateExceptionRecords(&exceptionRecord, &contextRecord);
+
+ exceptionRecord->ExceptionCode = dwExceptionCode;
+ exceptionRecord->ExceptionFlags = dwExceptionFlags;
+ exceptionRecord->ExceptionRecord = NULL;
+ exceptionRecord->ExceptionAddress = NULL;
+ exceptionRecord->NumberParameters = nNumberOfArguments;
+ if (nNumberOfArguments)
+ {
+ memcpy(exceptionRecord->ExceptionInformation, lpArguments,
+ nNumberOfArguments * sizeof(ULONG_PTR));
+ }
+
+ throw PAL_SEHException(exceptionRecord, contextRecord);
+}
+
+// WIN32 debug-output APIs. WASI build excludes pal/src/debug/debug.cpp; route
+// to the same internal channels stderr would on a real platform.
+extern "C" VOID PALAPI
+DebugBreak()
+{
+ _ASSERT(!"DebugBreak not implemented on wasi");
+ __builtin_debugtrap();
+ abort();
+}
+
+extern "C" VOID PALAPI
+OutputDebugStringA(IN LPCSTR lpOutputString)
+{
+ if (lpOutputString) fputs(lpOutputString, stderr);
+}
+
+extern "C" VOID PALAPI
+OutputDebugStringW(IN LPCWSTR lpOutputString)
+{
+ (void)lpOutputString; // wchar_t* — not converted here; stub only
+}
+
+// PAL exception-record allocation — normally in seh-unwind.cpp which we
+// exclude on WASI. Both records share a single allocation (mirroring the
+// Unix layout) so the matching free is a single free() of the combined
+// memory, keyed off the contextRecord pointer.
+struct WasiExceptionRecords
+{
+ CONTEXT ContextRecord;
+ EXCEPTION_RECORD ExceptionRecord;
+};
+
+extern "C" PALIMPORT VOID PALAPI
+PAL_FreeExceptionRecords(IN EXCEPTION_RECORD *exceptionRecord, IN CONTEXT *contextRecord)
+{
+ (void)exceptionRecord;
+ // contextRecord is the start of the combined WasiExceptionRecords allocation.
+ free(contextRecord);
+}
+
+VOID
+AllocateExceptionRecords(EXCEPTION_RECORD** exceptionRecord, CONTEXT** contextRecord)
+{
+ WasiExceptionRecords* records = (WasiExceptionRecords*)calloc(1, sizeof(WasiExceptionRecords));
+ if (records == nullptr)
+ {
+ // No fallback pool on WASI (single-threaded, no async signals).
+ abort();
+ }
+ *contextRecord = &records->ContextRecord;
+ *exceptionRecord = &records->ExceptionRecord;
+}
+
+extern "C" PALIMPORT BOOL PALAPI
+PAL_VirtualUnwind(CONTEXT *context)
+{
+ (void)context;
+ _ASSERT(!"PAL_VirtualUnwind not implemented on wasi");
+ return FALSE;
+}
+
+// Signal-handling surface — pal/src/exception/signal.cpp is excluded on WASI.
+// Stubs return success/no-op so the PAL init/shutdown paths continue.
+// These are C++ symbols (declared without extern "C" in pal/src/include/pal/signal.hpp).
+BOOL SEHInitializeSignals(CorUnix::CPalThread* pthrCurrent, DWORD flags)
+{
+ (void)pthrCurrent; (void)flags;
+ return TRUE;
+}
+
+void SEHCleanupSignals(bool isChildProcess)
+{
+ (void)isChildProcess;
+}
+
+void UnmaskActivationSignal()
+{
+}
+
+extern "C" VOID PALAPI
+PAL_EnableCrashReportBeforeSignalChaining(void)
+{
+ // No-op: WASI has no signal delivery.
+}
+
+// Per-thread context retrieval. Normally implemented via ptrace on Unix.
+// WASI single-process has no other thread to inspect.
+extern "C" PALIMPORT BOOL PALAPI
+GetThreadContext(IN HANDLE hThread, IN OUT LPCONTEXT lpContext)
+{
+ (void)hThread; (void)lpContext;
+ _ASSERT(!"GetThreadContext not implemented on wasi");
+ return FALSE;
+}
+
+// FlushInstructionCache: no-op on WASI. wasm has no separate I-cache to flush.
+extern "C" PALIMPORT BOOL PALAPI
+FlushInstructionCache(IN HANDLE hProcess, IN LPCVOID lpBaseAddress, IN SIZE_T dwSize)
+{
+ (void)hProcess; (void)lpBaseAddress; (void)dwSize;
+ return TRUE;
+}
+
+// SystemJS_GetLocaleInfo is the browser ICU shim entrypoint
+// (src/native/libs/System.Native.Browser/native/globalization-locale.ts).
+// On WASI we don't have a JS host; return null. The managed Globalization
+// layer must already gracefully handle this for the browser-not-loaded case.
+extern "C" uint16_t* SystemJS_GetLocaleInfo(
+ const uint16_t* locale, int32_t localeLength,
+ const uint16_t* culture, int32_t cultureLength,
+ const uint16_t* result, int32_t resultMaxLength,
+ int* resultLength)
+{
+ (void)locale; (void)localeLength; (void)culture; (void)cultureLength;
+ (void)result; (void)resultMaxLength;
+ if (resultLength) *resultLength = 0;
+ return nullptr;
+}
+
+// PAL also indirectly references getpwuid_r through PAL_GetUserName-style
+// paths (init/pal.cpp transitively includes ). Provide a stub so
+// transitive references resolve.
+extern "C" int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result)
+{
+ (void)uid; (void)pwd; (void)buf; (void)buflen;
+ if (result) *result = nullptr;
+ return ENOTSUP;
+}
+#endif // TARGET_WASI
diff --git a/src/coreclr/pal/src/configure.cmake b/src/coreclr/pal/src/configure.cmake
index 07b4ac4b7c706f..1bc34c65b42ba8 100644
--- a/src/coreclr/pal/src/configure.cmake
+++ b/src/coreclr/pal/src/configure.cmake
@@ -100,10 +100,12 @@ elseif (HAVE_PTHREAD_IN_LIBC)
set(PTHREAD_LIBRARY c)
endif()
-check_library_exists(${PTHREAD_LIBRARY} pthread_attr_get_np "" HAVE_PTHREAD_ATTR_GET_NP)
-check_library_exists(${PTHREAD_LIBRARY} pthread_getattr_np "" HAVE_PTHREAD_GETATTR_NP)
-check_library_exists(${PTHREAD_LIBRARY} pthread_getcpuclockid "" HAVE_PTHREAD_GETCPUCLOCKID)
-check_library_exists(${PTHREAD_LIBRARY} pthread_getaffinity_np "" HAVE_PTHREAD_GETAFFINITY_NP)
+if (PTHREAD_LIBRARY)
+ check_library_exists(${PTHREAD_LIBRARY} pthread_attr_get_np "" HAVE_PTHREAD_ATTR_GET_NP)
+ check_library_exists(${PTHREAD_LIBRARY} pthread_getattr_np "" HAVE_PTHREAD_GETATTR_NP)
+ check_library_exists(${PTHREAD_LIBRARY} pthread_getcpuclockid "" HAVE_PTHREAD_GETCPUCLOCKID)
+ check_library_exists(${PTHREAD_LIBRARY} pthread_getaffinity_np "" HAVE_PTHREAD_GETAFFINITY_NP)
+endif()
check_function_exists(fsync HAVE_FSYNC)
check_function_exists(futimes HAVE_FUTIMES)
@@ -367,7 +369,9 @@ set(CMAKE_REQUIRED_LIBRARIES)
-check_library_exists(${PTHREAD_LIBRARY} pthread_condattr_setclock "" HAVE_PTHREAD_CONDATTR_SETCLOCK)
+if (PTHREAD_LIBRARY)
+ check_library_exists(${PTHREAD_LIBRARY} pthread_condattr_setclock "" HAVE_PTHREAD_CONDATTR_SETCLOCK)
+endif()
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_RT_LIBS})
check_cxx_source_runs("
@@ -615,6 +619,8 @@ elseif(CLR_CMAKE_TARGET_HAIKU)
set(HAVE_SCHED_OTHER_ASSIGNABLE 1)
elseif(CLR_CMAKE_TARGET_BROWSER)
set(HAVE_SCHED_OTHER_ASSIGNABLE 0)
+elseif(CLR_CMAKE_TARGET_WASI)
+ set(HAVE_SCHED_OTHER_ASSIGNABLE 0)
else() # Anything else is Linux
# LTTNG is not available on Android, so don't error out
if(FEATURE_EVENTSOURCE_XPLAT AND NOT HAVE_LTTNG_TRACEPOINT_H)
diff --git a/src/coreclr/pal/src/exception/seh.cpp b/src/coreclr/pal/src/exception/seh.cpp
index dda145c6735fad..2b2676739061a9 100644
--- a/src/coreclr/pal/src/exception/seh.cpp
+++ b/src/coreclr/pal/src/exception/seh.cpp
@@ -361,4 +361,9 @@ bool CatchHardwareExceptionHolder::IsEnabled()
return pThread ? pThread->IsHardwareExceptionsEnabled() : false;
}
+#if !defined(TARGET_WASI)
+// seh-unwind.cpp uses libunwind which is unavailable on wasm32-wasip2.
+// The WASI build provides equivalent stubs (PAL_VirtualUnwind, RtlCaptureContext,
+// etc.) in arch/wasm/stubs.cpp directly.
#include "seh-unwind.cpp"
+#endif
diff --git a/src/coreclr/pal/src/file/file.cpp b/src/coreclr/pal/src/file/file.cpp
index 2ee393980c9dba..56b483b8496de7 100644
--- a/src/coreclr/pal/src/file/file.cpp
+++ b/src/coreclr/pal/src/file/file.cpp
@@ -133,6 +133,13 @@ FileCleanupRoutine(
if (!fShutdown && -1 != pLocalData->unix_fd)
{
+#if defined(TARGET_WASI)
+ // WASI: init_std_handle borrows fileno(stream) for stdin/stdout/stderr
+ // because WASI has no dup(). Never close a borrowed std fd here.
+ if (pLocalData->unix_fd != STDIN_FILENO &&
+ pLocalData->unix_fd != STDOUT_FILENO &&
+ pLocalData->unix_fd != STDERR_FILENO)
+#endif
close(pLocalData->unix_fd);
}
@@ -2397,7 +2404,13 @@ static HANDLE init_std_handle(HANDLE * pStd, FILE *stream)
/* duplicate the FILE *, so that we can fclose() in FILECloseHandle without
closing the original */
+#if defined(TARGET_WASI)
+ // WASI has no dup(); use fileno directly. FileCleanupRoutine skips
+ // close() for the borrowed standard-stream fds (STDIN/STDOUT/STDERR).
+ new_fd = fileno(stream);
+#else
new_fd = fcntl(fileno(stream), F_DUPFD_CLOEXEC, 0); // dup, but with CLOEXEC
+#endif
if(-1 == new_fd)
{
ERROR("dup() failed; errno is %d (%s)\n", errno, strerror(errno));
@@ -2479,6 +2492,13 @@ static HANDLE init_std_handle(HANDLE * pStd, FILE *stream)
}
else if (-1 != new_fd)
{
+#if defined(TARGET_WASI)
+ // See FileCleanupRoutine: on WASI, new_fd may be a borrowed std fd
+ // (STDIN/STDOUT/STDERR) because init_std_handle can't dup on WASI.
+ if (new_fd != STDIN_FILENO &&
+ new_fd != STDOUT_FILENO &&
+ new_fd != STDERR_FILENO)
+#endif
close(new_fd);
}
diff --git a/src/coreclr/pal/src/include/pal/context.h b/src/coreclr/pal/src/include/pal/context.h
index 04829d95a17ccf..f527d8b4290f0e 100644
--- a/src/coreclr/pal/src/include/pal/context.h
+++ b/src/coreclr/pal/src/include/pal/context.h
@@ -30,6 +30,19 @@ extern "C"
#endif // HAVE_UCONTEXT_H
typedef ucontext_t native_context_t;
+#elif defined(TARGET_WASI)
+// WASI (wasm32-wasip2 in wasi-sdk 33) has no signal/ucontext support:
+// is absent and hides siginfo_t/FPE_*/ILL_* behind
+// __wasilibc_unmodified_upstream. PAL declarations still reference these types
+// at the header level even though the call sites are excluded from compilation
+// on WASI (exception/signal.cpp, exception/seh-unwind.cpp). Provide opaque
+// native_context_t here; siginfo_t lives in pal/wasi/pal_wasi_missing.h
+// which is pulled in below.
+//
+// TODO: delete when wasi-libc exposes siginfo_t and a ucontext_t (no upstream
+// plan as of WASI 0.2.8).
+typedef struct { int _placeholder; } native_context_t;
+#include "pal/wasi/pal_wasi_missing.h"
#else // HAVE_UCONTEXT_T
#error Native context type is not known on this platform!
#endif // HAVE_UCONTEXT_T
diff --git a/src/coreclr/pal/src/include/pal/process.h b/src/coreclr/pal/src/include/pal/process.h
index 52e1f37aaa99a5..e4e4cc4e337c9a 100644
--- a/src/coreclr/pal/src/include/pal/process.h
+++ b/src/coreclr/pal/src/include/pal/process.h
@@ -25,6 +25,11 @@ Revision History:
#include "pal/palinternal.h"
#include "pal/stackstring.hpp"
+#include
+#if defined(TARGET_WASI)
+#include "pal/wasi/pal_wasi_missing.h"
+#endif
+
#ifdef __cplusplus
extern "C"
{
diff --git a/src/coreclr/pal/src/include/pal/signal.hpp b/src/coreclr/pal/src/include/pal/signal.hpp
index 1ce0affe8f6b01..1af75b3830c2c2 100644
--- a/src/coreclr/pal/src/include/pal/signal.hpp
+++ b/src/coreclr/pal/src/include/pal/signal.hpp
@@ -19,6 +19,11 @@ Module Name:
#ifndef _PAL_SIGNAL_HPP_
#define _PAL_SIGNAL_HPP_
+#include
+#if defined(TARGET_WASI)
+#include "pal/wasi/pal_wasi_missing.h"
+#endif
+
#if !HAVE_MACH_EXCEPTIONS
// Return context and status for the signal_handler_worker.
diff --git a/src/coreclr/pal/src/include/pal/wasi/pal_wasi_missing.h b/src/coreclr/pal/src/include/pal/wasi/pal_wasi_missing.h
new file mode 100644
index 00000000000000..a38bb37f286b17
--- /dev/null
+++ b/src/coreclr/pal/src/include/pal/wasi/pal_wasi_missing.h
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// Minimal definitions for POSIX types/symbols that wasi-libc (wasi-sdk 33 /
+// wasm32-wasip2) does not provide. Files include this explicitly inside an
+// #if defined(TARGET_WASI) guard; this is not a #include_next-style shim
+// and not a wholesale POSIX emulation — only what PAL headers reference.
+
+#ifndef _PAL_WASI_MISSING_H_
+#define _PAL_WASI_MISSING_H_
+
+#if !defined(TARGET_WASI)
+#error pal_wasi_missing.h must only be included on WASI targets
+#endif
+
+#include
+
+// PAL headers reference siginfo_t* in prototypes (PROCAbort, signal_handler_worker,
+// PROCCreateCrashDumpIfEnabled, …) even though the underlying source is
+// excluded from the WASI build. wasi-libc only declares siginfo_t under
+// __wasilibc_unmodified_upstream (off because WASI 0.2.8 has no sigaction).
+#ifndef si_signo
+typedef struct {
+ int si_signo;
+ int si_errno;
+ int si_code;
+ void *si_addr;
+} siginfo_t;
+#endif
+
+// wasi-libc has no ; PAL init/pal.cpp includes it transitively but
+// performs no lookup. Stub functions live in arch/wasm/stubs.cpp.
+struct passwd {
+ char *pw_name;
+ char *pw_passwd;
+ uid_t pw_uid;
+ gid_t pw_gid;
+ char *pw_gecos;
+ char *pw_dir;
+ char *pw_shell;
+};
+
+// wasi-libc has dlopen/dlsym/dlerror but not dladdr/Dl_info. PAL call sites
+// (dbgmsg.cpp, loader/module.cpp) already handle dladdr() failure gracefully.
+typedef struct {
+ const char *dli_fname;
+ void *dli_fbase;
+ const char *dli_sname;
+ void *dli_saddr;
+} Dl_info;
+
+static inline int dladdr(const void *addr, Dl_info *info)
+{
+ (void)addr; (void)info;
+ return 0;
+}
+
+// WASI has no user/group concept; PAL uses these only for st_uid/st_gid
+// permission checks. Always-zero makes the owner-equals-process branches behave.
+static inline uid_t getuid(void) { return 0; }
+static inline uid_t geteuid(void) { return 0; }
+static inline gid_t getgid(void) { return 0; }
+static inline gid_t getegid(void) { return 0; }
+
+#endif // _PAL_WASI_MISSING_H_
diff --git a/src/coreclr/pal/src/init/pal.cpp b/src/coreclr/pal/src/init/pal.cpp
index d7439969b7ccd7..4f70878825bf0d 100644
--- a/src/coreclr/pal/src/init/pal.cpp
+++ b/src/coreclr/pal/src/init/pal.cpp
@@ -44,7 +44,11 @@ SET_DEFAULT_DEBUG_CHANNEL(PAL); // some headers have code with asserts, so do th
#include
#include
+#if defined(TARGET_WASI)
+#include "pal/wasi/pal_wasi_missing.h"
+#else
#include
+#endif
#include
#include
#include
diff --git a/src/coreclr/pal/src/loader/module.cpp b/src/coreclr/pal/src/loader/module.cpp
index b0da58148f4d1f..fe3c78a7368131 100644
--- a/src/coreclr/pal/src/loader/module.cpp
+++ b/src/coreclr/pal/src/loader/module.cpp
@@ -40,9 +40,17 @@ SET_DEFAULT_DEBUG_CHANNEL(LOADER); // some headers have code with asserts, so do
#include
#include
+#if defined(TARGET_WASI)
+#include "pal/wasi/pal_wasi_missing.h"
+#endif
+
#ifdef __APPLE__
#include
#include
+#elif defined(TARGET_WASI)
+// WASI has no dynamic linker and no . PAL_CopyModuleData is stubbed
+// to return 0 in the TARGET_WASM branch below; 's dl_phdr_info /
+// link_map are not referenced anywhere this file actually compiles on WASI.
#else
#include
#endif // __APPLE__
diff --git a/src/coreclr/pal/src/map/map.cpp b/src/coreclr/pal/src/map/map.cpp
index a36c986280b530..796e76c3fdd54c 100644
--- a/src/coreclr/pal/src/map/map.cpp
+++ b/src/coreclr/pal/src/map/map.cpp
@@ -466,7 +466,19 @@ CorUnix::InternalCreateFileMapping(
// information, though...
//
+#ifdef TARGET_WASI
+ // WASI has no dup(); re-open by path with the same access mode
+ // so writable mappings still work. O_CLOEXEC isn't meaningful
+ // on WASI (no exec). Handles created by init_std_handle()
+ // (STDIN/STDOUT/STDERR) have no backing path, so there is nothing
+ // to re-open; fail gracefully instead of passing NULL to open().
+ UnixFd = (pFileLocalData->unix_filename != NULL)
+ ? open(pFileLocalData->unix_filename,
+ pFileLocalData->open_flags & O_ACCMODE)
+ : -1;
+#else
UnixFd = fcntl(pFileLocalData->unix_fd, F_DUPFD_CLOEXEC, 0); // dup, but with CLOEXEC
+#endif
if (-1 == UnixFd)
{
ERROR( "Unable to duplicate the Unix file descriptor!\n" );
diff --git a/src/coreclr/pal/src/map/virtual.cpp b/src/coreclr/pal/src/map/virtual.cpp
index 72511ea10722a3..caf5388b1b37c9 100644
--- a/src/coreclr/pal/src/map/virtual.cpp
+++ b/src/coreclr/pal/src/map/virtual.cpp
@@ -1615,6 +1615,11 @@ void ExecutableMemoryAllocator::TryReserveInitialMemory()
int32_t sizeOfAllocation = MaxExecutableMemorySizeNearCoreClr;
int32_t initialReserveLimit = -1;
+#if defined(TARGET_WASI)
+ // WASI has neither RLIMIT_AS nor RLIMIT_DATA nor RLIM_INFINITY; wasm32 has a
+ // hard 4 GiB limit and no separate VM accounting. Skip the rlimit-based
+ // sizing and let sizeOfAllocation stay at MaxExecutableMemorySizeNearCoreClr.
+#else
#ifdef RLIMIT_AS
int addressSpace = RLIMIT_AS;
#else
@@ -1641,6 +1646,7 @@ void ExecutableMemoryAllocator::TryReserveInitialMemory()
sizeOfAllocation = initialReserveLimit;
}
}
+#endif // !TARGET_WASI
#if defined(TARGET_ARM) || defined(TARGET_ARM64)
// Smaller steps on ARM because we try hard finding a spare memory in a 128Mb
// distance from coreclr so e.g. all calls from corelib to coreclr could use relocs
diff --git a/src/coreclr/pal/src/misc/dbgmsg.cpp b/src/coreclr/pal/src/misc/dbgmsg.cpp
index c9451c99ffbc37..65e18bbde4710b 100644
--- a/src/coreclr/pal/src/misc/dbgmsg.cpp
+++ b/src/coreclr/pal/src/misc/dbgmsg.cpp
@@ -34,6 +34,10 @@ Module Name:
#include
#include
+#if defined(TARGET_WASI)
+#include "pal/wasi/pal_wasi_missing.h"
+#endif
+
/* needs to be included after "palinternal.h" to avoid name
collision for va_start and va_end */
#include
diff --git a/src/coreclr/pal/src/misc/utils.cpp b/src/coreclr/pal/src/misc/utils.cpp
index 85dc7a89aad594..61566e4b0b68d8 100644
--- a/src/coreclr/pal/src/misc/utils.cpp
+++ b/src/coreclr/pal/src/misc/utils.cpp
@@ -21,6 +21,11 @@ Module Name:
SET_DEFAULT_DEBUG_CHANNEL(MISC); // some headers have code with asserts, so do this first
#include "pal/palinternal.h"
+
+#if defined(TARGET_WASI)
+#include "pal/wasi/pal_wasi_missing.h"
+#endif
+
#if HAVE_VM_ALLOCATE
#include
#endif //HAVE_VM_ALLOCATE
diff --git a/src/coreclr/pal/src/synchmgr/synchmanager.cpp b/src/coreclr/pal/src/synchmgr/synchmanager.cpp
index 8690734fdd6f68..1aa1c15f3f15f0 100644
--- a/src/coreclr/pal/src/synchmgr/synchmanager.cpp
+++ b/src/coreclr/pal/src/synchmgr/synchmanager.cpp
@@ -26,7 +26,9 @@ SET_DEFAULT_DEBUG_CHANNEL(SYNC); // some headers have code with asserts, so do t
#include
#include
#include
+#if !defined(TARGET_WASI)
#include
+#endif
#include
#include
#include
diff --git a/src/coreclr/pal/src/thread/process.cpp b/src/coreclr/pal/src/thread/process.cpp
index e172351dda6e76..be8991552f28bb 100644
--- a/src/coreclr/pal/src/thread/process.cpp
+++ b/src/coreclr/pal/src/thread/process.cpp
@@ -52,7 +52,9 @@ SET_DEFAULT_DEBUG_CHANNEL(PROCESS); // some headers have code with asserts, so d
#include
#include
#endif
+#if !defined(TARGET_WASI)
#include
+#endif
#include
#include
#include
diff --git a/src/coreclr/pal/src/thread/thread.cpp b/src/coreclr/pal/src/thread/thread.cpp
index 5460b784aa09df..543665e21ed5b6 100644
--- a/src/coreclr/pal/src/thread/thread.cpp
+++ b/src/coreclr/pal/src/thread/thread.cpp
@@ -658,10 +658,16 @@ ExitThread(
pThread->SetExitCode(dwExitCode);
/* kill the thread (itself), resulting in a call to InternalEndCurrentThread */
+#if defined(TARGET_WASI)
+ // wasi-libc pthread_exit is a _Static_assert stub. PAL only reaches here
+ // on the orderly-shutdown path which is not exercised on WASI.
+ abort();
+#else
pthread_exit(NULL);
ASSERT("pthread_exit should not return!\n");
while (true);
+#endif
}
/*++
@@ -922,6 +928,14 @@ CorUnix::InternalSetThreadPriority(
/* get the previous thread schedule parameters. We need to know the
scheduling policy to determine the priority range */
+#if defined(TARGET_WASI)
+ // wasi-libc replaces pthread scheduling functions with _Static_assert
+ // stubs (single-threaded). Record the requested priority but skip the
+ // pthread plumbing.
+ pTargetThread->m_iThreadPriority = iNewPriority;
+ palError = NO_ERROR;
+ goto InternalSetThreadPriorityExit;
+#else
if (pthread_getschedparam(
pTargetThread->GetPThreadSelf(),
&policy,
@@ -1017,6 +1031,7 @@ CorUnix::InternalSetThreadPriority(
}
pTargetThread->m_iThreadPriority = iNewPriority;
+#endif // !TARGET_WASI
InternalSetThreadPriorityExit:
@@ -1265,6 +1280,12 @@ CorUnix::GetThreadTimesInternal(
close(fd);
ts = status.pr_utime;
+#elif defined(TARGET_WASI)
+ // WASI 0.2.8 has no per-thread CPU time clock. Report zero rather than
+ // fail the build.
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
#else // HAVE_PTHREAD_GETCPUCLOCKID || HAVE_CLOCK_THREAD_CPUTIME
#error "Don't know how to obtain user cpu time on this platform."
#endif // HAVE_PTHREAD_GETCPUCLOCKID || HAVE_CLOCK_THREAD_CPUTIME
@@ -2333,7 +2354,14 @@ CPalThread::GetStackBase()
status = pthread_attr_init(&attr);
_ASSERT_MSG(status == 0, "pthread_attr_init call failed");
-#ifndef TARGET_BROWSER
+#if defined(TARGET_WASI)
+ // wasm-component-ld places the stack first with -Wl,-z,stack-size (see
+ // corerun CMakeLists.txt). Keep in sync with that value.
+ (void)thread; (void)stackAddr; (void)stackSize; (void)status;
+ pthread_attr_destroy(&attr);
+ constexpr size_t s_wasiStackSize = 8 * 1024 * 1024;
+ stackBase = (void*)s_wasiStackSize;
+#elif !defined(TARGET_BROWSER)
#if HAVE_PTHREAD_ATTR_GET_NP
status = pthread_attr_get_np(thread, &attr);
#elif HAVE_PTHREAD_GETATTR_NP
@@ -2385,7 +2413,13 @@ CPalThread::GetStackLimit()
status = pthread_attr_init(&attr);
_ASSERT_MSG(status == 0, "pthread_attr_init call failed");
-#ifndef TARGET_BROWSER
+#if defined(TARGET_WASI)
+ // See GetStackBase. CoreCLR rejects NULL stack limits, so return a
+ // small non-null placeholder.
+ (void)thread; (void)stackSize; (void)status;
+ pthread_attr_destroy(&attr);
+ stackLimit = (void*)4096;
+#elif !defined(TARGET_BROWSER)
#if HAVE_PTHREAD_ATTR_GET_NP
status = pthread_attr_get_np(thread, &attr);
#elif HAVE_PTHREAD_GETATTR_NP
diff --git a/src/coreclr/runtime.proj b/src/coreclr/runtime.proj
index 2c968913b26f4c..24da48c265efdc 100644
--- a/src/coreclr/runtime.proj
+++ b/src/coreclr/runtime.proj
@@ -6,6 +6,7 @@
true
GetPgoDataPackagePath
AcquireEmscriptenSdk;$(BuildRuntimeDependsOnTargets);GenerateEmccExports
+ AcquireWasiSdk;$(BuildRuntimeDependsOnTargets)
diff --git a/src/coreclr/tools/CMakeLists.txt b/src/coreclr/tools/CMakeLists.txt
index 31fc6f5a729665..c85de027cef4dc 100644
--- a/src/coreclr/tools/CMakeLists.txt
+++ b/src/coreclr/tools/CMakeLists.txt
@@ -1,3 +1,3 @@
-if (NOT CLR_CMAKE_TARGET_BROWSER)
+if (NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI)
add_subdirectory(superpmi)
endif()
diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
index 0b30c705387bc2..9b554a25635ed2 100644
--- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
+++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
@@ -4706,7 +4706,7 @@ private uint getJitFlags(ref CORJIT_FLAGS flags, uint sizeInBytes)
}
#if READYTORUN
- if (this.MethodBeingCompiled.Context.Target.OperatingSystem == TargetOS.Browser)
+ if (this.MethodBeingCompiled.Context.Target.Architecture == TargetArchitecture.Wasm32)
{
flags.Set(CorJitFlag.CORJIT_FLAG_PORTABLE_ENTRY_POINTS);
}
diff --git a/src/coreclr/vm/finalizerthread.cpp b/src/coreclr/vm/finalizerthread.cpp
index 0e36352feb1e5e..56db5891234347 100644
--- a/src/coreclr/vm/finalizerthread.cpp
+++ b/src/coreclr/vm/finalizerthread.cpp
@@ -43,11 +43,27 @@ bool FinalizerThread::IsCurrentThreadFinalizer()
return GetThreadNULLOk() == g_pFinalizerThread;
}
-#ifdef TARGET_BROWSER
+#if defined(TARGET_BROWSER) || defined(TARGET_WASI)
+#ifdef TARGET_BROWSER
+// Browser provides this in its JS host (libSystem.Native.Browser scheduling.ts);
+// it queues a microtask that pumps SystemJS_ExecuteFinalizationCallback.
extern "C" void SystemJS_ScheduleFinalization();
+#endif
-extern "C" void SystemJS_ExecuteFinalizationCallback()
+#ifdef TARGET_WASI
+// WASI has no host event loop; the equivalent of SystemJS_ScheduleFinalization
+// is a pure-native flag-set, drained from managed code via the QCall below.
+extern "C" void WasiFinalizer_Schedule();
+#endif
+
+// Runs one FinalizerThreadWorkerIteration on the current thread. Body is
+// identical on browser and WASI; the surrounding entry-point shape differs:
+// - Browser: raw wasm export invoked from a JS microtask after
+// SystemJS_ScheduleFinalization enqueued it. Not a QCall.
+// - WASI: QCall invoked from managed WasiEventLoop after
+// WasiFinalizer_TryClearPending observes the flag.
+static void RunFinalizerIterationOnCurrentThread()
{
CONTRACTL
{
@@ -65,7 +81,69 @@ extern "C" void SystemJS_ExecuteFinalizationCallback()
UNINSTALL_UNHANDLED_MANAGED_EXCEPTION_TRAP;
}
-#endif // TARGET_BROWSER
+#ifdef TARGET_BROWSER
+extern "C" void SystemJS_ExecuteFinalizationCallback()
+{
+ RunFinalizerIterationOnCurrentThread();
+}
+#else // TARGET_WASI
+extern "C" void QCALLTYPE WasiFinalizer_RunWorker()
+{
+ QCALL_CONTRACT;
+ BEGIN_QCALL;
+ RunFinalizerIterationOnCurrentThread();
+ END_QCALL;
+}
+#endif
+
+#endif // TARGET_BROWSER || TARGET_WASI
+
+#ifdef TARGET_WASI
+
+#ifdef FEATURE_WASM_MANAGED_THREADS
+// The WASI finalizer design below is single-threaded by construction: there is
+// no separate finalizer thread, and finalization is drained synchronously on the
+// main thread via a plain (non-atomic-swap) pending flag. If managed threads are
+// ever enabled on WASI this is unsound and needs a real finalizer thread, so fail
+// the build loudly rather than silently running the single-threaded path.
+#error "TARGET_WASI finalizer path assumes a single-threaded runtime; FEATURE_WASM_MANAGED_THREADS requires a real finalizer thread implementation."
+#endif // FEATURE_WASM_MANAGED_THREADS
+
+// On WASI there is no separate finalizer thread and no JS event loop to defer
+// work to. EnableFinalization runs inside the GC, so it cannot safely cross
+// back into managed code (queueing into ThreadPool itself is a managed
+// allocation/lock-protected operation that re-enters the GC).
+//
+// Pure-native flag: WasiFinalizer_Schedule sets it from inside the GC, and
+// the managed WasiEventLoop polling loop drains it via WasiFinalizer_TryClearPending
+// at a safe point and then calls WasiFinalizer_RunWorker (the QCall exposing
+// the real FinalizerThreadWorkerIteration; same body as browser's
+// SystemJS_ExecuteFinalizationCallback, exported under a WASI-specific name).
+static Volatile s_finalizationPending = false;
+
+extern "C" void WasiFinalizer_Schedule()
+{
+ // Called from inside the GC. Setting an atomic flag is the only thing
+ // we can safely do here — no allocation, no managed callback, no locks.
+ s_finalizationPending = true;
+}
+
+extern "C" CLR_BOOL QCALLTYPE WasiFinalizer_TryClearPending()
+{
+ QCALL_CONTRACT;
+ CLR_BOOL pending = FALSE;
+ BEGIN_QCALL;
+ // Volatile load + clear. Single-threaded WASI: no atomic swap needed.
+ pending = s_finalizationPending ? TRUE : FALSE;
+ if (pending)
+ {
+ s_finalizationPending = false;
+ }
+ END_QCALL;
+ return pending;
+}
+
+#endif // TARGET_WASI
void FinalizerThread::EnableFinalization()
{
@@ -73,13 +151,20 @@ void FinalizerThread::EnableFinalization()
#ifndef TARGET_WASM
hEventFinalizer->Set();
-#else // !TARGET_WASM
-#ifdef TARGET_BROWSER
+#elif defined(TARGET_BROWSER)
+ // Defer finalization to the host's JS event loop. Running it inline from
+ // here is unsafe: EnableFinalization is called from inside the GC, while
+ // FinalizerThreadWorkerIteration declares GC_TRIGGERS + MODE_COOPERATIVE
+ // and re-enters preemptive mode via EnablePreemptiveGC(). Re-entering the
+ // GC, transitioning thread modes from inside a collection, and the risk
+ // of unbounded recursion through finalizer-triggered allocations all make
+ // synchronous execution wrong here.
SystemJS_ScheduleFinalization();
-#else
- // WASI is not implemented yet
-#endif // TARGET_BROWSER
-#endif // !TARGET_WASM
+#else // TARGET_WASI
+ // Same constraints as browser; WASI sets a native flag observed by the
+ // managed WasiEventLoop polling loop. See WasiFinalizer_Schedule above.
+ WasiFinalizer_Schedule();
+#endif
}
namespace
@@ -318,6 +403,10 @@ void FinalizerThread::RaiseShutdownEvents()
// thread's context is needed (i.e. RCW cleanup)
hEventFinalizerToShutDown->Wait(INFINITE, /*alertable*/ TRUE);
}
+#else // TARGET_WASM
+ // No dedicated finalizer thread on WASM. Like every other CoreCLR
+ // target, finalizers queued at process exit are not pumped and
+ // leak by design.
#endif // !TARGET_WASM
}
@@ -779,5 +868,26 @@ void FinalizerThread::FinalizerThreadWait()
_ASSERTE(status == WAIT_OBJECT_0);
}
+#else // TARGET_WASM
+ // No separate finalizer thread on WASM. Drain synchronously on the
+ // calling thread so GC.WaitForPendingFinalizers() actually waits.
+ // The re-entry guard covers a user finalizer that calls
+ // WaitForPendingFinalizers itself; the non-WASM branch above uses
+ // IsCurrentThreadFinalizer() for the same purpose.
+ static thread_local bool s_inDrain = false;
+ if (s_inDrain)
+ {
+ return;
+ }
+ s_inDrain = true;
+
+ INSTALL_UNHANDLED_MANAGED_EXCEPTION_TRAP;
+ {
+ GCX_COOP();
+ ManagedThreadBase::KickOff(FinalizerThread::FinalizerThreadWorkerIteration, NULL);
+ }
+ UNINSTALL_UNHANDLED_MANAGED_EXCEPTION_TRAP;
+
+ s_inDrain = false;
#endif // !TARGET_WASM
}
diff --git a/src/coreclr/vm/pregeneratedstringthunks.cpp b/src/coreclr/vm/pregeneratedstringthunks.cpp
index 91d3fee06306ad..34a2c5fbaf9191 100644
--- a/src/coreclr/vm/pregeneratedstringthunks.cpp
+++ b/src/coreclr/vm/pregeneratedstringthunks.cpp
@@ -13,6 +13,9 @@
#include "loaderallocator.hpp"
#include "wasm/helpers.hpp"
#include "precode_portable.hpp"
+#ifdef FEATURE_WEBCIL
+#include "webcildecoder.h"
+#endif
static StringToThunkHash* s_pPregeneratedStringThunks = nullptr;
diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp
index f3cc5ef9ab1ebd..d73f2cecae953b 100644
--- a/src/coreclr/vm/qcallentrypoints.cpp
+++ b/src/coreclr/vm/qcallentrypoints.cpp
@@ -78,6 +78,10 @@
#include "entrypoints.h"
#endif // TARGET_BROWSER
+#ifdef TARGET_WASI
+#include "wasm/entrypoints.h"
+#endif // TARGET_WASI
+
#ifdef FEATURE_INTERPRETER
#include "interpexec.h"
#endif // FEATURE_INTERPRETER
@@ -539,6 +543,10 @@ static const Entry s_QCall[] =
DllImportEntry(SystemJS_ScheduleTimer)
DllImportEntry(SystemJS_ScheduleBackgroundJob)
#endif // TARGET_BROWSER
+#ifdef TARGET_WASI
+ DllImportEntry(WasiFinalizer_RunWorker)
+ DllImportEntry(WasiFinalizer_TryClearPending)
+#endif // TARGET_WASI
};
const void* QCallResolveDllImport(const char* name)
diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp
index b290e205367523..a5a8458135d362 100644
--- a/src/coreclr/vm/threads.cpp
+++ b/src/coreclr/vm/threads.cpp
@@ -922,8 +922,21 @@ HRESULT Thread::DetachThread(BOOL inTerminationCallback)
// but the thread is blocked.
// We do not consider blocked finalizer thread to be something to be robust against in general.
// Blocking on a finalizer thread is a bug in the user code that can cause all sorts of problems.
+#ifndef TARGET_WASI
+ // On WASI there is no dedicated finalizer thread. The native
+ // FinalizerThread::EnableFinalization sets a flag that the managed
+ // WasiEventLoop drains between poll iterations via
+ // WasiFinalizer_Schedule / WasiFinalizer_TryClearPending /
+ // WasiFinalizer_RunWorker (see WasiFinalizerScheduler.cs). Calling
+ // EnableFinalization from DetachThread runs at process exit from a
+ // C++ TLS destructor, after the EBR thread record for this thread
+ // has already been marked detached; the flag set is safe (just a
+ // volatile store) but no managed drain will ever observe it, so the
+ // call is skipped — the process is exiting and there is no other
+ // thread that could observe leftover detached-thread state.
if (g_fEEStarted)
FinalizerThread::EnableFinalization();
+#endif // !TARGET_WASI
return S_OK;
}
diff --git a/src/coreclr/vm/wasm/cgencpu.h b/src/coreclr/vm/wasm/cgencpu.h
index d6fee9135f3b50..9e296432ed6fe9 100644
--- a/src/coreclr/vm/wasm/cgencpu.h
+++ b/src/coreclr/vm/wasm/cgencpu.h
@@ -7,7 +7,6 @@
#include "stublink.h"
#include "utilcode.h"
-#include
// preferred alignment for data
#define DATA_ALIGNMENT 4
@@ -31,10 +30,15 @@ struct HijackArgs
{
};
-inline void* GetCurrentSP()
+// Reads the wasm __stack_pointer global (the C stack pointer that LLVM
+// maintains). Shared by browser and WASI: wasi-sdk has no
+// emscripten_stack_get_current() equivalent, and reading the global directly
+// works on both. Naked function: the body is exactly the emitted instructions.
+static __attribute__((naked)) void* GetCurrentSP()
{
- WRAPPER_NO_CONTRACT;
- return (void*)emscripten_stack_get_current();
+ __asm__(
+ "global.get __stack_pointer\n"
+ "return\n");
}
extern PCODE GetPreStubEntryPoint();
diff --git a/src/coreclr/vm/wasm/entrypoints.h b/src/coreclr/vm/wasm/entrypoints.h
index 3f71598b8715d6..637476db1fe6ef 100644
--- a/src/coreclr/vm/wasm/entrypoints.h
+++ b/src/coreclr/vm/wasm/entrypoints.h
@@ -17,4 +17,16 @@ extern "C" void SystemJS_DiagnosticServerQueueJob(size_t (*cb)(void *data), void
#endif
+#ifdef TARGET_WASI
+// Native callback that runs the finalizer worker; called by managed code from
+// the WasiEventLoop pump when WasiFinalizer_TryClearPending returns true.
+// Same implementation as browser's SystemJS_ExecuteFinalizationCallback, just
+// exported under a WASI-specific symbol; see src/coreclr/vm/finalizerthread.cpp.
+extern "C" void QCALLTYPE WasiFinalizer_RunWorker();
+// Atomic-flag drain used by managed WasiEventLoop to learn whether a GC has
+// requested finalization since the last poll. Returns TRUE once per scheduled
+// finalization, FALSE otherwise.
+extern "C" CLR_BOOL QCALLTYPE WasiFinalizer_TryClearPending();
+#endif // TARGET_WASI
+
#endif // HAVE_WASM_ENTRYPOINTS_H
\ No newline at end of file
diff --git a/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp b/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp
index 4df22937cc26e8..6359b4d3ede92a 100644
--- a/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp
+++ b/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp
@@ -310,12 +310,6 @@ namespace
*((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I64(2));
}
- static void CallFunc_I32_I32_I64_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
- {
- int32_t (*fptr)(int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int32_t))pcode;
- *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I64(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6));
- }
-
static void CallFunc_I32_I32_I64_I64_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
int32_t (*fptr)(int32_t, int32_t, int64_t, int64_t, int32_t) = (int32_t (*)(int32_t, int32_t, int64_t, int64_t, int32_t))pcode;
@@ -372,12 +366,6 @@ namespace
*((int32_t*)pRet) = (*fptr)(ARG_I64(0));
}
- static void CallFunc_I64_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
- {
- int32_t (*fptr)(int64_t, int32_t) = (int32_t (*)(int64_t, int32_t))pcode;
- *((int32_t*)pRet) = (*fptr)(ARG_I64(0), ARG_I32(1));
- }
-
static void CallFunc_I64_I32_I64_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
int32_t (*fptr)(int64_t, int32_t, int64_t, int32_t) = (int32_t (*)(int64_t, int32_t, int64_t, int32_t))pcode;
@@ -409,12 +397,6 @@ namespace
*((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2));
}
- static void CallFunc_I32_I32_I32_I32_I32_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet)
- {
- int64_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int64_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode;
- *((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4));
- }
-
static void CallFunc_I32_I32_I32_I64_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
int64_t (*fptr)(int32_t, int32_t, int32_t, int64_t) = (int64_t (*)(int32_t, int32_t, int32_t, int64_t))pcode;
@@ -535,24 +517,6 @@ namespace
(*fptr)(&framePointer, ARG_I32(0), pPortableEntryPoint);
}
- static void CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
- {
- void (*fptr)(double, double, double, double, double, double, double, double, double, int32_t, int32_t) = (void (*)(double, double, double, double, double, double, double, double, double, int32_t, int32_t))pcode;
- (*fptr)(ARG_F64(0), ARG_F64(1), ARG_F64(2), ARG_F64(3), ARG_F64(4), ARG_F64(5), ARG_F64(6), ARG_F64(7), ARG_F64(8), ARG_I32(9), ARG_I32(10));
- }
-
- static void CallFunc_F64_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
- {
- void (*fptr)(double, int32_t) = (void (*)(double, int32_t))pcode;
- (*fptr)(ARG_F64(0), ARG_I32(1));
- }
-
- static void CallFunc_F64_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
- {
- void (*fptr)(double, int32_t, int32_t, int32_t) = (void (*)(double, int32_t, int32_t, int32_t))pcode;
- (*fptr)(ARG_F64(0), ARG_I32(1), ARG_I32(2), ARG_I32(3));
- }
-
NOINLINE static void CallFunc_F64_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet)
{
alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK;
@@ -597,12 +561,6 @@ namespace
(*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_IND(3), ARG_I32(4), ARG_I32(5));
}
- static void CallFunc_I32_I32_F64_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
- {
- void (*fptr)(int32_t, int32_t, double) = (void (*)(int32_t, int32_t, double))pcode;
- (*fptr)(ARG_I32(0), ARG_I32(1), ARG_F64(2));
- }
-
static void CallFunc_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode;
@@ -639,12 +597,6 @@ namespace
(*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5));
}
- static void CallFunc_I32_I32_I32_I32_I64_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
- {
- void (*fptr)(int32_t, int32_t, int32_t, int32_t, int64_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int64_t))pcode;
- (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I64(4));
- }
-
NOINLINE static void CallFunc_I32_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet)
{
alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK;
@@ -659,6 +611,12 @@ namespace
(*fptr)(&framePointer, ARG_I32(0), ARG_I32(1), pPortableEntryPoint);
}
+ static void CallFunc_I32_I64_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ void (*fptr)(int32_t, int64_t, int32_t) = (void (*)(int32_t, int64_t, int32_t))pcode;
+ (*fptr)(ARG_I32(0), ARG_I64(1), ARG_I32(2));
+ }
+
NOINLINE static void CallFunc_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet)
{
alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK;
@@ -727,7 +685,6 @@ const StringToWasmSigThunk g_wasmThunks[] = {
{ "Miiiil", (void*)&CallFunc_I32_I32_I32_I64_RetI32 },
{ "Miiiip", (void*)&CallFunc_I32_I32_I32_RetI32_PE },
{ "Miiil", (void*)&CallFunc_I32_I32_I64_RetI32 },
- { "Miiiliiii", (void*)&CallFunc_I32_I32_I64_I32_I32_I32_I32_RetI32 },
{ "Miiilli", (void*)&CallFunc_I32_I32_I64_I64_I32_RetI32 },
{ "Miiip", (void*)&CallFunc_I32_I32_RetI32_PE },
{ "Miil", (void*)&CallFunc_I32_I64_RetI32 },
@@ -737,13 +694,11 @@ const StringToWasmSigThunk g_wasmThunks[] = {
{ "Miilli", (void*)&CallFunc_I32_I64_I64_I32_RetI32 },
{ "Miip", (void*)&CallFunc_I32_RetI32_PE },
{ "Mil", (void*)&CallFunc_I64_RetI32 },
- { "Mili", (void*)&CallFunc_I64_I32_RetI32 },
{ "Milili", (void*)&CallFunc_I64_I32_I64_I32_RetI32 },
{ "Mip", (void*)&CallFunc_Void_RetI32_PE },
{ "Ml", (void*)&CallFunc_Void_RetI64 },
{ "Mli", (void*)&CallFunc_I32_RetI64 },
{ "Mliii", (void*)&CallFunc_I32_I32_I32_RetI64 },
- { "Mliiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetI64 },
{ "Mliiil", (void*)&CallFunc_I32_I32_I32_I64_RetI64 },
{ "Mlili", (void*)&CallFunc_I32_I64_I32_RetI64 },
{ "Mlillp", (void*)&CallFunc_I32_I64_I64_RetI64_PE },
@@ -763,9 +718,6 @@ const StringToWasmSigThunk g_wasmThunks[] = {
{ "MvS8iiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_RetVoid },
{ "MvS8iiiiiiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid },
{ "MvTp", (void*)&CallFunc_This_RetVoid_PE },
- { "Mvdddddddddii", (void*)&CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_I32_RetVoid },
- { "Mvdi", (void*)&CallFunc_F64_I32_RetVoid },
- { "Mvdiii", (void*)&CallFunc_F64_I32_I32_I32_RetVoid },
{ "Mvdiip", (void*)&CallFunc_F64_I32_I32_RetVoid_PE },
{ "Mvfiip", (void*)&CallFunc_F32_I32_I32_RetVoid_PE },
{ "Mvi", (void*)&CallFunc_I32_RetVoid },
@@ -773,16 +725,15 @@ const StringToWasmSigThunk g_wasmThunks[] = {
{ "Mvii", (void*)&CallFunc_I32_I32_RetVoid },
{ "MviiS8S8i", (void*)&CallFunc_I32_I32_S8_S8_I32_RetVoid },
{ "MviiS8S8ii", (void*)&CallFunc_I32_I32_S8_S8_I32_I32_RetVoid },
- { "Mviid", (void*)&CallFunc_I32_I32_F64_RetVoid },
{ "Mviii", (void*)&CallFunc_I32_I32_I32_RetVoid },
{ "MviiiS8S8", (void*)&CallFunc_I32_I32_I32_S8_S8_RetVoid },
{ "MviiiS8S8i", (void*)&CallFunc_I32_I32_I32_S8_S8_I32_RetVoid },
{ "Mviiii", (void*)&CallFunc_I32_I32_I32_I32_RetVoid },
{ "Mviiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetVoid },
{ "Mviiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetVoid },
- { "Mviiiil", (void*)&CallFunc_I32_I32_I32_I32_I64_RetVoid },
{ "Mviiip", (void*)&CallFunc_I32_I32_I32_RetVoid_PE },
{ "Mviip", (void*)&CallFunc_I32_I32_RetVoid_PE },
+ { "Mvili", (void*)&CallFunc_I32_I64_I32_RetVoid },
{ "Mvip", (void*)&CallFunc_I32_RetVoid_PE },
{ "Mvl", (void*)&CallFunc_I64_RetVoid },
{ "Mvp", (void*)&CallFunc_Void_RetVoid_PE }
diff --git a/src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp b/src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp
index 8e63502831632b..6c7b0978257254 100644
--- a/src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp
+++ b/src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp
@@ -403,15 +403,15 @@ static const Entry s_libSystem_Native [] = {
};
static const Entry s_wasi_3A_clocks_2F_monotonic_clock_40_0_2_8 [] = {
- DllImportEntry(WasiPollWorld_wit_Imports_wasi_clocks_v0_2_8_23_wasi_3A_clocks_2F_monotonic_clock_40_0_2_8_23_now) // System.Private.CoreLib
- DllImportEntry(WasiPollWorld_wit_Imports_wasi_clocks_v0_2_8_23_wasi_3A_clocks_2F_monotonic_clock_40_0_2_8_23_resolution) // System.Private.CoreLib
- DllImportEntry(WasiPollWorld_wit_Imports_wasi_clocks_v0_2_8_23_wasi_3A_clocks_2F_monotonic_clock_40_0_2_8_23_subscribe_duration) // System.Private.CoreLib
- DllImportEntry(WasiPollWorld_wit_Imports_wasi_clocks_v0_2_8_23_wasi_3A_clocks_2F_monotonic_clock_40_0_2_8_23_subscribe_instant) // System.Private.CoreLib
+ { "now", (void*)&WasiPollWorld_wit_Imports_wasi_clocks_v0_2_8_23_wasi_3A_clocks_2F_monotonic_clock_40_0_2_8_23_now }, // System.Private.CoreLib
+ { "resolution", (void*)&WasiPollWorld_wit_Imports_wasi_clocks_v0_2_8_23_wasi_3A_clocks_2F_monotonic_clock_40_0_2_8_23_resolution }, // System.Private.CoreLib
+ { "subscribe-duration", (void*)&WasiPollWorld_wit_Imports_wasi_clocks_v0_2_8_23_wasi_3A_clocks_2F_monotonic_clock_40_0_2_8_23_subscribe_duration }, // System.Private.CoreLib
+ { "subscribe-instant", (void*)&WasiPollWorld_wit_Imports_wasi_clocks_v0_2_8_23_wasi_3A_clocks_2F_monotonic_clock_40_0_2_8_23_subscribe_instant }, // System.Private.CoreLib
};
static const Entry s_wasi_3A_io_2F_poll_40_0_2_8 [] = {
- DllImportEntry(WasiPollWorld_wit_Imports_wasi_io_v0_2_8_23_wasi_3A_io_2F_poll_40_0_2_8_23__5B_resource_drop_5D_pollable) // System.Private.CoreLib
- DllImportEntry(WasiPollWorld_wit_Imports_wasi_io_v0_2_8_23_wasi_3A_io_2F_poll_40_0_2_8_23_poll) // System.Private.CoreLib
+ { "[resource-drop]pollable", (void*)&WasiPollWorld_wit_Imports_wasi_io_v0_2_8_23_wasi_3A_io_2F_poll_40_0_2_8_23__5B_resource_drop_5D_pollable }, // System.Private.CoreLib
+ { "poll", (void*)&WasiPollWorld_wit_Imports_wasi_io_v0_2_8_23_wasi_3A_io_2F_poll_40_0_2_8_23_poll }, // System.Private.CoreLib
};
typedef struct PInvokeTable {
diff --git a/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp b/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp
index f3ff66bd008868..dede69947952fb 100644
--- a/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp
+++ b/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp
@@ -92,19 +92,6 @@ static void Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeH
ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid);
}
-static MethodDesc* MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid = nullptr;
-static void Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid(void * arg0, int32_t arg1, uint32_t arg2, int64_t arg3, int64_t arg4, void * arg5, void * arg6)
-{
- int64_t args[7] = { (int64_t)arg0, (int64_t)arg1, (int64_t)arg2, (int64_t)arg3, (int64_t)arg4, (int64_t)arg5, (int64_t)arg6 };
-
- // Lazy lookup of MethodDesc for the function export scenario.
- if (!MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid)
- {
- LookupUnmanagedCallersOnlyMethodByName("System.Diagnostics.Tracing.EventPipeEventProvider, System.Private.CoreLib", "Callback", &MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid);
- }
- ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid);
-}
-
static MethodDesc* MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid = nullptr;
static void Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid(void * arg0, void * arg1, void * arg2, void * arg3)
{
@@ -990,17 +977,6 @@ static uint32_t Call_System_Private_CoreLib_System_GC_RunFinalizers_Void_RetI32(
return result;
}
-static MethodDesc* MD_System_Private_CoreLib_System_Threading_WasiFinalizerScheduler_ScheduleFinalization_Void_RetVoid = nullptr;
-static void Call_System_Private_CoreLib_System_Threading_WasiFinalizerScheduler_ScheduleFinalization_Void_RetVoid()
-{
- // Lazy lookup of MethodDesc for the function export scenario.
- if (!MD_System_Private_CoreLib_System_Threading_WasiFinalizerScheduler_ScheduleFinalization_Void_RetVoid)
- {
- LookupUnmanagedCallersOnlyMethodByName("System.Threading.WasiFinalizerScheduler, System.Private.CoreLib", "ScheduleFinalization", &MD_System_Private_CoreLib_System_Threading_WasiFinalizerScheduler_ScheduleFinalization_Void_RetVoid);
- }
- ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_WasiFinalizerScheduler_ScheduleFinalization_Void_RetVoid, nullptr, 0, nullptr, (PCODE)&Call_System_Private_CoreLib_System_Threading_WasiFinalizerScheduler_ScheduleFinalization_Void_RetVoid);
-}
-
static MethodDesc* MD_System_Private_CoreLib_System_AppContext_Setup_I32_I32_I32_I32_RetVoid = nullptr;
static void Call_System_Private_CoreLib_System_AppContext_Setup_I32_I32_I32_I32_RetVoid(void * arg0, void * arg1, int32_t arg2, void * arg3)
{
@@ -1061,7 +1037,6 @@ const ReverseThunkMapEntry g_ReverseThunks[] =
{ 3962535319, "CallEntryPoint#5:System.Private.CoreLib:System:Environment", { &MD_System_Private_CoreLib_System_Environment_CallEntryPoint_I32_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Environment_CallEntryPoint_I32_I32_I32_I32_I32_RetVoid } },
{ 1821934012, "CallStartupHook#2:System.Private.CoreLib:System:StartupHookProvider", { &MD_System_Private_CoreLib_System_StartupHookProvider_CallStartupHook_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StartupHookProvider_CallStartupHook_I32_I32_RetVoid } },
{ 2915047114, "CallToString#3:System.Private.CoreLib:System.Runtime.CompilerServices:RuntimeHelpers", { &MD_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid } },
- { 4077371982, "Callback#7:System.Private.CoreLib:System.Diagnostics.Tracing:EventPipeEventProvider", { &MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid } },
{ 3358042195, "ClearManaged#4:System.Private.CoreLib:System.StubHelpers:MngdRefCustomMarshaler", { &MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid } },
{ 2311968855, "ClearNative#4:System.Private.CoreLib:System.StubHelpers:MngdRefCustomMarshaler", { &MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearNative_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearNative_I32_I32_I32_I32_RetVoid } },
{ 3378852959, "ConfigCallback#5:System.Private.CoreLib:System:GC", { &MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid } },
@@ -1127,7 +1102,6 @@ const ReverseThunkMapEntry g_ReverseThunks[] =
{ 3929107505, "RhThrowEx#2:System.Private.CoreLib:System.Runtime:EH", { &MD_System_Private_CoreLib_System_Runtime_EH_RhThrowEx_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Runtime_EH_RhThrowEx_I32_I32_RetVoid } },
{ 504238190, "RhThrowHwEx#2:System.Private.CoreLib:System.Runtime:EH", { &MD_System_Private_CoreLib_System_Runtime_EH_RhThrowHwEx_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Runtime_EH_RhThrowHwEx_I32_I32_RetVoid } },
{ 4273572779, "RunFinalizers#0:System.Private.CoreLib:System:GC", { &MD_System_Private_CoreLib_System_GC_RunFinalizers_Void_RetI32, (void*)&Call_System_Private_CoreLib_System_GC_RunFinalizers_Void_RetI32 } },
- { 1007743593, "ScheduleFinalization#0:System.Private.CoreLib:System.Threading:WasiFinalizerScheduler", { &MD_System_Private_CoreLib_System_Threading_WasiFinalizerScheduler_ScheduleFinalization_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_WasiFinalizerScheduler_ScheduleFinalization_Void_RetVoid } },
{ 1963568864, "Setup#4:System.Private.CoreLib:System:AppContext", { &MD_System_Private_CoreLib_System_AppContext_Setup_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_AppContext_Setup_I32_I32_I32_I32_RetVoid } },
{ 1343309100, "StartAssemblyLoad#3:System.Private.CoreLib:System.Runtime.Loader:AssemblyLoadContext", { &MD_System_Private_CoreLib_System_Runtime_Loader_AssemblyLoadContext_StartAssemblyLoad_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Runtime_Loader_AssemblyLoadContext_StartAssemblyLoad_I32_I32_I32_RetVoid } },
{ 3372184251, "StartCallback#1:System.Private.CoreLib:System.Threading:Thread", { &MD_System_Private_CoreLib_System_Threading_Thread_StartCallback_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_Thread_StartCallback_I32_RetVoid } },
diff --git a/src/coreclr/vm/wks/CMakeLists.txt b/src/coreclr/vm/wks/CMakeLists.txt
index bb69aec244f907..96ec823932fb08 100644
--- a/src/coreclr/vm/wks/CMakeLists.txt
+++ b/src/coreclr/vm/wks/CMakeLists.txt
@@ -67,7 +67,7 @@ target_compile_definitions(cee_wks_mergeable PUBLIC CORECLR_EMBEDDED)
target_compile_definitions(cee_wks_core PRIVATE GC_DESCRIPTOR)
-if (FEATURE_PERFTRACING_DISABLE_THREADS)
+if (FEATURE_PERFTRACING AND FEATURE_PERFTRACING_DISABLE_THREADS)
target_compile_definitions(cee_wks_core PRIVATE PERFTRACING_DISABLE_THREADS)
endif()
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 46c84b58ba3dca..11aee01cb8f771 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -837,7 +837,8 @@
-
+
+
@@ -3011,6 +3012,7 @@
+
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.Wasi.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.Wasi.cs
new file mode 100644
index 00000000000000..45e31ce42a35bd
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.Wasi.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Runtime.CompilerServices
+{
+ public static partial class AsyncHelpers
+ {
+ // On WASI the process's main thread is the event-loop pump.
+ // Route the compiler-generated async entry point through it
+ // instead of AsyncHelpers.NonBrowser.cs's GetAwaiter().GetResult(),
+ // whose blocking wait throws PNSE on !IsMultithreadingSupported.
+ // The poll helper pumps the loop to completion and then propagates the
+ // result with await semantics via GetAwaiter().GetResult().
+
+ ///
+ /// This method is intended to be used by a compiler-generated async entry point.
+ ///
+ /// The result from the main entry point to await.
+ [StackTraceHidden]
+ public static void HandleAsyncEntryPoint(Task task)
+ {
+ WasiEventLoop.PollWasiEventLoopUntilResolvedVoid(task);
+ }
+
+ ///
+ /// This method is intended to be used by a compiler-generated async entry point.
+ ///
+ /// The result from the main entry point to await.
+ [StackTraceHidden]
+ public static int HandleAsyncEntryPoint(Task task)
+ {
+ return WasiEventLoop.PollWasiEventLoopUntilResolved(task);
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs
index a1bed7a722a121..0dba1ec0041502 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs
@@ -57,19 +57,18 @@ internal static T PollWasiEventLoopUntilResolved(Task mainTask)
while (!mainTask.IsCompleted)
{
ThreadPoolWorkQueue.Dispatch();
+ WasiFinalizerScheduler.DrainIfPending();
}
}
finally
{
s_mainTask = null;
}
- var exception = mainTask.Exception;
- if (exception is not null)
- {
- throw exception;
- }
- return mainTask.Result;
+ // The pump loop above guarantees the task is completed, so GetResult() never
+ // reaches the blocking (PNSE-throwing) wait. It propagates with await semantics:
+ // the original exception for faults and TaskCanceledException for cancellation.
+ return mainTask.GetAwaiter().GetResult();
}
internal static void PollWasiEventLoopUntilResolvedVoid(Task mainTask)
@@ -80,6 +79,7 @@ internal static void PollWasiEventLoopUntilResolvedVoid(Task mainTask)
while (!mainTask.IsCompleted)
{
ThreadPoolWorkQueue.Dispatch();
+ WasiFinalizerScheduler.DrainIfPending();
}
}
finally
@@ -87,11 +87,10 @@ internal static void PollWasiEventLoopUntilResolvedVoid(Task mainTask)
s_mainTask = null;
}
- var exception = mainTask.Exception;
- if (exception is not null)
- {
- throw exception;
- }
+ // The pump loop above guarantees the task is completed, so GetResult() never
+ // reaches the blocking (PNSE-throwing) wait. It propagates with await semantics:
+ // the original exception for faults and TaskCanceledException for cancellation.
+ mainTask.GetAwaiter().GetResult();
}
internal static void ScheduleCheck()
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiFinalizerScheduler.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiFinalizerScheduler.cs
new file mode 100644
index 00000000000000..a1bcc6631e3988
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiFinalizerScheduler.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if !MONO
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+#endif
+
+namespace System.Threading
+{
+ // Bridges the CoreCLR finalizer machinery to the WASI event loop.
+ // EnableFinalization runs inside the GC, so it cannot re-enter managed
+ // code or the ThreadPool directly. Instead the native side sets an
+ // atomic flag (WasiFinalizer_Schedule) that WasiEventLoop drains at a
+ // safe point via TryClearPending + RunWorker.
+ internal static partial class WasiFinalizerScheduler
+ {
+#if MONO
+ // Mono WASI drives finalization through its own runtime machinery and
+ // does not expose the WasiFinalizer_* QCalls that CoreCLR uses, so the
+ // event-loop drain is a no-op here.
+ internal static void DrainIfPending() { }
+#else
+ [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "WasiFinalizer_TryClearPending")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static partial bool TryClearPendingFinalization();
+
+ [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "WasiFinalizer_RunWorker")]
+ internal static partial void ExecuteFinalizationCallback();
+
+ internal static void DrainIfPending()
+ {
+ if (TryClearPendingFinalization())
+ {
+ ExecuteFinalizationCallback();
+ }
+ }
+#endif
+ }
+}
diff --git a/src/native/libs/build-native.proj b/src/native/libs/build-native.proj
index f948d15426d434..bfa505630fb732 100644
--- a/src/native/libs/build-native.proj
+++ b/src/native/libs/build-native.proj
@@ -27,8 +27,6 @@
-
-
diff --git a/src/native/minipal/getexepath.h b/src/native/minipal/getexepath.h
index dbcf58e554727d..0a6bbb6524c75d 100644
--- a/src/native/minipal/getexepath.h
+++ b/src/native/minipal/getexepath.h
@@ -24,6 +24,8 @@
#elif defined(__HAIKU__)
#include
#include
+#elif defined(TARGET_WASI)
+#include
#elif HAVE_GETAUXVAL
#include
#endif
@@ -97,9 +99,30 @@ static inline char* minipal_getexepath(void)
}
return strdup(path);
-#elif defined(TARGET_WASM)
+#elif defined(TARGET_BROWSER)
const char *browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase
return strdup(browserVirtualAppBase);
+#elif defined(TARGET_WASI)
+ // WASI has no /proc, no AT_EXECFN, and argv[0] is unreliable (often "/").
+ // corerun.wasm is launched with the CORE_ROOT env var set to the directory
+ // that holds CoreCLR (System.Private.CoreLib.dll and friends). The PAL only
+ // needs a path whose dirname is that directory, so synthesize one here.
+ const char* coreRoot = getenv("CORE_ROOT");
+ if (coreRoot == NULL || coreRoot[0] == '\0')
+ {
+ return strdup("/");
+ }
+ size_t coreRootLen = strlen(coreRoot);
+ const char* suffix = "/corerun";
+ size_t suffixLen = strlen(suffix);
+ char* result = (char*)malloc(coreRootLen + suffixLen + 1);
+ if (result == NULL)
+ {
+ return NULL;
+ }
+ memcpy(result, coreRoot, coreRootLen);
+ memcpy(result + coreRootLen, suffix, suffixLen + 1);
+ return result;
#else
#ifdef __linux__
const char* symlinkEntrypointExecutable = "/proc/self/exe";
diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj
index a196a20e0256a4..7408a0dfcb061b 100644
--- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj
+++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj
@@ -62,10 +62,16 @@
-
+
+ <_RunGeneratorTargetOS Condition="'$(TargetOS)' == ''">browser
+ <_RunGeneratorTargetOS Condition="'$(TargetOS)' != ''">$(TargetOS)
+
+
+
diff --git a/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs
index 76f2ee537afef7..7542c7ddb55557 100644
--- a/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs
+++ b/src/tasks/WasmAppBuilder/coreclr/PInvokeTableGenerator.cs
@@ -174,7 +174,17 @@ private void EmitPInvokeTable(StreamWriter w, SortedDictionary m
.Where(l => l.Module == module && !l.Skip)
.OrderBy(l => l.EntryPoint, StringComparer.Ordinal)
.GroupBy(d => d.EntryPoint, StringComparer.Ordinal)
- .Select(l => $" DllImportEntry({CEntryPoint(l.First())}) // {ListRefs(l)}{w.NewLine}")
+ .Select(l =>
+ {
+ PInvoke p = l.First();
+ // Runtime resolver looks up by managed EntryPoint.
+ // [WasmImportLinkage] mangles the C symbol per module,
+ // so emit the entry-point string explicitly rather than
+ // stringifying the mangled name via DllImportEntry.
+ if (p.WasmLinkage)
+ return $" {{ \"{EscapeLiteral(p.EntryPoint)}\", (void*)&{CEntryPoint(p)} }}, // {ListRefs(l)}{w.NewLine}";
+ return $" DllImportEntry({CEntryPoint(p)}) // {ListRefs(l)}{w.NewLine}";
+ })
.ToList();
moduleImports[module] = imports;
diff --git a/src/tests/Common/CLRTest.Execute.Bash.targets b/src/tests/Common/CLRTest.Execute.Bash.targets
index 933b04c77a9942..6c9b3ff0e8d42d 100644
--- a/src/tests/Common/CLRTest.Execute.Bash.targets
+++ b/src/tests/Common/CLRTest.Execute.Bash.targets
@@ -268,7 +268,25 @@ fi
"$CORE_ROOT/corerun" $(CoreRunArgs) ${__DotEnvArg}
+
+ wasmtime run -W exceptions=y --dir "$PWD::/" --dir "$CORE_ROOT::/core" --env CORE_ROOT=/core --env DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true --env DOTNET_WASI_PRINT_EXIT_CODE=1 "$CORE_ROOT/corerun" $(CoreRunArgs) ${__DotEnvArg}
"$CORE_ROOT/watchdog" $_WatcherTimeoutMins
+
+ timeout --preserve-status "${_WatcherTimeoutMins}m"
-
-
+
+
+ " line on stderr
+# at shutdown. This mirrors Mono's wasi runtime contract
+# (src/mono/wasi/runtime/main.c, parsed by
+# src/mono/wasm/host/wasi/WasiEngineHost.cs). We run under wasmtime, tee
+# stderr to a temp file, and grep the marker back out as the test's
+# effective exit code.
+_DebuggerArgsSeparator=
+if [[ "$_DebuggerFullPath" == *lldb* ]];
+then
+ _DebuggerArgsSeparator=--
+elif [[ "$_DebuggerFullPath" == *gdb* ]]
+then
+ _DebuggerArgsSeparator=--args
+fi
+
+if [ ! -z "$CLRCustomTestLauncher" ];
+then
+ LAUNCHER="$CLRCustomTestLauncher $PWD/"
+elif [ "$_RunWithWatcher" == 1 ];
+then
+ LAUNCHER="$(WatcherRunFile) $(CLRTestRunFile)"
+else
+ LAUNCHER="$_DebuggerFullPath $_DebuggerArgsSeparator $(CLRTestRunFile)"
+fi
+
+if [ ! -z ${RunCrossGen2+x} ]%3B then
+ TakeLock
+fi
+
+if [ ! -z "$RunInterpreter" ]; then
+ if [ -z "$DOTNET_InterpMode" ]; then
+ export DOTNET_Interpreter='$(AssemblyName)!*'
+ fi
+fi
+
+_WasiStderr=%24(mktemp -t corerun-stderr.XXXXXX)
+echo $LAUNCHER $ExePath %24(printf "'%s' " "${CLRTestExecutionArguments[@]}")
+$LAUNCHER $ExePath "${CLRTestExecutionArguments[@]}" 2> >(tee "$_WasiStderr" >&2)
+_WasiHostExit=$?
+
+# Prefer the corerun-printed exit code: if the marker is missing (corerun
+# itself crashed / failed before reaching shutdown), fall back to the
+# wasmtime host exit code so failures are still surfaced. Matches the
+# regex used by src/mono/wasm/host/wasi/WasiEngineHost.cs:
+# /^WASM EXIT (-?\d+)$/.
+CLRTestExitCode=%24(awk '/^WASM EXIT -?[0-9]+$/{val=$3} END{print val}' "$_WasiStderr")
+rm -f "$_WasiStderr"
+if [ -z "$CLRTestExitCode" ]%3B then
+ echo "WARNING: corerun did not print a WASM EXIT marker. Using wasmtime host exit code $_WasiHostExit."
+ CLRTestExitCode=$_WasiHostExit
+fi
+
if [ ! -z ${RunCrossGen2+x} ]%3B then
ReleaseLock
fi
diff --git a/src/tests/Common/helixpublishwitharcade.proj b/src/tests/Common/helixpublishwitharcade.proj
index 81d86466cf1483..b029820516b12d 100644
--- a/src/tests/Common/helixpublishwitharcade.proj
+++ b/src/tests/Common/helixpublishwitharcade.proj
@@ -66,6 +66,33 @@
+
+
+
+
+
+ $([MSBuild]::NormalizeDirectory($(ArtifactsObjDir), 'helix-staging', 'wasmtime'))
+
+
+
+
+
+ <_WasmtimeFilesToStage Include="$(WasmtimeDir)**\*" />
+
+
+
+
$(TestBinDir)Tests\Core_Root\
$([MSBuild]::NormalizeDirectory($(CoreRootDirectory)))
@@ -77,7 +104,12 @@
true
false
true
- false
+ false
+ true
+
+ false
AppBundle
wwwroot
@@ -652,6 +684,7 @@
+
@@ -713,6 +746,12 @@
+
+
+
diff --git a/src/tests/JIT/Directed/tailcall/mutual_recursion.fsproj b/src/tests/JIT/Directed/tailcall/mutual_recursion.fsproj
index 9c14bde0bab0cd..c132bd749542ec 100644
--- a/src/tests/JIT/Directed/tailcall/mutual_recursion.fsproj
+++ b/src/tests/JIT/Directed/tailcall/mutual_recursion.fsproj
@@ -8,6 +8,12 @@
$(NetCoreAppToolCurrent)
true
+
+ true
diff --git a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.ilproj b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.ilproj
index 8a3f6ca933bfaf..c7df6a0250c1ec 100644
--- a/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.ilproj
+++ b/src/tests/JIT/Methodical/int64/unsigned/implicit_promotion_il.ilproj
@@ -1,5 +1,12 @@
+
+ Library
PdbOnly
@@ -8,3 +15,5 @@
+
+
diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_87393/Runtime_87393.fsproj b/src/tests/JIT/Regression/JitBlue/Runtime_87393/Runtime_87393.fsproj
index 7a082c37f4ec13..cbd629028eac27 100644
--- a/src/tests/JIT/Regression/JitBlue/Runtime_87393/Runtime_87393.fsproj
+++ b/src/tests/JIT/Regression/JitBlue/Runtime_87393/Runtime_87393.fsproj
@@ -3,9 +3,15 @@
True
$(NetCoreAppToolCurrent)
--tailcalls+
+
+ true
+