diff --git a/eng/AcquireWasiSdk.targets b/eng/AcquireWasiSdk.targets index 51c0e62ef45d97..ab3a1ecd703699 100644 --- a/eng/AcquireWasiSdk.targets +++ b/eng/AcquireWasiSdk.targets @@ -7,7 +7,7 @@ --> - <_WasiSdkVersion>25.0 + <_WasiSdkVersion>32.0 <_RuntimeLocalWasiSdkPath>$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'wasi-sdk')) diff --git a/eng/Subsets.props b/eng/Subsets.props index cbb84de541c88e..a9a83d0af01c03 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -32,6 +32,7 @@ <_CoreCLRSupportedOS Condition="'$(TargetsAndroid)' == 'true' and '$(TargetArchitecture)' != 'arm' 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 @@ -65,6 +66,7 @@ CoreCLR Mono Mono + Mono $(DefaultPrimaryRuntimeFlavor) @@ -102,6 +104,7 @@ CoreCLR CoreCLR CoreCLR + CoreCLR Mono Mono $(PrimaryRuntimeFlavor) @@ -113,6 +116,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 + provision.wasisdk+clr.native+clr.corelib+clr.nativecorelib+clr.tools+clr.packages+clr.crossarchtools+libs.native+host.native clr.iltools+clr.packages diff --git a/eng/native.wasm.targets b/eng/native.wasm.targets index 69067b6b0a7d41..25a180c41747b0 100644 --- a/eng/native.wasm.targets +++ b/eng/native.wasm.targets @@ -1,5 +1,6 @@ + @@ -34,6 +35,11 @@ + + + + + diff --git a/eng/native/build-commons.sh b/eng/native/build-commons.sh index c0631ecda1308e..0cd3abf6a894ef 100755 --- a/eng/native/build-commons.sh +++ b/eng/native/build-commons.sh @@ -177,6 +177,9 @@ build_native() echo "Error: Unknown tvOS architecture $__TargetArch." exit 1 fi + elif [[ "$targetOS" == wasi ]]; then + # Don't try to set CC/CXX in init-compiler.sh - it's handled in wasi-sdk-p2.cmake already + __Compiler="default" fi if [[ "$__UseNinja" == 1 ]]; then diff --git a/eng/native/configurecompiler.cmake b/eng/native/configurecompiler.cmake index ace8fd5fe426f3..98581b89e158b7 100644 --- a/eng/native/configurecompiler.cmake +++ b/eng/native/configurecompiler.cmake @@ -806,6 +806,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 23fbf67e6f0a74..46415c60e9f33d 100644 --- a/eng/native/configureplatform.cmake +++ b/eng/native/configureplatform.cmake @@ -222,6 +222,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) #-------------------------------------------- @@ -432,6 +433,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) @@ -513,7 +515,7 @@ else() add_compile_options(-msimd128) endif() if(CLR_CMAKE_TARGET_WASI) - add_compile_options(-fexceptions) + add_compile_options(-fwasm-exceptions) endif() endif() endif() diff --git a/eng/native/functions.cmake b/eng/native/functions.cmake index f7066de09a764c..a8a8e1f51df3af 100644 --- a/eng/native/functions.cmake +++ b/eng/native/functions.cmake @@ -560,20 +560,25 @@ 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. - install(FILES - "$/${targetName}.js" - "$/${targetName}.wasm" - DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT} ${INSTALL_CLR_OPTIONAL}) - - # Conditionally check for and copy any extra data file at install time. - install(CODE - " - if(EXISTS \"$/${targetName}.data\") - file(INSTALL \"$/${targetName}.data\" DESTINATION \"${CMAKE_INSTALL_PREFIX}/${destination}\") - endif() - " - COMPONENT ${INSTALL_CLR_COMPONENT} ${INSTALL_CLR_OPTIONAL}) + if (CLR_CMAKE_TARGET_WASI) + # WASI SDK produces a single .wasm file without .js glue + install(PROGRAMS $ DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT} ${INSTALL_CLR_OPTIONAL}) + else() + # Emscripten produces .js + .wasm assets that aren't directly executable. + install(FILES + "$/${targetName}.js" + "$/${targetName}.wasm" + DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT} ${INSTALL_CLR_OPTIONAL}) + + # Conditionally check for and copy any extra data file at install time. + install(CODE + " + if(EXISTS \"$/${targetName}.data\") + file(INSTALL \"$/${targetName}.data\" DESTINATION \"${CMAKE_INSTALL_PREFIX}/${destination}\") + endif() + " + COMPONENT ${INSTALL_CLR_COMPONENT} ${INSTALL_CLR_OPTIONAL}) + endif() 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-coreclr-build.yml b/eng/pipelines/common/templates/wasi-coreclr-build.yml new file mode 100644 index 00000000000000..0f05d030078439 --- /dev/null +++ b/eng/pipelines/common/templates/wasi-coreclr-build.yml @@ -0,0 +1,23 @@ +parameters: + extraBuildArgs: '' + isExtraPlatformsBuild: false + platforms: [] + +jobs: + +# +# Build CoreCLR native for WASI +# +- 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: _WasiCoreCLR_BuildOnly + buildArgs: -s clr.native -c $(_BuildConfig) ${{ parameters.extraBuildArgs }} + timeoutInMinutes: 120 diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 5ad8a75f81adb5..d223f52ff1c662 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -979,6 +979,12 @@ extends: - WasmTestOnFirefox - WasmTestOnV8 + # WASI CoreCLR native build + - template: /eng/pipelines/common/templates/wasi-coreclr-build.yml + parameters: + platforms: + - wasi_wasm + # EAT Library tests - only run on linux - template: /eng/pipelines/common/templates/wasm-library-aot-tests.yml parameters: diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt index 3806f9319c27f7..cf7801fb0c4687 100644 --- a/src/coreclr/CMakeLists.txt +++ b/src/coreclr/CMakeLists.txt @@ -30,10 +30,22 @@ 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) + # WASI doesn't define these fcntl constants — provide them as no-ops + add_compile_definitions(F_DUPFD_CLOEXEC=1030) + add_compile_definitions(O_CLOEXEC=0) + # WASI stub headers for missing POSIX headers + include_directories(BEFORE SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/pal/src/include/pal/wasi) +endif() + OPTION(CLR_CMAKE_ENABLE_CODE_COVERAGE "Enable code coverage" OFF) #---------------------------------------------------- @@ -119,7 +131,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,9 +161,9 @@ endif() include_directories("pal/prebuilt/inc") include_directories(${CLR_ARTIFACTS_OBJ_DIR}) -if (NOT CLR_CMAKE_TARGET_BROWSER) +if (NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI) add_subdirectory(tools/aot/jitinterface) -endif (NOT CLR_CMAKE_TARGET_BROWSER) +endif (NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI) if(NOT CLR_CROSS_COMPONENTS_BUILD) # NativeAOT only buildable for a subset of CoreCLR-supported configurations diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index c72920560ecaa7..9e850af559a85d 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -289,7 +289,7 @@ - + diff --git a/src/coreclr/clrfeatures.cmake b/src/coreclr/clrfeatures.cmake index 09aecadade5b19..666a7d37baacf7 100644 --- a/src/coreclr/clrfeatures.cmake +++ b/src/coreclr/clrfeatures.cmake @@ -24,7 +24,7 @@ if(CLR_CMAKE_TARGET_TIZEN_LINUX) endif() if(NOT DEFINED FEATURE_EVENT_TRACE) - if (NOT CLR_CMAKE_TARGET_BROWSER) + if (NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI) # To actually disable FEATURE_EVENT_TRACE, also change clr.featuredefines.props set(FEATURE_EVENT_TRACE 1) endif() diff --git a/src/coreclr/debug/debug-pal/CMakeLists.txt b/src/coreclr/debug/debug-pal/CMakeLists.txt index f1545c9902a070..654f5c25bbc8bc 100644 --- a/src/coreclr/debug/debug-pal/CMakeLists.txt +++ b/src/coreclr/debug/debug-pal/CMakeLists.txt @@ -24,7 +24,9 @@ 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) + 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/gc/unix/configure.cmake b/src/coreclr/gc/unix/configure.cmake index 26930243b3ca96..bfaaca55a1f07a 100644 --- a/src/coreclr/gc/unix/configure.cmake +++ b/src/coreclr/gc/unix/configure.cmake @@ -105,9 +105,10 @@ elseif (HAVE_PTHREAD_IN_LIBC) set(PTHREAD_LIBRARY c) endif() -check_library_exists(${PTHREAD_LIBRARY} pthread_condattr_setclock "" HAVE_PTHREAD_CONDATTR_SETCLOCK) - -check_library_exists(${PTHREAD_LIBRARY} pthread_setaffinity_np "" HAVE_PTHREAD_SETAFFINITY_NP) +if (PTHREAD_LIBRARY) + check_library_exists(${PTHREAD_LIBRARY} pthread_condattr_setclock "" HAVE_PTHREAD_CONDATTR_SETCLOCK) + check_library_exists(${PTHREAD_LIBRARY} pthread_setaffinity_np "" HAVE_PTHREAD_SETAFFINITY_NP) +endif() check_cxx_symbol_exists(_SC_PHYS_PAGES unistd.h HAVE__SC_PHYS_PAGES) check_cxx_symbol_exists(_SC_AVPHYS_PAGES unistd.h HAVE__SC_AVPHYS_PAGES) diff --git a/src/coreclr/gc/unix/events.cpp b/src/coreclr/gc/unix/events.cpp index 1a4a2111ea13bc..de690772c2c3f1 100644 --- a/src/coreclr/gc/unix/events.cpp +++ b/src/coreclr/gc/unix/events.cpp @@ -14,7 +14,7 @@ namespace { -#if HAVE_PTHREAD_CONDATTR_SETCLOCK +#if HAVE_PTHREAD_CONDATTR_SETCLOCK || defined(_WASI_EMULATED_PROCESS_CLOCKS) void TimeSpecAdd(timespec* time, uint32_t milliseconds) { uint64_t nsec = time->tv_nsec + (uint64_t)milliseconds * tccMilliSecondsToNanoSeconds; @@ -26,7 +26,7 @@ void TimeSpecAdd(timespec* time, uint32_t milliseconds) time->tv_nsec = nsec; } -#endif // HAVE_PTHREAD_CONDATTR_SETCLOCK +#endif // HAVE_PTHREAD_CONDATTR_SETCLOCK || _WASI_EMULATED_PROCESS_CLOCKS #if HAVE_CLOCK_GETTIME_NSEC_NP // Convert nanoseconds to the timespec structure @@ -129,7 +129,7 @@ class GCEvent::Impl NanosecondsToTimeSpec(nanoseconds, &endTime); endMachTime = clock_gettime_nsec_np(CLOCK_UPTIME_RAW) + nanoseconds; } -#elif HAVE_PTHREAD_CONDATTR_SETCLOCK +#elif HAVE_PTHREAD_CONDATTR_SETCLOCK || defined(_WASI_EMULATED_PROCESS_CLOCKS) if (milliseconds != INFINITE) { clock_gettime(CLOCK_MONOTONIC, &endTime); diff --git a/src/coreclr/gc/unix/gcenv.unix.cpp b/src/coreclr/gc/unix/gcenv.unix.cpp index 9b0aeb20f9ec83..50e699a3b8d08d 100644 --- a/src/coreclr/gc/unix/gcenv.unix.cpp +++ b/src/coreclr/gc/unix/gcenv.unix.cpp @@ -44,7 +44,7 @@ #error "sys/time.h required by GC PAL for the time being" #endif -#if HAVE_SYS_MMAN_H +#if HAVE_SYS_MMAN_H || defined(_WASI_EMULATED_MMAN) #include #else #error "sys/mman.h required by GC PAL" @@ -847,7 +847,7 @@ static uint64_t GetMemorySizeMultiplier(char units) return 1; } -#if !defined(__APPLE__) && !defined(__HAIKU__) && !defined(__EMSCRIPTEN__) +#if !defined(__APPLE__) && !defined(__HAIKU__) && !defined(__EMSCRIPTEN__) && !defined(__wasi__) // Try to read the MemAvailable entry from /proc/meminfo. // Return true if the /proc/meminfo existed, the entry was present and we were able to parse it. static bool ReadMemAvailable(uint64_t* memAvailable) @@ -1117,6 +1117,11 @@ uint64_t GetAvailablePhysicalMemory() } #elif defined(__EMSCRIPTEN__) available = emscripten_get_heap_max() - emscripten_get_heap_size(); +#elif defined(__wasi__) + // Match Emscripten's approach: max memory minus current usage. + // WASI wasm32 linear memory can grow up to 4GB when --max-memory is not set. + uint64_t current = (uint64_t)__builtin_wasm_memory_size(0) * 65536; + available = (uint64_t)4 * 1024 * 1024 * 1024 - current; #else // Linux static volatile bool tryReadMemInfo = true; diff --git a/src/coreclr/hosts/corerun/CMakeLists.txt b/src/coreclr/hosts/corerun/CMakeLists.txt index 58f715de6fdfe1..2034e69a31f65d 100644 --- a/src/coreclr/hosts/corerun/CMakeLists.txt +++ b/src/coreclr/hosts/corerun/CMakeLists.txt @@ -6,7 +6,7 @@ set(CORERUN_IN_BROWSER 0) if(CLR_CMAKE_HOST_WIN32) add_definitions(-DFX_VER_INTERNALNAME_STR=corerun.exe) -else() +elseif(NOT CLR_CMAKE_TARGET_WASM) include(configure.cmake) endif() @@ -36,9 +36,13 @@ 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) + elseif(CLR_CMAKE_TARGET_WASI) + target_link_libraries(corerun PRIVATE -ldl) + endif() # Android implements pthread natively. # WASM, linking against pthreads indicates Node.js workers are @@ -111,6 +115,17 @@ else() -sNODERAWFS=1 -lnodefs.js) endif() + elseif (CLR_CMAKE_TARGET_WASI) + target_link_options(corerun PRIVATE + -fwasm-exceptions + -Wl,-z,stack-size=8388608 + -Wl,--initial-memory=52428800 + -L$ENV{WASI_SDK_PATH}/share/wasi-sysroot/lib/wasm32-wasip2) + target_link_libraries(corerun PRIVATE + -lwasi-emulated-process-clocks + -lwasi-emulated-signal + -lwasi-emulated-getpid + -lunwind) endif() if (CORERUN_IN_BROWSER) diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp index d6032091a94adc..d4309d4faecf17 100644 --- a/src/coreclr/hosts/corerun/corerun.cpp +++ b/src/coreclr/hosts/corerun/corerun.cpp @@ -833,7 +833,11 @@ int MAIN(const int argc, const char_t* argv[]) return EXIT_FAILURE; if (config.self_test) +#ifdef TARGET_WASI + return EXIT_FAILURE; // self_test uses C++ exceptions not available on WASI +#else return self_test(); +#endif int exit_code = run(config); return exit_code; @@ -851,6 +855,8 @@ extern "C" DLL_EXPORT HRESULT CDECL GetCurrentClrDetails(void** clrInstance, uns // Self testing for corerun. // +#ifndef TARGET_WASI + #define THROW_IF_FALSE(stmt) if (!(stmt)) throw W(#stmt); #define THROW_IF_TRUE(stmt) if (stmt) throw W(#stmt); static int self_test() @@ -946,3 +952,4 @@ static int self_test() pal::fprintf(stdout, W("Self-test passed.\n")); return EXIT_SUCCESS; } +#endif // !TARGET_WASI diff --git a/src/coreclr/hosts/corerun/corerun.hpp b/src/coreclr/hosts/corerun/corerun.hpp index ec5a978814ec3c..c7ac24a51c0a13 100644 --- a/src/coreclr/hosts/corerun/corerun.hpp +++ b/src/coreclr/hosts/corerun/corerun.hpp @@ -387,7 +387,12 @@ class platform_specific_actions final #endif // CMake generated +#ifndef TARGET_WASM #include +#else +#define HAVE_GETAUXVAL 0 +#define HAVE_DIRENT_D_TYPE 1 +#endif #include #if __GNUC__ >= 4 diff --git a/src/coreclr/hosts/corerun/dotenv.cpp b/src/coreclr/hosts/corerun/dotenv.cpp index 8792d63151f56a..b070e7312e4249 100644 --- a/src/coreclr/hosts/corerun/dotenv.cpp +++ b/src/coreclr/hosts/corerun/dotenv.cpp @@ -332,6 +332,7 @@ void dotenv::load_into_current_process() const } } +#ifndef TARGET_WASI #define THROW_IF_FALSE(stmt) if (!(stmt)) throw W(#stmt); void dotenv::self_test() @@ -546,3 +547,4 @@ void dotenv::self_test() THROW_IF_FALSE(pal::getenv(W("CORERUN_DOTENV_SELF_TEST_LOAD2")) == W("A")); } } +#endif // !TARGET_WASI diff --git a/src/coreclr/pal/src/CMakeLists.txt b/src/coreclr/pal/src/CMakeLists.txt index 6903d78e15c9de..2c9c732e555086 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_TARGET_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_TARGET_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_TARGET_ARCH_WASM) @@ -128,6 +128,9 @@ if (CLR_CMAKE_TARGET_ARCH_WASM) set(PLATFORM_SOURCES arch/${PAL_ARCH_SOURCES_DIR}/stubs.cpp ) + if(CLR_CMAKE_TARGET_WASI) + list(APPEND PLATFORM_SOURCES arch/${PAL_ARCH_SOURCES_DIR}/mmap-wasi.c) + endif() endif() if(NOT CLR_CMAKE_TARGET_APPLE AND NOT CLR_CMAKE_TARGET_ARCH_WASM) @@ -150,9 +153,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 @@ -209,6 +210,13 @@ set(SOURCES thread/threadsusp.cpp ) +if (NOT CLR_CMAKE_TARGET_WASI) + list(APPEND SOURCES + debug/debug.cpp + exception/signal.cpp + ) +endif() + set_source_files_properties( com/guid.cpp PROPERTIES @@ -249,11 +257,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/mmap-wasi.c b/src/coreclr/pal/src/arch/wasm/mmap-wasi.c new file mode 100644 index 00000000000000..178595b49a9d14 --- /dev/null +++ b/src/coreclr/pal/src/arch/wasm/mmap-wasi.c @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +// WASI mmap/munmap — replaces wasi-emulated-mman. +// +// Anonymous mappings (GC heap, virtual reserve) use a bump allocator backed +// by a large arena to prevent address reuse. The GC reserves and releases +// large regions; with plain malloc, freed regions can be reused by file-backed +// mappings, and then the GC zeroes them — corrupting assembly data. +// +// File-backed mappings use malloc + read since they are long-lived and small. + +#include +#include +#include +#include +#include +#include + +// --- Bump allocator for anonymous mappings --- +// 256MB arena — sufficient for GC heap + runtime structures. +// On WASM this is virtual (backed by memory.grow on demand). +#define ANON_ARENA_SIZE (256 * 1024 * 1024) + +static char *s_arena_base; +static size_t s_arena_offset; + +static void ensure_arena(void) { + if (s_arena_base == NULL) { + s_arena_base = (char *)malloc(ANON_ARENA_SIZE); + if (s_arena_base == NULL) { + fprintf(stderr, "WASI mmap: failed to allocate %d byte arena\n", ANON_ARENA_SIZE); + abort(); + } + s_arena_offset = 0; + } +} + +// Allocate from the bump arena with alignment. Never reuses freed addresses. +static void *arena_alloc(size_t length, size_t align) { + ensure_arena(); + // Align the absolute address, not just the offset + uintptr_t base = (uintptr_t)s_arena_base; + uintptr_t current = base + s_arena_offset; + uintptr_t aligned_addr = (current + align - 1) & ~(align - 1); + size_t aligned_offset = aligned_addr - base; + if (aligned_offset + length > ANON_ARENA_SIZE) { + return NULL; + } + void *ptr = (void *)aligned_addr; + s_arena_offset = aligned_offset + length; + return ptr; +} + +static int is_arena_ptr(void *addr) { + if (s_arena_base == NULL) return 0; + return (char *)addr >= s_arena_base && + (char *)addr < s_arena_base + ANON_ARENA_SIZE; +} + +// --- Tracking for malloc-based file mappings --- +#define MAX_TRACKED 256 +static struct { void *addr; size_t size; } s_allocs[MAX_TRACKED]; +static int s_alloc_count; + +static void track_alloc(void *addr, size_t size) { + if (s_alloc_count < MAX_TRACKED) { + s_allocs[s_alloc_count].addr = addr; + s_allocs[s_alloc_count].size = size; + s_alloc_count++; + } +} + +static int untrack_alloc(void *addr) { + for (int i = 0; i < s_alloc_count; i++) { + if (s_allocs[i].addr == addr) { + s_allocs[i] = s_allocs[--s_alloc_count]; + return 1; + } + } + return 0; +} + +void *mmap(void *addr, size_t length, int prot, int flags, int fd, long long offset) { + (void)addr; (void)prot; + + if (length == 0) { errno = EINVAL; return (void *)-1; } + + if (fd >= 0) { + // File-backed mapping: use malloc + read (isolated from GC arena) + void *ptr = malloc(length); + if (ptr == NULL) { errno = ENOMEM; return (void *)-1; } + + long long orig = lseek(fd, 0, SEEK_CUR); + if (lseek(fd, offset, SEEK_SET) == -1) { + free(ptr); + errno = EIO; + return (void *)-1; + } + size_t total = 0; + while (total < length) { + ssize_t n = read(fd, (char*)ptr + total, length - total); + if (n <= 0) break; + total += n; + } + if (total < length) + memset((char*)ptr + total, 0, length - total); + lseek(fd, orig, SEEK_SET); + + track_alloc(ptr, length); + return ptr; + } else { + // Anonymous mapping: allocate from bump arena (never reuses addresses) + void *ptr = arena_alloc(length, 65536); // page-aligned + if (ptr == NULL) { errno = ENOMEM; return (void *)-1; } + memset(ptr, 0, length); + return ptr; + } +} + +int munmap(void *addr, size_t length) { + (void)length; + if (addr == NULL || addr == (void *)-1) + return 0; + + if (is_arena_ptr(addr)) { + // Arena allocations are never freed — bump allocator doesn't reuse. + // This matches real mmap behavior where munmap'd pages can't be + // returned to other callers. + return 0; + } + + // File-backed malloc allocation — free it + if (untrack_alloc(addr)) + free(addr); + return 0; +} + +int mprotect(void *addr, size_t length, int prot) { + (void)addr; (void)length; (void)prot; + return 0; +} diff --git a/src/coreclr/pal/src/arch/wasm/stubs.cpp b/src/coreclr/pal/src/arch/wasm/stubs.cpp index 7405e744889fd3..f83a77815fa4a1 100644 --- a/src/coreclr/pal/src/arch/wasm/stubs.cpp +++ b/src/coreclr/pal/src/arch/wasm/stubs.cpp @@ -3,7 +3,9 @@ #include "pal/dbgmsg.h" #include "pal/signal.hpp" +#ifdef __EMSCRIPTEN__ #include +#endif SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do this first @@ -18,6 +20,7 @@ DBG_DebugBreak() { #ifdef _DEBUG DBG_PrintInterpreterStack(); +#ifdef __EMSCRIPTEN__ double start = emscripten_get_now(); emscripten_debugger(); double end = emscripten_get_now(); @@ -28,11 +31,109 @@ DBG_DebugBreak() // to match other platforms and fail fast emscripten_throw_string("Debugger not attached"); } +#else + abort(); +#endif // __EMSCRIPTEN__ #else // _DEBUG +#ifdef __EMSCRIPTEN__ emscripten_throw_string("Debug break called in release build."); +#else + abort(); +#endif // __EMSCRIPTEN__ #endif // _DEBUG } +#ifdef TARGET_WASI +extern "C" void DebugBreak() +{ + DBG_DebugBreak(); +} + +extern "C" void PALAPI OutputDebugStringA(LPCSTR lpOutputString) +{ + (void)lpOutputString; +} + +extern "C" void PALAPI OutputDebugStringW(LPCWSTR lpOutputString) +{ + (void)lpOutputString; +} + +// C++ EH runtime symbols (__cxa_thread_atexit, __cxa_allocate_exception, +// __cxa_throw, __cxa_begin_catch, __cxa_end_catch) are provided by the +// wasm-exceptions-enabled libc++abi and libunwind. + +BOOL PAL_ProbeMemory(PVOID pBuffer, DWORD cbBuffer, BOOL fWriteAccess) +{ + (void)pBuffer; (void)cbBuffer; (void)fWriteAccess; + return TRUE; +} + +void SEHCleanupSignals(bool isChildProcess) +{ + (void)isChildProcess; +} + +void UnmaskActivationSignal() +{ +} + +BOOL SEHInitializeSignals(CorUnix::CPalThread *pthrCurrent, DWORD flags) +{ + (void)pthrCurrent; (void)flags; + return TRUE; +} + +extern "C" int shm_open(const char *name, int oflag, int mode) +{ + (void)name; (void)oflag; (void)mode; + return -1; +} + +extern "C" int shm_unlink(const char *name) +{ + (void)name; + return -1; +} + +BOOL PALAPI FlushInstructionCache(HANDLE hProcess, LPCVOID lpBaseAddress, SIZE_T dwSize) +{ + (void)hProcess; (void)lpBaseAddress; (void)dwSize; + return TRUE; +} + +BOOL PALAPI GetThreadContext(HANDLE hThread, LPCONTEXT lpContext) +{ + (void)hThread; (void)lpContext; + return FALSE; +} + +void PALAPI PAL_EnableCrashReportBeforeSignalChaining() +{ +} + +void PALAPI PAL_FreeExceptionRecords(IN EXCEPTION_RECORD *exceptionRecord, IN CONTEXT *contextRecord) +{ + (void)exceptionRecord; (void)contextRecord; +} + +BOOL PALAPI PAL_VirtualUnwind(CONTEXT *context) +{ + (void)context; + return FALSE; +} + +VOID PALAPI RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, CONST ULONG_PTR *lpArguments) +{ + (void)dwExceptionCode; (void)dwExceptionFlags; (void)nNumberOfArguments; (void)lpArguments; + abort(); +} + +extern "C" void SystemJS_GetLocaleInfo(void) +{ +} +#endif + /* context */ extern "C" void @@ -91,3 +192,12 @@ extern "C" int pthread_setschedparam(pthread_t, int, const struct sched_param *) _ASSERT(!"pthread_setschedparam not implemented on wasm"); return 0; } + +#ifdef TARGET_WASI +extern "C" int pthread_getschedparam(pthread_t, int *policy, struct sched_param *param) +{ + if (policy) *policy = 0; + if (param) memset(param, 0, sizeof(struct sched_param)); + return 0; +} +#endif // TARGET_WASI diff --git a/src/coreclr/pal/src/configure.cmake b/src/coreclr/pal/src/configure.cmake index 66e036c3fd26b3..aa283bc895969d 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) @@ -368,7 +370,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(" @@ -616,6 +620,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 5aea2481bd4162..b196ff3aeb2c3c 100644 --- a/src/coreclr/pal/src/exception/seh.cpp +++ b/src/coreclr/pal/src/exception/seh.cpp @@ -365,4 +365,6 @@ bool CatchHardwareExceptionHolder::IsEnabled() return pThread ? pThread->IsHardwareExceptionsEnabled() : false; } +#ifndef TARGET_WASI #include "seh-unwind.cpp" +#endif diff --git a/src/coreclr/pal/src/file/file.cpp b/src/coreclr/pal/src/file/file.cpp index 6c8df8cb67815f..cbeca4de3bc81c 100644 --- a/src/coreclr/pal/src/file/file.cpp +++ b/src/coreclr/pal/src/file/file.cpp @@ -289,7 +289,15 @@ CorUnix::InternalCanonicalizeRealPath(LPCSTR lpUnixPath, PathCharString& lpBuffe goto LExit; } -#if REALPATH_SUPPORTS_NONEXISTENT_FILES +#ifdef TARGET_WASI + // On WASI, realpath cannot resolve virtual preopen paths (e.g. "/app/foo") + // since they don't exist on a real filesystem. Use the path directly. + if (!lpBuffer.Set(lpUnixPath, strlen(lpUnixPath))) + { + palError = ERROR_NOT_ENOUGH_MEMORY; + goto LExit; + } +#elif REALPATH_SUPPORTS_NONEXISTENT_FILES RealPathHelper(lpUnixPath, lpBuffer); #else // !REALPATH_SUPPORTS_NONEXISTENT_FILES @@ -330,6 +338,13 @@ CorUnix::InternalCanonicalizeRealPath(LPCSTR lpUnixPath, PathCharString& lpBuffe lpBuffer.Append(lpExistingPath, strlen(lpExistingPath)); return NO_ERROR; } +#ifdef TARGET_WASI + // On WASI, realpath cannot resolve virtual preopen paths (e.g. "/app/foo") + // since they don't exist on a real filesystem. Use the path directly. + lpBuffer.Clear(); + lpBuffer.Append(lpExistingPath, strlen(lpExistingPath)); + return NO_ERROR; +#else // !TARGET_WASI else { bool fSetFilename = true; @@ -382,6 +397,7 @@ CorUnix::InternalCanonicalizeRealPath(LPCSTR lpUnixPath, PathCharString& lpBuffe // incase someone else adds another if clause below us. goto LExit; } +#endif // TARGET_WASI #endif // REALPATH_SUPPORTS_NONEXISTENT_FILES LExit: @@ -625,6 +641,8 @@ CorUnix::InternalCreateFile( palError = ERROR_INTERNAL_ERROR; goto done; } +#elif defined(TARGET_WASI) + // WASI: no uncached I/O support; silently ignore #else #error Insufficient support for uncached I/O on this platform #endif @@ -2419,7 +2437,15 @@ static HANDLE init_std_handle(HANDLE * pStd, FILE *stream) /* duplicate the FILE *, so that we can fclose() in FILECloseHandle without closing the original */ +#ifdef TARGET_WASI + // WASI: fcntl F_DUPFD_CLOEXEC is not implemented at runtime. + // Reuse the fd directly — fd ownership is safe on WASI because there + // is no fork/exec, stdio fds are never closed by the PAL, and only + // one handle references each standard stream. + 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)); diff --git a/src/coreclr/pal/src/include/pal/context.h b/src/coreclr/pal/src/include/pal/context.h index 8658cdebb25cbc..4b528d737add5b 100644 --- a/src/coreclr/pal/src/include/pal/context.h +++ b/src/coreclr/pal/src/include/pal/context.h @@ -38,6 +38,9 @@ extern "C" #endif // HAVE_UCONTEXT_H typedef ucontext_t native_context_t; +#elif defined(TARGET_WASI) +// WASI has no ucontext — provide a minimal stub +typedef int native_context_t; #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/wasi/dlfcn.h b/src/coreclr/pal/src/include/pal/wasi/dlfcn.h new file mode 100644 index 00000000000000..49cdd948350b9b --- /dev/null +++ b/src/coreclr/pal/src/include/pal/wasi/dlfcn.h @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// WASI dlfcn.h wrapper — adds Dl_info and dladdr stubs. + +#ifndef _WASI_DLFCN_WRAPPER_H +#define _WASI_DLFCN_WRAPPER_H + +#include_next + +#ifndef RTLD_NOLOAD +#define RTLD_NOLOAD 0 +#endif + +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; } + +#endif // _WASI_DLFCN_WRAPPER_H diff --git a/src/coreclr/pal/src/include/pal/wasi/link.h b/src/coreclr/pal/src/include/pal/wasi/link.h new file mode 100644 index 00000000000000..9b7677d309ba7f --- /dev/null +++ b/src/coreclr/pal/src/include/pal/wasi/link.h @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Stub link.h for WASI — provides minimal types for PAL compilation. + +#ifndef _WASI_LINK_H +#define _WASI_LINK_H + +#include +#include + +typedef uint32_t Elf32_Addr; +typedef uint32_t Elf32_Word; +typedef uint32_t Elf32_Off; +typedef uint16_t Elf32_Half; + +typedef struct { + Elf32_Word p_type; + Elf32_Off p_offset; + Elf32_Addr p_vaddr; + Elf32_Addr p_paddr; + Elf32_Word p_filesz; + Elf32_Word p_memsz; + Elf32_Word p_flags; + Elf32_Word p_align; +} Elf32_Phdr; + +struct dl_phdr_info { + Elf32_Addr dlpi_addr; + const char *dlpi_name; + const Elf32_Phdr *dlpi_phdr; + Elf32_Half dlpi_phnum; +}; + +static inline int dl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data) { (void)callback; (void)data; return 0; } + +#endif // _WASI_LINK_H diff --git a/src/coreclr/pal/src/include/pal/wasi/pthread.h b/src/coreclr/pal/src/include/pal/wasi/pthread.h new file mode 100644 index 00000000000000..582d830c35feea --- /dev/null +++ b/src/coreclr/pal/src/include/pal/wasi/pthread.h @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// WASI pthread.h wrapper — adds missing declarations that WASI hides. + +#ifndef _WASI_PTHREAD_WRAPPER_H +#define _WASI_PTHREAD_WRAPPER_H + +#include_next +#include + +#ifdef __wasi__ + +// These are declared in upstream musl but hidden on WASI behind +// __wasilibc_unmodified_upstream. Provide declarations here; +// link-time stubs are in pal/src/arch/wasm/stubs.cpp. +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _WASI_PTHREAD_EXIT_DECLARED +#define _WASI_PTHREAD_EXIT_DECLARED +_Noreturn void pthread_exit(void *); +int pthread_setschedparam(pthread_t, int, const struct sched_param *); +int pthread_getschedparam(pthread_t, int *__restrict, struct sched_param *__restrict); +#endif + +#ifdef __cplusplus +} +#endif + +#ifndef SCHED_OTHER +#define SCHED_OTHER 0 +#endif + +#endif // __wasi__ +#endif // _WASI_PTHREAD_WRAPPER_H diff --git a/src/coreclr/pal/src/include/pal/wasi/pwd.h b/src/coreclr/pal/src/include/pal/wasi/pwd.h new file mode 100644 index 00000000000000..e5954bb639c057 --- /dev/null +++ b/src/coreclr/pal/src/include/pal/wasi/pwd.h @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Stub pwd.h for WASI + +#ifndef _WASI_PWD_H +#define _WASI_PWD_H + +#include + +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; +}; + +static inline struct passwd *getpwuid(uid_t uid) { (void)uid; return (struct passwd *)0; } + +#endif // _WASI_PWD_H diff --git a/src/coreclr/pal/src/include/pal/wasi/signal.h b/src/coreclr/pal/src/include/pal/wasi/signal.h new file mode 100644 index 00000000000000..352a2672ddd8d3 --- /dev/null +++ b/src/coreclr/pal/src/include/pal/wasi/signal.h @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// WASI signal.h wrapper — provides full POSIX signal types needed by the +// CoreCLR PAL that WASI SDK hides behind __wasilibc_unmodified_upstream. + +#ifndef _WASI_SIGNAL_WRAPPER_H +#define _WASI_SIGNAL_WRAPPER_H + +#include_next +#include +#include + +#ifndef SA_SIGINFO + +#define SA_SIGINFO 4 +#define SA_RESTART 0x10000000 +#define SA_ONSTACK 0x08000000 +#define SA_NODEFER 0x40000000 +#define SA_RESETHAND 0x80000000 + +#define SIG_BLOCK 0 +#define SIG_UNBLOCK 1 +#define SIG_SETMASK 2 + +#define FPE_INTDIV 1 +#define FPE_INTOVF 2 +#define FPE_FLTDIV 3 +#define FPE_FLTOVF 4 +#define FPE_FLTUND 5 +#define FPE_FLTRES 6 +#define FPE_FLTINV 7 +#define FPE_FLTSUB 8 + +#define ILL_ILLOPC 1 +#define ILL_ILLOPN 2 +#define ILL_ILLADR 3 +#define ILL_ILLTRP 4 +#define ILL_PRVOPC 5 +#define ILL_PRVREG 6 +#define ILL_COPROC 7 +#define ILL_BADSTK 8 + +#define SEGV_MAPERR 1 +#define SEGV_ACCERR 2 + +#define BUS_ADRALN 1 +#define BUS_ADRERR 2 +#define BUS_OBJERR 3 + +#ifndef SS_DISABLE +#define SS_DISABLE 2 +#define SS_ONSTACK 1 +#endif + +union sigval { + int sival_int; + void *sival_ptr; +}; + +typedef struct { + int si_signo; + int si_errno; + int si_code; + pid_t si_pid; + uid_t si_uid; + void *si_addr; + int si_status; + union sigval si_value; +} siginfo_t; + +typedef struct sigaltstack { + void *ss_sp; + int ss_flags; + size_t ss_size; +} stack_t; + +struct sigaction { + union { + void (*sa_handler)(int); + void (*sa_sigaction)(int, siginfo_t *, void *); + } __sa_handler; + sigset_t sa_mask; + int sa_flags; + void (*sa_restorer)(void); +}; +#define sa_handler __sa_handler.sa_handler +#define sa_sigaction __sa_handler.sa_sigaction + +static inline int sigaction(int sig, const struct sigaction *act, struct sigaction *oact) { (void)sig; (void)act; (void)oact; return 0; } +static inline int sigemptyset(sigset_t *set) { if (set) *set = 0; return 0; } +static inline int sigfillset(sigset_t *set) { if (set) *set = ~(sigset_t)0; return 0; } +static inline int sigaddset(sigset_t *set, int sig) { if (set) *set |= (1UL << sig); return 0; } +static inline int sigdelset(sigset_t *set, int sig) { if (set) *set &= ~(1UL << sig); return 0; } +static inline int sigprocmask(int how, const sigset_t *set, sigset_t *oset) { (void)how; (void)set; (void)oset; return 0; } +static inline int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset) { (void)how; (void)set; (void)oset; return 0; } +static inline int pthread_kill(pthread_t t, int sig) { (void)t; (void)sig; return -1; } +static inline int kill(pid_t pid, int sig) { (void)pid; (void)sig; return -1; } + +#endif // SA_SIGINFO + + +// Additional signal codes not in the base set +#ifndef SI_USER +#define SI_USER 0 +#define SI_KERNEL 128 +#endif + +#ifndef TRAP_BRKPT +#define TRAP_BRKPT 1 +#define TRAP_TRACE 2 +#endif +#endif // _WASI_SIGNAL_WRAPPER_H diff --git a/src/coreclr/pal/src/include/pal/wasi/sys/resource.h b/src/coreclr/pal/src/include/pal/wasi/sys/resource.h new file mode 100644 index 00000000000000..ddd30221432611 --- /dev/null +++ b/src/coreclr/pal/src/include/pal/wasi/sys/resource.h @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Stub sys/resource.h wrapper for WASI — provides rlimit types. +// The WASI SDK has sys/resource.h but rlimit is hidden behind +// __wasilibc_unmodified_upstream. We provide the types directly. + +#ifndef _WASI_SYS_RESOURCE_H +#define _WASI_SYS_RESOURCE_H + +#include_next + +#ifndef RLIMIT_AS + +typedef unsigned long long rlim_t; + +struct rlimit { + rlim_t rlim_cur; + rlim_t rlim_max; +}; + +#define RLIM_INFINITY (~0ULL) +#define RLIMIT_AS 9 +#define RLIMIT_FSIZE 1 + +static inline int getrlimit(int resource, struct rlimit *rlim) { + rlim->rlim_cur = RLIM_INFINITY; + rlim->rlim_max = RLIM_INFINITY; + return 0; +} + +#endif // RLIMIT_AS + +#endif // _WASI_SYS_RESOURCE_H diff --git a/src/coreclr/pal/src/include/pal/wasi/sys/vfs.h b/src/coreclr/pal/src/include/pal/wasi/sys/vfs.h new file mode 100644 index 00000000000000..023a0486470b1b --- /dev/null +++ b/src/coreclr/pal/src/include/pal/wasi/sys/vfs.h @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Stub sys/vfs.h for WASI + +#ifndef _WASI_SYS_VFS_H +#define _WASI_SYS_VFS_H + +struct statfs { + long f_type; + long f_bsize; + long f_blocks; + long f_bfree; + long f_bavail; +}; + +static inline int statfs(const char *path, struct statfs *buf) { (void)path; (void)buf; return -1; } + +#endif // _WASI_SYS_VFS_H diff --git a/src/coreclr/pal/src/include/pal/wasi/sys/wait.h b/src/coreclr/pal/src/include/pal/wasi/sys/wait.h new file mode 100644 index 00000000000000..54ffbf0b6a0fb1 --- /dev/null +++ b/src/coreclr/pal/src/include/pal/wasi/sys/wait.h @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Stub sys/wait.h for WASI + +#ifndef _WASI_SYS_WAIT_H +#define _WASI_SYS_WAIT_H + +#include + +#define WNOHANG 1 +#define WUNTRACED 2 + +#define WIFEXITED(x) (1) +#define WEXITSTATUS(x) (0) +#define WIFSIGNALED(x) (0) +#define WTERMSIG(x) (0) +#define WIFSTOPPED(x) (0) +#define WSTOPSIG(x) (0) + +static inline pid_t waitpid(pid_t pid, int *status, int options) { (void)pid; (void)status; (void)options; return -1; } +static inline pid_t wait(int *status) { (void)status; return -1; } + +#endif // _WASI_SYS_WAIT_H diff --git a/src/coreclr/pal/src/include/pal/wasi/ucontext.h b/src/coreclr/pal/src/include/pal/wasi/ucontext.h new file mode 100644 index 00000000000000..eb53829fd3b785 --- /dev/null +++ b/src/coreclr/pal/src/include/pal/wasi/ucontext.h @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Stub ucontext.h for WASI + +#ifndef _WASI_UCONTEXT_H +#define _WASI_UCONTEXT_H + +typedef struct { + int dummy; +} mcontext_t; + +typedef struct ucontext_t { + mcontext_t uc_mcontext; +} ucontext_t; + +#endif // _WASI_UCONTEXT_H diff --git a/src/coreclr/pal/src/include/pal/wasi/unistd.h b/src/coreclr/pal/src/include/pal/wasi/unistd.h new file mode 100644 index 00000000000000..2f7249d3f70ea3 --- /dev/null +++ b/src/coreclr/pal/src/include/pal/wasi/unistd.h @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// WASI unistd.h wrapper — adds missing POSIX functions. + +#ifndef _WASI_UNISTD_WRAPPER_H +#define _WASI_UNISTD_WRAPPER_H + +#include_next + +#ifdef __wasi__ +static inline uid_t geteuid(void) { return 0; } +static inline gid_t getegid(void) { return 0; } +static inline pid_t getsid(pid_t pid) { (void)pid; return 0; } +static inline pid_t fork(void) { return -1; } +static inline int execv(const char *path, char *const argv[]) { (void)path; (void)argv; return -1; } +static inline int execve(const char *path, char *const argv[], char *const envp[]) { (void)path; (void)argv; (void)envp; return -1; } +static inline int pipe(int pipefd[2]) { (void)pipefd; return -1; } + +#ifndef PIPE_BUF +#define PIPE_BUF 4096 +#endif + +#ifndef SCHED_OTHER +#define SCHED_OTHER 0 +#endif +#endif // __wasi__ + +#endif // _WASI_UNISTD_WRAPPER_H diff --git a/src/coreclr/pal/src/map/map.cpp b/src/coreclr/pal/src/map/map.cpp index a36c986280b530..8263cdbad408fd 100644 --- a/src/coreclr/pal/src/map/map.cpp +++ b/src/coreclr/pal/src/map/map.cpp @@ -466,7 +466,15 @@ CorUnix::InternalCreateFileMapping( // information, though... // +#ifdef TARGET_WASI + // WASI: fcntl F_DUPFD_CLOEXEC is not implemented at runtime. + // Reuse the fd directly — fd ownership is safe on WASI because + // there is no fork/exec, so CLOEXEC is irrelevant, and file + // mappings are long-lived alongside their source handles. + UnixFd = pFileLocalData->unix_fd; +#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/thread/context.cpp b/src/coreclr/pal/src/thread/context.cpp index 522fe66aad1282..ae7403afdbc5c1 100644 --- a/src/coreclr/pal/src/thread/context.cpp +++ b/src/coreclr/pal/src/thread/context.cpp @@ -483,6 +483,10 @@ BOOL CONTEXT_GetRegisters(DWORD processId, LPCONTEXT lpContext) } else { +#ifdef TARGET_WASI + // WASI: cannot get context of another process + return FALSE; +#else ucontext_t registers; #if HAVE_PT_REGS struct pt_regs ptrace_registers; @@ -509,6 +513,7 @@ BOOL CONTEXT_GetRegisters(DWORD processId, LPCONTEXT lpContext) #undef ASSIGN_REG CONTEXTFromNativeContext(®isters, lpContext, lpContext->ContextFlags); +#endif // !TARGET_WASI } bRet = TRUE; diff --git a/src/coreclr/pal/src/thread/thread.cpp b/src/coreclr/pal/src/thread/thread.cpp index 6d956b86d50e4f..53bf87e3115169 100644 --- a/src/coreclr/pal/src/thread/thread.cpp +++ b/src/coreclr/pal/src/thread/thread.cpp @@ -41,7 +41,7 @@ SET_DEFAULT_DEBUG_CHANNEL(THREAD); // some headers have code with asserts, so do #include #endif -#if defined(TARGET_BROWSER) +#if defined(TARGET_BROWSER) && defined(__EMSCRIPTEN__) #include #endif @@ -1269,9 +1269,15 @@ CorUnix::GetThreadTimesInternal( ts = status.pr_utime; #else // HAVE_PTHREAD_GETCPUCLOCKID || HAVE_CLOCK_THREAD_CPUTIME +#ifdef TARGET_WASI + pTargetThread->Unlock(pThread); + goto SetTimesToZero; +#else #error "Don't know how to obtain user cpu time on this platform." +#endif #endif // HAVE_PTHREAD_GETCPUCLOCKID || HAVE_CLOCK_THREAD_CPUTIME +#ifndef TARGET_WASI pTargetThread->Unlock(pThread); /* Calculate time in nanoseconds and assign to user time */ @@ -1286,6 +1292,7 @@ CorUnix::GetThreadTimesInternal( retval = TRUE; goto GetThreadTimesInternalExit; +#endif #endif //HAVE_MACH_THREADS @@ -2333,7 +2340,7 @@ CPalThread::GetStackBase() status = pthread_attr_init(&attr); _ASSERT_MSG(status == 0, "pthread_attr_init call failed"); -#ifndef TARGET_BROWSER +#ifndef TARGET_WASM #if HAVE_PTHREAD_ATTR_GET_NP status = pthread_attr_get_np(thread, &attr); #elif HAVE_PTHREAD_GETATTR_NP @@ -2350,9 +2357,13 @@ CPalThread::GetStackBase() _ASSERT_MSG(status == 0, "pthread_attr_destroy call failed"); stackBase = (void*)((size_t)stackAddr + stackSize); -#else // TARGET_BROWSER +#elif defined(__EMSCRIPTEN__) stackBase = (void*)emscripten_stack_get_base(); -#endif // TARGET_BROWSER +#else // WASI + // WASI: use current frame as approximation for stack base. + // The stack grows down from __stack_pointer set at link time. + stackBase = __builtin_frame_address(0); +#endif // TARGET_WASM #endif // !TARGET_APPLE return stackBase; @@ -2377,7 +2388,7 @@ CPalThread::GetStackLimit() status = pthread_attr_init(&attr); _ASSERT_MSG(status == 0, "pthread_attr_init call failed"); -#ifndef TARGET_BROWSER +#ifndef TARGET_WASM #if HAVE_PTHREAD_ATTR_GET_NP status = pthread_attr_get_np(thread, &attr); #elif HAVE_PTHREAD_GETATTR_NP @@ -2392,7 +2403,7 @@ CPalThread::GetStackLimit() status = pthread_attr_destroy(&attr); _ASSERT_MSG(status == 0, "pthread_attr_destroy call failed"); -#else // TARGET_BROWSER +#elif defined(__EMSCRIPTEN__) uintptr_t stackLimitMaybe = emscripten_stack_get_end(); if (stackLimitMaybe == 0) // emscripten_stack_get_end can return 0. { @@ -2403,7 +2414,13 @@ CPalThread::GetStackLimit() emscripten_stack_set_limits(GetStackBase(), (void*)stackLimitMaybe); } stackLimit = (void*)stackLimitMaybe; -#endif // TARGET_BROWSER +#else // WASI + // WASI: estimate stack limit from current frame minus configured stack size. + // Stack size is set at link time via -Wl,-z,stack-size=N (default 8MB). + stackLimit = (void*)((size_t)__builtin_frame_address(0) - 8 * 1024 * 1024); + if ((size_t)stackLimit < sizeof(size_t)) + stackLimit = (void*)sizeof(size_t); +#endif // TARGET_WASM #endif // !TARGET_APPLE return stackLimit; diff --git a/src/coreclr/runtime.proj b/src/coreclr/runtime.proj index 5d085be67b1061..8ebfe274edd344 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/utilcode/clrhost.cpp b/src/coreclr/utilcode/clrhost.cpp index 2c6915c3fdfad5..c0b8f3702076a6 100644 --- a/src/coreclr/utilcode/clrhost.cpp +++ b/src/coreclr/utilcode/clrhost.cpp @@ -12,6 +12,11 @@ #include "clrnt.h" #include "contract.h" +#ifdef TARGET_WASI +#include +#include +#endif + #if HOST_WINDOWS extern "C" IMAGE_DOS_HEADER __ImageBase; #else @@ -62,6 +67,48 @@ DWORD GetClrModulePathName(SString& buffer) #ifdef HOST_WINDOWS return WszGetModuleFileName((HINSTANCE)GetClrModuleBase(), buffer); #else +#ifdef TARGET_WASI + // On WASI, the runtime is statically linked and there's no loadable module. + // Use CORE_ROOT env var to determine where CoreLib lives. + const char* coreRoot = getenv("CORE_ROOT"); + if (coreRoot != NULL && coreRoot[0] != '\0') + { + char absPath[4096]; + const char* resolvedRoot = coreRoot; + if (coreRoot[0] != '/') + { + // Use getcwd + relative path (not realpath which resolves through symlinks + // and on WASI returns '/' for '.', losing the preopen-relative path) + char cwd[4096]; + if (getcwd(cwd, sizeof(cwd)) != NULL) + { + size_t cwdLen = strlen(cwd); + // Skip "./" prefix if CORE_ROOT is "." + if (strcmp(coreRoot, ".") == 0) + { + resolvedRoot = cwd; + } + else + { + snprintf(absPath, sizeof(absPath), "%s%s%s", + cwd, + (cwdLen > 0 && cwd[cwdLen-1] != '/') ? "/" : "", + coreRoot); + resolvedRoot = absPath; + } + } + } + // Construct a fake module path so GetClrModuleDirectory extracts the dir + SString corePath; + corePath.SetUTF8(resolvedRoot); + size_t len = strlen(resolvedRoot); + if (len == 0 || resolvedRoot[len - 1] != '/') + corePath.Append(DIRECTORY_SEPARATOR_CHAR_W); + corePath.AppendUTF8("libcoreclr.so"); + buffer.Set(corePath); + return buffer.GetCount(); + } +#endif // TARGET_WASI #ifndef HOST_WASM HMODULE hModule = PAL_GetPalHostModule(); #else diff --git a/src/coreclr/vm/finalizerthread.cpp b/src/coreclr/vm/finalizerthread.cpp index 0e36352feb1e5e..99fffe02b43ffe 100644 --- a/src/coreclr/vm/finalizerthread.cpp +++ b/src/coreclr/vm/finalizerthread.cpp @@ -77,7 +77,8 @@ void FinalizerThread::EnableFinalization() #ifdef TARGET_BROWSER SystemJS_ScheduleFinalization(); #else - // WASI is not implemented yet + // WASI: run finalization synchronously (no separate thread, no JS event loop) + FinalizerThreadWorkerIteration(nullptr); #endif // TARGET_BROWSER #endif // !TARGET_WASM } diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 7fcaae1f738923..670a500c02d59c 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -813,7 +813,7 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J #ifdef FEATURE_EVENT_TRACE PCODE pNativeCodeStartAddress = pCode; -#ifdef FEATURE_INTERPRETER +#if defined(FEATURE_INTERPRETER) && !defined(FEATURE_PORTABLE_ENTRYPOINTS) if (isInterpreterCode) { // If this is interpreter code, we need to get the native code start address from the interpreter Precode @@ -821,7 +821,7 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J InterpByteCodeStart* interpreterCode = dac_cast(pPrecode->GetData()->ByteCodeAddr); pNativeCodeStartAddress = PINSTRToPCODE(dac_cast(interpreterCode)); } -#endif // FEATURE_INTERPRETER +#endif // FEATURE_INTERPRETER && !FEATURE_PORTABLE_ENTRYPOINTS ETW::MethodLog::MethodJitted(this, &namespaceOrClassName, &methodName, diff --git a/src/coreclr/vm/wasm/cgencpu.h b/src/coreclr/vm/wasm/cgencpu.h index 5829f5fd798592..cf40a764ac1893 100644 --- a/src/coreclr/vm/wasm/cgencpu.h +++ b/src/coreclr/vm/wasm/cgencpu.h @@ -7,7 +7,9 @@ #include "stublink.h" #include "utilcode.h" +#ifdef __EMSCRIPTEN__ #include +#endif // preferred alignment for data #define DATA_ALIGNMENT 4 @@ -34,7 +36,11 @@ struct HijackArgs inline void* GetCurrentSP() { WRAPPER_NO_CONTRACT; +#ifdef __EMSCRIPTEN__ return (void*)emscripten_stack_get_current(); +#else + return __builtin_frame_address(0); +#endif } extern PCODE GetPreStubEntryPoint(); diff --git a/src/native/libs/System.Globalization.Native/pal_icushim_static.c b/src/native/libs/System.Globalization.Native/pal_icushim_static.c index 54560d29049981..011f7adb7e97c2 100644 --- a/src/native/libs/System.Globalization.Native/pal_icushim_static.c +++ b/src/native/libs/System.Globalization.Native/pal_icushim_static.c @@ -83,7 +83,7 @@ int32_t mono_wasi_load_icu_data(const void* pData) static int32_t load_icu_data(const void* pData) { - UErrorCode status = 0; + UErrorCode status = U_ZERO_ERROR; udata_setCommonData(pData, &status); if (U_FAILURE(status)) @@ -108,6 +108,7 @@ static const char * cstdlib_load_icu_data(const char *path) { char *file_buf = NULL; + long file_buf_size = 0; FILE *fp = fopen(path, "rb"); if (fp == NULL) @@ -122,7 +123,7 @@ cstdlib_load_icu_data(const char *path) goto error; } - long file_buf_size = ftell(fp); + file_buf_size = ftell(fp); if (file_buf_size == -1) { @@ -130,7 +131,7 @@ cstdlib_load_icu_data(const char *path) goto error; } - file_buf = malloc(sizeof(char) * (unsigned long)(file_buf_size + 1)); + file_buf = (char*)malloc(sizeof(char) * (unsigned long)(file_buf_size + 1)); if (file_buf == NULL) { @@ -223,7 +224,7 @@ int32_t GlobalizationNative_LoadICU(void) } #endif - UErrorCode status = 0; + UErrorCode status = U_ZERO_ERROR; UVersionInfo version; // Request the CLDR version to perform basic ICU initialization and find out // whether it worked. diff --git a/src/native/libs/System.Native/pal_networking.c b/src/native/libs/System.Native/pal_networking.c index 7f13890ca61fe3..dda5e357063e07 100644 --- a/src/native/libs/System.Native/pal_networking.c +++ b/src/native/libs/System.Native/pal_networking.c @@ -3681,6 +3681,7 @@ int32_t SystemNative_SendFile(intptr_t out_fd, intptr_t in_fd, int64_t offset, i // Emulate sendfile using a simple read/send loop. *sent = 0; char* buffer = NULL; + size_t bufferLength = 0; // Save the original input file position and seek to the offset position off_t inputFileOrigOffset = lseek(infd, 0, SEEK_CUR); @@ -3690,7 +3691,7 @@ int32_t SystemNative_SendFile(intptr_t out_fd, intptr_t in_fd, int64_t offset, i } // Allocate a buffer - size_t bufferLength = Min((size_t)count, 80 * 1024 * sizeof(char)); + bufferLength = Min((size_t)count, 80 * 1024 * sizeof(char)); buffer = (char*)malloc(bufferLength); if (buffer == NULL) { diff --git a/src/native/libs/build-native.proj b/src/native/libs/build-native.proj index 2e9588da2dc019..d4d5c40780f25b 100644 --- a/src/native/libs/build-native.proj +++ b/src/native/libs/build-native.proj @@ -27,8 +27,6 @@ - -