Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ed1b70b
[wasm] Dispose the metadata provider in WebcilReader
pavelsavara Sep 7, 2026
a30d8e1
[wasm] Match prebuilt R2R images by MVID instead of assembly version
pavelsavara Sep 7, 2026
d8c1d10
[wasm][coreclr] Move the ReadyToRun crossgen2 wiring into the WebAsse…
pavelsavara Sep 7, 2026
b4110b1
[wasm][coreclr] Wire ReadyToRun into the WebAssembly publish and buil…
pavelsavara Sep 7, 2026
9330b80
[wasm][coreclr] Trigger the native relink on WasmBuildNative as well
pavelsavara Sep 7, 2026
d836bcd
[wasm] Do not reference ILLink.Tasks from the wasm nested publish
pavelsavara Sep 7, 2026
9c51a87
[wasm][coreclr] Add Wasm.Build.Tests coverage for ReadyToRun
pavelsavara Sep 7, 2026
e00a2dd
[wasm][coreclr] Run the JavaScript interop tests with ReadyToRun
pavelsavara Sep 7, 2026
9cbc88f
[wasm][coreclr] Resolve crossgen2 for ReadyToRun via the SDK-restored…
pavelsavara Sep 8, 2026
aee15bf
[wasm][coreclr] Address PR review feedback on the ReadyToRun wiring
pavelsavara Sep 8, 2026
89fdaf4
[wasm][coreclr] Move test-only asset-copy targets to the test infra
pavelsavara Sep 9, 2026
528ec51
Update ReferenceCopyLocalPaths for trimmed closure
pavelsavara Sep 9, 2026
4b894b6
Merge branch 'main' into browsehost_load_r2r_9
pavelsavara Sep 9, 2026
1407d12
fix
pavelsavara Sep 9, 2026
b126e04
publish incrementality fix
pavelsavara Sep 10, 2026
245cb70
Merge branch 'main' into browsehost_load_r2r_9
pavelsavara Sep 10, 2026
b11f79f
Merge browser-wasm R2R publishing prerequisite (#133378)
lewing Sep 10, 2026
1c8ecbe
Honor dynamic-code compilation in CoreLib trimming substitutions
lewing Sep 10, 2026
6ceb8f2
Enable trimmed CoreCLR WebAssembly R2R library tests
lewing Sep 11, 2026
e220984
Merge main after CoreLib trimming substitution fix
lewing Sep 11, 2026
282d12d
[wasm][coreclr] Raise R2R library-test lane timeout to 480 min
pavelsavara Sep 11, 2026
c3619ff
Merge main through the landed WebAssembly caller fix
lewing Sep 12, 2026
d7517ee
Re-enable WebAssembly caller-identification conformance tests
lewing Sep 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/workflow/testing/libraries/testing-wasm.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,59 @@ At the moment supported values are:

By default, `chrome` browser is used.

## CoreCLR ReadyToRun library tests

Build the browser CoreCLR runtime, libraries, packs, and host crossgen2 first:

```bash
./build.sh clr+libs+host+packs -os browser -c Release /p:AotHostArchitecture=arm64 /p:AotHostOS=osx
```

Use the architecture and OS of the build machine for `AotHostArchitecture` and `AotHostOS`
(for example, `x64` and `linux` on an x64 Linux host).
On macOS, ensure a supported Python installation is ahead of Xcode's Python on `PATH`.

Run a library suite with trimmed ReadyToRun images:

```bash
XHARNESS_COMMAND=test-browser ./dotnet.sh build /t:Test \
src/libraries/System.Runtime/tests/System.IO.UnmanagedMemoryStream.Tests/System.IO.UnmanagedMemoryStream.Tests.csproj \
/p:TargetOS=browser /p:TargetArchitecture=wasm /p:RuntimeFlavor=CoreCLR /p:Configuration=Release \
/p:TestWasmReadyToRun=true /p:EnableAggressiveTrimming=true \
/p:Scenario=WasmTestOnChrome /p:InstallChromeForTests=true
```

`TestWasmReadyToRun` enables `PublishReadyToRun` only in browser CoreCLR library test projects.
Do not pass `PublishReadyToRun=true` globally to `build.sh`: that also attempts to publish
host-side build tools with ReadyToRun.
`EnableAggressiveTrimming` selects the shared mobile test trimming configuration, including
the xUnit and test-utility descriptors and trimming-aware test exclusions.
Library-specific descriptors retain their existing platform conditions; Apple-only roots
are not enabled for browser tests. The property must also be passed when building the test utilities.
R2R browser test apps also set `TEST_READY_TO_RUN_MODE=1`, so existing
`PlatformDetection.IsReadyToRunCompiled` conditions apply. Quarantines for shared WebAssembly
R2R issues use `PlatformDetection.IsWasmReadyToRun`, covering browser and WASI R2R while
leaving interpreter coverage enabled on both hosts.
`TestUtilities.dll` stays interpreted while its guarded platform probes are affected by
[the R2R platform-probe issue](https://github.com/dotnet/runtime/issues/133614).
The library and test assemblies still use ReadyToRun.

For the existing CoreCLR library smoke set, use:

```bash
XHARNESS_COMMAND=test-browser ./build.sh libs.tests -test -os browser -c Release \
/p:RuntimeFlavor=CoreCLR /p:TestWasmReadyToRun=true /p:EnableAggressiveTrimming=true \
/p:RunSmokeTestsOnly=true /p:Scenario=WasmTestOnChrome /p:InstallChromeForTests=true
```

Omit `RunSmokeTestsOnly` to build and run all supported library suites. To isolate trimming
from R2R, keep `EnableAggressiveTrimming=true` and pass `PublishReadyToRun=false`.
Use clean project-specific `bin` and `obj` browser-wasm outputs when switching modes;
stale staged assets can otherwise cause assembly-loading failures before tests start.

The `LibraryTestsCoreCLR_R2R` CI jobs use the same configuration and archive the published
tests for execution on Helix. The existing interpreter jobs remain separate.

## AOT library tests

- Building library tests with AOT, and (even) with `EnableAggressiveTrimming` takes 3-9mins on CI, and that adds up for all the assemblies, causing
Expand Down
4 changes: 4 additions & 0 deletions eng/liveILLink.targets
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@

<ItemGroup Condition="'$(_RequiresLiveILLink)' == 'true'">
<!-- The assembly shouldn't be referenced, nor promoted to a package dependency, nor copied to the output directory. -->
<!-- The wasm nested publish evaluates this project with different global properties, so MSBuild builds it a
second time and copies obj -> bin over the assembly the outer pass already loaded (MSB3027). The outer
pass has built it by then, so referencing it again from the nested pass is redundant as well as harmful. -->
<ProjectReference Include="$(_ILLinkTasksSourceDir)ILLink.Tasks.csproj"
Condition="'$(WasmBuildingForNestedPublish)' != 'true'"
ReferenceOutputAssembly="false"
PrivateAssets="all"
Private="false"
Expand Down
17 changes: 13 additions & 4 deletions eng/pipelines/common/templates/wasm-coreclr-library-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ parameters:
isWasmOnlyBuild: false
nameSuffix: ''
platforms: []
readyToRun: false
scenarios: ['WasmTestOnChrome']
shouldContinueOnError: false
shouldRunSmokeOnly: false
# Default matches the fast interpreter lane. The R2R lane crossgen2-publishes every
# test app serially (/maxcpucount:1), which is ~5.5x slower, so its callers raise this.
timeoutInMinutes: 240
useHelixMonitor: false

jobs:
Expand Down Expand Up @@ -77,13 +81,18 @@ jobs:
value: /p:InstallV8ForTests=true
${{ else }}:
value: ''
- name: wasmReadyToRunArgs
${{ if eq(parameters.readyToRun, true) }}:
value: /p:TestWasmReadyToRun=true /p:EnableAggressiveTrimming=true
${{ else }}:
value: ''

jobParameters:
isExtraPlatforms: ${{ parameters.isExtraPlatformsBuild }}
testGroup: innerloop
nameSuffix: LibraryTestsCoreCLR${{ parameters.nameSuffix }}
buildArgs: -s clr+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:BrowserHost=$(_hostedOs) $(_wasmRunSmokeTestsOnlyArg) $(chromeInstallArg) $(firefoxInstallArg) $(v8InstallArg) /maxcpucount:1 ${{ parameters.extraBuildArgs }}
timeoutInMinutes: 240
buildArgs: -s clr+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:BrowserHost=$(_hostedOs) $(_wasmRunSmokeTestsOnlyArg) $(chromeInstallArg) $(firefoxInstallArg) $(v8InstallArg) $(wasmReadyToRunArgs) /maxcpucount:1 ${{ parameters.extraBuildArgs }}
timeoutInMinutes: ${{ parameters.timeoutInMinutes }}
# if !alwaysRun, then:
# if this is runtime-wasm (isWasmOnlyBuild):
# - then run only if it would not have run on default pipelines (based
Expand All @@ -98,7 +107,7 @@ jobs:
- template: /eng/pipelines/libraries/helix.yml
parameters:
creator: dotnet-bot
testRunNamePrefixSuffix: CoreCLR_$(_BuildConfig)
extraHelixArguments: /p:BrowserHost=$(_hostedOs) $(_wasmRunSmokeTestsOnlyArg) ${{ parameters.extraHelixArguments }}
testRunNamePrefixSuffix: CoreCLR${{ parameters.nameSuffix }}_$(_BuildConfig)
extraHelixArguments: /p:BrowserHost=$(_hostedOs) $(_wasmRunSmokeTestsOnlyArg) $(wasmReadyToRunArgs) ${{ parameters.extraHelixArguments }}
scenarios: ${{ parameters.scenarios }}
useHelixMonitor: ${{ parameters.useHelixMonitor }}
16 changes: 16 additions & 0 deletions eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ jobs:
scenarios:
- WasmTestOnChrome

# CoreCLR ReadyToRun library tests, complementary to the default pipeline's lane.
- template: /eng/pipelines/common/templates/wasm-coreclr-library-tests.yml
parameters:
platforms:
- browser_wasm
nameSuffix: _R2R
readyToRun: true
timeoutInMinutes: 480
extraBuildArgs: /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS)
isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }}
isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }}
alwaysRun: ${{ parameters.isWasmOnlyBuild }}
useHelixMonitor: ${{ parameters.useHelixMonitor }}
scenarios:
- WasmTestOnChrome

