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 +