From fa59f6d7b27a44deafed1085a781817bf8f22c87 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Sat, 18 Jul 2026 18:09:21 -0700 Subject: [PATCH 01/17] Use Wasm runtime pack for building wasm in ILCompiler.ReadyToRun.Tests --- eng/pipelines/runtime.yml | 45 +++++ .../ILCompiler.ReadyToRun.Tests.csproj | 51 +++++- .../TestCases/R2RTestSuites.cs | 11 +- .../TestCasesRunner/R2RResultChecker.cs | 18 +- .../TestCasesRunner/R2RTestCaseCompiler.cs | 24 +-- .../TestCasesRunner/R2RTestRunner.cs | 94 +++++++--- .../TestCasesRunner/TestPaths.cs | 169 +++--------------- ...rosoft.NETCore.App.Runtime.CoreCLR.sfxproj | 2 +- 8 files changed, 207 insertions(+), 207 deletions(-) diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 77ec17437b8a1e..a122b1b40d0829 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -861,9 +861,24 @@ extends: jobParameters: timeoutInMinutes: 120 nameSuffix: CLR_Tools_Tests + # Depend on the browser-wasm libraries built below so the wasm crossgen2 R2R test + # (ILCompiler.ReadyToRun.Tests) can compile against real browser-wasm references. + dependsOn: + - build_browser_wasm_linux_Release_CLR_Tools_Tests_WasmLibs buildArgs: -s clr.aot+clr.iltools+clr.corelib+clr.nativecorelib+libs.sfx+clr.toolstests+tools.cdac+tools.cdactests -c $(_BuildConfig) -test enablePublishTestResults: true testResultsFormat: 'xunit' + preBuildSteps: + # Lay the browser-wasm libraries into artifacts/bin so TestPaths' WasmRuntimePackDir + # resolves and the wasm R2R test compiles against real browser-wasm references. + - template: /eng/pipelines/common/download-artifact-step.yml + parameters: + artifactName: CLR_Tools_Tests_WasmLibs_BuildArtifacts_browser_wasm_Release + artifactFileName: CLR_Tools_Tests_WasmLibs_BuildArtifacts_browser_wasm_Release$(archiveExtension) + unpackFolder: $(Build.SourcesDirectory)/artifacts/bin + cleanUnpackFolder: false + overwriteExistingFiles: true + displayName: Browser-Wasm Libraries # We want to run AOT tests when illink changes because there's share code and tests from illink which are used by AOT condition: >- or( @@ -871,6 +886,36 @@ extends: eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_illink.containsChange'], true), eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_cdac.containsChange'], true), eq(variables['isRollingBuild'], true)) + + # Build the browser-wasm managed runtime pack that the CLR_Tools_Tests job uses + # as crossgen2 references for the wasm ReadyToRun test. Native runtime and host + # assets are omitted because the test only consumes managed assemblies. + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: Release + platforms: + - browser_wasm + jobParameters: + nameSuffix: CLR_Tools_Tests_WasmLibs + buildArgs: -s clr.corelib+libs+libs.pretest -c Release /p:IncludeStaticLibrariesInPack=false + timeoutInMinutes: 120 + postBuildSteps: + - template: /eng/pipelines/common/upload-artifact-step.yml + parameters: + rootFolder: $(Build.SourcesDirectory)/artifacts/bin + includeRootFolder: false + archiveType: $(archiveType) + archiveExtension: $(archiveExtension) + tarCompression: $(tarCompression) + artifactName: CLR_Tools_Tests_WasmLibs_BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) + displayName: Browser-Wasm Libraries + condition: >- + or( + eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_coreclr.containsChange'], true), + eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_illink.containsChange'], true), + eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_cdac.containsChange'], true), + eq(variables['isRollingBuild'], true)) # # Build CrossDacs # diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index e3f0ab6ea0cd15..14cec02ab0230e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -12,6 +12,12 @@ Debug;Release;Checked true -notrait category=failing + false + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'microsoft.netcore.app.runtime.browser-wasm')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'native')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'native')) @@ -72,6 +78,25 @@ SkipUnchangedFiles="true" /> + + + + + + + + + + + @@ -81,6 +106,7 @@ + $(CoreCLRArtifactsPath)/$(BuildArchitecture)/crossgen2 @@ -90,18 +116,25 @@ $(MicrosoftNetCoreAppRuntimePackNativeDir) - - $(MicrosoftNetCoreAppRefPackRefDir) - - - $(CoreCLRArtifactsPath) + + + + $(RequireWasmReferences) - - $(TargetArchitecture) + + $(WasmRuntimePackDir) - - $(TargetOS) + + $(WasmRuntimePackNativeDir) + + $(CoreCLRConfiguration) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index e29a57c61cccc2..8750b3551ff007 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -76,13 +76,10 @@ public void WasmWebcilModule() new(nameof(WasmWebcilModule), [new CrossgenAssembly(wasmWebcilModule)]) { OutputFileExtension = ".wasm", - AdditionalArgs = - { - "--targetarch", - "wasm", - "--targetos", - "browser", - }, + // Reference the real browser-wasm framework assemblies when they are available + // (e.g. the wasm tools-test job downloads them); otherwise the runner falls back + // to host references so this test still runs everywhere. + TargetRid = TargetRid.BrowserWasm, Validate = Validate, }, ])); 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 33b4ed1d743516..f7f11d6f5c5369 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RResultChecker.cs @@ -1194,11 +1194,15 @@ public static bool MethodILIsPresent(string msilFilePath, string declaringType, /// internal sealed class SimpleAssemblyResolver : IAssemblyResolver { - private readonly TestPaths _paths; + private readonly Dictionary _assemblyPaths; - public SimpleAssemblyResolver(TestPaths paths) + public SimpleAssemblyResolver(IEnumerable referencePaths) { - _paths = paths; + _assemblyPaths = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (string referencePath in referencePaths) + { + _assemblyPaths[Path.GetFileNameWithoutExtension(referencePath)] = referencePath; + } } public IAssemblyMetadata? FindAssembly(MetadataReader metadataReader, AssemblyReferenceHandle assemblyReferenceHandle, string parentFile) @@ -1217,10 +1221,12 @@ public SimpleAssemblyResolver(TestPaths paths) string candidate = Path.Combine(dir, simpleName + ".dll"); if (!File.Exists(candidate)) - candidate = Path.Combine(_paths.RuntimePackDir, simpleName + ".dll"); + { + if (!_assemblyPaths.TryGetValue(simpleName, out string? referencePath) || referencePath is null) + return null; - if (!File.Exists(candidate)) - return null; + candidate = referencePath; + } return new SimpleAssemblyMetadata(candidate); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs index 7bd12c8887fe59..cffccdf5c65b34 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; +using Xunit.Abstractions; namespace ILCompiler.ReadyToRun.Tests.TestCasesRunner; @@ -19,26 +20,13 @@ internal sealed class R2RTestCaseCompiler { private readonly List _frameworkReferences; - public R2RTestCaseCompiler(TestPaths paths) + public R2RTestCaseCompiler(List references) { _frameworkReferences = new List(); - // Add reference assemblies from the ref pack (needed for Roslyn compilation) - string refPackDir = paths.RefPackDir; - if (Directory.Exists(refPackDir)) + foreach (string refPath in references) { - foreach (string refPath in Directory.EnumerateFiles(refPackDir, "*.dll")) - { - _frameworkReferences.Add(MetadataReference.CreateFromFile(refPath)); - } - } - else - { - // Fallback to runtime pack implementation assemblies - foreach (string refPath in paths.GetFrameworkReferencePaths()) - { - _frameworkReferences.Add(MetadataReference.CreateFromFile(refPath)); - } + _frameworkReferences.Add(MetadataReference.CreateFromFile(refPath)); } } @@ -85,7 +73,7 @@ public string CompileAssembly( syntaxTrees, references, new CSharpCompilationOptions(outputKind) - .WithOptimizationLevel(OptimizationLevel.Release) + .WithOptimizationLevel(OptimizationLevel.Debug) .WithAllowUnsafe(true) .WithNullableContextOptions(NullableContextOptions.Enable)); @@ -97,7 +85,7 @@ public string CompileAssembly( .Where(d => d.Severity == DiagnosticSeverity.Error) .Select(d => d.ToString()); throw new InvalidOperationException( - $"Compilation of '{assemblyName}' failed:\n{string.Join("\n", errors)}"); + $"Compilation of '{assemblyName}' failed:\nReferences:\n{string.Join(Environment.NewLine, compilation.References.Select(r => r.Display))}\n\n{string.Join("\n", errors)}"); } return outputPath; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs index c6a2e26649671e..dc23a142c40946 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs @@ -11,6 +11,12 @@ namespace ILCompiler.ReadyToRun.Tests.TestCasesRunner; +enum TargetRid +{ + Host, + BrowserWasm +} + /// /// Describes an assembly compiled by Roslyn as part of a test case. /// @@ -94,6 +100,16 @@ internal sealed class CrossgenCompilation(string name, List as /// public Action? Validate { get; init; } + /// + /// When true, this compilation references the browser-wasm runtime pack (real wasm framework + /// assemblies and the wasm System.Private.CoreLib) if it is available, instead of the host + /// runtime pack. When the browser-wasm libs are not present the runner falls back to host + /// references so the test still runs (with a host-arch corelib). Set this on wasm compilations + /// (--targetarch wasm --targetos browser) so they reference target-matching libraries when run + /// in a job that downloads the browser-wasm libs. + /// + public TargetRid TargetRid { get; init; } = TargetRid.Host; + public string Name => name; public bool IsComposite => Options.Contains(Crossgen2Option.Composite); @@ -205,10 +221,18 @@ public void Run(R2RTestCase testCase) // Step 2: Run each crossgen2 compilation and validate var driver = new R2RDriver(_output, _paths); - var refPaths = BuildReferencePaths(); + List hostRefPaths = BuildReferencePaths(useWasmReferences: false); + List? wasmRefPaths = null; foreach(var compilation in testCase.Compilations) { + List refPaths = hostRefPaths; + if (compilation.TargetRid == TargetRid.BrowserWasm) + { + wasmRefPaths ??= BuildReferencePaths(useWasmReferences: true); + refPaths = wasmRefPaths; + } + string outputPath = RunCrossgenCompilation( testCase.Name, compilation, driver, compilation.FilePath, refPaths, assemblyPaths); @@ -216,7 +240,7 @@ public void Run(R2RTestCase testCase) { Assert.True(File.Exists(outputPath), $"R2R image not found: {outputPath}"); _output.WriteLine($" Validating R2R image: {outputPath}"); - var reader = new ReadyToRunReader(new SimpleAssemblyResolver(_paths), outputPath); + var reader = new ReadyToRunReader(new SimpleAssemblyResolver(refPaths), outputPath); compilation.Validate(reader); } } @@ -234,11 +258,13 @@ public void Run(R2RTestCase testCase) private Dictionary CompileAllAssemblies( IEnumerable assemblies) { - var compiler = new R2RTestCaseCompiler(_paths); var paths = new Dictionary(); foreach (var asm in assemblies) { + // Tests shouldn't require a platform-specific runtime/ref pack for Roslyn compilation + var defaultReferences = BuildReferencePaths(useWasmReferences: false); + var compiler = new R2RTestCaseCompiler(defaultReferences); var sources = asm.SourceResourceNames .Select(R2RTestCaseCompiler.ReadEmbeddedSource) .ToList(); @@ -252,7 +278,7 @@ private Dictionary CompileAllAssemblies( additionalReferences: asm.References.Select(r => r.FilePath).ToList(), features: asm.Features.Count > 0 ? asm.Features : null); paths[asm.AssemblyName] = ilPath; - _output.WriteLine($" Roslyn compiled '{asm.AssemblyName}' -> {ilPath}"); + _output.WriteLine($"Roslyn compiled '{asm.AssemblyName}' -> {ilPath}"); } return paths; @@ -307,6 +333,13 @@ private static string RunCrossgenCompilation( foreach (var option in compilation.Options) args.Add(option.ToArg()); + args.AddRange(compilation.TargetRid switch + { + TargetRid.Host => [], + TargetRid.BrowserWasm => ["--targetos", "browser", "--targetarch", "wasm"], + _ => throw new InvalidOperationException($"Unknown target RID: {compilation.TargetRid}") + }); + // Caller-supplied raw args (for options that take values, e.g. --determinism-stress=N) args.AddRange(compilation.AdditionalArgs); @@ -335,32 +368,47 @@ private static void AddRefArgs(List args, List refPaths) } } - private List BuildReferencePaths() + /// + /// Runtime packs are used as references for both Roslyn compilation and crossgen2 compilation. This method builds the reference paths for both. + /// + /// Whether to try to use browser-wasm references. + private List BuildReferencePaths(bool useWasmReferences) { - var paths = new List(); + List paths; - paths.Add(Path.Combine(_paths.RuntimePackDir, "*.dll")); - - // SPCL lives in the runtime pack native/ dir in full builds (placed by - // externals.csproj BinPlace during libs.pretest). In partial CI builds - // that skip libs.pretest, the runtime pack layout may not exist, but the - // CoreCLR artifacts directory always has SPCL after clr.nativecorelib. - string spcl = Path.Combine(_paths.RuntimePackNativeDir, "System.Private.CoreLib.dll"); - if (!File.Exists(spcl)) + if (useWasmReferences) { - string fallback = Path.Combine(_paths.CoreCLRArtifactsDir, "System.Private.CoreLib.dll"); - if (File.Exists(fallback)) + string? wasmRuntimePackDir = _paths.WasmRuntimePackDir; + string? wasmCoreLibPath = _paths.WasmRuntimePackNativeDir is null + ? null + : Path.Combine(_paths.WasmRuntimePackNativeDir, "System.Private.CoreLib.dll"); + + if (wasmRuntimePackDir is not null && wasmCoreLibPath is not null && File.Exists(wasmCoreLibPath)) { - _output.WriteLine($"[R2RTestRunner] SPCL not found at '{spcl}'; using CoreCLR artifacts fallback '{fallback}'"); - spcl = fallback; + _output.WriteLine($"Using browser-wasm runtime pack references from '{wasmRuntimePackDir}'"); + paths = + [ + ..Directory.GetFiles(wasmRuntimePackDir, "*.dll"), + wasmCoreLibPath + ]; + return paths; + } + else + { + Assert.False( + _paths.RequireWasmReferences, + "Browser-wasm framework references are required, but the browser-wasm runtime pack was not found."); + _output.WriteLine($"Browser-wasm runtime pack references not available. Falling back to host references."); } } - Assert.True(File.Exists(spcl), - $"System.Private.CoreLib.dll not found at '{spcl}'. " + - $"Searched RuntimePackNativeDir='{_paths.RuntimePackNativeDir}' and " + - $"CoreCLRArtifactsDir='{_paths.CoreCLRArtifactsDir}'"); - paths.Add(spcl); + string coreLibPath = Path.Combine(_paths.RuntimePackNativeDir, "System.Private.CoreLib.dll"); + Assert.True(File.Exists(coreLibPath), $"System.Private.CoreLib.dll not found: {coreLibPath}"); + + paths = [ + ..Directory.GetFiles(_paths.RuntimePackDir, "*.dll"), + coreLibPath + ]; return paths; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs index be18b5ff9aeb91..eec3b189895884 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Text.RegularExpressions; @@ -29,51 +28,10 @@ private static string GetRequiredConfig(string key) ?? throw new InvalidOperationException($"Missing RuntimeHostConfigurationOption '{key}'. Was the project built with the correct properties?"); } - /// - /// Tries to find an existing directory by swapping the build configuration path segment - /// (Debug, Release, Checked) in the path. Returns the first match, or the original - /// path if no fallback exists. - /// - private string ProbeConfigFallback(string dir) + private static string? GetOptionalConfig(string key) { - if (Directory.Exists(dir)) - return dir; - - foreach (string fallbackConfig in new[] { "Release", "Checked", "Debug" }) - { - string fallback = Regex.Replace( - dir, @"(?<=[/\\])(Debug|Release|Checked)(?=[/\\])", fallbackConfig); - if (fallback != dir && Directory.Exists(fallback)) - { - _output.WriteLine($"[TestPaths] '{dir}' not found; falling back to '{fallback}'"); - return fallback; - } - } - - return dir; - } - - /// - /// Like but for paths where the configuration name - /// is embedded in a dot-delimited segment (e.g. linux.x64.Checked). - /// - private string ProbeDottedConfigFallback(string dir) - { - if (Directory.Exists(dir)) - return dir; - - foreach (string fallbackConfig in new[] { "Checked", "Release", "Debug" }) - { - string fallback = Regex.Replace( - dir, @"\.(Debug|Release|Checked)([/\\])", $".{fallbackConfig}$2"); - if (fallback != dir && Directory.Exists(fallback)) - { - _output.WriteLine($"[TestPaths] '{dir}' not found; falling back to '{fallback}'"); - return fallback; - } - } - - return dir; + string? value = AppContext.GetData(key) as string; + return string.IsNullOrEmpty(value) ? null : value; } /// @@ -81,24 +39,9 @@ private string ProbeDottedConfigFallback(string dir) /// e.g. artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/ /// Falls back to Checked or Release if the configured path doesn't exist. /// - public string Crossgen2Dir - { - get - { - string dir = GetRequiredConfig("R2RTest.Crossgen2Dir"); - if (!File.Exists(Path.Combine(dir, Crossgen2ExeName))) - { - string fallback = ProbeDottedConfigFallback(dir); - if (File.Exists(Path.Combine(fallback, Crossgen2ExeName))) - return fallback; - } + public string Crossgen2Dir => GetRequiredConfig("R2RTest.Crossgen2Dir"); - return dir; - } - } - - private static string Crossgen2ExeName => - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "crossgen2.exe" : "crossgen2"; + private static string Crossgen2ExeName => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "crossgen2.exe" : "crossgen2"; /// /// Path to the self-contained crossgen2 executable (from crossgen2_inbuild). @@ -106,97 +49,37 @@ public string Crossgen2Dir public string Crossgen2Exe => Path.Combine(Crossgen2Dir, Crossgen2ExeName); /// - /// Path to the runtime pack managed assemblies directory. - /// e.g. artifacts/bin/microsoft.netcore.app.runtime.linux-x64/Release/runtimes/linux-x64/lib/net11.0/ - /// Falls back to a different build configuration if the path doesn't exist. - /// - public string RuntimePackDir - { - get - { - string dir = GetRequiredConfig("R2RTest.RuntimePackDir"); - return ProbeConfigFallback(dir); - } - } - - /// - /// Path to the runtime pack native directory (contains System.Private.CoreLib.dll and native runtime). - /// e.g. artifacts/bin/microsoft.netcore.app.runtime.linux-x64/Release/runtimes/linux-x64/native/ - /// Falls back to a different build configuration if the path doesn't exist. + /// Path to the host runtime pack managed assemblies directory. /// - public string RuntimePackNativeDir - { - get - { - string dir = GetRequiredConfig("R2RTest.RuntimePackNativeDir"); - return ProbeConfigFallback(dir); - } - } + public string RuntimePackDir => GetRequiredConfig("R2RTest.RuntimePackDir"); /// - /// Path to the CoreCLR artifacts directory (contains native bits like corerun). - /// e.g. artifacts/bin/coreclr/linux.x64.Checked/ - /// Falls back to Checked or Release if the configured path doesn't exist. + /// Path to the host runtime pack native directory. /// - public string CoreCLRArtifactsDir - { - get - { - string dir = GetRequiredConfig("R2RTest.CoreCLRArtifactsDir"); - return ProbeDottedConfigFallback(dir); - } - } - - public static string TargetArchitecture => GetRequiredConfig("R2RTest.TargetArchitecture"); - public static string TargetOS => GetRequiredConfig("R2RTest.TargetOS"); - public static string CoreCLRConfiguration => GetRequiredConfig("R2RTest.CoreCLRConfiguration"); - public static bool IsReleaseCoreCLR => string.Equals(CoreCLRConfiguration, "Release", StringComparison.OrdinalIgnoreCase); - public static bool IsNotReleaseCoreCLR => !IsReleaseCoreCLR; + public string RuntimePackNativeDir => GetRequiredConfig("R2RTest.RuntimePackNativeDir"); /// - /// Path to the reference assembly pack (for Roslyn compilation). - /// e.g. artifacts/bin/microsoft.netcore.app.ref/ref/net11.0/ + /// Path to the browser-wasm runtime pack managed assemblies directory, or null when the + /// browser-wasm runtime pack has not been built/downloaded (e.g. in the default linux-x64 tools + /// test job). When present, the wasm crossgen2 compilation references these real browser-wasm + /// framework assemblies instead of the host runtime pack. + /// e.g. artifacts/bin/microsoft.netcore.app.runtime.browser-wasm/Release/runtimes/browser-wasm/lib/net11.0/ /// - public string RefPackDir - { - get - { - string dir = GetRequiredConfig("R2RTest.RefPackDir"); - if (!Directory.Exists(dir)) - { - // Try the artifacts/bin/ref/net* fallback - string artifactsBin = Path.GetFullPath(Path.Combine(CoreCLRArtifactsDir, "..", "..")); - string refDir = Path.Combine(artifactsBin, "ref"); - if (Directory.Exists(refDir)) - { - foreach (string subDir in Directory.GetDirectories(refDir, "net*")) - { - if (File.Exists(Path.Combine(subDir, "System.Runtime.dll"))) - { - _output.WriteLine($"[TestPaths] '{dir}' not found; falling back to '{subDir}'"); - return subDir; - } - } - } - } - - return dir; - } - } + public string? WasmRuntimePackDir => GetOptionalConfig("R2RTest.WasmRuntimePackDir"); /// - /// Returns the target triple string for crossgen2 (e.g. "linux-x64"). + /// Path to the browser-wasm runtime pack native directory (contains the wasm + /// System.Private.CoreLib.dll), or null when not available. + /// e.g. artifacts/bin/microsoft.netcore.app.runtime.browser-wasm/Release/runtimes/browser-wasm/native/ /// - public static string TargetTriple => $"{TargetOS.ToLowerInvariant()}-{TargetArchitecture.ToLowerInvariant()}"; + public string? WasmRuntimePackNativeDir => GetOptionalConfig("R2RTest.WasmRuntimePackNativeDir"); - /// - /// Returns all framework reference assembly paths (*.dll in the runtime pack). - /// - public IEnumerable GetFrameworkReferencePaths() - { - if (!Directory.Exists(RuntimePackDir)) - throw new DirectoryNotFoundException($"Runtime pack directory not found: {RuntimePackDir}"); + public bool RequireWasmReferences => string.Equals( + GetOptionalConfig("R2RTest.RequireWasmReferences"), + bool.TrueString, + StringComparison.OrdinalIgnoreCase); - return Directory.EnumerateFiles(RuntimePackDir, "*.dll"); - } + public static string CoreCLRConfiguration => GetRequiredConfig("R2RTest.CoreCLRConfiguration"); + public static bool IsReleaseCoreCLR => string.Equals(CoreCLRConfiguration, "Release", StringComparison.OrdinalIgnoreCase); + public static bool IsNotReleaseCoreCLR => !IsReleaseCoreCLR; } diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj index 45a5d877f608a9..61dcef292c508d 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj @@ -15,7 +15,7 @@ true true - true + true From 4f17299b5e9f48777b19a709e259fcdb663a3175 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Sat, 18 Jul 2026 18:11:37 -0700 Subject: [PATCH 02/17] Clean up ILCompiler.ReadyToRun.Tests.csproj - Use CopyAllJitLibrariesToAotCompilerOutput=true instead of copying ourselves. - Set crossgenDir based on crossgen2_inbuild.csproj output items - Reorder properties and items to keep related items close to each other --- .../ILCompiler.ReadyToRun.Tests.csproj | 139 +++++++----------- 1 file changed, 55 insertions(+), 84 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index 14cec02ab0230e..9c2ff4fdf2e558 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -13,11 +13,6 @@ true -notrait category=failing false - $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'microsoft.netcore.app.runtime.browser-wasm')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'native')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'native')) @@ -25,57 +20,33 @@ + + + + + + - - - CopyAllJitLibrariesToAotCompilerOutput=true - Configuration=$(CoreCLRConfiguration);CopyAllJitLibrariesToAotCompilerOutput=true + + Configuration=$(CoreCLRConfiguration);CopyAllJitLibrariesToAotCompilerOutput=true - - - <_BuildJitsCrossArch Condition="'$(CrossBuild)' == 'true' or '$(BuildArchitecture)' != '$(TargetArchitecture)' or '$(HostOS)' != '$(TargetOS)' or '$(EnableNativeSanitizers)' != ''">true - <_BuildJitsProperties>ClrAllJitsSubset=true;ClrWasmJitSubset=true;Configuration=$(CoreCLRConfiguration) - <_BuildJitsProperties Condition="!$([MSBuild]::IsOsPlatform(Windows))">$(_BuildJitsProperties);Ninja=true - <_BuildJitsProperties Condition="'$(_BuildJitsCrossArch)' == 'true'">$(_BuildJitsProperties);HostArchitecture=$(BuildArchitecture);HostCrossOS=$(HostOS);CrossBuild=false;BuildSubdirectory=$(BuildArchitecture);CMakeArgs=$(CMakeArgs) -DCLR_CROSS_COMPONENTS_BUILD=1;PgoInstrument=false;NoPgoOptimize=true - - + + + <_BuildJitsCrossArch Condition="'$(CrossBuild)' == 'true' or '$(BuildArchitecture)' != '$(TargetArchitecture)' or '$(HostOS)' != '$(TargetOS)' or '$(EnableNativeSanitizers)' != ''">true + <_BuildJitsProperties>ClrAllJitsSubset=true;ClrWasmJitSubset=true;Configuration=$(CoreCLRConfiguration) + <_BuildJitsProperties Condition="!$([MSBuild]::IsOsPlatform(Windows))">$(_BuildJitsProperties);Ninja=true + <_BuildJitsProperties Condition="'$(_BuildJitsCrossArch)' == 'true'">$(_BuildJitsProperties);HostArchitecture=$(BuildArchitecture);HostCrossOS=$(HostOS);CrossBuild=false;BuildSubdirectory=$(BuildArchitecture);CMakeArgs=$(CMakeArgs) -DCLR_CROSS_COMPONENTS_BUILD=1;PgoInstrument=false;NoPgoOptimize=true + - - - <_WasmJitCrossHostArchSubdir Condition="'$(_BuildJitsCrossArch)' == 'true'">$(BuildArchitecture)/ - <_WasmJitFileName>$(HostLibPrefix)clrjit_universal_wasm_$(BuildArchitecture)$(HostLibSuffix) - <_WasmJitSourcePath>$(CoreCLRArtifactsPath)$(_WasmJitCrossHostArchSubdir)$(_WasmJitFileName) - <_Crossgen2InbuildPublishDir>$(CoreCLRArtifactsPath)$(BuildArchitecture)/crossgen2/ - - + - - - - - + + - - - - - $(CoreCLRArtifactsPath)/$(BuildArchitecture)/crossgen2 - - - $(MicrosoftNetCoreAppRuntimePackRidLibTfmDir) - - - $(MicrosoftNetCoreAppRuntimePackNativeDir) - + + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'microsoft.netcore.app.runtime.browser-wasm')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'native')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'native')) + - - - $(RequireWasmReferences) - - - $(WasmRuntimePackDir) - - - $(WasmRuntimePackNativeDir) - + + + @(Crossgen2InbuildOutputItems->WithMetadataValue('Filename', 'crossgen2')->WithMetadataValue('Extension', '$(ExeSuffix)')->'%(RootDir)%(Directory)') + + + $(MicrosoftNetCoreAppRuntimePackRidLibTfmDir) + + + $(MicrosoftNetCoreAppRuntimePackNativeDir) + - - - $(CoreCLRConfiguration) - - + + $(RequireWasmReferences) + + + $(WasmRuntimePackDir) + + + $(WasmRuntimePackNativeDir) + + + + + $(CoreCLRConfiguration) + + + + From 898d14be4ef04bcfb6b9b49966d39551e763451f Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Sat, 18 Jul 2026 18:32:00 -0700 Subject: [PATCH 03/17] Cleanup --- .../TestCases/R2RTestSuites.cs | 13 +++++++++---- .../TestCasesRunner/R2RDriver.cs | 2 -- .../TestCasesRunner/R2RTestCaseCompiler.cs | 3 +-- .../TestCasesRunner/R2RTestRunner.cs | 10 ++-------- .../TestCasesRunner/TestPaths.cs | 1 + 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index 8750b3551ff007..4c92350b7fe581 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -150,7 +150,12 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + // Note: The arm tests compile for an arm32 target, but they are compiled with the host Corelib. If there are + // load-bearing dependencies in the arm32 corelib (that aren't present in the host corelib) that crossgen2 requires, + // these tests may fail. Wasm tests are similar, but since it doesn't require a special cross-build setup, they use + // a wasm corelib in CI. arm32 tests could be similarly set up, but it hasn't been done yet. + + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.ArmOnHostOSSupported))] public void ArmThumbBitRelocationTargets() { var inlineableLib = new CompiledAssembly @@ -174,7 +179,7 @@ public void ArmThumbBitRelocationTargets() new CrossgenAssembly(inlineableLib) { Kind = Crossgen2InputKind.Reference }, ]) { - Options = [Crossgen2Option.TargetArchArm], + TargetRid = TargetRid.HostArm, Validate = Validate, }, ])); @@ -187,7 +192,7 @@ static void Validate(ReadyToRunReader reader) } // JitStressProcedureSplitting is only available in Debug/Checked JIT builds. - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotReleaseCoreCLR))] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotReleaseCoreCLR), nameof(TestPaths.ArmOnHostOSSupported))] public void ArmThumbBitHotColdRuntimeFunctions() { var hotColdSplitting = new CompiledAssembly @@ -201,9 +206,9 @@ public void ArmThumbBitHotColdRuntimeFunctions() [ new(nameof(ArmThumbBitHotColdRuntimeFunctions), [new CrossgenAssembly(hotColdSplitting)]) { + TargetRid = TargetRid.HostArm, Options = [ - Crossgen2Option.TargetArchArm, Crossgen2Option.Optimize, Crossgen2Option.HotColdSplitting, ], diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs index a359f7bfa45d05..c598f917dc0787 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs @@ -32,7 +32,6 @@ internal enum Crossgen2Option InputBubble, HotColdSplitting, Optimize, - TargetArchArm, StripILBodies, } @@ -59,7 +58,6 @@ internal static class Crossgen2OptionsExtensions Crossgen2Option.InputBubble => $"--input-bubble", Crossgen2Option.HotColdSplitting => $"--hot-cold-splitting", Crossgen2Option.Optimize => $"--optimize", - Crossgen2Option.TargetArchArm => $"--targetarch:arm", Crossgen2Option.StripILBodies => $"--strip-il-bodies", _ => throw new ArgumentOutOfRangeException(nameof(kind)), }; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs index cffccdf5c65b34..816801c96cdc6c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; -using Xunit.Abstractions; namespace ILCompiler.ReadyToRun.Tests.TestCasesRunner; @@ -85,7 +84,7 @@ public string CompileAssembly( .Where(d => d.Severity == DiagnosticSeverity.Error) .Select(d => d.ToString()); throw new InvalidOperationException( - $"Compilation of '{assemblyName}' failed:\nReferences:\n{string.Join(Environment.NewLine, compilation.References.Select(r => r.Display))}\n\n{string.Join("\n", errors)}"); + $"Compilation of '{assemblyName}' failed:\n{string.Join("\n", errors)}"); } return outputPath; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs index dc23a142c40946..769cdad89c2773 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs @@ -14,6 +14,7 @@ namespace ILCompiler.ReadyToRun.Tests.TestCasesRunner; enum TargetRid { Host, + HostArm, BrowserWasm } @@ -100,14 +101,6 @@ internal sealed class CrossgenCompilation(string name, List as /// public Action? Validate { get; init; } - /// - /// When true, this compilation references the browser-wasm runtime pack (real wasm framework - /// assemblies and the wasm System.Private.CoreLib) if it is available, instead of the host - /// runtime pack. When the browser-wasm libs are not present the runner falls back to host - /// references so the test still runs (with a host-arch corelib). Set this on wasm compilations - /// (--targetarch wasm --targetos browser) so they reference target-matching libraries when run - /// in a job that downloads the browser-wasm libs. - /// public TargetRid TargetRid { get; init; } = TargetRid.Host; public string Name => name; @@ -336,6 +329,7 @@ private static string RunCrossgenCompilation( args.AddRange(compilation.TargetRid switch { TargetRid.Host => [], + TargetRid.HostArm => ["--targetarch", "arm"], TargetRid.BrowserWasm => ["--targetos", "browser", "--targetarch", "wasm"], _ => throw new InvalidOperationException($"Unknown target RID: {compilation.TargetRid}") }); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs index eec3b189895884..4947a4604dd50a 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs @@ -82,4 +82,5 @@ private static string GetRequiredConfig(string key) public static string CoreCLRConfiguration => GetRequiredConfig("R2RTest.CoreCLRConfiguration"); public static bool IsReleaseCoreCLR => string.Equals(CoreCLRConfiguration, "Release", StringComparison.OrdinalIgnoreCase); public static bool IsNotReleaseCoreCLR => !IsReleaseCoreCLR; + public static bool ArmOnHostOSSupported => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); } From 08fc40a5d8fcf5bda8f392f8ea00e1f0c25b7295 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 20 Jul 2026 09:07:50 -0700 Subject: [PATCH 04/17] Address copilot feedback --- .../ILCompiler.ReadyToRun.Tests.csproj | 18 ++++++++++-------- .../TestCasesRunner/R2RTestRunner.cs | 6 +++--- .../TestCasesRunner/TestPaths.cs | 1 - 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index 9c2ff4fdf2e558..7d99e13fac1471 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -34,8 +34,18 @@ + + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'microsoft.netcore.app.runtime.browser-wasm')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'native')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) + $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'native')) + + + @@ -74,14 +84,6 @@ BeforeTargets="GenerateBuildRuntimeConfigurationFiles" Condition="'$(DesignTimeBuild)' != 'true'"> - - $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'microsoft.netcore.app.runtime.browser-wasm')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'native')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'native')) - - @(Crossgen2InbuildOutputItems->WithMetadataValue('Filename', 'crossgen2')->WithMetadataValue('Extension', '$(ExeSuffix)')->'%(RootDir)%(Directory)') diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs index 769cdad89c2773..850bf53ffc0750 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs @@ -253,11 +253,11 @@ private Dictionary CompileAllAssemblies( { var paths = new Dictionary(); + // Tests shouldn't require a platform-specific runtime/ref pack for Roslyn compilation + var defaultReferences = BuildReferencePaths(useWasmReferences: false); + var compiler = new R2RTestCaseCompiler(defaultReferences); foreach (var asm in assemblies) { - // Tests shouldn't require a platform-specific runtime/ref pack for Roslyn compilation - var defaultReferences = BuildReferencePaths(useWasmReferences: false); - var compiler = new R2RTestCaseCompiler(defaultReferences); var sources = asm.SourceResourceNames .Select(R2RTestCaseCompiler.ReadEmbeddedSource) .ToList(); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs index 4947a4604dd50a..71661567fe2daf 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs @@ -37,7 +37,6 @@ private static string GetRequiredConfig(string key) /// /// Path to the crossgen2 output directory (contains the self-contained crossgen2 executable and clrjit). /// e.g. artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/ - /// Falls back to Checked or Release if the configured path doesn't exist. /// public string Crossgen2Dir => GetRequiredConfig("R2RTest.Crossgen2Dir"); From 7e40733b9c8cd0c7f00e32825e438411df6cda41 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:05:33 -0700 Subject: [PATCH 05/17] Check runtime pack dir exists before enumerating --- .../TestCasesRunner/R2RTestRunner.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs index 850bf53ffc0750..e8c1704bd3fd58 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs @@ -365,7 +365,7 @@ private static void AddRefArgs(List args, List refPaths) /// /// Runtime packs are used as references for both Roslyn compilation and crossgen2 compilation. This method builds the reference paths for both. /// - /// Whether to try to use browser-wasm references. + /// Whether to try to use browser-wasm references. The host references are used as a fallback if browser-wasm references are not available. private List BuildReferencePaths(bool useWasmReferences) { List paths; @@ -377,7 +377,8 @@ private List BuildReferencePaths(bool useWasmReferences) ? null : Path.Combine(_paths.WasmRuntimePackNativeDir, "System.Private.CoreLib.dll"); - if (wasmRuntimePackDir is not null && wasmCoreLibPath is not null && File.Exists(wasmCoreLibPath)) + if (wasmRuntimePackDir is not null && wasmCoreLibPath is not null + && File.Exists(wasmCoreLibPath) && Directory.Exists(wasmRuntimePackDir)) { _output.WriteLine($"Using browser-wasm runtime pack references from '{wasmRuntimePackDir}'"); paths = @@ -398,6 +399,7 @@ private List BuildReferencePaths(bool useWasmReferences) string coreLibPath = Path.Combine(_paths.RuntimePackNativeDir, "System.Private.CoreLib.dll"); Assert.True(File.Exists(coreLibPath), $"System.Private.CoreLib.dll not found: {coreLibPath}"); + Assert.True(Directory.Exists(_paths.RuntimePackDir), $"Runtime pack directory not found: {_paths.RuntimePackDir}"); paths = [ ..Directory.GetFiles(_paths.RuntimePackDir, "*.dll"), From fd66c73edb747a082bef19948e8466a53aada1a0 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:59:00 -0700 Subject: [PATCH 06/17] Download built arm64 and wasm runtime packs for new clr.r2rtests subset --- eng/Subsets.props | 10 ++- eng/pipelines/runtime.yml | 69 ++++++++++--------- .../ILCompiler.ReadyToRun.Tests.csproj | 56 +++++++-------- .../TestCases/R2RTestSuites.cs | 5 -- .../TestCasesRunner/R2RDriver.cs | 7 ++ .../TestCasesRunner/R2RTestCaseCompiler.cs | 2 +- .../TestCasesRunner/R2RTestRunner.cs | 58 ++++++++-------- .../TestCasesRunner/TestPaths.cs | 26 +++---- ...rosoft.NETCore.App.Runtime.CoreCLR.sfxproj | 2 - 9 files changed, 117 insertions(+), 118 deletions(-) diff --git a/eng/Subsets.props b/eng/Subsets.props index 14b2d3eedab02a..549b541d80a2c0 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -163,7 +163,7 @@ clr.nativeprereqs+clr.iltools+clr.runtime+clr.native+clr.aot+clr.nativeaotlibs+clr.nativeaotruntime+clr.crossarchtools - $(AllSubsetsExpansion)+clr.paltests+clr.paltestlist+clr.hosts+clr.jit+clr.alljits+clr.alljitscommunity+clr.spmi+clr.corelib+clr.nativecorelib+clr.tools+clr.toolstests+clr.packages + $(AllSubsetsExpansion)+clr.paltests+clr.paltestlist+clr.hosts+clr.jit+clr.alljits+clr.alljitscommunity+clr.spmi+clr.corelib+clr.nativecorelib+clr.tools+clr.toolstests+clr.r2rtests+clr.packages $(AllSubsetsExpansion)+linuxdac+alpinedac $(AllSubsetsExpansion)+mono.runtime+provision.emsdk+mono.aotcross+mono.corelib+mono.manifests+mono.packages+mono.tools+mono.wasmruntime+mono.wasiruntime+mono.wasmworkload+mono.mscordbi+mono.workloads $(AllSubsetsExpansion)+tools.illink+tools.cdac+tools.illinktests+tools.cdactests+tools.cdacdumptests @@ -227,6 +227,7 @@ + @@ -508,14 +509,17 @@ Test="true" Category="clr" Condition="'$(DotNetBuildSourceOnly)' != 'true' and '$(NativeAotSupported)' == 'true'"/> - + + + + <_CDacToolsBuilt Condition="'$(_CDacToolsBuilt)' != 'true'">true diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 156abcda32a04a..5dfa9e699e1aa9 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -223,6 +223,7 @@ extends: eq(variables['wasmDarcDependenciesChanged'], true), eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_illink.containsChange'], true), eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_wasm_coreclr_runtimetests.containsChange'], true), + eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_coreclr.containsChange'], true), eq(variables['isRollingBuild'], true)) - template: /eng/pipelines/common/platform-matrix.yml @@ -872,24 +873,9 @@ extends: jobParameters: timeoutInMinutes: 120 nameSuffix: CLR_Tools_Tests - # Depend on the browser-wasm libraries built below so the wasm crossgen2 R2R test - # (ILCompiler.ReadyToRun.Tests) can compile against real browser-wasm references. - dependsOn: - - build_browser_wasm_linux_Release_CLR_Tools_Tests_WasmLibs buildArgs: -s clr.aot+clr.iltools+clr.corelib+clr.nativecorelib+libs.sfx+clr.toolstests+tools.cdac+tools.cdactests -c $(_BuildConfig) -test enablePublishTestResults: true testResultsFormat: 'xunit' - preBuildSteps: - # Lay the browser-wasm libraries into artifacts/bin so TestPaths' WasmRuntimePackDir - # resolves and the wasm R2R test compiles against real browser-wasm references. - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - artifactName: CLR_Tools_Tests_WasmLibs_BuildArtifacts_browser_wasm_Release - artifactFileName: CLR_Tools_Tests_WasmLibs_BuildArtifacts_browser_wasm_Release$(archiveExtension) - unpackFolder: $(Build.SourcesDirectory)/artifacts/bin - cleanUnpackFolder: false - overwriteExistingFiles: true - displayName: Browser-Wasm Libraries # We want to run AOT tests when illink changes because there's share code and tests from illink which are used by AOT condition: >- or( @@ -898,35 +884,52 @@ extends: eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_cdac.containsChange'], true), eq(variables['isRollingBuild'], true)) - # Build the browser-wasm managed runtime pack that the CLR_Tools_Tests job uses - # as crossgen2 references for the wasm ReadyToRun test. Native runtime and host - # assets are omitted because the test only consumes managed assemblies. - template: /eng/pipelines/common/platform-matrix.yml parameters: jobTemplate: /eng/pipelines/common/global-build-job.yml - buildConfig: Release + buildConfig: checked platforms: - - browser_wasm + - linux_x64 jobParameters: - nameSuffix: CLR_Tools_Tests_WasmLibs - buildArgs: -s clr.corelib+libs+libs.pretest -c Release /p:IncludeStaticLibrariesInPack=false + dependsOn: + - build_browser_wasm_linux_checked_CoreCLR_ReleaseLibraries + - build_linux_arm_checked_CoreCLR_ReleaseLibraries + - build_linux_x64_checked_CoreCLR_ReleaseLibraries timeoutInMinutes: 120 - postBuildSteps: - - template: /eng/pipelines/common/upload-artifact-step.yml + nameSuffix: CLR_R2R_Tests + buildArgs: -s clr.r2rtests -lc Release -c $(_BuildConfig) -test /p:RequireTargetArchRuntimePack=true + enablePublishTestResults: true + testResultsFormat: 'xunit' + preBuildSteps: + - template: /eng/pipelines/common/download-artifact-step.yml parameters: - rootFolder: $(Build.SourcesDirectory)/artifacts/bin - includeRootFolder: false - archiveType: $(archiveType) - archiveExtension: $(archiveExtension) - tarCompression: $(tarCompression) - artifactName: CLR_Tools_Tests_WasmLibs_BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) - displayName: Browser-Wasm Libraries + artifactName: CoreCLR_ReleaseLibraries_BuildArtifacts_browser_wasm_linux_Checked + artifactFileName: CoreCLR_ReleaseLibraries_BuildArtifacts_browser_wasm_linux_Checked$(archiveExtension) + unpackFolder: $(Build.SourcesDirectory)/artifacts/bin + cleanUnpackFolder: false + overwriteExistingFiles: true + displayName: Download browser-wasm artifacts + - template: /eng/pipelines/common/download-artifact-step.yml + parameters: + artifactName: CoreCLR_ReleaseLibraries_BuildArtifacts_linux_arm_Checked + artifactFileName: CoreCLR_ReleaseLibraries_BuildArtifacts_linux_arm_Checked$(archiveExtension) + unpackFolder: $(Build.SourcesDirectory)/artifacts/bin + cleanUnpackFolder: false + overwriteExistingFiles: true + displayName: Download linux-arm artifacts + - template: /eng/pipelines/common/download-artifact-step.yml + parameters: + artifactName: CoreCLR_ReleaseLibraries_BuildArtifacts_linux_x64_Checked + artifactFileName: CoreCLR_ReleaseLibraries_BuildArtifacts_linux_x64_Checked$(archiveExtension) + unpackFolder: $(Build.SourcesDirectory)/artifacts/bin + cleanUnpackFolder: false + overwriteExistingFiles: true + displayName: Download linux-x64 artifacts condition: >- or( eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_coreclr.containsChange'], true), - eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_illink.containsChange'], true), - eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_cdac.containsChange'], true), eq(variables['isRollingBuild'], true)) + # # Build CrossDacs # diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index 7d99e13fac1471..17373e0c6f8457 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -4,15 +4,10 @@ ILCompiler.ReadyToRun.Tests $(NetCoreAppToolCurrent) enable - false true - x64;x86 - AnyCPU - linux-x64;win-x64;osx-x64 - Debug;Release;Checked true -notrait category=failing - false + false @@ -40,24 +35,13 @@ $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'native')) $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'native')) - - - - - - <_BuildJitsCrossArch Condition="'$(CrossBuild)' == 'true' or '$(BuildArchitecture)' != '$(TargetArchitecture)' or '$(HostOS)' != '$(TargetOS)' or '$(EnableNativeSanitizers)' != ''">true - <_BuildJitsProperties>ClrAllJitsSubset=true;ClrWasmJitSubset=true;Configuration=$(CoreCLRConfiguration) - <_BuildJitsProperties Condition="!$([MSBuild]::IsOsPlatform(Windows))">$(_BuildJitsProperties);Ninja=true - <_BuildJitsProperties Condition="'$(_BuildJitsCrossArch)' == 'true'">$(_BuildJitsProperties);HostArchitecture=$(BuildArchitecture);HostCrossOS=$(HostOS);CrossBuild=false;BuildSubdirectory=$(BuildArchitecture);CMakeArgs=$(CMakeArgs) -DCLR_CROSS_COMPONENTS_BUILD=1;PgoInstrument=false;NoPgoOptimize=true - - - + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'microsoft.netcore.app.runtime.$(TargetOS)-arm')) + $([MSBuild]::NormalizeDirectory('$(ArmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', '$(TargetOS)-arm', 'lib', '$(NetCoreAppCurrent)')) + $([MSBuild]::NormalizeDirectory('$(ArmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', '$(TargetOS)-arm', 'native')) + $([MSBuild]::NormalizeDirectory('$(ArmRuntimePackRoot)', 'Release', 'runtimes', '$(TargetOS)-arm', 'lib', '$(NetCoreAppCurrent)')) + $([MSBuild]::NormalizeDirectory('$(ArmRuntimePackRoot)', 'Release', 'runtimes', '$(TargetOS)-arm', 'native')) + - - - + + - + + @@ -95,9 +83,10 @@ $(MicrosoftNetCoreAppRuntimePackNativeDir) - - $(RequireWasmReferences) + + $(RequireTargetArchRuntimePack) + $(WasmRuntimePackDir) @@ -105,6 +94,13 @@ $(WasmRuntimePackNativeDir) + + $(ArmRuntimePackDir) + + + $(ArmRuntimePackNativeDir) + + $(CoreCLRConfiguration) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index c0fc1616e29bc5..1223c2f30db233 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -232,11 +232,6 @@ static void Validate(ReadyToRunReader reader) } } - // Note: The arm tests compile for an arm32 target, but they are compiled with the host Corelib. If there are - // load-bearing dependencies in the arm32 corelib (that aren't present in the host corelib) that crossgen2 requires, - // these tests may fail. Wasm tests are similar, but since it doesn't require a special cross-build setup, they use - // a wasm corelib in CI. arm32 tests could be similarly set up, but it hasn't been done yet. - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.ArmOnHostOSSupported))] public void ArmThumbBitRelocationTargets() { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs index c598f917dc0787..24341440e31f35 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs @@ -18,6 +18,13 @@ internal enum Crossgen2AssemblyOption CrossModuleOptimization, } +internal enum CoreLibTargetArchitecture +{ + Host, + Arm32, + Wasm +} + internal enum Crossgen2InputKind { InputAssembly, diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs index 816801c96cdc6c..476ac89c09ce6e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestCaseCompiler.cs @@ -72,7 +72,7 @@ public string CompileAssembly( syntaxTrees, references, new CSharpCompilationOptions(outputKind) - .WithOptimizationLevel(OptimizationLevel.Debug) + .WithOptimizationLevel(OptimizationLevel.Release) .WithAllowUnsafe(true) .WithNullableContextOptions(NullableContextOptions.Enable)); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs index e8c1704bd3fd58..ce88a5bff97241 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs @@ -214,17 +214,23 @@ public void Run(R2RTestCase testCase) // Step 2: Run each crossgen2 compilation and validate var driver = new R2RDriver(_output, _paths); - List hostRefPaths = BuildReferencePaths(useWasmReferences: false); + List hostRefPaths = BuildReferencePaths(CoreLibTargetArchitecture.Host); List? wasmRefPaths = null; + List? armRefPaths = null; foreach(var compilation in testCase.Compilations) { List refPaths = hostRefPaths; if (compilation.TargetRid == TargetRid.BrowserWasm) { - wasmRefPaths ??= BuildReferencePaths(useWasmReferences: true); + wasmRefPaths ??= BuildReferencePaths(CoreLibTargetArchitecture.Wasm); refPaths = wasmRefPaths; } + else if (compilation.TargetRid == TargetRid.HostArm) + { + armRefPaths ??= BuildReferencePaths(CoreLibTargetArchitecture.Arm32); + refPaths = armRefPaths; + } string outputPath = RunCrossgenCompilation( testCase.Name, compilation, driver, compilation.FilePath, refPaths, assemblyPaths); @@ -254,7 +260,7 @@ private Dictionary CompileAllAssemblies( var paths = new Dictionary(); // Tests shouldn't require a platform-specific runtime/ref pack for Roslyn compilation - var defaultReferences = BuildReferencePaths(useWasmReferences: false); + var defaultReferences = BuildReferencePaths(CoreLibTargetArchitecture.Host); var compiler = new R2RTestCaseCompiler(defaultReferences); foreach (var asm in assemblies) { @@ -365,47 +371,43 @@ private static void AddRefArgs(List args, List refPaths) /// /// Runtime packs are used as references for both Roslyn compilation and crossgen2 compilation. This method builds the reference paths for both. /// - /// Whether to try to use browser-wasm references. The host references are used as a fallback if browser-wasm references are not available. - private List BuildReferencePaths(bool useWasmReferences) + private List BuildReferencePaths(CoreLibTargetArchitecture coreLibArch) { List paths; - - if (useWasmReferences) + (string runtimePackDir, string runtimePackNativeDir) = coreLibArch switch { - string? wasmRuntimePackDir = _paths.WasmRuntimePackDir; - string? wasmCoreLibPath = _paths.WasmRuntimePackNativeDir is null - ? null - : Path.Combine(_paths.WasmRuntimePackNativeDir, "System.Private.CoreLib.dll"); - - if (wasmRuntimePackDir is not null && wasmCoreLibPath is not null - && File.Exists(wasmCoreLibPath) && Directory.Exists(wasmRuntimePackDir)) + CoreLibTargetArchitecture.Host => (_paths.RuntimePackDir, _paths.RuntimePackNativeDir), + CoreLibTargetArchitecture.Arm32 => (_paths.ArmRuntimePackDir, _paths.ArmRuntimePackNativeDir), + CoreLibTargetArchitecture.Wasm => (_paths.WasmRuntimePackDir, _paths.WasmRuntimePackNativeDir), + _ => throw new InvalidOperationException($"Unknown CoreLibTargetArchitecture: {coreLibArch}") + }; + if (!RuntimePackPathsAreValid(runtimePackDir, runtimePackNativeDir)) + { + if (_paths.RequireTargetArchRuntimePack) { - _output.WriteLine($"Using browser-wasm runtime pack references from '{wasmRuntimePackDir}'"); - paths = - [ - ..Directory.GetFiles(wasmRuntimePackDir, "*.dll"), - wasmCoreLibPath - ]; - return paths; + throw new InvalidOperationException($"Required runtime pack for {coreLibArch} not found: {runtimePackDir} or {runtimePackNativeDir}"); } else { - Assert.False( - _paths.RequireWasmReferences, - "Browser-wasm framework references are required, but the browser-wasm runtime pack was not found."); - _output.WriteLine($"Browser-wasm runtime pack references not available. Falling back to host references."); + _output.WriteLine($"Warning: Required runtime pack for {coreLibArch} not found: {runtimePackDir} or {runtimePackNativeDir}. Falling back to host references."); + (runtimePackDir, runtimePackNativeDir) = (_paths.RuntimePackDir, _paths.RuntimePackNativeDir); } } - string coreLibPath = Path.Combine(_paths.RuntimePackNativeDir, "System.Private.CoreLib.dll"); + string coreLibPath = Path.Combine(runtimePackNativeDir, "System.Private.CoreLib.dll"); Assert.True(File.Exists(coreLibPath), $"System.Private.CoreLib.dll not found: {coreLibPath}"); - Assert.True(Directory.Exists(_paths.RuntimePackDir), $"Runtime pack directory not found: {_paths.RuntimePackDir}"); + Assert.True(Directory.Exists(runtimePackDir), $"Runtime pack directory not found: {runtimePackDir}"); paths = [ - ..Directory.GetFiles(_paths.RuntimePackDir, "*.dll"), + ..Directory.GetFiles(runtimePackDir, "*.dll"), coreLibPath ]; return paths; + + bool RuntimePackPathsAreValid(string runtimePackDir, string runtimePackNativeDir) + { + return Directory.Exists(runtimePackDir) && File.Exists(Path.Combine(runtimePackNativeDir, "System.Private.CoreLib.dll")); + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs index 71661567fe2daf..f6bff9fe1b2305 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs @@ -28,12 +28,6 @@ private static string GetRequiredConfig(string key) ?? throw new InvalidOperationException($"Missing RuntimeHostConfigurationOption '{key}'. Was the project built with the correct properties?"); } - private static string? GetOptionalConfig(string key) - { - string? value = AppContext.GetData(key) as string; - return string.IsNullOrEmpty(value) ? null : value; - } - /// /// Path to the crossgen2 output directory (contains the self-contained crossgen2 executable and clrjit). /// e.g. artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/ @@ -58,23 +52,23 @@ private static string GetRequiredConfig(string key) public string RuntimePackNativeDir => GetRequiredConfig("R2RTest.RuntimePackNativeDir"); /// - /// Path to the browser-wasm runtime pack managed assemblies directory, or null when the - /// browser-wasm runtime pack has not been built/downloaded (e.g. in the default linux-x64 tools - /// test job). When present, the wasm crossgen2 compilation references these real browser-wasm + /// Path to the browser-wasm runtime pack managed assemblies directory. + /// When present, the wasm crossgen2 compilation references these real browser-wasm /// framework assemblies instead of the host runtime pack. - /// e.g. artifacts/bin/microsoft.netcore.app.runtime.browser-wasm/Release/runtimes/browser-wasm/lib/net11.0/ /// - public string? WasmRuntimePackDir => GetOptionalConfig("R2RTest.WasmRuntimePackDir"); + public string WasmRuntimePackDir => GetRequiredConfig("R2RTest.WasmRuntimePackDir"); /// /// Path to the browser-wasm runtime pack native directory (contains the wasm - /// System.Private.CoreLib.dll), or null when not available. - /// e.g. artifacts/bin/microsoft.netcore.app.runtime.browser-wasm/Release/runtimes/browser-wasm/native/ + /// System.Private.CoreLib.dll). /// - public string? WasmRuntimePackNativeDir => GetOptionalConfig("R2RTest.WasmRuntimePackNativeDir"); + public string WasmRuntimePackNativeDir => GetRequiredConfig("R2RTest.WasmRuntimePackNativeDir"); + + public string ArmRuntimePackDir => GetRequiredConfig("R2RTest.ArmRuntimePackDir"); + public string ArmRuntimePackNativeDir => GetRequiredConfig("R2RTest.ArmRuntimePackNativeDir"); - public bool RequireWasmReferences => string.Equals( - GetOptionalConfig("R2RTest.RequireWasmReferences"), + public bool RequireTargetArchRuntimePack => string.Equals( + GetRequiredConfig("R2RTest.RequireTargetArchRuntimePack"), bool.TrueString, StringComparison.OrdinalIgnoreCase); diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj index 61dcef292c508d..063aa3124cd217 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj @@ -14,8 +14,6 @@ NetCore.SharedFramework true true - - true From 6b3b3d16c2d896c812ddcea229bb7122ce49aa10 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 24 Jul 2026 11:02:22 -0700 Subject: [PATCH 07/17] Revert change to remove static libs from wasm runtime pack, fix test setup, remove alljits from toolstest build --- eng/Subsets.props | 1 - .../TestCases/R2RTestSuites.cs | 8 +------- .../Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj | 2 ++ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/eng/Subsets.props b/eng/Subsets.props index 549b541d80a2c0..60061312f36f7b 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -383,7 +383,6 @@ - <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' != 'wasm' and $(_subset.Contains('+clr.toolstests+'))" Include="ClrAllJitsSubset=true" /> <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' != 'wasm' and ($(_subset.Contains('+clr.tools+')) or $(_subset.Contains('+clr.nativecorelib+')) or $(_subset.Contains('+clr.crossarchtools+')))" Include="ClrJitSubset=true" /> <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' == 'wasm' and ($(_subset.Contains('+clr.tools+')) or $(_subset.Contains('+clr.nativecorelib+')) or $(_subset.Contains('+clr.crossarchtools+')))" Include="ClrWasmJitSubset=true" /> diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index 1223c2f30db233..f18f14749937dd 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -133,13 +133,7 @@ public void WasmSimdModule() new(nameof(WasmSimdModule), [new CrossgenAssembly(wasmSimdModule)]) { OutputFileExtension = ".wasm", - AdditionalArgs = - { - "--targetarch", - "wasm", - "--targetos", - "browser", - }, + TargetRid = TargetRid.BrowserWasm, Validate = Validate, }, ])); diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj index 063aa3124cd217..45a5d877f608a9 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj @@ -14,6 +14,8 @@ NetCore.SharedFramework true true + + true From 6302dd380223824792a3cd1ca05355ac9c40613d Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:41:16 -0700 Subject: [PATCH 08/17] Compile R2R tests for the build's own target, one CI leg per target The R2R test suite previously compiled every shared test case for the host (x64) and only cross-compiled two cases for arm, so a CI leg that built for a non-host target had no assets to run against. Make every test case compile for whatever target the outer build is producing, and run the suite once per target in CI. The test assembly itself always runs on the build machine, so pin it to the host RID and keep the target out of its output path. Each root build instead stages its own crossgen2 and shared-framework libraries into a $(TargetOS)-$(TargetArchitecture) subdirectory of the shared output directory, and publishes the target through runtimeconfig.json for the tests to read. Builds for different targets accumulate side by side rather than clobbering each other, which keeps a future all-targets-in-one-run possible. Collapsing the output directory needs Platform=AnyCPU set before eng/OSArch.props infers it from TargetArchitecture, hence the explicit SDK import. Sharing the output directory in turn means IncrementalClean would treat another target's staged files as orphans and delete them, so give each target its own file-write log via CleanFile. The intermediate directory stays shared so that the runtimeconfig incrementality cache is invalidated on a target switch. Target-incompatible cases are gated with ConditionalFact rather than being silently skipped: wasm cannot run most of them (crossgen2 asserts in the wasm JIT, and R2R output is a wasm module rather than a PE image that ILCompiler.Reflection.ReadyToRun can read back). Coverage improves: 32 shared cases now cross-compile for arm where previously only 2 did. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/pipelines/runtime.yml | 66 +++++-- .../ILCompiler.ReadyToRun.Tests.csproj | 168 ++++++++++-------- .../TestCases/R2RTestSuites.cs | 82 ++++----- .../TestCasesRunner/R2RDriver.cs | 25 +-- .../TestCasesRunner/R2RTestRunner.cs | 82 ++------- .../TestCasesRunner/TestPaths.cs | 102 +++++++---- 6 files changed, 269 insertions(+), 256 deletions(-) diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 5dfa9e699e1aa9..42ebc00c3330b8 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -884,6 +884,10 @@ extends: eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_cdac.containsChange'], true), eq(variables['isRollingBuild'], true)) + # The R2R test suite compiles for one target per run: the test assembly itself always runs on + # the build machine, while crossgen2 and the shared framework it compiles against are staged + # for whatever the build targets. Each leg therefore needs only its own target's artifacts, + # and test cases the target cannot compile are skipped rather than failed. - template: /eng/pipelines/common/platform-matrix.yml parameters: jobTemplate: /eng/pipelines/common/global-build-job.yml @@ -892,12 +896,38 @@ extends: - linux_x64 jobParameters: dependsOn: - - build_browser_wasm_linux_checked_CoreCLR_ReleaseLibraries - - build_linux_arm_checked_CoreCLR_ReleaseLibraries - build_linux_x64_checked_CoreCLR_ReleaseLibraries timeoutInMinutes: 120 - nameSuffix: CLR_R2R_Tests - buildArgs: -s clr.r2rtests -lc Release -c $(_BuildConfig) -test /p:RequireTargetArchRuntimePack=true + nameSuffix: CLR_R2R_Tests_linux_x64 + buildArgs: -s clr.r2rtests -os linux -arch x64 -lc Release -c $(_BuildConfig) -test + enablePublishTestResults: true + testResultsFormat: 'xunit' + preBuildSteps: + - template: /eng/pipelines/common/download-artifact-step.yml + parameters: + artifactName: CoreCLR_ReleaseLibraries_BuildArtifacts_linux_x64_Checked + artifactFileName: CoreCLR_ReleaseLibraries_BuildArtifacts_linux_x64_Checked$(archiveExtension) + unpackFolder: $(Build.SourcesDirectory)/artifacts/bin + cleanUnpackFolder: false + overwriteExistingFiles: true + displayName: Download linux-x64 artifacts + condition: >- + or( + eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_coreclr.containsChange'], true), + eq(variables['isRollingBuild'], true)) + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: checked + platforms: + - linux_x64 + jobParameters: + dependsOn: + - build_browser_wasm_linux_checked_CoreCLR_ReleaseLibraries + timeoutInMinutes: 120 + nameSuffix: CLR_R2R_Tests_browser_wasm + buildArgs: -s clr.r2rtests -os browser -arch wasm -lc Release -c $(_BuildConfig) -test enablePublishTestResults: true testResultsFormat: 'xunit' preBuildSteps: @@ -909,6 +939,26 @@ extends: cleanUnpackFolder: false overwriteExistingFiles: true displayName: Download browser-wasm artifacts + condition: >- + or( + eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_coreclr.containsChange'], true), + eq(variables['isRollingBuild'], true)) + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: checked + platforms: + - linux_x64 + jobParameters: + dependsOn: + - build_linux_arm_checked_CoreCLR_ReleaseLibraries + timeoutInMinutes: 120 + nameSuffix: CLR_R2R_Tests_linux_arm + buildArgs: -s clr.r2rtests -os linux -arch arm -lc Release -c $(_BuildConfig) -test + enablePublishTestResults: true + testResultsFormat: 'xunit' + preBuildSteps: - template: /eng/pipelines/common/download-artifact-step.yml parameters: artifactName: CoreCLR_ReleaseLibraries_BuildArtifacts_linux_arm_Checked @@ -917,14 +967,6 @@ extends: cleanUnpackFolder: false overwriteExistingFiles: true displayName: Download linux-arm artifacts - - template: /eng/pipelines/common/download-artifact-step.yml - parameters: - artifactName: CoreCLR_ReleaseLibraries_BuildArtifacts_linux_x64_Checked - artifactFileName: CoreCLR_ReleaseLibraries_BuildArtifacts_linux_x64_Checked$(archiveExtension) - unpackFolder: $(Build.SourcesDirectory)/artifacts/bin - cleanUnpackFolder: false - overwriteExistingFiles: true - displayName: Download linux-x64 artifacts condition: >- or( eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_coreclr.containsChange'], true), diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index 17373e0c6f8457..2b0ec747e062eb 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -1,4 +1,15 @@ - + + + + + AnyCPU + + + ILCompiler.ReadyToRun.Tests @@ -7,7 +18,21 @@ true true -notrait category=failing - false + + $(NETCoreSdkRuntimeIdentifier) + false + + + + + $(MSBuildProjectFile).$(TargetOS).$(TargetArchitecture).FileListAbsolute.txt @@ -24,89 +49,84 @@ - - Configuration=$(CoreCLRConfiguration);CopyAllJitLibrariesToAotCompilerOutput=true + + + Configuration=$(CoreCLRConfiguration);CopyAllJitLibrariesToAotCompilerOutput=true;TargetArchitecture=$(TargetArchitecture);TargetOS=$(TargetOS) - $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'microsoft.netcore.app.runtime.browser-wasm')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', 'browser-wasm', 'native')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'lib', '$(NetCoreAppCurrent)')) - $([MSBuild]::NormalizeDirectory('$(WasmRuntimePackRoot)', 'Release', 'runtimes', 'browser-wasm', 'native')) - - $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'microsoft.netcore.app.runtime.$(TargetOS)-arm')) - $([MSBuild]::NormalizeDirectory('$(ArmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', '$(TargetOS)-arm', 'lib', '$(NetCoreAppCurrent)')) - $([MSBuild]::NormalizeDirectory('$(ArmRuntimePackRoot)', '$(LibrariesConfiguration)', 'runtimes', '$(TargetOS)-arm', 'native')) - $([MSBuild]::NormalizeDirectory('$(ArmRuntimePackRoot)', 'Release', 'runtimes', '$(TargetOS)-arm', 'lib', '$(NetCoreAppCurrent)')) - $([MSBuild]::NormalizeDirectory('$(ArmRuntimePackRoot)', 'Release', 'runtimes', '$(TargetOS)-arm', 'native')) + + $(TargetOS)-$(TargetArchitecture) - - - - - - - - - - - - - - - - + + + + + + - - @(Crossgen2InbuildOutputItems->WithMetadataValue('Filename', 'crossgen2')->WithMetadataValue('Extension', '$(ExeSuffix)')->'%(RootDir)%(Directory)') - - - $(MicrosoftNetCoreAppRuntimePackRidLibTfmDir) - - - $(MicrosoftNetCoreAppRuntimePackNativeDir) - - - - $(RequireTargetArchRuntimePack) - - - - $(WasmRuntimePackDir) - - - $(WasmRuntimePackNativeDir) - - - - $(ArmRuntimePackDir) - - - $(ArmRuntimePackNativeDir) - - - - - $(CoreCLRConfiguration) - + <_SharedFrameworkAssembly Include="$(LibrariesSharedFrameworkBinArtifactsPath)*.dll" /> + <_SharedFrameworkAssembly Include="$(CoreCLRArtifactsPath)IL/System.Private.CoreLib.dll" /> + + + + + + + + + + + + + + + + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index f18f14749937dd..0065e926628aa9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -29,7 +29,7 @@ public R2RTestSuites(ITestOutputHelper output) _output = output; } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void BasicCrossModuleInlining() { var inlineableLib = new CompiledAssembly @@ -62,7 +62,7 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWasmTarget))] public void WasmWebcilModule() { var wasmWebcilModule = new CompiledAssembly @@ -80,7 +80,6 @@ public void WasmWebcilModule() // Reference the real browser-wasm framework assemblies when they are available // (e.g. the wasm tools-test job downloads them); otherwise the runner falls back // to host references so this test still runs everywhere. - TargetRid = TargetRid.BrowserWasm, Validate = Validate, }, ])); @@ -118,7 +117,7 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWasmTarget))] public void WasmSimdModule() { var wasmSimdModule = new CompiledAssembly @@ -133,7 +132,6 @@ public void WasmSimdModule() new(nameof(WasmSimdModule), [new CrossgenAssembly(wasmSimdModule)]) { OutputFileExtension = ".wasm", - TargetRid = TargetRid.BrowserWasm, Validate = Validate, }, ])); @@ -193,7 +191,7 @@ static WebcilImageReader.WasmFunctionInfo ResolveWasmBody( } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void RuntimeFunctionsSectionSizeExcludesSentinel() { var lib = new CompiledAssembly @@ -226,7 +224,7 @@ static void Validate(ReadyToRunReader reader) } } - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.ArmOnHostOSSupported))] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsArmTarget))] public void ArmThumbBitRelocationTargets() { var inlineableLib = new CompiledAssembly @@ -250,7 +248,6 @@ public void ArmThumbBitRelocationTargets() new CrossgenAssembly(inlineableLib) { Kind = Crossgen2InputKind.Reference }, ]) { - TargetRid = TargetRid.HostArm, Validate = Validate, }, ])); @@ -263,7 +260,7 @@ static void Validate(ReadyToRunReader reader) } // JitStressProcedureSplitting is only available in Debug/Checked JIT builds. - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotReleaseCoreCLR), nameof(TestPaths.ArmOnHostOSSupported))] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotReleaseCoreCLR), nameof(TestPaths.IsArmTarget))] public void ArmThumbBitHotColdRuntimeFunctions() { var hotColdSplitting = new CompiledAssembly @@ -277,7 +274,6 @@ public void ArmThumbBitHotColdRuntimeFunctions() [ new(nameof(ArmThumbBitHotColdRuntimeFunctions), [new CrossgenAssembly(hotColdSplitting)]) { - TargetRid = TargetRid.HostArm, Options = [ Crossgen2Option.Optimize, @@ -299,7 +295,7 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void TransitiveReferences() { var externalLib = new CompiledAssembly() @@ -342,7 +338,7 @@ public void TransitiveReferences() ])); } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void AsyncCrossModuleInlining() { var asyncInlineableLib = new CompiledAssembly @@ -382,7 +378,7 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeBasic() { var compositeLib = new CompiledAssembly @@ -418,7 +414,7 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeManifestAssemblyMvidsAreAligned() { var compositeLib = new CompiledAssembly @@ -454,9 +450,7 @@ static void Validate(ReadyToRunReader reader) } } - public static bool IsWindows => System.OperatingSystem.IsWindows(); - - [ConditionalFact(nameof(IsWindows))] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWindows), nameof(TestPaths.IsNotWasmTarget))] public void CompositeManifestAssemblyMvidsArePaddedWhenPdbPresent() { var compositeLib = new CompiledAssembly @@ -506,7 +500,7 @@ static void Validate(ReadyToRunReader reader) /// boundary. When they landed on an unaligned RVA the runtime faulted on ARM32 (which does not /// permit unaligned multi-word loads) during coreclr_initialize; x64/arm64 tolerated it. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeManifestSectionsAreAligned() { var compositeLib = new CompiledAssembly @@ -548,7 +542,7 @@ static void Validate(ReadyToRunReader reader) /// sections off a 4-byte boundary without the fix. Windows-only because it relies on Windows PDB /// generation. /// - [ConditionalFact(nameof(IsWindows))] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWindows), nameof(TestPaths.IsNotWasmTarget))] public void CompositeManifestSectionsArePaddedWhenPdbPresent() { var compositeLib = new CompiledAssembly @@ -585,7 +579,7 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void RuntimeAsyncMethodEmission() { var runtimeAsyncMethodEmission = new CompiledAssembly @@ -623,7 +617,7 @@ static void Validate(ReadyToRunReader reader) /// It must also strip a non-async Task-returning method whose async variant has already been /// compiled, since the IL is no longer needed at runtime. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void RuntimeAsyncStripILBodiesPreservesTaskReturningIL() { var stripILBodies = new CompiledAssembly @@ -682,7 +676,7 @@ static void Validate(ReadyToRunReader reader) /// produce ContinuationLayout fixups encoding the GC ref map. /// PR #124203: Resumption stubs for methods with suspension points. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void RuntimeAsyncContinuationLayout() { var runtimeAsyncContinuationLayout = new CompiledAssembly @@ -721,7 +715,7 @@ static void Validate(ReadyToRunReader reader) /// PR #125420: [ASYNC] variant generation for devirtualizable async call patterns /// (sealed class and interface dispatch through AsyncAwareVirtualMethodResolutionAlgorithm). /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void RuntimeAsyncDevirtualize() { var runtimeAsyncDevirtualize = new CompiledAssembly @@ -756,7 +750,7 @@ static void Validate(ReadyToRunReader reader) /// PR #124203: Async methods without yield points may omit resumption stubs. /// Validates that no-yield async methods still produce [ASYNC] variants. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void RuntimeAsyncNoYield() { var runtimeAsyncNoYield = new CompiledAssembly @@ -793,7 +787,7 @@ static void Validate(ReadyToRunReader reader) /// --determinism-stress), each compiled method should have exactly one /// ResumptionStubEntryPoint fixup. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void RuntimeAsyncResumptionStubFixupDedup() { var asm = new CompiledAssembly @@ -836,7 +830,7 @@ static void Validate(ReadyToRunReader reader) /// PR #121679: MutableModule async references + cross-module inlining /// of runtime-async methods with cross-module dependency. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void RuntimeAsyncCrossModule() { var asyncDepLib = new CompiledAssembly @@ -895,7 +889,7 @@ static void Validate(ReadyToRunReader reader) /// Validates that inlining info (CrossModuleInlineInfo or InliningInfo2) is /// properly populated (CompositeBasic only validates ManifestRef). /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeCrossModuleInlining() { var inlineableLib = new CompiledAssembly @@ -937,7 +931,7 @@ static void Validate(ReadyToRunReader reader) /// assemblies does NOT produce a CrossModuleInlineInfo section. CrossModuleInlineInfo only records /// inlining where the inlinee module is outside the compiled image's version bubble /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeDoesNotProduceCrossModuleInliningInfo() { var inlineableLib = new CompiledAssembly @@ -984,7 +978,7 @@ static void Validate(ReadyToRunReader reader) /// comes from an assembly outside the version bubble (passed as a Reference with /// --opt-cross-module). /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeProducesCrossModuleInliningInfoForExternalReference() { var inlineableLib = new CompiledAssembly @@ -1036,7 +1030,7 @@ static void Validate(ReadyToRunReader reader) /// Composite mode with runtime-async methods in both assemblies. /// Validates async variants exist in composite output. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeAsync() { var asyncCompositeLib = new CompiledAssembly @@ -1081,7 +1075,7 @@ static void Validate(ReadyToRunReader reader) /// Verifies that, in composite mode, awaitless async candidates ARE inlined into /// their callers. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeAsyncInliningMatrix() { var asyncInlineCandidatesLib = new CompiledAssembly @@ -1136,7 +1130,7 @@ static void Validate(ReadyToRunReader reader) /// https://github.com/dotnet/runtime/pull/126904 added support for ensuring the OwningType signature modifier is emitted /// for these methods. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeAsyncGenericTypes() { var asyncGenericTypeLib = new CompiledAssembly @@ -1201,7 +1195,7 @@ static void Validate(ReadyToRunReader reader) /// captures GC refs across await points. Validates that ContinuationLayout /// fixups correctly reference cross-module types via MutableModule tokens. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void AsyncCrossModuleContinuation() { var asyncDepLibCont = new CompiledAssembly @@ -1256,7 +1250,7 @@ static void Validate(ReadyToRunReader reader) /// Two-step compilation: composite A+B, then non-composite C referencing A. /// Exercises the multi-compilation model. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void MultiStepCompositeAndNonComposite() { var libA = new CompiledAssembly @@ -1321,7 +1315,7 @@ public void MultiStepCompositeAndNonComposite() /// Composite + runtime-async + cross-module devirtualization. /// Interface defined in AsyncInterfaceLib, call sites in CompositeAsyncDevirtMain. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeAsyncDevirtualize() { var asyncInterfaceLib = new CompiledAssembly @@ -1369,7 +1363,7 @@ static void Validate(ReadyToRunReader reader) /// devirtualizes to a sealed receiver. Resolving the callee's async-variant thunk must unwrap it /// to the underlying EcmaMethod. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeAsyncDevirtNonAsyncCallee() { // Compiled WITHOUT runtime-async so the awaited virtuals get synthesized async-variant thunks. @@ -1416,7 +1410,7 @@ static void Validate(ReadyToRunReader reader) /// Composite with 3 assemblies in A→B→C transitive chain. /// Validates manifest refs for all three and transitive inlining. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeTransitive() { var externalLib = new CompiledAssembly @@ -1464,7 +1458,7 @@ static void Validate(ReadyToRunReader reader) /// Non-composite runtime-async + transitive cross-module inlining. /// Chain: AsyncTransitiveMain → AsyncTransitiveLib → AsyncExternalLib. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void AsyncCrossModuleTransitive() { var asyncExternalLib = new CompiledAssembly @@ -1525,7 +1519,7 @@ static void Validate(ReadyToRunReader reader) /// Composite + runtime-async + transitive (3 assemblies). /// Full combination of composite, async, and transitive references. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CompositeAsyncTransitive() { var asyncExternalLib = new CompiledAssembly @@ -1584,7 +1578,7 @@ static void Validate(ReadyToRunReader reader) /// Step 1: Composite of async libs. Step 2: Non-composite consumer /// with cross-module inlining of async methods. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void MultiStepCompositeAndNonCompositeAsync() { var asyncDepLib = new CompiledAssembly @@ -1655,7 +1649,7 @@ public void MultiStepCompositeAndNonCompositeAsync() /// CrossModuleInlineInfo section, exercising the absolute-index encoding /// (not delta-encoded) for cross-module inliner entries. /// - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void CrossModuleGenericMultiInliner() { var crossModuleGenericLib = new CompiledAssembly @@ -1701,7 +1695,7 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void VirtualMethodGenericsNonGVM() { var nonGvmLib = new CompiledAssembly @@ -1746,7 +1740,7 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void VirtualMethodGenericsGVM() { var gvmLib = new CompiledAssembly @@ -1791,7 +1785,7 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsNotWasmTarget))] public void VirtualMethodGenericsGenericLookup() { var genericLookupLib = new CompiledAssembly diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs index 24341440e31f35..1f8a0d718f1577 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs @@ -18,13 +18,6 @@ internal enum Crossgen2AssemblyOption CrossModuleOptimization, } -internal enum CoreLibTargetArchitecture -{ - Host, - Arm32, - Wasm -} - internal enum Crossgen2InputKind { InputAssembly, @@ -88,29 +81,23 @@ internal sealed class R2RDriver { private static readonly TimeSpan ProcessTimeout = TimeSpan.FromMinutes(2); private readonly ITestOutputHelper _output; - private readonly TestPaths _paths; - public R2RDriver(ITestOutputHelper output, TestPaths paths) + public R2RDriver(ITestOutputHelper output) { _output = output; - _paths = paths; - - if (!File.Exists(_paths.Crossgen2Exe)) - throw new FileNotFoundException($"crossgen2 executable not found at {_paths.Crossgen2Exe}"); } /// - /// Runs crossgen2 with the given arguments. + /// Runs the crossgen2 that compiles for this build's target RID with the given arguments. /// public R2RCompilationResult Compile(List args) { - var fullArgs = new List(args); - return RunCrossgen2(fullArgs); + return RunCrossgen2(TestPaths.Crossgen2Exe, new List(args)); } - private R2RCompilationResult RunCrossgen2(List crossgen2Args) + private R2RCompilationResult RunCrossgen2(string crossgen2Exe, List crossgen2Args) { - var psi = new ProcessStartInfo(_paths.Crossgen2Exe, crossgen2Args) + var psi = new ProcessStartInfo(crossgen2Exe, crossgen2Args) { RedirectStandardOutput = true, RedirectStandardError = true, @@ -124,7 +111,7 @@ private R2RCompilationResult RunCrossgen2(List crossgen2Args) psi.Environment[envVar] = null; } - string commandLine = $"{_paths.Crossgen2Exe} {string.Join(" ", crossgen2Args)}"; + string commandLine = $"{crossgen2Exe} {string.Join(" ", crossgen2Args)}"; _output.WriteLine($" crossgen2 command: {commandLine}"); using var process = Process.Start(psi)!; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs index ce88a5bff97241..1eccfeb0938f4c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs @@ -11,13 +11,6 @@ namespace ILCompiler.ReadyToRun.Tests.TestCasesRunner; -enum TargetRid -{ - Host, - HostArm, - BrowserWasm -} - /// /// Describes an assembly compiled by Roslyn as part of a test case. /// @@ -101,8 +94,6 @@ internal sealed class CrossgenCompilation(string name, List as /// public Action? Validate { get; init; } - public TargetRid TargetRid { get; init; } = TargetRid.Host; - public string Name => name; public bool IsComposite => Options.Contains(Crossgen2Option.Composite); @@ -185,12 +176,10 @@ public void SetOutputDir(string outputDir) internal sealed class R2RTestRunner { private readonly ITestOutputHelper _output; - private readonly TestPaths _paths; public R2RTestRunner(ITestOutputHelper output) { _output = output; - _paths = new TestPaths(output); } /// @@ -213,25 +202,11 @@ public void Run(R2RTestCase testCase) var assemblyPaths = CompileAllAssemblies(assembliesToCompile); // Step 2: Run each crossgen2 compilation and validate - var driver = new R2RDriver(_output, _paths); - List hostRefPaths = BuildReferencePaths(CoreLibTargetArchitecture.Host); - List? wasmRefPaths = null; - List? armRefPaths = null; + var driver = new R2RDriver(_output); + List refPaths = BuildReferencePaths(); foreach(var compilation in testCase.Compilations) { - List refPaths = hostRefPaths; - if (compilation.TargetRid == TargetRid.BrowserWasm) - { - wasmRefPaths ??= BuildReferencePaths(CoreLibTargetArchitecture.Wasm); - refPaths = wasmRefPaths; - } - else if (compilation.TargetRid == TargetRid.HostArm) - { - armRefPaths ??= BuildReferencePaths(CoreLibTargetArchitecture.Arm32); - refPaths = armRefPaths; - } - string outputPath = RunCrossgenCompilation( testCase.Name, compilation, driver, compilation.FilePath, refPaths, assemblyPaths); @@ -260,7 +235,7 @@ private Dictionary CompileAllAssemblies( var paths = new Dictionary(); // Tests shouldn't require a platform-specific runtime/ref pack for Roslyn compilation - var defaultReferences = BuildReferencePaths(CoreLibTargetArchitecture.Host); + var defaultReferences = BuildReferencePaths(); var compiler = new R2RTestCaseCompiler(defaultReferences); foreach (var asm in assemblies) { @@ -332,13 +307,7 @@ private static string RunCrossgenCompilation( foreach (var option in compilation.Options) args.Add(option.ToArg()); - args.AddRange(compilation.TargetRid switch - { - TargetRid.Host => [], - TargetRid.HostArm => ["--targetarch", "arm"], - TargetRid.BrowserWasm => ["--targetos", "browser", "--targetarch", "wasm"], - _ => throw new InvalidOperationException($"Unknown target RID: {compilation.TargetRid}") - }); + args.AddRange(["--targetos", TestPaths.TargetOS, "--targetarch", TestPaths.TargetArchitecture]); // Caller-supplied raw args (for options that take values, e.g. --determinism-stress=N) args.AddRange(compilation.AdditionalArgs); @@ -369,45 +338,18 @@ private static void AddRefArgs(List args, List refPaths) } /// - /// Runtime packs are used as references for both Roslyn compilation and crossgen2 compilation. This method builds the reference paths for both. + /// Builds the reference paths used for both Roslyn and crossgen2 compilation, from the + /// shared framework IL assemblies staged for this build's target RID. /// - private List BuildReferencePaths(CoreLibTargetArchitecture coreLibArch) + private static List BuildReferencePaths() { - List paths; - (string runtimePackDir, string runtimePackNativeDir) = coreLibArch switch - { - CoreLibTargetArchitecture.Host => (_paths.RuntimePackDir, _paths.RuntimePackNativeDir), - CoreLibTargetArchitecture.Arm32 => (_paths.ArmRuntimePackDir, _paths.ArmRuntimePackNativeDir), - CoreLibTargetArchitecture.Wasm => (_paths.WasmRuntimePackDir, _paths.WasmRuntimePackNativeDir), - _ => throw new InvalidOperationException($"Unknown CoreLibTargetArchitecture: {coreLibArch}") - }; - if (!RuntimePackPathsAreValid(runtimePackDir, runtimePackNativeDir)) - { - if (_paths.RequireTargetArchRuntimePack) - { - throw new InvalidOperationException($"Required runtime pack for {coreLibArch} not found: {runtimePackDir} or {runtimePackNativeDir}"); - } - else - { - _output.WriteLine($"Warning: Required runtime pack for {coreLibArch} not found: {runtimePackDir} or {runtimePackNativeDir}. Falling back to host references."); - (runtimePackDir, runtimePackNativeDir) = (_paths.RuntimePackDir, _paths.RuntimePackNativeDir); - } - } - - string coreLibPath = Path.Combine(runtimePackNativeDir, "System.Private.CoreLib.dll"); - Assert.True(File.Exists(coreLibPath), $"System.Private.CoreLib.dll not found: {coreLibPath}"); - Assert.True(Directory.Exists(runtimePackDir), $"Runtime pack directory not found: {runtimePackDir}"); + string librariesDir = TestPaths.LibrariesDir; - paths = [ - ..Directory.GetFiles(runtimePackDir, "*.dll"), - coreLibPath - ]; + List paths = [.. Directory.GetFiles(librariesDir, "*.dll")]; + Assert.True( + paths.Exists(p => Path.GetFileName(p) == "System.Private.CoreLib.dll"), + $"System.Private.CoreLib.dll not found in staged references: {librariesDir}"); return paths; - - bool RuntimePackPathsAreValid(string runtimePackDir, string runtimePackNativeDir) - { - return Directory.Exists(runtimePackDir) && File.Exists(Path.Combine(runtimePackNativeDir, "System.Private.CoreLib.dll")); - } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs index f6bff9fe1b2305..a760287f7d8b03 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs @@ -4,8 +4,6 @@ using System; using System.IO; using System.Runtime.InteropServices; -using System.Text.RegularExpressions; -using Xunit.Abstractions; namespace ILCompiler.ReadyToRun.Tests.TestCasesRunner; @@ -13,67 +11,97 @@ namespace ILCompiler.ReadyToRun.Tests.TestCasesRunner; /// Provides paths to build artifacts needed by the test infrastructure. /// All paths come from RuntimeHostConfigurationOption items in the csproj. /// -internal sealed class TestPaths +internal static class TestPaths { - private readonly ITestOutputHelper _output; - - public TestPaths(ITestOutputHelper output) - { - _output = output; - } - private static string GetRequiredConfig(string key) { return AppContext.GetData(key) as string ?? throw new InvalidOperationException($"Missing RuntimeHostConfigurationOption '{key}'. Was the project built with the correct properties?"); } - /// - /// Path to the crossgen2 output directory (contains the self-contained crossgen2 executable and clrjit). - /// e.g. artifacts/bin/coreclr/linux.x64.Checked/x64/crossgen2/ - /// - public string Crossgen2Dir => GetRequiredConfig("R2RTest.Crossgen2Dir"); - private static string Crossgen2ExeName => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "crossgen2.exe" : "crossgen2"; /// - /// Path to the self-contained crossgen2 executable (from crossgen2_inbuild). + /// The OS and architecture this root build targets, e.g. linux/x64 or + /// browser/wasm. Every test case compiles for this target. /// - public string Crossgen2Exe => Path.Combine(Crossgen2Dir, Crossgen2ExeName); + /// + /// The test assembly itself always runs on the build machine; this is what crossgen2 compiles + /// for, which is independent of what the tests run on. + /// + public static string TargetOS => GetRequiredConfig("R2RTest.TargetOS"); + public static string TargetArchitecture => GetRequiredConfig("R2RTest.TargetArchitecture"); + + private static string TargetDescription => $"{TargetOS}-{TargetArchitecture}"; + + public static bool IsWasmTarget => TargetArchitecture == "wasm"; /// - /// Path to the host runtime pack managed assemblies directory. + /// Gates the test cases that are not wasm-specific. crossgen2's wasm backend cannot yet compile + /// most of what these tests exercise, and the R2R images it does produce are wasm modules rather + /// than PE files, so cannot read + /// them back to validate. Test cases are therefore opt-in for wasm rather than opt-out. /// - public string RuntimePackDir => GetRequiredConfig("R2RTest.RuntimePackDir"); + public static bool IsNotWasmTarget => !IsWasmTarget; + + public static bool IsArmTarget => TargetArchitecture is "arm" or "armel"; + + public static bool IsWindows => OperatingSystem.IsWindows(); /// - /// Path to the host runtime pack native directory. + /// Subdirectory of the output directory holding the assets this root build staged. Keyed on OS + /// as well as architecture so that targets sharing an architecture do not collide. + /// Matches the TargetAssetDirName property in the csproj. /// - public string RuntimePackNativeDir => GetRequiredConfig("R2RTest.RuntimePackNativeDir"); + private static string TargetAssetDirName => TargetDescription; /// - /// Path to the browser-wasm runtime pack managed assemblies directory. - /// When present, the wasm crossgen2 compilation references these real browser-wasm - /// framework assemblies instead of the host runtime pack. + /// Path to the crossgen2 that compiles for this build's target. /// - public string WasmRuntimePackDir => GetRequiredConfig("R2RTest.WasmRuntimePackDir"); + /// + /// A root build stages the crossgen2 it built into a subdirectory named after its target, + /// so builds for different targets accumulate rather than clobber each other. + /// Only the subdirectory for this build's own target is ever read: a crossgen2 built for one + /// target does not carry the cross-targeting JIT for any other, so substituting a different + /// one either fails with a DllNotFoundException or silently compiles for the wrong + /// architecture. + /// + public static string Crossgen2Exe + { + get + { + string exe = Path.Combine(AppContext.BaseDirectory, TargetAssetDirName, "crossgen2", Crossgen2ExeName); + if (!File.Exists(exe)) + { + throw new FileNotFoundException( + $"No crossgen2 staged for {TargetDescription} at '{exe}'. Build the clr and libs subsets for " + + $"{TargetDescription}, then rebuild this test project."); + } + + return exe; + } + } /// - /// Path to the browser-wasm runtime pack native directory (contains the wasm - /// System.Private.CoreLib.dll). + /// Directory holding the shared framework IL assemblies crossgen2 compiles against. /// - public string WasmRuntimePackNativeDir => GetRequiredConfig("R2RTest.WasmRuntimePackNativeDir"); - - public string ArmRuntimePackDir => GetRequiredConfig("R2RTest.ArmRuntimePackDir"); - public string ArmRuntimePackNativeDir => GetRequiredConfig("R2RTest.ArmRuntimePackNativeDir"); + public static string LibrariesDir + { + get + { + string dir = Path.Combine(AppContext.BaseDirectory, TargetAssetDirName, "libraries"); + if (!Directory.Exists(dir)) + { + throw new DirectoryNotFoundException( + $"No shared framework assemblies staged for {TargetDescription} at '{dir}'. Build the clr and " + + $"libs subsets for {TargetDescription}, then rebuild this test project."); + } - public bool RequireTargetArchRuntimePack => string.Equals( - GetRequiredConfig("R2RTest.RequireTargetArchRuntimePack"), - bool.TrueString, - StringComparison.OrdinalIgnoreCase); + return dir; + } + } public static string CoreCLRConfiguration => GetRequiredConfig("R2RTest.CoreCLRConfiguration"); public static bool IsReleaseCoreCLR => string.Equals(CoreCLRConfiguration, "Release", StringComparison.OrdinalIgnoreCase); public static bool IsNotReleaseCoreCLR => !IsReleaseCoreCLR; - public static bool ArmOnHostOSSupported => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); } From a40e9a73eaacb7b568577cc5ff7af71e6406c2b4 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:54:30 -0700 Subject: [PATCH 09/17] Give the R2R tests a per-target output directory Follow the layout the sibling AOT tool projects already use instead of collapsing every target into one output directory. crossgen2_inbuild.csproj pins RuntimeIdentifier to the host RID so the tool runs on the build machine while still being written to a per-target path; do the same here, and stage the target's crossgen2 and shared-framework libraries directly beside the test assembly rather than under a target-named subdirectory. This drops the Platform=AnyCPU pin and with it the explicit SDK import that existed only because Platform had to be set before Directory.Build.props was imported, the per-target CleanFile that kept IncrementalClean from deleting another target's staged files out of the shared output directory, and the TargetAssetDirName indirection on both the MSBuild and C# sides. PlatformTarget still has to be AnyCPU: $(Platform) otherwise defaults it to the target architecture, which the SDK rejects as incompatible with the host RID (NETSDK1032) when building for arm. crossgen2.props and the other AOT tool projects all set it for the same reason. A single shared output directory would let one run exercise every target at once, which is worth having, but nothing needs it yet. Restore it when that run actually exists. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ILCompiler.ReadyToRun.Tests.csproj | 55 ++++--------------- .../TestCasesRunner/TestPaths.cs | 20 ++----- 2 files changed, 16 insertions(+), 59 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index 2b0ec747e062eb..33b540d2fcadc4 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -1,15 +1,4 @@ - - - - - AnyCPU - - - + ILCompiler.ReadyToRun.Tests @@ -19,20 +8,13 @@ true -notrait category=failing + output path, the same way crossgen2_inbuild.csproj does. PlatformTarget must be AnyCPU to + go with it: $(Platform) defaults it to the target architecture, which the SDK then rejects + as incompatible with the host RID (NETSDK1032). The sibling AOT tool projects, including + crossgen2.props, all do the same. --> $(NETCoreSdkRuntimeIdentifier) false - - - - - $(MSBuildProjectFile).$(TargetOS).$(TargetArchitecture).FileListAbsolute.txt + AnyCPU @@ -57,19 +39,7 @@ - - - $(TargetOS)-$(TargetArchitecture) - - - + GenerateBuildRuntimeConfigurationFiles gets to run, so values added from a target are + invisible to it and a stale runtimeconfig would be silently reused. --> @@ -127,6 +96,4 @@ - - diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs index a760287f7d8b03..2265d04fbc3df1 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs @@ -48,29 +48,19 @@ private static string GetRequiredConfig(string key) public static bool IsWindows => OperatingSystem.IsWindows(); - /// - /// Subdirectory of the output directory holding the assets this root build staged. Keyed on OS - /// as well as architecture so that targets sharing an architecture do not collide. - /// Matches the TargetAssetDirName property in the csproj. - /// - private static string TargetAssetDirName => TargetDescription; - /// /// Path to the crossgen2 that compiles for this build's target. /// /// - /// A root build stages the crossgen2 it built into a subdirectory named after its target, - /// so builds for different targets accumulate rather than clobber each other. - /// Only the subdirectory for this build's own target is ever read: a crossgen2 built for one - /// target does not carry the cross-targeting JIT for any other, so substituting a different - /// one either fails with a DllNotFoundException or silently compiles for the wrong - /// architecture. + /// A build stages the crossgen2 it built next to the test assembly. A crossgen2 built for one + /// target does not carry the cross-targeting JIT for any other, so substituting a different one + /// either fails with a DllNotFoundException or silently compiles for the wrong architecture. /// public static string Crossgen2Exe { get { - string exe = Path.Combine(AppContext.BaseDirectory, TargetAssetDirName, "crossgen2", Crossgen2ExeName); + string exe = Path.Combine(AppContext.BaseDirectory, "crossgen2", Crossgen2ExeName); if (!File.Exists(exe)) { throw new FileNotFoundException( @@ -89,7 +79,7 @@ public static string LibrariesDir { get { - string dir = Path.Combine(AppContext.BaseDirectory, TargetAssetDirName, "libraries"); + string dir = Path.Combine(AppContext.BaseDirectory, "libraries"); if (!Directory.Exists(dir)) { throw new DirectoryNotFoundException( From cfd0352f9c4269ddd13e1a161dcbd80e0fac2914 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:00:54 -0700 Subject: [PATCH 10/17] Key the R2R test output directory on the target RID Arcade lays projects out under $(PlatformName), which eng/OSArch.props sets to the bare target architecture. That cannot distinguish browser-wasm from wasi-wasm, or linux-x64 from windows-x64, so two such targets built in the same tree would overwrite each other's staged crossgen2 and libraries. src/coreclr/Directory.Build.props already makes this correction for the intermediate directory, where it uses $(TargetOS).$(TargetArchitecture) rather than $(PlatformName). Do the same for the output directory here, using the target RID. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ILCompiler.ReadyToRun.Tests.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index 33b540d2fcadc4..997a07e70e7ed8 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -15,6 +15,11 @@ $(NETCoreSdkRuntimeIdentifier) false AnyCPU + + $(BaseOutputPath)$(TargetRid)\$(Configuration)\ From 72aae643c27c68926877243dd74b7b16444e0b3c Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:15:02 -0700 Subject: [PATCH 11/17] Make r2rtests build for a specific target without requiring host-machine-targeting artifacts --- .../ILCompiler.ReadyToRun.Tests.csproj | 54 ++++--------------- .../TestCases/R2RTestSuites.cs | 7 +-- .../TestCasesRunner/R2RTestRunner.cs | 25 +++++---- .../TestCasesRunner/TestPaths.cs | 2 +- 4 files changed, 26 insertions(+), 62 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index 997a07e70e7ed8..a3fdd76f9b1295 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -1,25 +1,22 @@ + + + ILCompiler.ReadyToRun.Tests $(NetCoreAppToolCurrent) enable - true - true -notrait category=failing - + + AnyCPU $(NETCoreSdkRuntimeIdentifier) false - AnyCPU - - $(BaseOutputPath)$(TargetRid)\$(Configuration)\ + + $(BaseOutputPath)$(Configuration)\$(CoreCLRConfiguration)_CLR\$(TargetRid)\ @@ -27,8 +24,6 @@ - @@ -36,33 +31,17 @@ - Configuration=$(CoreCLRConfiguration);CopyAllJitLibrariesToAotCompilerOutput=true;TargetArchitecture=$(TargetArchitecture);TargetOS=$(TargetOS) - - - - - - - diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index 0065e926628aa9..3af4dc9de1868f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -77,9 +77,6 @@ public void WasmWebcilModule() new(nameof(WasmWebcilModule), [new CrossgenAssembly(wasmWebcilModule)]) { OutputFileExtension = ".wasm", - // Reference the real browser-wasm framework assemblies when they are available - // (e.g. the wasm tools-test job downloads them); otherwise the runner falls back - // to host references so this test still runs everywhere. Validate = Validate, }, ])); @@ -450,7 +447,7 @@ static void Validate(ReadyToRunReader reader) } } - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWindows), nameof(TestPaths.IsNotWasmTarget))] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWindowsTarget))] public void CompositeManifestAssemblyMvidsArePaddedWhenPdbPresent() { var compositeLib = new CompiledAssembly @@ -542,7 +539,7 @@ static void Validate(ReadyToRunReader reader) /// sections off a 4-byte boundary without the fix. Windows-only because it relies on Windows PDB /// generation. /// - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWindows), nameof(TestPaths.IsNotWasmTarget))] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWindowsTarget))] public void CompositeManifestSectionsArePaddedWhenPdbPresent() { var compositeLib = new CompiledAssembly diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs index 1eccfeb0938f4c..6ade70213e235a 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs @@ -208,7 +208,7 @@ public void Run(R2RTestCase testCase) foreach(var compilation in testCase.Compilations) { string outputPath = RunCrossgenCompilation( - testCase.Name, compilation, driver, compilation.FilePath, refPaths, assemblyPaths); + testCase.Name, compilation, driver, compilation.FilePath, assemblyPaths); if (compilation.Validate is not null) { @@ -234,7 +234,6 @@ private Dictionary CompileAllAssemblies( { var paths = new Dictionary(); - // Tests shouldn't require a platform-specific runtime/ref pack for Roslyn compilation var defaultReferences = BuildReferencePaths(); var compiler = new R2RTestCaseCompiler(defaultReferences); foreach (var asm in assemblies) @@ -271,7 +270,6 @@ private static string RunCrossgenCompilation( CrossgenCompilation compilation, R2RDriver driver, string outputFile, - List refPaths, Dictionary assemblyPaths) { var args = new List(); @@ -312,8 +310,8 @@ private static string RunCrossgenCompilation( // Caller-supplied raw args (for options that take values, e.g. --determinism-stress=N) args.AddRange(compilation.AdditionalArgs); - // Global refs (runtime pack + System.Private.CoreLib) - AddRefArgs(args, refPaths); + // Global refs (shared framework + System.Private.CoreLib) + AddRefArgs(args); EnsureDirectoryExists(Path.GetDirectoryName(outputFile)); @@ -328,18 +326,19 @@ private static string RunCrossgenCompilation( return outputFile; } - private static void AddRefArgs(List args, List refPaths) + /// + /// Adds the shared framework references for crossgen2 as a single directory wildcard. + /// + private static void AddRefArgs(List args) { - foreach (string refPath in refPaths) - { - args.Add("-r"); - args.Add(refPath); - } + args.Add("-r"); + args.Add(Path.Combine(TestPaths.LibrariesDir, "*.dll")); } /// - /// Builds the reference paths used for both Roslyn and crossgen2 compilation, from the - /// shared framework IL assemblies staged for this build's target RID. + /// Builds the explicit reference paths used for Roslyn compilation and for resolving assemblies + /// when reading back an R2R image, from the shared framework IL assemblies staged for this + /// build's target RID. crossgen2 takes the same assemblies as a wildcard; see . /// private static List BuildReferencePaths() { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs index 2265d04fbc3df1..0d57a97a99205c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs @@ -46,7 +46,7 @@ private static string GetRequiredConfig(string key) public static bool IsArmTarget => TargetArchitecture is "arm" or "armel"; - public static bool IsWindows => OperatingSystem.IsWindows(); + public static bool IsWindowsTarget => TargetOS is "windows" or "win"; /// /// Path to the crossgen2 that compiles for this build's target. From 443385c4c349b4e1a745437a5ffd12b308dec9f2 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:41:10 -0700 Subject: [PATCH 12/17] Add an ios-arm64 R2R test leg Adds a fourth CLR_R2R_Tests leg that compiles the crossgen2 test suite for ios-arm64, exercising the Apple ARM64 ABI codegen path. The same input produces a different R2R image for --targetos ios (74,752 bytes) than for --targetos linux (74,240 bytes) at the same arch, so this is not redundant with the existing linux-arm leg. The leg runs on a linux_x64 agent with no Apple SDK. Unlike the other three legs it builds its inputs from source rather than downloading a prebuilt artifact, because the iOS CoreCLR jobs run on macOS pools and their cross components are Mach-O. Two details make this work: - clr.crossarchtools targets the build host, not the build target. build-runtime.sh passes __HostOS as build_native's targetOS, so the cross components configure for linux and land in ios.arm64.Checked/x64/. libs.sfx is used rather than libs, since libs.native does need the Apple SDK. - The subsets are split across two build invocations. clr.r2rtests is declared ahead of libs.sfx in Subsets.props, and Build.proj is a Traversal over ProjectToBuild in declaration order, so a single invocation would run the tests before the shared framework exists. Also drops the hardcoded --targetos/--targetarch overrides from the two StripILBodies test cases so they compile for the build's own target, and gates AppleMobileStripILBodiesUsesFixedInstructionSet on ios-arm64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/pipelines/runtime.yml | 28 +++++++++++++++++++ .../TestCases/R2RTestSuites.cs | 4 +-- .../TestCasesRunner/TestPaths.cs | 2 ++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 00e72d68bc4b95..b09f3f7cd45a3a 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -972,6 +972,34 @@ extends: eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_coreclr.containsChange'], true), eq(variables['isRollingBuild'], true)) + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: checked + platforms: + - linux_x64 + jobParameters: + timeoutInMinutes: 120 + nameSuffix: CLR_R2R_Tests_ios_arm64 + # No prebuilt artifact can supply this leg: the iOS jobs run on macOS, so their cross + # components are Mach-O and unusable here. The cross components are built from source + # instead - they target the build host (linux-x64), not iOS, so no Apple SDK is needed. + # libs.sfx rather than libs, because libs.native does require the Apple SDK. + buildArgs: -s clr.crossarchtools+clr.corelib+libs.sfx -os ios -arch arm64 -rf CoreCLR -lc Release -c $(_BuildConfig) + enablePublishTestResults: true + testResultsFormat: 'xunit' + postBuildSteps: + # A separate invocation because clr.r2rtests is ordered ahead of libs.sfx in + # Subsets.props, so a single build would run the tests before their inputs exist. + - template: /eng/pipelines/common/templates/global-build-step.yml + parameters: + buildArgs: -s clr.r2rtests -os ios -arch arm64 -rf CoreCLR -lc Release -c $(_BuildConfig) -test + displayName: Build and run R2R tests + condition: >- + or( + eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_coreclr.containsChange'], true), + eq(variables['isRollingBuild'], true)) + # # Build CrossDacs # diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index c2e13e2fd185e5..2f27ba9bcae801 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -634,7 +634,6 @@ public void RuntimeAsyncStripILBodiesPreservesTaskReturningIL() new(nameof(RuntimeAsyncStripILBodiesPreservesTaskReturningIL), [new CrossgenAssembly(stripILBodies)]) { Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize, Crossgen2Option.StripILBodies], - AdditionalArgs = ["--targetarch:x64"], Validate = Validate, }, ])); @@ -670,7 +669,7 @@ static void Validate(ReadyToRunReader reader) } } - [Fact] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsIosArm64Target))] public void AppleMobileStripILBodiesUsesFixedInstructionSet() { var stripILBodies = new CompiledAssembly @@ -690,7 +689,6 @@ public void AppleMobileStripILBodiesUsesFixedInstructionSet() new(nameof(AppleMobileStripILBodiesUsesFixedInstructionSet), [new CrossgenAssembly(stripILBodies)]) { Options = [Crossgen2Option.Composite, Crossgen2Option.Optimize, Crossgen2Option.StripILBodies], - AdditionalArgs = ["--targetos:ios", "--targetarch:arm64"], Validate = Validate, }, ])); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs index 282b988916c43e..0a2b64327f8e71 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs @@ -47,6 +47,8 @@ private static string GetRequiredConfig(string key) public static bool IsWindowsTarget => TargetOS is "windows" or "win"; + public static bool IsIosArm64Target => TargetOS is "ios" && TargetArchitecture is "arm64"; + /// /// Path to the crossgen2 that compiles for this build's target. /// From b90057a29390d60e1982181e67811c15b9186551 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:22:41 -0700 Subject: [PATCH 13/17] Address review feedback: gate --pdb tests on the host, add a Windows leg The two CompositeManifest*ArePaddedWhenPdbPresent cases were switched from a host-OS gate to a target-OS gate when the suite became per-target. Both constraints are actually required, for different reasons: - crossgen2.props stages Microsoft.DiaSymReader.Native only when crossgen2's own target is Windows, so a non-Windows target never has the library at all. - That library is a Windows binary, so even when staged it can only load on a Windows host. Compiling with --pdb on Linux fails with a DllNotFoundException before writing anything. Gate on both. Gating on the target alone (as before this commit) would let the tests run on a Linux host and fail; gating on the host alone would let them run on a Windows host cross-targeting wasm or iOS, where the library is absent. With every leg running on a Linux host, these two cases were being skipped everywhere, so add a windows-x64 leg to restore the coverage. It also gives the per-target test infrastructure its first Windows run, covering the command-line length limit that the reference-argument wildcard exists for. Also refresh two stale doc comments in TestPaths that still described the pre-per-target artifact probing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/pipelines/runtime.yml | 30 +++++++++++++++++ .../TestCases/R2RTestSuites.cs | 13 +++++--- .../TestCasesRunner/TestPaths.cs | 32 ++++++------------- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index b09f3f7cd45a3a..4e452a6a71aefc 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -1000,6 +1000,36 @@ extends: eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_coreclr.containsChange'], true), eq(variables['isRollingBuild'], true)) + # The only leg with a Windows host, which the two --pdb test cases require: crossgen2 writes + # Windows PDBs through Microsoft.DiaSymReader.Native, which is restored for Windows only. + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: checked + platforms: + - windows_x64 + jobParameters: + dependsOn: + - build_windows_x64_checked_CoreCLR_ReleaseLibraries + timeoutInMinutes: 120 + nameSuffix: CLR_R2R_Tests_windows_x64 + buildArgs: -s clr.r2rtests -os windows -arch x64 -lc Release -c $(_BuildConfig) -test + enablePublishTestResults: true + testResultsFormat: 'xunit' + preBuildSteps: + - template: /eng/pipelines/common/download-artifact-step.yml + parameters: + artifactName: CoreCLR_ReleaseLibraries_BuildArtifacts_windows_x64_Checked + artifactFileName: CoreCLR_ReleaseLibraries_BuildArtifacts_windows_x64_Checked$(archiveExtension) + unpackFolder: $(Build.SourcesDirectory)/artifacts/bin + cleanUnpackFolder: false + overwriteExistingFiles: true + displayName: Download windows-x64 artifacts + condition: >- + or( + eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_coreclr.containsChange'], true), + eq(variables['isRollingBuild'], true)) + # # Build CrossDacs # diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index 2f27ba9bcae801..fea166a7bd9739 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -447,7 +447,11 @@ static void Validate(ReadyToRunReader reader) } } - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWindowsTarget))] + /// + /// Requires a Windows host for --pdb (see ), and a + /// Windows target because the padding being verified is in the composite PE image. + /// + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWindowsHost), nameof(TestPaths.IsWindowsTarget))] public void CompositeManifestAssemblyMvidsArePaddedWhenPdbPresent() { var compositeLib = new CompiledAssembly @@ -536,10 +540,11 @@ static void Validate(ReadyToRunReader reader) /// /// Complements using the same trigger as the /// MVID-table test: --pdb emits an odd-sized debug directory section that shifts the manifest - /// sections off a 4-byte boundary without the fix. Windows-only because it relies on Windows PDB - /// generation. + /// sections off a 4-byte boundary without the fix. Requires a Windows host for --pdb + /// (see ), and a Windows target because the padding being + /// verified is in the composite PE image. /// - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWindowsTarget))] + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWindowsHost), nameof(TestPaths.IsWindowsTarget))] public void CompositeManifestSectionsArePaddedWhenPdbPresent() { var compositeLib = new CompiledAssembly diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs index 0a2b64327f8e71..8df61102255e4f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs @@ -8,8 +8,8 @@ namespace ILCompiler.ReadyToRun.Tests.TestCasesRunner; /// -/// Provides paths to build artifacts needed by the test infrastructure. -/// All paths come from RuntimeHostConfigurationOption items in the csproj. +/// Provides paths to build artifacts needed by the test infrastructure, and the target and host +/// predicates that gate test cases. /// internal static class TestPaths { @@ -21,26 +21,15 @@ private static string GetRequiredConfig(string key) private static string Crossgen2ExeName => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "crossgen2.exe" : "crossgen2"; - /// - /// Path to System.Private.CoreLib.dll. It lives in the runtime pack native/ dir in full builds - /// (placed by externals.csproj BinPlace during libs.pretest), but partial builds that skip - /// libs.pretest only have it in the CoreCLR artifacts directory. - /// public static string SystemPrivateCoreLibPath => Path.Combine(LibrariesDir, "System.Private.CoreLib.dll"); public static string TargetOS => GetRequiredConfig("R2RTest.TargetOS"); public static string TargetArchitecture => GetRequiredConfig("R2RTest.TargetArchitecture"); - private static string TargetDescription => $"{TargetOS}-{TargetArchitecture}"; + private static string TargetRid => $"{TargetOS}-{TargetArchitecture}"; public static bool IsWasmTarget => TargetArchitecture == "wasm"; - /// - /// Gates the test cases that are not wasm-specific. crossgen2's wasm backend cannot yet compile - /// most of what these tests exercise, and the R2R images it does produce are wasm modules rather - /// than PE files, so cannot read - /// them back to validate. Test cases are therefore opt-in for wasm rather than opt-out. - /// public static bool IsNotWasmTarget => !IsWasmTarget; public static bool IsArmTarget => TargetArchitecture is "arm" or "armel"; @@ -49,14 +38,11 @@ private static string GetRequiredConfig(string key) public static bool IsIosArm64Target => TargetOS is "ios" && TargetArchitecture is "arm64"; + public static bool IsWindowsHost => OperatingSystem.IsWindows(); + /// /// Path to the crossgen2 that compiles for this build's target. /// - /// - /// A build stages the crossgen2 it built next to the test assembly. A crossgen2 built for one - /// target does not carry the cross-targeting JIT for any other, so substituting a different one - /// either fails with a DllNotFoundException or silently compiles for the wrong architecture. - /// public static string Crossgen2Exe { get @@ -65,8 +51,8 @@ public static string Crossgen2Exe if (!File.Exists(exe)) { throw new FileNotFoundException( - $"No crossgen2 staged for {TargetDescription} at '{exe}'. Build the clr and libs subsets for " + - $"{TargetDescription}, then rebuild this test project."); + $"No crossgen2 staged for {TargetRid} at '{exe}'. Build the clr and libs subsets for " + + $"{TargetRid}, then rebuild this test project."); } return exe; @@ -84,8 +70,8 @@ public static string LibrariesDir if (!Directory.Exists(dir)) { throw new DirectoryNotFoundException( - $"No shared framework assemblies staged for {TargetDescription} at '{dir}'. Build the clr and " + - $"libs subsets for {TargetDescription}, then rebuild this test project."); + $"No shared framework assemblies staged for {TargetRid} at '{dir}'. Build the clr and " + + $"libs subsets for {TargetRid}, then rebuild this test project."); } return dir; From c586d573decf3fb02bab327441b5f743deb40fd7 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:05:01 -0700 Subject: [PATCH 14/17] Restrict WasmArgumentLayoutTests to wasm targets The class builds a wasm type system context unconditionally, but reads System.Private.CoreLib from whatever the outer build targets. On a non-wasm leg it was therefore computing wasm argument layouts from an ios-arm64 or linux-x64 corelib, which passes without testing anything meaningful. ConditionalClass filters the whole class at discovery, so the 20 theory cases no longer appear at all outside the wasm leg (59 -> 39 cases there) and still run and pass on it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.cs index e617c30d1cdaf3..3ace6b5af8c7eb 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.cs @@ -25,8 +25,12 @@ namespace ILCompiler.ReadyToRun.Tests; /// /// Unit tests for the wasm argument layout crossgen2 computes. These drive the compiler's type -/// system directly, so they need neither a wasm JIT nor a runtime to execute against. +/// system directly, so they need neither a wasm JIT nor a runtime to execute against. They do +/// need a wasm System.Private.CoreLib, though: the type system context they build is always a +/// wasm one, but the corelib it reads comes from whatever the outer build targets, so on any +/// other target they would be computing wasm layouts from the wrong assembly. /// +[ConditionalClass(typeof(TestPaths), nameof(TestPaths.IsWasmTarget))] public class WasmArgumentLayoutTests { private const string Vector128OfT = "Vector128`1"; From af17aab6aec04859e584d04f7b5247bdad745d39 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:01:27 -0700 Subject: [PATCH 15/17] Pass the R2R test target via _osParameter/_archParameter instead of buildArgs The R2R test legs all restated -os/-arch in buildArgs even though global-build-step.yml already passes $(_osParameter)/$(_archParameter) to the build script, so every leg emitted both and relied on last-wins argument parsing. Override those two variables per leg instead. The job-level variables list is expanded after global-build-job.yml defines its defaults, so a later definition wins - the same mechanism global-build-job.yml already uses to redefine _osParameter for linux-bionic. The iOS leg gets both of its build invocations covered by the one override. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/pipelines/runtime.yml | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 6821ca3a6c5eae..4df53112406fc1 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -897,7 +897,7 @@ extends: - build_linux_x64_checked_CoreCLR_ReleaseLibraries timeoutInMinutes: 120 nameSuffix: CLR_R2R_Tests_linux_x64 - buildArgs: -s clr.r2rtests -os linux -arch x64 -lc Release -c $(_BuildConfig) -test + buildArgs: -s clr.r2rtests -lc Release -c $(_BuildConfig) -test enablePublishTestResults: true testResultsFormat: 'xunit' preBuildSteps: @@ -920,12 +920,17 @@ extends: buildConfig: checked platforms: - linux_x64 + variables: + - name: _osParameter + value: -os browser + - name: _archParameter + value: -arch wasm jobParameters: dependsOn: - build_browser_wasm_linux_checked_CoreCLR_ReleaseLibraries timeoutInMinutes: 120 nameSuffix: CLR_R2R_Tests_browser_wasm - buildArgs: -s clr.r2rtests -os browser -arch wasm -lc Release -c $(_BuildConfig) -test + buildArgs: -s clr.r2rtests -lc Release -c $(_BuildConfig) -test enablePublishTestResults: true testResultsFormat: 'xunit' preBuildSteps: @@ -948,12 +953,15 @@ extends: buildConfig: checked platforms: - linux_x64 + variables: + - name: _archParameter + value: -arch arm jobParameters: dependsOn: - build_linux_arm_checked_CoreCLR_ReleaseLibraries timeoutInMinutes: 120 nameSuffix: CLR_R2R_Tests_linux_arm - buildArgs: -s clr.r2rtests -os linux -arch arm -lc Release -c $(_BuildConfig) -test + buildArgs: -s clr.r2rtests -lc Release -c $(_BuildConfig) -test enablePublishTestResults: true testResultsFormat: 'xunit' preBuildSteps: @@ -976,6 +984,11 @@ extends: buildConfig: checked platforms: - linux_x64 + variables: + - name: _osParameter + value: -os ios + - name: _archParameter + value: -arch arm64 jobParameters: timeoutInMinutes: 120 nameSuffix: CLR_R2R_Tests_ios_arm64 @@ -983,7 +996,7 @@ extends: # components are Mach-O and unusable here. The cross components are built from source # instead - they target the build host (linux-x64), not iOS, so no Apple SDK is needed. # libs.sfx rather than libs, because libs.native does require the Apple SDK. - buildArgs: -s clr.crossarchtools+clr.corelib+libs.sfx -os ios -arch arm64 -rf CoreCLR -lc Release -c $(_BuildConfig) + buildArgs: -s clr.crossarchtools+clr.corelib+libs.sfx -rf CoreCLR -lc Release -c $(_BuildConfig) enablePublishTestResults: true testResultsFormat: 'xunit' postBuildSteps: @@ -991,7 +1004,7 @@ extends: # Subsets.props, so a single build would run the tests before their inputs exist. - template: /eng/pipelines/common/templates/global-build-step.yml parameters: - buildArgs: -s clr.r2rtests -os ios -arch arm64 -rf CoreCLR -lc Release -c $(_BuildConfig) -test + buildArgs: -s clr.r2rtests -rf CoreCLR -lc Release -c $(_BuildConfig) -test displayName: Build and run R2R tests condition: >- or( @@ -1011,7 +1024,7 @@ extends: - build_windows_x64_checked_CoreCLR_ReleaseLibraries timeoutInMinutes: 120 nameSuffix: CLR_R2R_Tests_windows_x64 - buildArgs: -s clr.r2rtests -os windows -arch x64 -lc Release -c $(_BuildConfig) -test + buildArgs: -s clr.r2rtests -lc Release -c $(_BuildConfig) -test enablePublishTestResults: true testResultsFormat: 'xunit' preBuildSteps: From c8fafdbc5dd08cc636689e91cf6d88592e73e6b4 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:05:53 -0700 Subject: [PATCH 16/17] Use OutputPath for copying crossgen2_inbuild output items --- .../ILCompiler.ReadyToRun.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index 61f9f40b11f8c0..5206ca6dc93bbe 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -54,7 +54,7 @@ - From a1ce566335e97a56328d7195095a09bedf515d49 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:55:51 -0700 Subject: [PATCH 17/17] Use OutputPath filename for crossgen2 TargetPath --- .../ILCompiler.ReadyToRun.Tests.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj index 5206ca6dc93bbe..1c77956d8bbe6b 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/ILCompiler.ReadyToRun.Tests.csproj @@ -54,9 +54,10 @@ + + TargetPath="crossgen2/$([System.IO.Path]::GetFileName('%(Crossgen2InbuildOutputItems.OutputPath)'))" />