# Library tests with full threading
- template: /eng/pipelines/common/templates/wasm-library-tests.yml
parameters:
Expand Down
14 changes: 14 additions & 0 deletions eng/pipelines/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,20 @@ extends:
scenarios:
- WasmTestOnChrome

# WebAssembly CoreCLR with trimmed ReadyToRun library tests
- template: /eng/pipelines/common/templates/wasm-coreclr-library-tests.yml
parameters:
platforms:
- browser_wasm
alwaysRun: ${{ variables.isRollingBuild }}
nameSuffix: _R2R
readyToRun: true
timeoutInMinutes: 480
extraBuildArgs: /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS)
useHelixMonitor: ${{ variables.enableHelixJobMonitor }}
scenarios:
- WasmTestOnChrome

# WebAssembly CoreCLR - smoke tests only on Firefox and V8
- template: /eng/pipelines/common/templates/wasm-coreclr-library-tests.yml
parameters:
Expand Down
41 changes: 40 additions & 1 deletion eng/testing/tests.browser.targets
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<PublishTrimmed>false</PublishTrimmed>
<!-- Merged runtime-test wrappers are not trim-safe (they crash ILLink); keep them untrimmed unless they explicitly opt in below. -->
<PublishTrimmed Condition="'$(TestFramework)' == 'GeneratedRunner'">false</PublishTrimmed>
<PublishTrimmed Condition="'$(RunAOTCompilation)' == 'true' or '$(PublishReadyToRun)' == 'true' or '$(TestTrimming)' == 'true'">true</PublishTrimmed>
<PublishTrimmed Condition="'$(EnableAggressiveTrimming)' == 'true' or '$(RunAOTCompilation)' == 'true' or '$(PublishReadyToRun)' == 'true' or '$(TestTrimming)' == 'true'">true</PublishTrimmed>
<_WasmInTreeDefaults>false</_WasmInTreeDefaults>
<WasmBuildNativeImplicitInReleaseConfiguration>false</WasmBuildNativeImplicitInReleaseConfiguration>

Expand Down Expand Up @@ -60,13 +60,52 @@
so copy framework files to the output directory. See https://github.com/dotnet/runtime/issues/127257. -->
<_WasmFrameworkCopyToOutputDirectory>PreserveNewest</_WasmFrameworkCopyToOutputDirectory>
</PropertyGroup>

<!-- CoreCLR-only, test-infra-only. These two targets exist solely because library tests set
_WasmFrameworkCopyToOutputDirectory=PreserveNewest above (a real app leaves the default Never), so they
live here rather than in the shipped WebAssembly SDK. The CoreCLR nested publish shares $(OutDir) and runs
its build-phase webcil conversion before ILLink/crossgen, so without suppression it would stage the
UNTRIMMED framework into the served bin/ directory beside the outer build's trimmed R2R set. -->
<Target Name="_WasmCoreClrSuppressNestedPublishAssetCopy"
Condition="'$(RuntimeFlavor)' == 'CoreCLR' and '$(WasmBuildingForNestedPublish)' == 'true'"
BeforeTargets="_ComputeWasmBuildCandidates">
<PropertyGroup>
<_WasmFrameworkCopyToOutputDirectory>Never</_WasmFrameworkCopyToOutputDirectory>
</PropertyGroup>
<ItemGroup>
<ReferenceCopyLocalPaths Update="@(ReferenceCopyLocalPaths)" CopyToOutputDirectory="Never" />
</ItemGroup>
</Target>

