Skip to content

[browser] More Wasm.Build.Tests on CoreCLR#128296

Draft
maraf wants to merge 47 commits into
dotnet:mainfrom
maraf:maraf/wbt-coreclr-enable-non-aot-tests
Draft

[browser] More Wasm.Build.Tests on CoreCLR#128296
maraf wants to merge 47 commits into
dotnet:mainfrom
maraf:maraf/wbt-coreclr-enable-non-aot-tests

Conversation

@maraf
Copy link
Copy Markdown
Member

@maraf maraf commented May 17, 2026

Note

This PR description was AI-generated (GitHub Copilot CLI).

Follow-up to #127073. Plumbs the CoreCLR-on-WASM publish-time native re-link end-to-end so apps without the WebAssembly workload can rebuild dotnet.native.wasm against their own [UnmanagedCallersOnly] / NativeFileReference / NativeLibrary inputs, and then enables the Wasm.Build.Tests (WBT) classes that exercise that path on CoreCLR.

Runtime pack & Helix payload

Change Purpose
src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj Set IncludeStaticLibrariesInPack=true on browser/wasi so .a files ship in the runtime pack and apps can relink with emcc.
src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props Add libBrowserHost.a and libSystem.Native.Browser.extpost.js to the platform manifest.
eng/liveBuilds.targets Exclude libminipal.a, libSystem.IO.Compression.Native.a, libz.a from the runtime-tests browser-host file set (they're libraries content, not host content).
src/libraries/sendtohelix-browser.targets Stage src/native/minipal/{utils,entrypoints}.h and src/coreclr/vm/wasm/callhelpers.hpp into the Helix correlation payload (under build/minipal/ and build/coreclr-vm-wasm/), and expose MINIPAL_INCLUDE_DIR / CORECLR_VM_WASM_INCLUDE_DIR so per-app relink can resolve the #includes emitted by ManagedToNativeGenerator.
src/native/corehost/corehost.proj, src/native/corehost/browserhost/libBrowserHost.footer.js, src/native/libs/Common/JavaScript/CMakeLists.txt Misc browser-host packaging tweaks consumed by the above manifest entries.

BrowserWasmApp.CoreCLR.targets rework

  • _CoreCLRSetWasmBuildNativeDefaults: auto-enable WasmBuildNative when the project has NativeFileReference or NativeLibrary items (mirrors a subset of the Mono path's _SetWasmBuildNativeDefaults).
  • _CoreCLRWasmNativeForBuild / _CoreCLRWasmNative: force the build-phase / publish-phase relink to run before _ResolveWasmOutputs / ProcessPublishFilesForWasm in CoreCLR no-workload mode (the SDK's _WasmNativeForBuild / _WasmNative are gated on UsingBrowserRuntimeWorkload=true).
  • WasmBuildOnlyAfterPublish default flipped to true during publish so a stale for-build/dotnet.native.wasm (whose pinvoke-table is generated before the user assembly is available) doesn't get staged into the publish bundle.
  • Nested publish (WasmNestedPublishApp) now returns WasmAssembliesFinal / WasmNativeAsset / FileWrites with an OriginalItemName metadata key, and WasmTriggerPublishApp routes results back by that key rather than by file extension. DeployOnBuild / _IsPublishing are cleared on the nested invocation.
  • Compile pipeline split into two RSPs:
    • emcc-compile.rsp for user NativeFileReference .c/.cpp sources — no force-included compat header (would break C sources).
    • emcc-compile-generated.rsp for ManagedToNativeGenerator output (pinvoke-table.cpp, wasm_m2n_invoke.g.cpp) — force-includes coreclr_compat.h, adds -I for minipal and coreclr/vm/wasm, both env-var-overridable.
  • Generated _WasmPInvokeModules now also include user .c/.cpp source filenames (so pinvoke scanning finds them).

New src/mono/browser/build/coreclr_compat.h

Replaces the previous inline-generated stub header in _CoreCLRWriteCompileRsp. Provides the minimal prerequisite types (MethodDesc / PCODE / ULONG / INTERP_STACK_SLOT_SIZE) and logging/assertion stubs (LF_INTEROP, LL_INFO1000, LOG, PORTABILITY_ASSERT) that the real CoreCLR headers (<callhelpers.hpp>, <minipal/entrypoints.h>) assume are already in scope from vm/common.h. The generated .cpp files keep including the real headers directly.

Wasm.Build.Tests infrastructure

File Change
Common/BuildEnvironment.cs Resolve runtime pack as Microsoft.NETCore.App.Runtime.{rid} (no flavor segment) for CoreCLR; propagate MINIPAL_INCLUDE_DIR / CORECLR_VM_WASM_INCLUDE_DIR env vars to child builds.
Common/EnvironmentVariables.cs Expose the two new env vars.
Wasm.Build.Tests.csproj Exclude category=native, category=mono, category=workload on CoreCLR.
BrowserStructures/AssertBundleOptions.cs New RuntimePackDir option so ICU asset assertions can resolve sources from the build-resolved runtime pack instead of a workload-installed pack.
ProjectProviderBase.cs, WasmSdkBasedProjectProvider.cs, Templates/WasmTemplateTestsBase.cs Parse the runtime pack dir from MSBuild output, thread it through AssertBundleOptions, and add ReplaceMainJsWithMinimalRunMain helper for tests whose program doesn't use JS interop (otherwise the trimmer drops the JS interop assembly and the template main.js's getAssemblyExports call fails at startup).
data/RunScriptTemplate.sh Propagate the two new env vars locally.

Enabled WBT classes on CoreCLR

eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt adds 9 classes; xunit merges class-level and method-level traits, so each class drops its class-level [TestCategory("native")] and re-applies a sub-category per method:

Class Approach
NativeBuildTests Drop class tag. Method tag AOTNotSupportedWithNoTrimming, IntermediateBitcodeToObjectFilesAreNotLLVMIR, NativeBuildIsRequired (AOT-only).
DllImportTests Drop class tag (all methods non-AOT, all run on CoreCLR).
PInvokeTableGeneratorTests Drop class tag. Method tag EnsureWasmAbiRulesAreFollowedInAOT, EnsureComInteropCompilesInAOT. UnmanagedCallersOnly_Namespaced and IcallWithOverloadedParametersAndEnum kept as [TestCategory("mono")] (reverse-thunk linking / WasmAppBuilder.dll workload pack not yet available on CoreCLR). EnsureWasmAbiRulesAreFollowed reads pinvoke-table.cpp on CoreCLR vs pinvoke-table.h on Mono, with format-specific assertion.
NativeLibraryTests Drop class tag. Split four theories that had both [BuildAndRun(aot:false)] and [BuildAndRun(aot:true)] into a non-AOT method and an _AOT method tagged native, delegating to a shared …Core helper. The pre-existing [ActiveIssue(#103566)] on ProjectUsingSkiaSharp is applied to both halves.
IcuShardingTests, IcuShardingTests2, IcuTests Re-tag method-level native as native-coreclr so they run on CoreCLR.
IcuTestsBase Use ReplaceMainJsWithMinimalRunMain (ICU test program doesn't use JS interop).
InvariantGlobalizationTests [ConditionalTheory(... IsMonoRuntime)] on both theories — gated on CoreCLR until #128219 (libSystem.Globalization.Native stub) is resolved; RelinkingWithoutAOT re-tagged native-coreclr; uses ReplaceMainJsWithMinimalRunMain.
MemoryTests Re-tag class as native-coreclr.

Splitting (rather than runtime-skip) keeps test reporting honest: AOT-on-CoreCLR shows up as "excluded by filter" instead of a false pass.

Test assets

  • src/mono/wasm/testassets/EntryPoints/PInvoke/{AbiRules,BittableDifferentAssembly,BittableDifferentAssembly_Lib,BittableSameAssembly}.cs — adjustments so the existing PInvoke ABI tests compile/run under the CoreCLR-WASM pinvoke-table layout.
  • src/mono/wasm/testassets/EntryPoints/icu_main.js (new) — minimal main.js for ICU test apps that don't use JS interop.
  • src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs — tracks the test-side expectations.

Not in scope: other [TestCategory("native")] classes in WBT (BuildPublishTests, NativeRebuildTests/*, WasmSIMDTests, SatelliteAssembliesTests, …) — those encode reasons beyond "class-level native is too broad" and are left for a follow-up.

Validation

  • Local build: ./dotnet.sh build src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj -c Release /p:TargetOS=browser /p:RuntimeFlavor=CoreCLR /p:Scenario=BuildWasmApps → 0W/0E.
  • Helix end-to-end validation: this PR's CI.

maraf and others added 30 commits April 24, 2026 08:05
Remove class-level [TestCategory("native")] from NativeBuildTests,
NativeLibraryTests, PInvokeTableGeneratorTests, and DllImportTests so
non-AOT methods pass the CoreCLR xunit filter (-notrait category=native).
AOT-only methods get a method-level [TestCategory("native")] to keep
them excluded on CoreCLR.

NativeLibraryTests: four mixed theories (both aot=false and aot=true
inline data) are split into a non-AOT theory and a separate AOT-only
theory tagged 'native', because xunit trait filtering is per-method,
not per-theory-row.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Change [TestCategory("native")] to [TestCategory("native-coreclr")] for tests
that use native relink but should also run on CoreCLR:

- IcuShardingTests: CustomIcuShard, AutomaticShardSelectionDependingOnEnvLocale
- IcuShardingTests2: DefaultAvailableIcuShardsFromRuntimePack
- IcuTests: FullIcuFromRuntimePackWithInvariant, FullIcuFromRuntimePackWithCustomIcu
- InvariantGlobalizationTests: RelinkingWithoutAOT
- MemoryTests: entire class (AllocateLargeHeapThenRepeatedlyInterop)

CoreCLR always has native relink support, so these tests can run on both
Mono and CoreCLR. The CoreCLR xunit filter excludes category=native but
not category=native-coreclr.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Without these entries in BuildWasmAppsJobsListCoreCLR.txt, the test
classes whose class-level [TestCategory("native")] was removed (or
retagged to native-coreclr) in this PR would still not be scheduled
as Helix work items for the CoreCLR BuildWasmApps scenario.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EnsureWasmTemplatesInstalled placed .dotnet-cli-home next to the
template nupkg (which on Linux Helix agents is under the read-only
correlation payload), causing 'Read-only file system' / 'Access denied'
errors for every test class that calls CreateWasmTemplateProject.

Always use BuildEnvironment.TmpPath — the harness's writable scratch
dir — instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EnsureWasmTemplatesInstalled set DOTNET_CLI_HOME to TmpPath/.dotnet-cli-home
when running 'dotnet new install'. The subsequent 'dotnet new <template>'
call goes through DotNetCommand(useDefaultArgs: false), which does not
apply BuildEnvironment.EnvVars to the child process, so it inherited the
Helix-set DOTNET_CLI_HOME (e.g. /root/helix/work/workitem/e/.dotnet).

Two different DOTNET_CLI_HOME values mean two different template caches:
the install lands in one, the lookup misses in the other, producing
'No templates or subcommands found matching: wasmbrowser' (exit 103) on
every test class that calls CreateWasmTemplateProject.

Prefer the inherited DOTNET_CLI_HOME so install and lookup share a cache;
fall back to TmpPath only when not set (preserves the read-only-FS fix
for environments without an inherited value).

Reproduced locally with DOTNET_CLI_HOME=/tmp/helix-cli-home; templates
were installed under wbt artifacts/.dotnet-cli-home but 'dotnet new'
read from /tmp/helix-cli-home. After the fix, both paths share
/tmp/helix-cli-home and 'dotnet new wasmbrowser' succeeds.

Also adds [diag] Console.WriteLine entries logging both the install-time
and invocation-time DOTNET_CLI_HOME so Helix work-item console logs make
similar future regressions trivial to diagnose.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Inline callhelpers.hpp / minipal/entrypoints.h declarations into the
  pre-included coreclr_compat.h and stop emitting #include lines for
  those headers from the CoreCLR generators, so pinvoke-table.cpp and
  wasm_m2n_invoke.g.cpp compile on Helix where the in-repo header paths
  do not exist.
- Make BuildEnvironment.GetRuntimePackDir flavor-aware: on CoreCLR the
  runtime pack is Microsoft.NETCore.App.Runtime.<rid> (no .Mono. segment).
- Mark DllImportTests.NativeLibraryWithVariadicFunctions as Mono-only;
  CoreCLR's PInvoke generator does not emit the expected varargs warning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
….targets

The previous attempt to generate coreclr_compat.h from a _CompatHeaderLines
ItemGroup hit an MSBuild item-spec dedup/parsing issue: items whose Include
value contained both `{...}` braces and embedded `%3B` semicolons were
silently dropped from the output (verified with a minimal repro). That left
the generated header with missing `StringToWasmSigThunk` and
`ReverseThunkMapEntry` struct declarations, so pinvoke-table.cpp /
wasm_m2n_invoke.g.cpp failed to compile on Helix.

Replace the inline ItemGroup generation with a static coreclr_compat.h next
to BrowserWasmApp.CoreCLR.targets and `-include` it via
$(MSBuildThisFileDirectory). The file ships into the Helix correlation
payload via the existing HelixCorrelationPayload Include="$(BrowserBuildTargetsDir)"
mapping (sendtohelix-browser.targets), so it lands at
correlation/build/wasm/coreclr_compat.h alongside the targets file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The ICU tests override Program.cs with code that does not use JS interop,
but kept the wasmbrowser template's main.js which calls setModuleImports
and getAssemblyExports. With CoreCLR-Wasm, the trimmer drops
System.Runtime.InteropServices.JavaScript because nothing managed roots
it, and JS startup then fails when trying to bind its exports.

Add a minimal icu_main.js tailored for ICU tests (no JS interop calls,
runMainAndExit, diagnostic tracing + console forwarding) and have
CreateIcuProject overwrite the template main.js with it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The force-include of coreclr_compat.h is required for the
ManagedToNativeGenerator output (pinvoke-table.cpp,
wasm_m2n_invoke.g.cpp), but it breaks user .c sources brought in via
NativeFileReference: the compat header uses C++-style un-tagged struct
references that don't compile in C.

Split the emcc invocation into two passes with separate RSP files:
- user NativeFileReference sources (.c/.cpp) without -include compat
- generated cpp from ManagedToNativeGenerator with -include compat

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ble-non-aot-tests

# Conflicts:
#	eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt
ManagedToNativeGenerator output (wasm_m2n_invoke.g.cpp) uses NOINLINE,
which is defined in src/native/minipal/utils.h. On Helix, the WBT
correlation payload only had coreclr_compat.h — no NOINLINE definition —
so per-app native re-link failed for newly-enabled CoreCLR WBT classes
(InvariantGlobalizationTests.RelinkingWithoutAOT, DllImportTests.*).

Stage src/native/minipal/utils.h into the WBT correlation payload at
build/minipal and force-include it from BrowserWasmApp.CoreCLR.targets
alongside coreclr_compat.h. The include directory is provided to the
targets via MINIPAL_INCLUDE_DIR (HelixPreCommand on Helix,
RunScriptCommands locally), with an in-repo fallback for full repo
builds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
IcuShardingTests2.DefaultAvailableIcuShardsFromRuntimePack publishes from
the per-test NuGet feed (NUGET_PACKAGES override), but
ProjectProviderBase.AssertIcuAssets compared the published shard against
BuildEnvironment.GetRuntimeNativeDir(...), which resolves to the
SDK-installed dotnet/packs/ copy. On CoreCLR no-workload Helix runs the
two diverge and the asserts fail looking for icudt_EFIGS/CJK/no_CJK.dat
in dotnet-none/packs while the publish-time pack used a different copy
under wbt artifacts/nuget/.../microsoft.netcore.app.runtime.browser-wasm.

Parse 'MicrosoftNetCoreAppRuntimePackDir' from MSBuild output (the existing
regex used by AssertRuntimePackPath) and thread it through AssertBundle /
AssertBundleOptions. AssertIcuAssets prefers that path when present, so
ICU shard comparisons read from the same pack the publish output came
from. For workload runs the resolved path equals GetRuntimeNativeDir, so
behavior is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The CoreCLR WASM relink path in BrowserWasmApp.CoreCLR.targets references
'$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libSystem.Native.Browser.extpost.js'
as the emcc --extern-post-js input, and corehost.proj copies the file
into the local runtime pack output directory. However, the file is not
listed in the platform manifest in
src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props, so
it gets dropped when the Microsoft.NETCore.App.Runtime.browser-wasm
NuGet package is created. CI legs that consume the packed runtime pack
(e.g. Wasm.Build.Tests on Helix using the per-test NuGet feed) then fail
the link with:

  emcc : error : '--extern-post-js': file not found:
    .../microsoft.netcore.app.runtime.browser-wasm/11.0.0-ci/runtimes/
    browser-wasm/native/libSystem.Native.Browser.extpost.js

Add the file to the manifest alongside the other libSystem.Native.Browser.*
entries so it ships with the runtime pack.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit added the file to PlatformManifestFileEntry in
Microsoft.NETCore.App/Directory.Build.props, but that only registers
the file in the platform manifest — it does not cause the file to be
included in the runtime-pack nupkg. The nupkg sources its content from
$(LibrariesSharedFrameworkDir) (artifacts/bin/native/<rid-tfm>/sharedFramework/)
via the *.js glob. Because libSystem.Native.Browser.extpost.js is a
hand-written passthrough source (not a rollup output), nothing was
installing it into that directory.

Add an explicit cmake install for the file in
src/native/libs/Common/JavaScript/CMakeLists.txt so it lands in
sharedFramework and is picked up by:

  - the runtime-pack nuspec (-> ships in
    Microsoft.NETCore.App.Runtime.browser-wasm.nupkg)
  - corehost.proj's CopyWasmNativeFiles via its existing
    $(LibrariesSharedFrameworkDir)*.js glob (-> populates the in-tree
    runtime pack layout)

Drop the now-redundant explicit include in
src/native/corehost/corehost.proj that pointed at the source file
directly; it is no longer needed once the *.js glob picks it up.

Verified: rebuilt libs.native and the runtime-pack sfxproj; the
generated nupkg now contains
runtimes/browser-wasm/native/libSystem.Native.Browser.extpost.js,
which previously was missing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WBT requires all .a archives (libBrowserHost.a, libSystem.Native.Browser.a,
libicu*.a, etc.) in the runtime pack to relink with emcc per-app.

- Set IncludeStaticLibrariesInPack=true for browser/wasi CoreCLR sfxproj
  so static libs from LibrariesRuntimeFiles are not filtered out.
- Add libBrowserHost.a to the platform manifest.
- Exclude duplicate libminipal.a from LibrariesSharedFrameworkDir; the
  CoreCLR-side libminipal.a is the one referenced by browserhost.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…veLibrary present

The Mono path's _SetWasmBuildNativeDefaults auto-flips WasmBuildNative=true
when a project has @(NativeFileReference) items, so a project with native
P/Invoke targets gets relinked into dotnet.native.wasm. The CoreCLR path had
no equivalent, so apps with a NativeLibrary/NativeFileReference would build
successfully but fail at runtime with 'DllNotFoundException: dynamic linking
not enabled'.

Add _CoreCLRSetWasmBuildNativeDefaults that flips WasmBuildNative=true when
either NativeFileReference or NativeLibrary items are present. NativeLibrary
is later converted to NativeFileReference in _CoreCLRPrepareForNativeBuild,
so we count both at the entry-point level.

Run the defaults via DependsOnTargets (rather than BeforeTargets) so the
mutated WasmBuildNative is visible when _CoreCLRWasmBuildAppCore's Condition
is evaluated. The outer entry-point WasmBuildNative check is removed because
the inner _CoreCLRWasmBuildAppCore already gates on it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
libSystem.IO.Compression.Native.a, libz.a, and libminipal.a are installed
into both $(CoreCLRSharedFrameworkDir) (consumed by corehost when linking
dotnet.native.js, and packed via the CoreCLRSharedFrameworkDir RuntimeFiles
glob in liveBuilds.targets) and $(LibrariesSharedFrameworkDir) (packed via
the browser+CoreCLR LibrariesRuntimeFiles ItemGroup).

With IncludeStaticLibrariesInPack=true, both flows produce
runtimes/browser-wasm/native/<file>.a, causing NuGet pack to emit NU5118
(warnings-as-errors).

Extend the existing Exclude= for libminipal.a to also drop the
LibrariesSharedFrameworkDir copies of libSystem.IO.Compression.Native.a
and libz.a. corehost continues to consume the CoreCLRSharedFrameworkDir
copy at link time, and the runtime pack ends up with exactly one copy of
each archive.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both DllImportWithFunctionPointersCompilesWithoutWarning and
DllImportWithFunctionPointers_ForVariadicFunction_CompilesWithWarning use the
WasmBasicTestApp main.js, which unconditionally calls getAssemblyExports on
the main assembly. With CoreCLR-Wasm the trimmer drops
System.Runtime.InteropServices.JavaScript because the test program does not
use JS interop, so JS startup fails with Arg_TargetInvocationException out of
JSHostImplementation.BindAssemblyExports.

Mirror the IcuTestsBase fix and overwrite the template main.js with the
minimal icu_main.js (no JS interop calls, just runMainAndExit) inside
PrepareProjectForVariadicFunction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generalize the icu_main.js fix into a shared ReplaceMainJsWithMinimalRunMain
helper on WasmTemplateTestsBase, and apply it to additional CoreCLR-Wasm
NoWorkload tests whose managed program does not use JS interop. Without it,
the WasmBasicTestApp / wasmbrowser template main.js calls getAssemblyExports,
which fails at startup with Arg_TargetInvocationException out of
JSHostImplementation.BindAssemblyExports because the trimmer drops
System.Runtime.InteropServices.JavaScript.

Tests updated:
- InvariantGlobalizationTests.RelinkingWithoutAOT (and AOT path)
- NativeBuildTests.SimpleNativeBuild
- NativeLibraryTests.ProjectWithNativeReference / ProjectWithNativeLibrary
- PInvokeTableGeneratorTests.UCOWithSpecialCharacters
- PInvokeTableGeneratorTests.BuildNativeInNonEnglishCulture

Also refactor the existing inline copies in DllImportTests and IcuTestsBase
to use the new helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The test loads WasmAppBuilder.dll from the
Microsoft.NET.Runtime.WebAssembly.Sdk workload pack, which is not present in
the CoreCLR-Wasm NoWorkload Helix payload, so the test fails with
DirectoryNotFoundException there. Restrict it to the Mono pipeline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CoreCLR WBT runs in NoWorkload mode but still performs per-app native
relink via BrowserWasmApp.CoreCLR.targets (imported by
data/Local.Directory.Build.targets). AssertBuildBundle was forcing
expectedFileType=FromRuntimePack whenever isUsingWorkloads was false,
causing it to look for dotnet.native.* in fx/_framework/ instead of
obj/.../wasm/for-build/ where the CoreCLR relink actually places them.

Treat CoreCLR as having native-rebuild capability regardless of
isUsingWorkloads so GetExpectedFileType (which honors the test-supplied
isNativeBuild hint) is consulted. This matches the existing CoreCLR
detection pattern already used in this file for dotnet.diagnostics.js.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When InvariantGlobalization=true, the CoreCLR app re-link omits the
ICU static libraries (libSystem.Globalization.Native.a, libicu*.a),
so wasm_load_icu_data is undefined. libBrowserHost.footer.js was
listing it in explicitDeps, which caused emcc to emit
--export=wasm_load_icu_data and wasm-ld to fail with:

  wasm-ld : error : undefined symbol: wasm_load_icu_data.
  Required by BrowserHost_ExternalAssemblyProbe

The loadIcuData() JS code path only runs when ICU data is actually
present, so the symbol doesn't need to be a hard link-time dependency.
Drop it from explicitDeps so invariant-globalization apps relink.

Cherry-picked from dotnet#127906.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
NativeLibraryTests were shipping the pristine runtimepack dotnet.native.wasm
instead of the per-app relinked one, so user pinvokes (e.g. print_line in
AppUsingNativeLib) failed at runtime with DllNotFoundException.

Two issues:

1) Without WasmBuildOnlyAfterPublish, WasmBuildApp ran a build-phase relink
   into for-build/ before ManagedToNativeGenerator had the publish-phase
   pinvoke set, baking a dotnet.native.wasm with a pinvoke-table.cpp missing
   the user entries. Mirror Mono's auto-default in BrowserWasmApp.CoreCLR.targets.

2) The WebAssembly SDK's _WasmNative target forces WasmTriggerPublishApp to
   run BeforeTargets=ProcessPublishFilesForWasm so the for-publish/ relink
   output is the WasmNativeAsset that ComputeWasmPublishAssets picks up. It
   is gated on UsingBrowserRuntimeWorkload == true, which is false in the
   CoreCLR no-workload flow, so the nested publish only ran AfterTargets=
   Publish - too late, and the runtimepack wasm was published instead.
   Add an equivalent _CoreCLRWasmNative trigger in the CoreCLR targets.

Also align WasmTriggerPublishApp/WasmNestedPublishApp with the Mono pattern:
use OriginalItemName metadata to discriminate WasmAssembliesFinal vs
WasmNativeAsset vs FileWrites returned from the nested publish, instead of
the .dll/non-.dll heuristic that misclassified satellite resource .wasm files
as native assets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The CoreCLR ManagedToNativeGenerator emits pinvoke-table.cpp (see
BrowserWasmApp.CoreCLR.targets), while Mono emits pinvoke-table.h.
The test hard-coded .h and failed with FileNotFoundException on the
CoreCLR-Wasm Helix leg.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Define _WasmNativeForBuild in BrowserWasmApp.CoreCLR.targets so it chains
_GatherWasmFilesToBuild -> WasmBuildApp. The WebAssembly SDK's own
_WasmNativeForBuild is gated on UsingBrowserRuntimeWorkload, which we
don't want to flip on globally because it makes the SDK demand the
wasm-tools workload and breaks NoWorkload Wasm.Build.Tests scenarios.
Overriding the target avoids that gating while still ensuring
WasmAssembliesToBundle is populated for the CoreCLR M2N generator.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Populate WasmAssembliesToBundle from @(IntermediateAssembly) and
@(ReferenceCopyLocalPaths) before _CoreCLRGenerateManagedToNative runs,
so the M2N generator scans the app's own assemblies (not just CoreLib).

This fixes pinvoke / UCO blittability diagnostics that depend on
inspecting types declared in the app or its referenced libraries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
NativeFileReference items with .c/.cpp extensions are routed to
_WasmSourceFileToCompile and only added to _WasmNativeFileForLinking
after M2N generation. Add their FileName directly so the pinvoke
table includes their exported entries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
UnmanagedCallersOnly callbacks under [DisableRuntimeMarshalling] may have
non-blittable struct parameters (e.g. LayoutKind.Auto multi-field). MapType
already maps these to void* in the emitted C prototype, but FixedSymbolName
threw InvalidSignatureCharException when SignatureMapper produced no token.
Fall back to the by-ref-struct token (IND) for any value type that doesn't
otherwise resolve, giving callbacks a stable symbol name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
maraf and others added 16 commits May 13, 2026 14:16
The WebAssembly SDK's _WasmNativeForBuild target hooks WasmBuildApp before
_ResolveWasmOutputs so that the relinked dotnet.native.wasm is staged into
the build's static web assets manifest. It is gated on UsingBrowserRuntimeWorkload=true,
which is false in CoreCLR no-workload mode. Without an analog target the
relink only runs via WasmBuildApp's AfterTargets="Build" — too late, so
GenerateStaticWebAssetsManifest registers the original runtime-pack
dotnet.native.wasm and the relinked binary (with the user's pinvoke-table
overrides) is never served at runtime, causing DllNotFoundException for
any user NativeFileReference.

Add _CoreCLRWasmNativeForBuild as the build-time analog of the existing
_CoreCLRWasmNative (publish) target.

Also make the EnsureWasmAbiRulesAreFollowed pinvoke-table assertion
runtime-aware (CoreCLR emits DllImportEntry(name) where Mono emits the
"name", name pair).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…erator

The generated extern "C" wrapper for UnmanagedCallersOnly methods with an
EntryPoint was hardcoded to void return and dropped the result of Call_<sym>.
Emit the correct return type and propagate the value so user UCO exports
match the C declaration on the native side.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
UnmanagedCallersOnly_Namespaced and UCOWithSpecialCharacters require the
user-generated reverse pinvoke table to be linked into dotnet.native.wasm.
On CoreCLR-Wasm today, reverse-pinvoke-table.cpp is intentionally not
compiled because libcoreclr_static.a already defines g_ReverseThunks for
framework callbacks (duplicate-symbol conflict). Skip these tests on
CoreCLR via [TestCategory("mono")] until user reverse-thunks are wired
in (e.g. by mirroring Mono's GEN_PINVOKE include-and-recompile pattern).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ble-non-aot-tests

# Conflicts:
#	src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs
PR dotnet#127438 split the browser+CoreCLR <LibrariesRuntimeFiles> element into
two: one with the $(LibrariesSharedFrameworkDir)*.a glob, and a separate
one for the host files. The Exclude= added in commit c0b0e63 for the
three duplicate static libs (libminipal.a, libSystem.IO.Compression.Native.a,
libz.a) stayed on the host element, where it had no effect because that
element does not include any $(LibrariesSharedFrameworkDir) items.

This caused NU5118 (duplicate file) to return for browser-wasm CoreCLR
runtime pack builds: the three .a files are produced under both
$(CoreCLRSharedFrameworkDir) and $(LibrariesSharedFrameworkDir), and both
copies were getting packed.

Move the Exclude= to the ItemGroup that actually contains the *.a glob
so the duplicates are dropped again.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CoreCLR-Wasm native relink fails with undefined GlobalizationNative_*
symbols when InvariantGlobalization=true, because libSystem.Globalization.Native.a
is not linked into the browser-wasm app but pinvoke-table.o still
references the entry points.

Gate the tests to Mono until CoreCLR-Wasm provides a stub for the
System.Globalization.Native library. Tracked by dotnet#128219.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Here I assume crossgen2 will replace the generator soon. Currently the
generator is not able to handle fully the structures
https://github.com/dotnet/runtime/blob/5d6e5b8246d731058b220c4b7b7be61809c7db49/src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs#L17-L27

In case we will need that sooner, we would need to calculate the size
properly in the generator too.
The CoreCLR product build (vm/wks CMake target) compiles these three
files without force-including coreclr_compat.h, which is only pre-included
by BrowserWasmApp.CoreCLR.targets during WasmAppBuilder relink. Without
the direct includes, types like Entry/DllImportEntry/MethodDesc are
undefined and the build fails.

Restore the original includes manually; the generators in
src/tasks/WasmAppBuilder/coreclr/ should be updated in a follow-up so
they don't strip the includes on regeneration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tive

This test relies on a native build pipeline that currently only works on
Mono-Wasm. Add [TestCategory("native")] so it is skipped by the
NoWorkload CoreCLR-Wasm workitem (which does not run the 'native'
category) and is exercised by the mono native workitem instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move generated wasm relink sources back to including the real CoreCLR
headers (<callhelpers.hpp>, <minipal/entrypoints.h>, <minipal/utils.h>)
instead of relying on stubs in coreclr_compat.h. This eliminates the
drift risk of hand-mirroring StringToWasmSigThunk / ReverseThunkMapEntry
/ Entry / minipal_resolve_dllimport and lets the in-tree
src/coreclr/vm/wasm/callhelpers-*.cpp files share the same source of
truth as the WBT-generated pinvoke-table.cpp / wasm_m2n_invoke.g.cpp.

Changes:
* Revert PInvokeTableGenerator and InterpToNativeGenerator to emit the
  real #include lines again.
* Slim coreclr_compat.h to just the prerequisites callhelpers.hpp
  assumes are in scope from the in-tree vm/common.h PCH:
  MethodDesc/PCODE/ULONG typedefs, INTERP_STACK_SLOT_SIZE, and the
  LF_INTEROP/LL_INFO1000/LOG/PORTABILITY_ASSERT logging stubs. Drop
  the duplicate g_wasmThunks/g_ReverseThunks/Entry/DllImportEntry
  decls (now sourced from the real headers).
* Ship src/coreclr/vm/wasm/callhelpers.hpp and
  src/native/minipal/entrypoints.h to the WBT Helix payload alongside
  the already-shipped minipal/utils.h.
* Add CORECLR_VM_WASM_INCLUDE_DIR env var (HelixPreCommand on Helix,
  RunScriptCommands locally) and wire -I flags in
  BrowserWasmApp.CoreCLR.targets so the <callhelpers.hpp> /
  <minipal/...> includes resolve. In-repo fallback paths cover full
  repo builds. Propagate the env var through the WBT test harness.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 17, 2026 08:11
@maraf maraf added arch-wasm WebAssembly architecture test-enhancement Improvements of test source code area-Build-mono os-browser Browser variant of arch-wasm labels May 17, 2026
@maraf maraf self-assigned this May 17, 2026
@maraf maraf added this to the 11.0.0 milestone May 17, 2026 — with GitHub Codespaces
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands CoreCLR-on-WASM “no workload” support by plumbing publish-time native re-link (via emcc) end-to-end and enabling additional Wasm.Build.Tests coverage on CoreCLR, including the necessary runtime-pack payload and Helix correlation payload adjustments.

Changes:

  • Ship additional CoreCLR WASM native relink inputs via runtime packs / manifests (static .a libs and libSystem.Native.Browser.extpost.js) and adjust Helix/live-build payload composition.
  • Rework BrowserWasmApp.CoreCLR.targets to run relink at the right points in the build/publish pipeline, split user vs generated compile flags, and allow include paths to be supplied via env vars.
  • Update Wasm.Build.Tests infrastructure and test assets to run more native-relink scenarios on CoreCLR (trait retagging, runtime-pack path parsing for assertions, and a minimal main.js for non-JS-interop test apps).

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs Adds struct-size overrides for new WBT testasset types.
src/native/libs/Common/JavaScript/CMakeLists.txt Installs libSystem.Native.Browser.extpost.js into the shared framework/runtime pack.
src/native/corehost/corehost.proj Stops copying extpost.js from the source tree into runtime-pack native files (now installed via CMake).
src/native/corehost/browserhost/libBrowserHost.footer.js Adjusts explicit dependency list to avoid hard-linking wasm_load_icu_data in invariant mode.
src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs Threads resolved runtime-pack root into bundle assertions; treats CoreCLR as native-rebuild capable in no-workload mode.
src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj Adds local env-var exports for CoreCLR include dirs (MINIPAL_INCLUDE_DIR, CORECLR_VM_WASM_INCLUDE_DIR).
src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs Improves template-install diagnostics and adds helper to replace main.js for non-JS-interop apps.
src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs Parses runtime-pack dir from MSBuild output and uses it for ICU asset source comparisons.
src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs Retags tests for CoreCLR enablement and adjusts CoreCLR pinvoke-table assertions; uses minimal main.js where needed.
src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs Splits AOT vs non-AOT cases and retags; uses minimal main.js for non-JS-interop apps.
src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs Removes class-level native trait and retags AOT-only methods; uses minimal main.js for non-JS-interop program.
src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs Retags to native-coreclr.
src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs Gates invariant-globalization relink tests to Mono pending CoreCLR stub availability; uses minimal main.js.
src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs Replaces template main.js with minimal runner for ICU test apps.
src/mono/wasm/Wasm.Build.Tests/IcuTests.cs Retags ICU tests to native-coreclr.
src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs Retags sharding test to native-coreclr.
src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs Retags sharding tests to native-coreclr.
src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs Drops class-level native tag; marks a Mono-only test and retags remaining native coverage.
src/mono/wasm/Wasm.Build.Tests/data/RunScriptTemplate.sh Propagates new include-dir env vars to child processes.
src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs Exposes MINIPAL_INCLUDE_DIR and CORECLR_VM_WASM_INCLUDE_DIR.
src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs Propagates new env vars and fixes runtime-pack naming for CoreCLR browser-wasm packs.
src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs Adds RuntimePackDir to support runtime-pack-consistent ICU assertions.
src/mono/wasm/testassets/EntryPoints/PInvoke/BittableSameAssembly.cs Moves testasset types into WasmAppBuilderTests namespace for CoreCLR mapping.
src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly.cs Adjusts testasset to reference namespaced types.
src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly_Lib.cs Moves library types into WasmAppBuilderTests namespace.
src/mono/wasm/testassets/EntryPoints/PInvoke/AbiRules.cs Moves ABI-rule structs into WasmAppBuilderTests namespace; formatting adjustments.
src/mono/wasm/testassets/EntryPoints/icu_main.js Adds a minimal main.js variant that avoids JS-interop startup calls.
src/mono/browser/build/coreclr_compat.h Adds a compat header for compiling generated CoreCLR interop sources outside the full in-tree CoreCLR PCH context.
src/mono/browser/build/BrowserWasmApp.CoreCLR.targets Refactors CoreCLR relink pipeline ordering; splits compile RSPs; adds env-var-overridable include roots; includes user sources in pinvoke scanning.
src/libraries/sendtohelix-browser.targets Stages minipal + CoreCLR wasm headers into Helix payload and exports include-dir env vars.
src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj Enables static libraries in browser/wasi runtime packs for relinking.
src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props Adds libBrowserHost.a and libSystem.Native.Browser.extpost.js to platform manifest.
eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt Enables additional WBT test classes on CoreCLR lane.
eng/liveBuilds.targets Excludes select .a files from the browser-host runtime-tests file set.

Comment on lines +1 to +5
// Auto-included CoreCLR compat header for app native build.
//
// This header is pre-included via -include when compiling pinvoke-table.cpp
// and wasm_m2n_invoke.g.cpp produced by ManagedToNativeGenerator. It provides
// only the prerequisite types/macros that the real CoreCLR headers
Comment on lines +6 to +13
const { setModuleImports, getAssemblyExports, getConfig } = await dotnet
.withApplicationArguments("start")
.withDiagnosticTracing(true)
.withConfig({ forwardConsole: true, appendElementOnExit: true, logExitCode: true, exitOnUnhandledError: true })
.create();

const config = getConfig();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-Build-mono os-browser Browser variant of arch-wasm test-enhancement Improvements of test source code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants