Repository navigation
[browser][coreCLR] Assembly resolution has no VFS fallback — private/copy-local assemblies fail to load (FileNotFoundException) where Mono succeeds #132308
Description
Description
On browser-wasm CoreCLR (browserhost), loading a managed assembly that is present on the emscripten VFS but not eagerly registered from the boot config fails with FileNotFoundException. The same code works on Mono WASM, because Mono's assembly loader can probe the VFS filesystem while the CoreCLR browserhost probe is an in-memory-only lookup.
Surfaced by System.Runtime.Serialization.Formatters in System.Runtime.Tests (and others) on CoreCLR:
System.IO.FileNotFoundException : Could not load file or assembly
'System.Runtime.Serialization.Formatters, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
The system cannot find the file specified.
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
This assembly is a private, copy-local reference (a NetCoreAppMinimum build with a functional BinaryFormatter) added by the test project, not part of the shared framework:
<!-- src/libraries/System.Runtime/tests/System.Runtime.Tests/System.Runtime.Tests.csproj -->
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.Serialization.Formatters\src\System.Runtime.Serialization.Formatters.csproj"
Private="true"
SetTargetFramework="TargetFramework=$(NetCoreAppMinimum)" />Affected tests (browser CoreCLR, Release)
Observed on the LibraryTestsCoreCLR legs of the R2R bring-up PR #129634 (build 1551759, head d3f76ed6):
System.Tests.DateTimeTests.GetObjectData_Invoke_ReturnsExpectedSystem.Formats.Nrbf.Tests.EdgeCaseTests.*(fails during xUnit discovery —CustomAttributeFormatException->FileNotFoundException)System.Collections.Tests.InternalHashCodeTests_*.ComparerImplementations_Dictionary_WithWellKnownStringComparers(×16 fixtures)System.Collections.Tests.HashSet_NonGeneric_Tests.HashSet_CopyConstructor_ShouldWorkWithRandomizedEffectiveComparer
Root cause
CoreCLR browserhost resolves every managed assembly through external_assembly_probe, which is an in-memory Map with no filesystem/VFS fallback.
The host wires BrowserHost_ExternalAssemblyProbe as host_contract.external_assembly_probe (src/native/corehost/browserhost/browserhost.cpp). Its implementation (src/native/libs/Common/JavaScript/host/assets.ts) only consults loadedAssemblies, which is populated exclusively from boot-config assets (finishWebcilInstance — "this loader receives them from boot config"):
export function BrowserHost_ExternalAssemblyProbe(pathPtr, outDataStartPtr, outSize): boolean {
const path = _ems_.UTF8ArrayToString(_ems_.dotnetApi.localHeapViewU8(), pathPtr);
const assembly = loadedAssemblies.get(path); // in-memory map only
if (assembly) { /* return ptr + length */ return true; }
_ems_.dotnetLogger.debug(`Assembly not found: '${path}'`);
return false; // -> FileNotFoundException
}There is no FS/VFS read anywhere in the CoreCLR probe path. If an assembly is not in the eagerly-registered boot-config set, CoreCLR cannot load it even when the file physically exists on the emscripten VFS.
Why Mono succeeds (comparison)
Mono's asset loader (src/mono/browser/runtime/assets.ts) writes assets to the emscripten VFS as real files in addition to any in-memory registration:
case "vfs": {
...
Module.FS_createPath("/", parentDirectory, true, true);
Module.FS_createDataFile(parentDirectory, fileName, bytes, /*canRead*/ true, /*canWrite*/ true, /*canOwn*/ true);
break;
}
// and, for behavior === "assembly":
cwraps.mono_wasm_add_assembly(virtualName, offset, bytes.length);The app base is a VFS directory (FS.chdir(virtualWorkingDirectory) in src/mono/browser/runtime/startup.ts), so Mono's native assembly loader resolves an assembly that isn't in the eager in-memory set by probing the VFS filesystem under the app base. That is what makes System.Runtime.Serialization.Formatters load on Mono but not on CoreCLR.
Candidate fixes
- Bundling parity — ensure the CoreCLR WASM publish registers private/copy-local assemblies as eager
"assembly"boot-config assets (so they land inloadedAssemblies), matching what Mono's in-memory path gets. This keeps the runtime probe in-memory-only. - Runtime fallback — on a
loadedAssembliesmiss, haveBrowserHost_ExternalAssemblyProberead the file from the emscripten FS at the probed path, mirroring Mono's filesystem probing.
Open question to confirm before picking a fix: whether the private copy is present on the VFS-but-not-in-boot-config (favor fix 1 or 2), or missing from the publish bundle entirely (bundling gap). This can be checked from the _framework boot manifest of a CoreCLR test bundle.
Configuration
-os browser -a wasm, CoreCLR (browserhost), Release; reproduces in V8, Firefox, and Chrome.- Not R2R-specific — it's an assembly-resolution/probe limitation of the CoreCLR browser host.
Regression?
No — this is a gap in the new CoreCLR-on-browser host rather than a regression from a previously working CoreCLR configuration. Tracking so the CoreCLR wasm library test legs can match Mono coverage.
Note
This issue was researched and drafted with the assistance of GitHub Copilot.
Activity
dotnet-policy-service commentedon Aug 14, 2026
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.
dotnet-policy-service commentedon Aug 14, 2026
Tagging subscribers to this area: @agocke, @elinor-fung
See info in area-owners.md if you want to be subscribed.