<!-- The metadata set above is STICKY: a native relink returns the nested publish's assembly set to the outer
build, metadata included, and the outer build would then define those assets in the manifest yet never
copy them - the boot config requests a fingerprint that is not on disk and startup fails on a 500. -->
<Target Name="_WasmCoreClrRestoreCopyToOutputDirectory"
Condition="'$(RuntimeFlavor)' == 'CoreCLR' and '$(_WasmEnableWebcil)' == 'true' and '$(WasmBuildingForNestedPublish)' != 'true'"
BeforeTargets="_ComputeWasmBuildCandidates">
<ItemGroup>
<ReferenceCopyLocalPaths Update="@(ReferenceCopyLocalPaths)"
Condition="'%(ReferenceCopyLocalPaths.CopyToOutputDirectory)' == 'Never'"
CopyToOutputDirectory="$(_WasmFrameworkCopyToOutputDirectory)" />
<WasmAssembliesFinal Update="@(WasmAssembliesFinal)"
Condition="'%(WasmAssembliesFinal.CopyToOutputDirectory)' == 'Never'"
CopyToOutputDirectory="$(_WasmFrameworkCopyToOutputDirectory)" />
</ItemGroup>
</Target>

<PropertyGroup Condition="'$(RuntimeFlavor)' == 'CoreCLR'">
<WasmTestSupport>true</WasmTestSupport>
<WasmTestExitOnUnhandledError>true</WasmTestExitOnUnhandledError>
<WasmTestAppendElementOnExit>true</WasmTestAppendElementOnExit>
<WasmTestLogExitCode>true</WasmTestLogExitCode>
</PropertyGroup>

<ItemGroup Condition="'$(RuntimeFlavor)' == 'CoreCLR' and '$(PublishReadyToRun)' == 'true'">
<WasmEnvironmentVariable Include="TEST_READY_TO_RUN_MODE" Value="1" />
<!-- Keep the harness's platform probes interpreted: https://github.com/dotnet/runtime/issues/133614 -->
<PublishReadyToRunExclude Include="TestUtilities.dll" />
</ItemGroup>

<!-- TODO-WASM https://github.com/dotnet/runtime/issues/120226 (CoreCLR on wasm defaults) -->
<PropertyGroup Condition="'$(RuntimeFlavor)' == 'CoreCLR'">
<InvariantGlobalization Condition="'$(InvariantGlobalization)' == ''">false</InvariantGlobalization>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ private static bool GetLinqExpressionsBuiltWithIsInterpretingOnly()
public static bool IsSingleFile => !HasAssemblyFiles;

public static bool IsReadyToRunCompiled => Environment.GetEnvironmentVariable("TEST_READY_TO_RUN_MODE") == "1";
public static bool IsWasmReadyToRun => IsWasm && IsReadyToRunCompiled;

private static volatile Tuple<bool> s_lazyNonZeroLowerBoundArraySupported;
public static bool IsNonZeroLowerBoundArraySupported
Expand Down
5 changes: 5 additions & 0 deletions src/libraries/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@
<UseLocalAppHostPack>true</UseLocalAppHostPack>
</PropertyGroup>

<!-- A global PublishReadyToRun would also try to crossgen the host-side build tasks. -->
<PropertyGroup Condition="'$(TestWasmReadyToRun)' == 'true' and '$(IsTestProject)' == 'true' and '$(TargetsBrowser)' == 'true' and '$(RuntimeFlavor)' == 'CoreCLR'">
<PublishReadyToRun Condition="'$(PublishReadyToRun)' == ''">true</PublishReadyToRun>
</PropertyGroup>

