Repository navigation
Description
Background
CoreCLR runtime tests (src/tests) that call into test-provided native code could not run on browser-wasm, because WebAssembly has no dynamic loading — there is no way for the shared CORE_ROOT/corerun.js to pick up a test's native library at runtime.
This is now addressed by building a test-specific corerun for each such test: the test's native code is compiled as a static archive, the WasmAppBuilder ManagedToNativeGenerator emits the P/Invoke / reverse-P/Invoke call tables, and both are linked together with the runtime's corerun link kit into a corerun.wasm placed next to the test's artifacts. The generated run script prefers that binary over CORE_ROOT/corerun.js.
With that in place the whole src/tests tree builds for browser-wasm, and a first set of tests genuinely runs its native code. This issue tracks the tests that still cannot be enabled, so they can be revisited as the gaps are closed.
Newly enabled and passing
Nine test assemblies now link their own corerun. Comparing each assembly's result with the
native-assets gate on (as on main) and off (with this change), 20 test methods go from skipped to
executed, and none are lost:
| Test | Methods | Newly executed | Remainder still skipped |
|---|---|---|---|
Interop/DllImportSearchPaths/DllImportSearchPathsTest |
11 | 5 | 3 are ConditionalFact(IsNativeAot); 3 gate on CanLoadAssemblyInSubdirectory, which is false for IsBrowser() |
Interop/ICustomMarshaler/ConflictingNames/MultipleALCs |
1 | 1 | — |
Interop/ICustomMarshaler/ConflictingNames/SameNameDifferentAssembly |
1 | 1 | — |
Interop/SuppressGCTransition/SuppressGCTransitionTest |
1 | 0 | SkipOnCoreClr(..., RuntimeTestModes.InterpreterActive), and browser CoreCLR is interpreter-only |
Interop/UnmanagedCallConv/UnmanagedCallConvTest |
1 | 1 | — |
JIT/Directed/PrimitiveABI/PrimitiveABI |
10 | 10 | — |
JIT/Regression/JitBlue/Runtime_101046 |
1 | 1 | — |
JIT/Regression/JitBlue/Runtime_76194 |
1 | 1 | — |
JIT/interpreter/InterpreterTester |
1 | 0 | already ran on main; it now exercises its P/Invokes for real |
SuppressGCTransitionTest and InterpreterTester still build and link their own corerun, so they
pick up native coverage as soon as their unrelated skip conditions stop applying.
Second wave — #133367, after gaps 1 and 2 closed
Nine more projects dropped their opt-out. All 41 were re-tested (built for browser-wasm with the property removed, then run); these nine both link a corerun.wasm and pass:
Interop/StructMarshalling/PInvoke/NestedStruct · JIT/Directed/StructABI/StructABI · JIT/Directed/callconv/{Cdecl,PlatformDefault,StdCall}MemberFunction · JIT/Directed/callconv/ThisCall/ThisCallTest · JIT/Methodical/Methodical_others · JIT/SIMD/JIT.SIMD_{r,ro}
Blocking gaps
1. WASM0067 — unknown multi-field structs in SignatureMapper — closed by #131877
~~src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs carries a hardcoded s_knownStructSizes table used to pick an ABI signature for by-value struct arguments. Any struct not in that table fails the build:
error WASM0067: SignatureMapper: unknown multi-field struct 'X' (fields: N) - add its size to s_knownStructSizes in SignatureMapper.cs
Over 40 distinct structs across the test tree hit this.~~ The generator now computes size and layout from crossgen2's type system.
2. Unsupported parameter types in reverse P/Invoke signatures — struct half closed by #131877
~~PInvokeCollector.CollectPInvokes rejects delegate / UnmanagedCallersOnly signatures that take or return structs:
System.NotSupportedException: Unsupported parameter type in method 'X.Invoke'
Roughly 100 distinct methods are affected.~~ Generic callbacks are still rejected:
Parameter types of pinvoke callback method 'System.Int32 CallbackMethodGeneric[T](T)' needs to be blittable
3. Negative [DllImport] tests become hard link errors
Several tests deliberately declare [DllImport]s for entry points that do not exist and assert that EntryPointNotFoundException is thrown. Everywhere else that resolution is lazy. On wasm the generated table references the symbol statically, so it fails at link time instead:
wasm-ld: error: undefined symbol: CallManagedProc_Fastcall
wasm-ld: error: undefined symbol: MarshalPointer_Int_InOut2
wasm-ld: error: undefined symbol: Marshal_Int_InOut2
wasm-ld: error: undefined symbol: SetIsHandleClosedCallback
wasm-ld: error: undefined symbol: UpdateTestObjectAsIDispatch
This is a structural tension with the static-table approach. Emitting a stub that raises EntryPointNotFoundException for symbols the linker cannot resolve would be one way out.
4. Duplicate entries for the same entry point
DllImportPathTest imports one entry point through several differently-spelled module names, and the generator emits one definition per spelling, producing a redefinition of ... error in callhelpers-pinvoke.cpp. The generator should deduplicate on the resolved symbol.
5. NativeLibrary.Load / GetExport are not served by the static table
The generated table only backs [DllImport] resolution. Tests that load a library explicitly at runtime still go through dlopen and fail even though the native code is linked into their corerun:
System.DllNotFoundException: Unable to load shared library 'NativeFunctions' or one of its dependencies.
Making NativeLibrary.Load resolve against the statically linked table would enable this class of test.
6. Reached by tests that now run, but will not work on wasm
These build and start, so the plumbing works, but they need something wasm does not provide. None is a gap to close:
| Test | Failure |
|---|---|
Interop/NativeLibrary/Callback/CallbackTests |
Needs a shared-library file name; the shared NativeLibraryToLoad.GetLibraryFileName helper throws for any OS other than Windows/Linux/macOS |
Interop/ICustomMarshaler/Primitives/ICustomMarshaler_TargetUnix |
DllNotFoundException for libc — the test loads the platform C library directly |
JIT/Directed/callconv/ThisCall/EmptyThisCallTest |
MarshalDirectiveException: Invalid PInvoke calling convention. Thiscall requires that the first parameter is present and can be enregistered. |
Five more need Marshal.GetFunctionPointerForDelegate, which throws PlatformNotSupported_DynamicEntrypoint under FEATURE_PORTABLE_ENTRYPOINTS (src/coreclr/vm/comdelegate.cpp:1254). Per @jkotas that is deliberate and expected to stay that way, so BestFitMapping, FuncPtrAsDelegateParam, CriticalHandles/Test, DelegatePInvokeTest and ReversePInvokeTest should keep their opt-out permanently.
7. Merged test runners cannot be excluded per project
Interop/Interop.csproj and JIT/Methodical/Methodical_others.csproj are merged runners that Compile Include many test sources directly into their own assembly. The affected P/Invokes therefore live in the wrapper assembly and cannot be turned off one project at a time. Both opted out of the test-specific corerun entirely via WasmBuildTestCorerun=false. Methodical_others was re-enabled by #133367 once gaps 1 and 2 closed; Interop.csproj still opts out, because it also merges the negative [DllImport] tests of gap 3. Splitting those sources into their own projects, or closing gap 3, would let it opt back in too.
8. Vector128<T> cannot be emitted
Four DisabledRuntimeMarshalling projects fail the build:
crossgen2 : error : Cannot generate an interop thunk for
'DisabledRuntimeMarshallingNative.CallWith(Vector128`1<int32>)':
its signature 'vV' contains a 128-bit vector
'V' has no case in the C++ emission helpers. wasm has v128 natively, so this is missing code rather than a platform limit.
9. Six tests now reach the runtime and crash
These previously failed inside the generator, so this is the first time they execute. They look like real ABI defects rather than missing features:
RuntimeError: memory access out of bounds—DisabledRuntimeMarshalling_Disabled_NativeAssemblyEnabled,CriticalHandles/StructTest,MarshalStructAsLayoutSeq,MarshalStructAsLayoutExp. All four die while passing a by-value struct; the last line before the trap is e.g.Calling MarshalStructAsParam_AsSeqByVal2...RuntimeError: null function or function signature mismatch—JIT/Directed/StructABI/EmptyStructs,PrimitiveMarshalling/EnumMarshalling/EnumTest
CriticalHandles/StructTest was previously listed under gap 6 with a different symptom (Early return from JIT/EE interface method); it now fails this way.
Tests without a test-specific corerun
The tests below hit one of the gaps above while building their corerun, so an [ActiveIssue] on
the test method cannot help — the generator walks the assembly's entire interop surface regardless of
which tests xunit would end up running.
They therefore set WasmBuildTestCorerun=false instead of being disabled. They still build and run on
browser, against CORE_ROOT's shared corerun.js, exactly as they did before this work; their
native-dependent methods skip through the existing PlatformDoesNotSupportNativeTestAssets gate.
Closing a gap above is what lets a test move from "skips its native methods" to "runs them".
The list below was re-measured after #133367; it is 32 now, and the classification comes from building and running every one of the 41 opt-outs rather than from the original triage.
Test projects (32)
Grouped by what blocks them.
Gap 3 — linker rejects deliberately-absent entry points (8)
Exceptions/ForeignThread/ForeignThreadExceptions.csprojInterop/COM/ComWrappers/GlobalInstance/GlobalInstanceTrackerSupportTests_TargetUnix.csprojInterop/DllImportAttribute/ExactSpelling/ExactSpellingTest.csprojInterop/Interop.csproj(merged runner, also gap 7)Interop/PInvoke/CriticalHandles/ArrayTest/ArrayTest.csprojInterop/PrimitiveMarshalling/Bool/BoolTest.csprojInterop/UnmanagedCallersOnly/UnmanagedCallersOnlyTest.csprojInterop/UnmanagedCallersOnlyBasic/UnmanagedCallersOnlyBasicTest.csproj
Gap 8 — Vector128<T> (4)
Interop/DisabledRuntimeMarshalling/DisabledRuntimeMarshalling_Disabled_NativeAssemblyDisabled.csprojInterop/DisabledRuntimeMarshalling/DisabledRuntimeMarshalling_Disabled_NativeTypeInAssembly.csprojInterop/DisabledRuntimeMarshalling/DisabledRuntimeMarshalling_Disabled_NativeTypeInAssembly_ro.csprojInterop/DisabledRuntimeMarshalling/DisabledRuntimeMarshalling_NativeAssemblyDisabled.csproj
Gap 4 — duplicate entries for one entry point (1)
Interop/DllImportAttribute/DllImportPath/DllImportPathTest.csproj
Gap 5 — NativeLibrary.Load / GetExport (4)
Interop/ICustomMarshaler/Primitives/ICustomMarshaler_TargetUnix.csproj(also gap 6 — loadslibc)Interop/NativeLibrary/Callback/CallbackStressTest_TargetUnix.csprojJIT/Directed/aliasing_retbuf/aliasing_retbuf.csprojbaseservices/callconvs/TestCallingConventions.csproj
Gap 9 — crashes at run time (6)
Interop/DisabledRuntimeMarshalling/DisabledRuntimeMarshalling_Disabled_NativeAssemblyEnabled.csprojInterop/PInvoke/CriticalHandles/StructTest/StructTest.csprojInterop/PrimitiveMarshalling/EnumMarshalling/EnumTest.csprojInterop/StructMarshalling/PInvoke/MarshalStructAsLayoutExp.csprojInterop/StructMarshalling/PInvoke/MarshalStructAsLayoutSeq.csprojJIT/Directed/StructABI/EmptyStructs.csproj
Gap 6 — will not work on wasm (8)
Interop/BestFitMapping/BestFitMapping.csprojInterop/FuncPtrAsDelegateParam/FuncPtrAsDelegateParam.csprojInterop/NativeLibrary/Callback/CallbackTests.csprojInterop/PInvoke/CriticalHandles/Test/Test.csprojInterop/StructMarshalling/ReversePInvoke/MarshalSeqStruct/DelegatePInvoke/DelegatePInvokeTest.csprojInterop/StructMarshalling/ReversePInvoke/MarshalSeqStruct/ReversePInvoke/ReversePInvokeTest.csprojJIT/Directed/callconv/ThisCall/EmptyThisCallTest.csproj- (
ICustomMarshaler_TargetUnix, listed under gap 5)
Not tests — OutputType=Library helpers (2)
Interop/DisabledRuntimeMarshalling/Native_Default/DisabledRuntimeMarshallingNative_Default.csprojInterop/DisabledRuntimeMarshalling/Native_DisabledMarshalling/DisabledRuntimeMarshallingNative_DisabledMarshalling.csproj
The whole src/tests/profiler tree is also disabled on browser via its Directory.Build.props. Profiler tests launch a second corerun process and load the profiler from a shared library path; WebAssembly has neither process creation nor dynamic loading, so they cannot run there regardless of these gaps. These tests were not part of the browser-wasm CoreCLR test leg before this change either, so making the exclusion explicit loses no coverage.
Note
This issue was drafted with the help of GitHub Copilot.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
dotnet-policy-service commentedon Aug 4, 2026
Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.
Skip the test-specific corerun for priority 1 tests wasm cannot link …
dotnet-policy-service commentedon Aug 10, 2026
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.
Enable browser-wasm runtime tests that require native library relinki…
[wasm] Compute CoreCLR P/Invoke struct sizes with crossgen2's type sy…
[wasm] Build a test-specific corerun for nine more runtime tests (#13…
lewing commentedon Sep 10, 2026
#133565 identified one additional case for gap 6:
JIT/Methodical/structs/systemvbringup/structpinvoketests.cspasses managed delegates through P/Invoke, so CoreCLR browser-wasm fails before entering native code when delegate marshaling requests a dynamic unmanaged entrypoint underFEATURE_PORTABLE_ENTRYPOINTS.#133569 keeps the supported
structinregmanaged ABI groups active and isolates only that delegate-callback group behind the browser CoreCLR active issue. The callback coverage still runs on supported CoreCLR targets.Note
This comment was generated with the help of GitHub Copilot.
Skip wasm corerun for struct-returning callconv tests
Gate permanent wasm skips on their capability