diff --git a/docs/workflow/testing/libraries/testing-wasm.md b/docs/workflow/testing/libraries/testing-wasm.md
index bc6965cd5dc4e4..4906192c6c1600 100644
--- a/docs/workflow/testing/libraries/testing-wasm.md
+++ b/docs/workflow/testing/libraries/testing-wasm.md
@@ -144,6 +144,56 @@ 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.
+
+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
diff --git a/eng/pipelines/common/templates/wasm-coreclr-library-tests.yml b/eng/pipelines/common/templates/wasm-coreclr-library-tests.yml
index 61bb488f777113..f723450db55e3a 100644
--- a/eng/pipelines/common/templates/wasm-coreclr-library-tests.yml
+++ b/eng/pipelines/common/templates/wasm-coreclr-library-tests.yml
@@ -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 trims and crossgen2-publishes
+ # every test app, which is substantially slower, so its callers raise this.
+ timeoutInMinutes: 240
useHelixMonitor: false
jobs:
@@ -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) ${{ 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
@@ -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 }}
diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml
index 0dfb371196d872..798b775a8f13a7 100644
--- a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml
+++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml
@@ -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:
diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml
index 74c90c04774dc9..f41e63fbf85ef6 100644
--- a/eng/pipelines/runtime.yml
+++ b/eng/pipelines/runtime.yml
@@ -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:
diff --git a/eng/testing/ILLink.Descriptor.xunit.xml b/eng/testing/ILLink.Descriptor.xunit.xml
index 78ee4cddc3246a..6ab8003bbec88f 100644
--- a/eng/testing/ILLink.Descriptor.xunit.xml
+++ b/eng/testing/ILLink.Descriptor.xunit.xml
@@ -16,6 +16,9 @@
+
+
+
diff --git a/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets
index 0276d1a0ec8aea..a2eae35f007daf 100644
--- a/eng/testing/tests.browser.targets
+++ b/eng/testing/tests.browser.targets
@@ -100,6 +100,10 @@
true
+
+
+
+
false
@@ -382,7 +386,7 @@
-
+
diff --git a/eng/testing/tests.wasi.targets b/eng/testing/tests.wasi.targets
index 9a591156932b1f..c9242792ecc76e 100644
--- a/eng/testing/tests.wasi.targets
+++ b/eng/testing/tests.wasi.targets
@@ -151,7 +151,7 @@
Condition="'%(WasmFilesToIncludeFromPublishDir.Identity)' != ''" />
-
+
diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets
index 63a30b0f8a6741..dff8304d5266bc 100644
--- a/eng/testing/tests.wasm.targets
+++ b/eng/testing/tests.wasm.targets
@@ -122,7 +122,7 @@
-
+
value;
+ }
+
+ private sealed class DerivedVirtualTarget : VirtualTarget
+ {
+ public override int Transform(int value) => value + 1;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static int CallVirtual(VirtualTarget target, int value) => target.Transform(value);
+
[MethodImpl(MethodImplOptions.NoInlining)]
public static async Task MultipleAwaits()
{
- int x = 1;
+ int x = CallVirtual(new DerivedVirtualTarget(), 0);
await Task.Yield();
x++;
await Task.Yield();
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs
index eef9655dc30ae7..54324b56d65ef3 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs
@@ -872,6 +872,119 @@ public static bool AsyncMethodsWithResumptionStubsAreAdjacent(ReadyToRunReader r
return true;
}
+ ///
+ /// Returns true if each Wasm async resume target uses the RuntimeFunctions index immediately
+ /// following its parent async method and its funclets.
+ ///
+ public static bool WasmAsyncResumeTargetsMatchRuntimeFunctionOrder(ReadyToRunReader reader, out string diagnostic)
+ {
+ var failures = new List();
+ var resumptionStubTargets = new HashSet();
+ var storeMultiTargets = new List<(string Owner, uint Target)>();
+ int checkedMethodCount = 0;
+
+ foreach (ReadyToRunMethod method in GetAllMethods(reader))
+ {
+ if (method.Fixups is null)
+ continue;
+
+ bool foundResumptionStub = false;
+ foreach (FixupCell cell in method.Fixups)
+ {
+ ReadyToRunImportSection importSection = reader.ImportSections[(int)cell.TableIndex];
+ ReadyToRunImportSection.ImportSectionEntry entry = importSection.Entries[(int)cell.CellOffset];
+ ReadyToRunFixupKind? kind = entry.Signature?.FixupKind;
+ if (kind is not (ReadyToRunFixupKind.ResumptionStubEntryPoint or ReadyToRunFixupKind.StoreMultiCallableAddrOfCode))
+ continue;
+
+ int offset = reader.GetOffset(checked((int)entry.SignatureRVA)) + sizeof(byte);
+ uint targetIndex = BinaryPrimitives.ReadUInt32LittleEndian(reader.Image.AsSpan(offset, sizeof(uint)));
+
+ if (kind == ReadyToRunFixupKind.StoreMultiCallableAddrOfCode)
+ {
+ storeMultiTargets.Add((method.SignatureString, targetIndex));
+ continue;
+ }
+
+ foundResumptionStub = true;
+ resumptionStubTargets.Add(targetIndex);
+ uint expectedIndex = checked((uint)(method.EntryPointRuntimeFunctionId + method.RuntimeFunctionCount - 1));
+ if (targetIndex != expectedIndex)
+ {
+ failures.Add(
+ $"'{method.SignatureString}' has {kind} target {targetIndex}; " +
+ $"expected RuntimeFunctions index {expectedIndex}.");
+ }
+ }
+
+ if (foundResumptionStub)
+ checkedMethodCount++;
+ }
+
+ foreach ((string owner, uint target) in storeMultiTargets)
+ {
+ if (!resumptionStubTargets.Contains(target))
+ {
+ failures.Add(
+ $"'{owner}' has StoreMultiCallableAddrOfCode target {target}, " +
+ "which is not registered by a ResumptionStubEntryPoint fixup.");
+ }
+ }
+
+ if (checkedMethodCount == 0)
+ {
+ diagnostic = "No methods with ResumptionStubEntryPoint fixups were found.";
+ return false;
+ }
+
+ if (storeMultiTargets.Count == 0)
+ {
+ diagnostic = "No StoreMultiCallableAddrOfCode fixups were found.";
+ return false;
+ }
+
+ if (!HasWasmVirtualDispatchThunk(reader))
+ {
+ diagnostic = "No virtual-dispatch thunk was found.";
+ return false;
+ }
+
+ diagnostic = failures.Count == 0
+ ? $"Found {checkedMethodCount} async method(s) and {storeMultiTargets.Count} StoreMultiCallableAddrOfCode fixup(s) whose resume targets match RuntimeFunctions ordering in an image containing a virtual-dispatch thunk."
+ : string.Join(Environment.NewLine, failures);
+ return failures.Count == 0;
+ }
+
+ private static bool HasWasmVirtualDispatchThunk(ReadyToRunReader reader)
+ {
+ foreach (ReadyToRunImportSection section in reader.ImportSections)
+ {
+ if (section.Entries is null)
+ continue;
+
+ foreach (ReadyToRunImportSection.ImportSectionEntry entry in section.Entries)
+ {
+ if (entry.Signature?.FixupKind != ReadyToRunFixupKind.InjectStringThunks)
+ continue;
+
+ int offset = reader.GetOffset(checked((int)entry.SignatureRVA)) + sizeof(byte);
+ while (reader.Image[offset] != 0)
+ {
+ int terminator = reader.Image.AsSpan(offset).IndexOf((byte)0);
+ if (terminator < 0)
+ return false;
+
+ if (reader.Image[offset] == (byte)'V')
+ return true;
+
+ offset += terminator + 1 + sizeof(uint);
+ }
+ }
+ }
+
+ return false;
+ }
+
///
/// Returns true if the R2R image contains at least one ContinuationLayout fixup.
///
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmVirtualDispatchThunkNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmVirtualDispatchThunkNode.cs
index 18318aedb3e4ff..233897cdefb077 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmVirtualDispatchThunkNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmVirtualDispatchThunkNode.cs
@@ -56,7 +56,8 @@ protected override string GetName(NodeFactory factory)
return sb.ToString();
}
- public override int ClassCode => 115732791;
+ // Keep virtual dispatch thunks after compiled methods and the existing Wasm transition thunks.
+ public override int ClassCode => 948271451;
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
diff --git a/src/libraries/Common/tests/System/GenericMathTestMemberData.cs b/src/libraries/Common/tests/System/GenericMathTestMemberData.cs
index 6e29ad5e95b6fd..0cceb281ef407b 100644
--- a/src/libraries/Common/tests/System/GenericMathTestMemberData.cs
+++ b/src/libraries/Common/tests/System/GenericMathTestMemberData.cs
@@ -1373,7 +1373,8 @@ public static IEnumerable