<!-- To enable the interpreter for mono desktop, we need to pass an env switch -->
<PropertyGroup>
<MonoEnvOptions Condition="'$(MonoEnvOptions)' == '' and '$(TargetsMobile)' != 'true' and '$(MonoForceInterpreter)' == 'true'">--interpreter</MonoEnvOptions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
Suppress the NU1511 warning in the whole project as putting it on a P2P doesn't work: https://github.com/NuGet/Home/issues/14121 -->
<NoWarn>$(NoWarn);NU1511</NoWarn>
<WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>

<!-- Remove when PublishReadyToRun becomes default https://github.com/dotnet/runtime/issues/132466 -->
<PublishReadyToRun Condition="'$(PublishReadyToRun)' == '' and '$(RuntimeFlavor)' == 'CoreCLR'">true</PublishReadyToRun>
</PropertyGroup>

<!-- Make debugging easier -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@
<PackageReference Include="Moq" Version="$(MoqVersion)" />
<PackageReference Include="System.Runtime.Numerics.TestData" Version="$(SystemRuntimeNumericsTestDataVersion)" GeneratePathProperty="true" />

<ProjectReference Include="$(CommonPath)..\tests\System\TestILAssembly\TestILAssembly.ilproj" Link="Common\System\TestILAssembly\TestILAssembly.ilproj" />
<ProjectReference Include="$(CommonPath)..\tests\System\TestILAssembly\TestILAssembly.ilproj" />
<ProjectReference Include="TestLoadAssembly\TestLoadAssembly.csproj" />
<ProjectReference Include="TestCollectibleAssembly\TestCollectibleAssembly.csproj" />
<ProjectReference Include="TestEquivalentTypes\System.TestEquivalentTypes.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ public static IEnumerable<object[]> BinarySearch_TypesNotComparable_TestData()

[Theory]
[MemberData(nameof(BinarySearch_TypesNotComparable_TestData))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/133613", typeof(PlatformDetection), nameof(PlatformDetection.IsWasmReadyToRun))]
public static void BinarySearch_TypesNotIComparable_ThrowsInvalidOperationException<T>(T[] array, object value)
{
Assert.Throws<InvalidOperationException>(() => Array.BinarySearch(array, value));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private static void EmptyFunc() { }
public delegate TestStruct StructReturningDelegate();

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/133618", typeof(PlatformDetection), nameof(PlatformDetection.IsWasmReadyToRun))]
public static void ClosedStaticDelegate()
{
TestClass foo = new TestClass();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3182,6 +3182,7 @@ private static async Task StateMachineAsync_SingleThread_ChainEventsAndCallstack
}

[ConditionalFact(typeof(AsyncProfilerTests), nameof(IsStateMachineAsyncSupported))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/133626", typeof(PlatformDetection), nameof(PlatformDetection.IsWasmReadyToRun))]
public async Task StateMachineAsync_SingleThread_ChainEventsAndCallstack()
{
var events = await CollectEventsAsync(ResumeStateMachineAsyncCallstackKeyword | StateMachineAsyncCoreKeywords | StateMachineAsyncMethodKeywords, async () =>
Expand Down Expand Up @@ -3255,6 +3256,7 @@ private static async ValueTask StateMachineAsync_ValueTask_SingleThread_ChainEve
}

[ConditionalFact(typeof(AsyncProfilerTests), nameof(IsStateMachineAsyncSupported))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/133626", typeof(PlatformDetection), nameof(PlatformDetection.IsWasmReadyToRun))]
public async Task StateMachineAsync_ValueTask_SingleThread_ChainEventsAndCallstack()
{
var events = await CollectEventsAsync(ResumeStateMachineAsyncCallstackKeyword | StateMachineAsyncCoreKeywords | StateMachineAsyncMethodKeywords, async () =>
Expand Down Expand Up @@ -3331,6 +3333,7 @@ private static async ValueTask StateMachineAsync_PoolingValueTask_SingleThread_C
}

[ConditionalFact(typeof(AsyncProfilerTests), nameof(IsStateMachineAsyncSupported))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/133626", typeof(PlatformDetection), nameof(PlatformDetection.IsWasmReadyToRun))]
public async Task StateMachineAsync_PoolingValueTask_SingleThread_ChainEventsAndCallstack()
{
var events = await CollectEventsAsync(ResumeStateMachineAsyncCallstackKeyword | StateMachineAsyncCoreKeywords | StateMachineAsyncMethodKeywords, async () =>
Expand Down
Loading
Loading