From 4bb9930af3c72a9eae4a1ab2d98edb0d6e2e5534 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sun, 18 Jan 2026 18:39:00 +0100 Subject: [PATCH 1/3] wip --- .../corehost/browserhost/browserhost.cpp | 40 +++------------ src/native/corehost/browserhost/host/host.ts | 49 ++++++++++++++++++- .../browserhost/libBrowserHost.footer.js | 46 ++++++++--------- .../corehost/browserhost/loader/config.ts | 4 ++ .../Common/JavaScript/types/ems-ambient.ts | 5 +- 5 files changed, 82 insertions(+), 62 deletions(-) diff --git a/src/native/corehost/browserhost/browserhost.cpp b/src/native/corehost/browserhost/browserhost.cpp index 14d26f295d1df6..78ce28e62ec153 100644 --- a/src/native/corehost/browserhost/browserhost.cpp +++ b/src/native/corehost/browserhost/browserhost.cpp @@ -89,44 +89,20 @@ static const void* pinvoke_override(const char* library_name, const char* entry_ return nullptr; } -static pal::string_t app_path; -static pal::string_t search_paths; -static pal::string_t tpa; -static const pal::string_t app_domain_name = "corehost"; -static const pal::string_t exe_path = "/managed"; -static std::vector propertyKeys; -static std::vector propertyValues; -static pal::char_t ptr_to_string_buffer[STRING_LENGTH("0xffffffffffffffff") + 1]; - -// WASM-TODO: pass TPA via argument, not env -// WASM-TODO: pass app_path via argument, not env -// WASM-TODO: pass search_paths via argument, not env -extern "C" int BrowserHost_InitializeCoreCLR(void) +static host_runtime_contract host_contract = { sizeof(host_runtime_contract), nullptr }; + +extern "C" void* BrowserHost_CreateHostContract(void) { - pal::getenv(HOST_PROPERTY_APP_PATHS, &app_path); - pal::getenv(HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES, &search_paths); - pal::getenv(HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES, &tpa); - - // Set base initialization properties. - propertyKeys.push_back(HOST_PROPERTY_APP_PATHS); - propertyValues.push_back(app_path.c_str()); - propertyKeys.push_back(HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES); - propertyValues.push_back(search_paths.c_str()); - propertyKeys.push_back(HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES); - propertyValues.push_back(tpa.c_str()); - - host_runtime_contract host_contract = { sizeof(host_runtime_contract), nullptr }; host_contract.pinvoke_override = &pinvoke_override; host_contract.external_assembly_probe = &BrowserHost_ExternalAssemblyProbe; + return &host_contract; +} - pal::snwprintf(ptr_to_string_buffer, ARRAY_SIZE(ptr_to_string_buffer), _X("0x%zx"), (size_t)(&host_contract)); - - propertyKeys.push_back(HOST_PROPERTY_RUNTIME_CONTRACT); - propertyValues.push_back(ptr_to_string_buffer); - +extern "C" int BrowserHost_InitializeCoreCLR(int propertiesCount, const char** propertyKeys, const char** propertyValues) +{ coreclr_set_error_writer(log_error_info); - int retval = coreclr_initialize(exe_path.c_str(), app_domain_name.c_str(), (int)propertyKeys.size(), propertyKeys.data(), propertyValues.data(), &CurrentClrInstance, &CurrentAppDomainId); + int retval = coreclr_initialize("/managed", "corehost", propertiesCount, propertyKeys, propertyValues, &CurrentClrInstance, &CurrentAppDomainId); if (retval < 0) { diff --git a/src/native/corehost/browserhost/host/host.ts b/src/native/corehost/browserhost/host/host.ts index a4cdfa1c90d54e..5a6a54a13af60d 100644 --- a/src/native/corehost/browserhost/host/host.ts +++ b/src/native/corehost/browserhost/host/host.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { CharPtr, VfsAsset, VoidPtr, VoidPtrPtr } from "./types"; +import type { CharPtr, CharPtrPtr, VfsAsset, VoidPtr, VoidPtrPtr } from "./types"; import { _ems_ } from "../../../libs/Common/JavaScript/ems-ambient"; const loadedAssemblies: Map = new Map(); @@ -90,8 +90,53 @@ export function installVfsFile(bytes: Uint8Array, asset: VfsAsset) { ); } + +const HOST_PROPERTY_RUNTIME_CONTRACT = "HOST_RUNTIME_CONTRACT"; +const HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES = "TRUSTED_PLATFORM_ASSEMBLIES"; +const HOST_PROPERTY_ENTRY_ASSEMBLY_NAME = "ENTRY_ASSEMBLY_NAME"; +const HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES = "NATIVE_DLL_SEARCH_DIRECTORIES"; +const HOST_PROPERTY_APP_PATHS = "APP_PATHS"; +const APP_CONTEXT_BASE_DIRECTORY = "APP_CONTEXT_BASE_DIRECTORY"; +const RUNTIME_IDENTIFIER = "RUNTIME_IDENTIFIER"; + export function initializeCoreCLR(): number { - return _ems_._BrowserHost_InitializeCoreCLR(); + const loaderConfig = _ems_.dotnetApi.getConfig(); + const hostContractPtr = _ems_._BrowserHost_CreateHostContract(); + const runtimeConfigProperties = new Map(); + if (loaderConfig.runtimeConfig?.runtimeOptions?.configProperties) { + for (const [key, value] of Object.entries(loaderConfig.runtimeConfig?.runtimeOptions?.configProperties)) { + runtimeConfigProperties.set(key, "" + value); + } + } + const assemblyPaths = loaderConfig.resources!.assembly.map(a => "/" + a.virtualPath); + const coreAssemblyPaths = loaderConfig.resources!.coreAssembly.map(a => "/" + a.virtualPath); + const tpa = [...coreAssemblyPaths, ...assemblyPaths].join(":"); + runtimeConfigProperties.set(HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES, tpa); + runtimeConfigProperties.set(HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES, loaderConfig.virtualWorkingDirectory!); + runtimeConfigProperties.set(HOST_PROPERTY_APP_PATHS, loaderConfig.virtualWorkingDirectory!); + runtimeConfigProperties.set(HOST_PROPERTY_ENTRY_ASSEMBLY_NAME, loaderConfig.mainAssemblyName!); + runtimeConfigProperties.set(APP_CONTEXT_BASE_DIRECTORY, "/"); + runtimeConfigProperties.set(RUNTIME_IDENTIFIER, "browser-wasm"); + runtimeConfigProperties.set(HOST_PROPERTY_RUNTIME_CONTRACT, `0x${(hostContractPtr as unknown as number).toString(16)}`); + + const buffers: VoidPtr[] = []; + const appctx_keys = _ems_._malloc(4 * runtimeConfigProperties.size) as any as CharPtrPtr; + const appctx_values = _ems_._malloc(4 * runtimeConfigProperties.size) as any as CharPtrPtr; + buffers.push(appctx_keys as any); + buffers.push(appctx_values as any); + + let propertyCount = 0; + for (const [key, value] of runtimeConfigProperties.entries()) { + const keyPtr = _ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key); + const valuePtr = _ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value); + _ems_.dotnetApi.setHeapU32((appctx_keys as any) + (propertyCount * 4), keyPtr); + _ems_.dotnetApi.setHeapU32((appctx_values as any) + (propertyCount * 4), valuePtr); + propertyCount++; + buffers.push(keyPtr as any); + buffers.push(valuePtr as any); + } + + return _ems_._BrowserHost_InitializeCoreCLR(propertyCount, appctx_keys, appctx_values); } // bool BrowserHost_ExternalAssemblyProbe(const char* pathPtr, /*out*/ void **outDataStartPtr, /*out*/ int64_t* outSize); diff --git a/src/native/corehost/browserhost/libBrowserHost.footer.js b/src/native/corehost/browserhost/libBrowserHost.footer.js index 49504a4252d243..a64f55ecc6975d 100644 --- a/src/native/corehost/browserhost/libBrowserHost.footer.js +++ b/src/native/corehost/browserhost/libBrowserHost.footer.js @@ -19,10 +19,14 @@ const exports = {}; libBrowserHost(exports); + // libBrowserHostFn is too complex for acorn-optimizer.mjs to find the dependencies + let explicitDeps = [ + "wasm_load_icu_data", "BrowserHost_CreateHostContract", "BrowserHost_InitializeCoreCLR", "BrowserHost_ExecuteAssembly" + ]; let commonDeps = [ "$DOTNET", "$DOTNET_INTEROP", "$ENV", "$FS", "$NODEFS", "$libBrowserHostFn", - "wasm_load_icu_data", "BrowserHost_InitializeCoreCLR", "BrowserHost_ExecuteAssembly" + ...explicitDeps ]; const lib = { $BROWSER_HOST: { @@ -35,28 +39,18 @@ exports.dotnetInitializeModule(dotnetInternals); BROWSER_HOST.assignExports(exports, BROWSER_HOST); - const HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES = "TRUSTED_PLATFORM_ASSEMBLIES"; - const HOST_PROPERTY_ENTRY_ASSEMBLY_NAME = "ENTRY_ASSEMBLY_NAME"; - const HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES = "NATIVE_DLL_SEARCH_DIRECTORIES"; - const HOST_PROPERTY_APP_PATHS = "APP_PATHS"; - - const config = dotnetInternals[2/*InternalExchangeIndex.LoaderConfig*/]; - if (!config.resources.assembly || - !config.resources.coreAssembly || - config.resources.coreAssembly.length === 0 || - !config.mainAssemblyName || - !config.virtualWorkingDirectory || - !config.environmentVariables) { + const loaderConfig = dotnetInternals[2/*InternalExchangeIndex.LoaderConfig*/]; + if (!loaderConfig.resources.assembly || + !loaderConfig.resources.coreAssembly || + loaderConfig.resources.coreAssembly.length === 0 || + !loaderConfig.mainAssemblyName || + !loaderConfig.virtualWorkingDirectory || + !loaderConfig.environmentVariables) { throw new Error("Invalid runtime config, cannot initialize the runtime."); } - const assemblyPaths = config.resources.assembly.map(a => "/" + a.virtualPath); - const coreAssemblyPaths = config.resources.coreAssembly.map(a => "/" + a.virtualPath); - config.environmentVariables[HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES] = [...coreAssemblyPaths, ...assemblyPaths].join(":"); - config.environmentVariables[HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES] = config.virtualWorkingDirectory; - config.environmentVariables[HOST_PROPERTY_APP_PATHS] = config.virtualWorkingDirectory; - config.environmentVariables[HOST_PROPERTY_ENTRY_ASSEMBLY_NAME] = config.mainAssemblyName; - for (const key in config.environmentVariables) { - ENV[key] = config.environmentVariables[key]; + + for (const key in loaderConfig.environmentVariables) { + ENV[key] = loaderConfig.environmentVariables[key]; } if (ENVIRONMENT_IS_NODE) { @@ -68,11 +62,6 @@ } } }, - // libBrowserHostFn is too complex for acorn-optimizer.mjs to find the dependencies - AJSDCE_Deps: function () { - _BrowserHost_InitializeCoreCLR(); - _BrowserHost_ExecuteAssembly(); - }, }, $libBrowserHostFn: libBrowserHost, $BROWSER_HOST__postset: "BROWSER_HOST.selfInitialize()", @@ -80,13 +69,18 @@ }; let assignExportsBuilder = ""; + let explicitImportsBuilder = ""; for (const exportName of Reflect.ownKeys(exports)) { const name = String(exportName); if (name === "dotnetInitializeModule") continue; lib[name] = () => "dummy"; assignExportsBuilder += `_${String(name)} = exports.${String(name)};\n`; } + for (const importName of explicitDeps) { + explicitImportsBuilder += `_${importName}();\n`; + } lib.$BROWSER_HOST.assignExports = new Function("exports", assignExportsBuilder); + lib.$BROWSER_HOST.explicitImports = new Function(explicitImportsBuilder); autoAddDeps(lib, "$BROWSER_HOST"); addToLibrary(lib); diff --git a/src/native/corehost/browserhost/loader/config.ts b/src/native/corehost/browserhost/loader/config.ts index 5a8421a1b1db6c..e68688c3a794b1 100644 --- a/src/native/corehost/browserhost/loader/config.ts +++ b/src/native/corehost/browserhost/loader/config.ts @@ -40,6 +40,7 @@ function mergeConfigs(target: LoaderConfigInternal, source: Partial void; _SystemJS_ExecuteTimerCallback: () => void; _SystemJS_ExecuteBackgroundJobCallback: () => void; - _BrowserHost_InitializeCoreCLR: () => number; + _BrowserHost_CreateHostContract: () => VoidPtr; + _BrowserHost_InitializeCoreCLR: (propertiesCount: number, propertyKeys: CharPtrPtr, propertyValues: CharPtrPtr) => number; _BrowserHost_ExecuteAssembly: (mainAssemblyNamePtr: number, argsLength: number, argsPtr: number) => number; _wasm_load_icu_data: (dataPtr: VoidPtr) => number; _SystemInteropJS_GetManagedStackTrace: (args: JSMarshalerArguments) => void; From 7d9bec228d66f41210b5c6bb001b984e1236386c Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 19 Jan 2026 12:56:33 +0100 Subject: [PATCH 2/3] fix node self start fix manual rollup run --- src/native/corehost/browserhost/loader/config.ts | 10 +++++++--- src/native/package.json | 4 ++-- src/native/rollup.config.defines.js | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/native/corehost/browserhost/loader/config.ts b/src/native/corehost/browserhost/loader/config.ts index e68688c3a794b1..5221562a2d5372 100644 --- a/src/native/corehost/browserhost/loader/config.ts +++ b/src/native/corehost/browserhost/loader/config.ts @@ -88,9 +88,13 @@ function normalizeConfig(target: LoaderConfigInternal) { normalizeResources(target.resources!); if (!target.environmentVariables) target.environmentVariables = {}; if (!target.runtimeOptions) target.runtimeOptions = []; - if (!target.runtimeConfig) target.runtimeConfig = { - runtimeOptions: { configProperties: {} }, - }; + if (!target.runtimeConfig) { + target.runtimeConfig = { runtimeOptions: { configProperties: {} }, }; + } else if (!target.runtimeConfig.runtimeOptions) { + target.runtimeConfig.runtimeOptions = { configProperties: {} }; + } else if (!target.runtimeConfig.runtimeOptions.configProperties) { + target.runtimeConfig.runtimeOptions.configProperties = {}; + } } function normalizeResources(target: Assets) { diff --git a/src/native/package.json b/src/native/package.json index 5f4d6f1f0cfbd5..a21651a2457791 100644 --- a/src/native/package.json +++ b/src/native/package.json @@ -12,8 +12,8 @@ "scripts": { "rollup:stub": "node rollup.stub.js", "rollup:cmake": "rollup -c --environment ", - "rollup:release": "rollup -c --environment Configuration:Release,ProductVersion:11.0.0-dev,ContinuousIntegrationBuild:false", - "rollup:debug": "rollup -c --environment Configuration:Debug,ProductVersion:11.0.0-dev,ContinuousIntegrationBuild:false", + "rollup:release": "rollup -c --environment Configuration:Release,ProductVersion:11.0,ContinuousIntegrationBuild:false", + "rollup:debug": "rollup -c --environment Configuration:Debug,ProductVersion:11.0,ContinuousIntegrationBuild:false", "lint": "eslint --no-color --max-warnings=0 \"./**/*.ts\" \"./**/*.js\"", "format": "eslint --fix \"./**/*.ts\" \"./*.js\"" }, diff --git a/src/native/rollup.config.defines.js b/src/native/rollup.config.defines.js index af36b8eb99be63..675e964c570058 100644 --- a/src/native/rollup.config.defines.js +++ b/src/native/rollup.config.defines.js @@ -18,7 +18,7 @@ if (process.env.ProductVersion === undefined) { export const configuration = process.env.Configuration !== "Release" && process.env.Configuration !== "RELEASE" ? "Debug" : "Release"; export const productVersion = process.env.ProductVersion; export const isContinuousIntegrationBuild = process.env.ContinuousIntegrationBuild === "true" ? true : false; -export const staticLibDestination = process.env.StaticLibDestination || "../../artifacts/bin/browser-wasm.Debug/corehost"; +export const staticLibDestination = process.env.StaticLibDestination || `../../artifacts/obj/native/net${productVersion}-browser-${configuration}-wasm/lib`; console.log(`Rollup configuration: Configuration=${configuration}, ProductVersion=${productVersion}, ContinuousIntegrationBuild=${isContinuousIntegrationBuild}`); From edc44489630087ab142b5440e2138855341b602b Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 19 Jan 2026 13:05:18 +0100 Subject: [PATCH 3/3] fix memory leak feedback --- src/native/corehost/browserhost/host/host.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/native/corehost/browserhost/host/host.ts b/src/native/corehost/browserhost/host/host.ts index 5a6a54a13af60d..2f9f263fc7975c 100644 --- a/src/native/corehost/browserhost/host/host.ts +++ b/src/native/corehost/browserhost/host/host.ts @@ -136,7 +136,11 @@ export function initializeCoreCLR(): number { buffers.push(valuePtr as any); } - return _ems_._BrowserHost_InitializeCoreCLR(propertyCount, appctx_keys, appctx_values); + const res = _ems_._BrowserHost_InitializeCoreCLR(propertyCount, appctx_keys, appctx_values); + for (const buf of buffers) { + _ems_._free(buf as any); + } + return res; } // bool BrowserHost_ExternalAssemblyProbe(const char* pathPtr, /*out*/ void **outDataStartPtr, /*out*/ int64_t* outSize);