diff --git a/eng/Subsets.props b/eng/Subsets.props
index be4331b5e2fda6..14b2d3eedab02a 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -112,7 +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.native+clr.corelib+clr.nativecorelib+clr.tools+clr.packages+clr.crossarchtools+libs.native+host.native
clr.iltools+clr.packages
diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets
index 5f71357d1e165e..85becf0f76299e 100644
--- a/eng/liveBuilds.targets
+++ b/eng/liveBuilds.targets
@@ -325,10 +325,12 @@
IsNative="true" />
-
+
+
+
+
diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props
index bd717001f5e476..3720e00b56f0c7 100644
--- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props
+++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props
@@ -282,6 +282,7 @@
+
diff --git a/src/native/corehost/CMakeLists.txt b/src/native/corehost/CMakeLists.txt
index 180994d6ae86ab..d7d95b26c7d5c3 100644
--- a/src/native/corehost/CMakeLists.txt
+++ b/src/native/corehost/CMakeLists.txt
@@ -81,7 +81,7 @@ if(CLR_CMAKE_TARGET_LINUX OR CLR_CMAKE_TARGET_SUNOS)
add_link_options(LINKER:-Bsymbolic)
endif()
-if(NOT CLR_CMAKE_TARGET_BROWSER)
+if(NOT CLR_CMAKE_TARGET_BROWSER AND NOT CLR_CMAKE_TARGET_WASI)
add_library(fxr_resolver INTERFACE)
target_sources(fxr_resolver INTERFACE fxr_resolver.c)
target_include_directories(fxr_resolver INTERFACE fxr)
@@ -112,9 +112,12 @@ if(NOT CLR_CMAKE_TARGET_BROWSER)
if(CLR_CMAKE_BUILD_HOST_TESTS)
add_subdirectory(test)
endif()
-else()
+elseif(CLR_CMAKE_TARGET_BROWSER)
add_subdirectory(hostmisc)
add_subdirectory(browserhost)
+else() # CLR_CMAKE_TARGET_WASI
+ # The wasi host is self-contained (shares the corerun pal header, no hostmisc dependency).
+ add_subdirectory(wasihost)
endif()
# If there's a dynamic ASAN runtime, then install it in the directories where we put our executables.
diff --git a/src/native/corehost/corehost.proj b/src/native/corehost/corehost.proj
index cb8d246824a806..c07994e55ce3f6 100644
--- a/src/native/corehost/corehost.proj
+++ b/src/native/corehost/corehost.proj
@@ -14,6 +14,8 @@
GenerateRuntimeVersionFile
$(BuildCoreHostDependsOn);InitializeSourceControlInformationFromSourceControlManager
AcquireEmscriptenSdk;$(BuildCoreHostDependsOn);GenerateEmccExports;ResolveRuntimeFilesFromLocalBuild
+
+ AcquireWasiSdk;$(BuildCoreHostDependsOn)
BuildCoreHostOnWindows
BuildCoreHostOnUnix
$(ArtifactsObjDir)$(TargetRid).$(Configuration)\
@@ -92,12 +94,17 @@
$(BuildArgs) -cmakeargs "-DCLR_CMAKE_BUILD_HOST_PRODUCT=$(BuildNativeHostProduct.ToUpper())"
+
+
+ <_CoreHostBuildEnvironmentVariables>WASI_SDK_PATH=$(RuntimeBuildWasiSdkPath)
+
+
-
+
+
+ <_CoreHostBuildEnvironmentVariables>WASI_SDK_PATH=$(RuntimeBuildWasiSdkPath)
+
+
-
+
@@ -197,6 +209,19 @@
SkipUnchangedFiles="true" />
+
+
+
+ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(HostSharedFrameworkDir)libWasiHost.a" />
+
+
+
+
+
diff --git a/src/native/corehost/wasihost/CMakeLists.txt b/src/native/corehost/wasihost/CMakeLists.txt
new file mode 100644
index 00000000000000..284835cfc6eb0c
--- /dev/null
+++ b/src/native/corehost/wasihost/CMakeLists.txt
@@ -0,0 +1,42 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+
+project(wasihost)
+set(DOTNET_PROJECT_NAME "WasiHost-Static")
+
+# HAVE_DIRENT_D_TYPE / HAVE_GETAUXVAL for the shared corerun pal header.
+include(configure.cmake)
+
+set(WASIHOST_SOURCES
+ ./wasihost.cpp
+)
+
+# Reuse the corerun pal header (path/CORE_ROOT/TPA helpers); it's header-only, so no corerun object
+# is linked here.
+set(WASIHOST_CORERUN_DIR ${CLR_REPO_ROOT_DIR}/src/coreclr/hosts/corerun)
+
+add_library(WasiHost-Static
+ STATIC
+ ${WASIHOST_SOURCES}
+)
+set_target_properties(WasiHost-Static PROPERTIES OUTPUT_NAME WasiHost CLEAN_DIRECT_OUTPUT 1)
+
+# The coreclr build sets these wasi emulation defines globally; the corehost build does not. The
+# matching -lwasi-emulated-* libraries are supplied at the per-app relink.
+if (CLR_CMAKE_TARGET_WASI)
+ target_compile_definitions(WasiHost-Static PRIVATE
+ _WASI_EMULATED_MMAN
+ _WASI_EMULATED_GETPID
+ _WASI_EMULATED_SIGNAL
+ _WASI_EMULATED_PROCESS_CLOCKS)
+endif()
+
+target_include_directories(WasiHost-Static PRIVATE
+ ${CMAKE_CURRENT_BINARY_DIR} # generated config.h
+ ${WASIHOST_CORERUN_DIR} # corerun.hpp (shared pal)
+ ${CLR_SRC_NATIVE_DIR} # minipal/*.h
+)
+
+# The runtime static libraries and the app-generated callhelpers are supplied at the per-app relink
+# by the CoreCLR-WASI app builder, which pulls this archive whole-archive for main().
+install(TARGETS WasiHost-Static DESTINATION sharedFramework COMPONENT runtime)
diff --git a/src/native/corehost/wasihost/config.h.in b/src/native/corehost/wasihost/config.h.in
new file mode 100644
index 00000000000000..0c2e459443b136
--- /dev/null
+++ b/src/native/corehost/wasihost/config.h.in
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#ifndef __CONFIG_H__
+#define __CONFIG_H__
+
+#cmakedefine01 HAVE_GETAUXVAL
+#cmakedefine01 HAVE_DIRENT_D_TYPE
+
+#endif // __CONFIG_H__
diff --git a/src/native/corehost/wasihost/configure.cmake b/src/native/corehost/wasihost/configure.cmake
new file mode 100644
index 00000000000000..a24a6e69d05b3e
--- /dev/null
+++ b/src/native/corehost/wasihost/configure.cmake
@@ -0,0 +1,15 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+
+# Mirrors src/coreclr/hosts/corerun/configure.cmake: the shared corerun pal header (corerun.hpp)
+# consumes HAVE_DIRENT_D_TYPE / HAVE_GETAUXVAL from this generated config.h. The corehost build
+# does not include the CMake check modules globally (unlike the coreclr build), so include them.
+include(CheckSymbolExists)
+include(CheckStructHasMember)
+
+check_symbol_exists(getauxval sys/auxv.h HAVE_GETAUXVAL)
+check_struct_has_member ("struct dirent" d_type dirent.h HAVE_DIRENT_D_TYPE)
+
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/config.h)
diff --git a/src/native/corehost/wasihost/wasihost.cpp b/src/native/corehost/wasihost/wasihost.cpp
new file mode 100644
index 00000000000000..9d34a920480a9d
--- /dev/null
+++ b/src/native/corehost/wasihost/wasihost.cpp
@@ -0,0 +1,271 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// Thin CoreCLR-WASI corehost, built as a static archive (libWasiHost.a) and linked per-app by the
+// CoreCLR-WASI app builder against the statically-linked runtime. Mirrors browserhost, but with a
+// real wasi:cli/run main() instead of a JS driver.
+// See https://github.com/dotnet/runtime/issues/130129.
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// Shared pal (path handling, CORE_ROOT/TPA helpers); header-only, so no corerun object is linked.
+#include "corerun.hpp"
+
+#include
+
+using pal::char_t;
+using pal::string_t;
+
+namespace envvar
+{
+ const char_t* const coreRoot = W("CORE_ROOT");
+ const char_t* const coreLibraries = W("CORE_LIBRARIES");
+ const char_t* const printExitCode = W("DOTNET_WASI_PRINT_EXIT_CODE");
+}
+
+// Statically linked at the per-app relink, so declared extern here (as browserhost does).
+extern "C"
+{
+ int coreclr_initialize(
+ const char* exePath,
+ const char* appDomainFriendlyName,
+ int propertyCount,
+ const char** propertyKeys,
+ const char** propertyValues,
+ void** hostHandle,
+ unsigned int* domainId);
+
+ int coreclr_execute_assembly(
+ void* hostHandle,
+ unsigned int domainId,
+ int argc,
+ const char** argv,
+ const char* managedAssemblyPath,
+ unsigned int* exitCode);
+
+ int coreclr_shutdown_2(
+ void* hostHandle,
+ unsigned int domainId,
+ int* latchedExitCode);
+
+ int coreclr_set_error_writer(void (*errorWriter)(const char* line));
+}
+
+// App-generated P/Invoke resolver (per-app, replaces libcoreclr_gen_static.a at the relink), passed
+// to the runtime via the host contract below so app callhelpers and reverse thunks resolve. Plain
+// C++ linkage to match the generated definition (not extern "C").
+const void* callhelpers_pinvoke_override(const char* library_name, const char* entry_point_name);
+
+// Weak: only linked (from libSystem.Globalization.Native.a) in a non-invariant relink; null and
+// skipped otherwise.
+extern "C" __attribute__((weak)) int32_t GlobalizationNative_LoadICUData(const char* path);
+
+// Init properties, kept alive for the process so the runtime contract callback can serve them.
+static std::vector s_property_keys;
+static std::vector s_property_values;
+
+static void log_error_info(const char* line)
+{
+ std::fprintf(stderr, "%s\n", line);
+}
+
+// Include only the first instance of each simple assembly name (CoreCLR may otherwise prefer a
+// later ni over an earlier il).
+static string_t build_tpa(const string_t& core_root, const string_t& core_libraries)
+{
+ static const char_t* const tpa_extensions[] =
+ {
+ W(".dll"),
+ W(".exe"),
+ nullptr
+ };
+
+ std::set name_set;
+ pal::stringstream_t tpa_list;
+
+ for (const char_t* const* curr_ext = tpa_extensions; *curr_ext != nullptr; ++curr_ext)
+ {
+ const char_t* ext = *curr_ext;
+ const size_t ext_len = pal::strlen(ext);
+
+ for (const string_t& dir : { core_libraries, core_root })
+ {
+ if (dir.empty())
+ continue;
+
+ string_t tmp = pal::build_file_list(dir, ext, [&](const char_t* file)
+ {
+ string_t file_local{ file };
+
+ if (pal::string_ends_with(file_local, ext_len, ext))
+ file_local = file_local.substr(0, file_local.length() - ext_len);
+
+ return name_set.insert(file_local).second;
+ });
+
+ tpa_list << tmp;
+ }
+ }
+
+ return tpa_list.str();
+}
+
+static size_t HOST_CONTRACT_CALLTYPE get_runtime_property(
+ const char* key,
+ /*out*/ char* value_buffer,
+ size_t value_buffer_size,
+ void* /*contract_context*/)
+{
+ for (size_t i = 0; i < s_property_keys.size(); ++i)
+ {
+ if (s_property_keys[i] == key)
+ {
+ const std::string& value = s_property_values[i];
+ size_t len = value.length();
+ if (value_buffer != nullptr && value_buffer_size > len)
+ ::memcpy(value_buffer, value.c_str(), len + 1);
+
+ return len + 1;
+ }
+ }
+
+ return (size_t)-1;
+}
+
+int main(int argc, char* argv[])
+{
+ if (argc < 2)
+ {
+ std::fprintf(stderr, "USAGE: %s [arguments]\n", argc > 0 ? argv[0] : "wasihost");
+ return -1;
+ }
+
+ // argv[1] is the managed entry assembly; argv[2..] are passed to it. Copy the slice into a
+ // const char* vector rather than casting char** to const char**.
+ string_t entry_assembly = pal::get_absolute_path(argv[1]);
+ std::vector entry_argv;
+ for (int i = 2; i < argc; ++i)
+ entry_argv.push_back(argv[i]);
+
+ string_t app_path;
+ {
+ string_t file;
+ pal::split_path_to_dir_filename(entry_assembly, app_path, file);
+ pal::ensure_trailing_delimiter(app_path);
+ }
+
+ string_t core_libs = pal::getenv(envvar::coreLibraries);
+ if (!core_libs.empty())
+ pal::ensure_trailing_delimiter(core_libs);
+
+ // CORE_ROOT locates the framework assemblies for the TPA list (the runtime itself is static on
+ // wasi). On wasi the bundle co-locates the framework with the entry assembly, so default to the
+ // entry assembly's directory; CORE_ROOT remains an optional override.
+ string_t core_root = pal::getenv(envvar::coreRoot);
+ if (core_root.empty())
+ core_root = app_path;
+ pal::ensure_trailing_delimiter(core_root);
+
+ string_t exe_path = pal::get_exe_path();
+
+ string_t tpa_list = build_tpa(core_root, core_libs);
+
+ s_property_keys.push_back(HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES);
+ s_property_values.push_back(tpa_list);
+
+ s_property_keys.push_back(HOST_PROPERTY_APP_PATHS);
+ s_property_values.push_back(app_path);
+
+ // NATIVE_DLL_SEARCH_DIRECTORIES is intentionally not set: on wasi native libraries are
+ // statically linked and every P/Invoke is resolved by the pinvoke_override (callhelpers) before
+ // the runtime's native-library search runs, and wasm has no shared-library/dlopen support, so
+ // the search directories can never contribute a load.
+
+ // Static: the contract must outlive coreclr_initialize (the runtime keeps its address). Assign
+ // fields by name (not positionally) so contract layout changes can't mis-wire the callbacks.
+ // The pinvoke_override field is forwarded to PInvokeOverride::SetPInvokeOverride by the runtime.
+ static host_runtime_contract host_contract = { sizeof(host_runtime_contract), nullptr };
+ host_contract.get_runtime_property = &get_runtime_property;
+ host_contract.pinvoke_override = &callhelpers_pinvoke_override;
+ {
+ std::stringstream ss;
+ ss << "0x" << std::hex << (size_t)(&host_contract);
+ s_property_keys.push_back(HOST_PROPERTY_RUNTIME_CONTRACT);
+ s_property_values.push_back(ss.str());
+ }
+
+ std::vector property_keys;
+ std::vector property_values;
+ for (const std::string& key : s_property_keys)
+ property_keys.push_back(key.c_str());
+ for (const std::string& value : s_property_values)
+ property_values.push_back(value.c_str());
+
+ coreclr_set_error_writer(log_error_info);
+
+ void* host_handle = nullptr;
+ unsigned int domain_id = 0;
+ int result = coreclr_initialize(
+ exe_path.c_str(),
+ "wasihost",
+ (int)property_keys.size(),
+ property_keys.data(),
+ property_values.data(),
+ &host_handle,
+ &domain_id);
+ if (result < 0)
+ {
+ std::fprintf(stderr, "coreclr_initialize failed - Error: 0x%08x\n", result);
+ return -1;
+ }
+
+ coreclr_set_error_writer(nullptr);
+
+ // The static ICU shim needs icudt.dat preloaded before managed globalization inits, otherwise it
+ // falls back to invariant (mirrors the browser JS host's wasm_load_icu_data). icudt.dat is a
+ // framework asset, so load it from core_root. Skipped for invariant relinks (weak symbol null)
+ // and tolerant of a missing file.
+ if (GlobalizationNative_LoadICUData != nullptr)
+ {
+ string_t icu_data_path = core_root;
+ icu_data_path.append(W("icudt.dat"));
+ GlobalizationNative_LoadICUData(icu_data_path.c_str());
+ }
+
+ unsigned int exit_code = 0;
+ result = coreclr_execute_assembly(
+ host_handle,
+ domain_id,
+ (int)entry_argv.size(),
+ entry_argv.data(),
+ entry_assembly.c_str(),
+ &exit_code);
+ if (result < 0)
+ {
+ std::fprintf(stderr, "coreclr_execute_assembly failed - Error: 0x%08x\n", result);
+ return -1;
+ }
+
+ int latched_exit_code = exit_code;
+ int shutdown_result = coreclr_shutdown_2(host_handle, domain_id, &latched_exit_code);
+ if (shutdown_result < 0)
+ {
+ std::fprintf(stderr, "coreclr_shutdown_2 failed - Error: 0x%08x\n", shutdown_result);
+ latched_exit_code = -1;
+ }
+
+ // wasi:cli/exit's exit() only signals ok/err, so wasmtime collapses a non-zero result to host
+ // exit 1. Under DOTNET_WASI_PRINT_EXIT_CODE=1, emit a "WASM EXIT " marker the WASI launcher
+ // parses (matching Mono). exit-with-code is stable in WASI 0.3 but still @unstable in the wasip2
+ // world this targets; see corerun.cpp.
+ if (pal::getenv(envvar::printExitCode) == W("1"))
+ std::fprintf(stderr, "WASM EXIT %d\n", latched_exit_code);
+
+ return latched_exit_code;
+}