From fb733206ce905db66c004043afa45ddf5311f0cd Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 26 May 2026 14:09:27 -0700 Subject: [PATCH 1/3] Add basic test for crossgen2 WebCIL --- .../TestCases/R2RTestSuites.cs | 36 +++++++++++++++++++ .../TestCases/Webcil/WasmWebcilModule.cs | 12 +++++++ .../TestCasesRunner/R2RDriver.cs | 6 ---- .../TestCasesRunner/R2RTestRunner.cs | 7 +++- 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmWebcilModule.cs 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 a65791887ce865..348319ca5b76d6 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using ILCompiler.ReadyToRun.Tests.TestCasesRunner; using ILCompiler.Reflection.ReadyToRun; @@ -57,6 +58,41 @@ static void Validate(ReadyToRunReader reader) } } + [Fact] + public void WasmWebcilModule() + { + var wasmWebcilModule = new CompiledAssembly + { + AssemblyName = nameof(WasmWebcilModule), + SourceResourceNames = ["Webcil/WasmWebcilModule.cs"], + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(WasmWebcilModule), + [ + new(nameof(WasmWebcilModule), [new CrossgenAssembly(wasmWebcilModule)]) + { + OutputFileExtension = ".wasm", + AdditionalArgs = + { + "--targetarch", + "wasm", + "--targetos", + "browser", + }, + Validate = Validate, + }, + ])); + + static void Validate(ReadyToRunReader reader) + { + var webcilReader = Assert.IsType(reader.CompositeReader); + Assert.True(webcilReader.IsWasmWrapped); + Assert.True(R2RAssert.GetAllMethods(reader).Exists(method => + method.SignatureString.Contains("AddIntegers", StringComparison.Ordinal))); + } + } + [Fact] public void TransitiveReferences() { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmWebcilModule.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmWebcilModule.cs new file mode 100644 index 00000000000000..209a61a56155ed --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmWebcilModule.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Webcil; + +public static class WasmWebcilModule +{ + public static int AddIntegers(int left, int right) + { + return left + right; + } +} 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 6a28f9118c7478..708c5d35be3be5 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RDriver.cs @@ -30,11 +30,8 @@ internal enum Crossgen2Option { Composite, InputBubble, - ObjectFormat, HotColdSplitting, Optimize, - TargetArch, - TargetOS, } internal static class Crossgen2OptionsExtensions @@ -58,11 +55,8 @@ internal static class Crossgen2OptionsExtensions { Crossgen2Option.Composite => $"--composite", Crossgen2Option.InputBubble => $"--input-bubble", - Crossgen2Option.ObjectFormat => $"--object-format", Crossgen2Option.HotColdSplitting => $"--hot-cold-splitting", Crossgen2Option.Optimize => $"--optimize", - Crossgen2Option.TargetArch => $"--target-arch", - Crossgen2Option.TargetOS => $"--target-os", _ => throw new ArgumentOutOfRangeException(nameof(kind)), }; } 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 980edfe8f75e72..c6a2e26649671e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs @@ -105,9 +105,14 @@ internal sealed class CrossgenCompilation(string name, List as /// to avoid colliding with component stubs that crossgen2 creates alongside the composite image. /// public string FilePath => _outputDir != null - ? Path.Combine(_outputDir, "CG2", Name + (IsComposite ? "-composite" : "") + ".dll") + ? Path.Combine(_outputDir, "CG2", Name + (IsComposite ? "-composite" : "") + OutputFileExtension) : throw new InvalidOperationException("Output directory not set"); + /// + /// File extension for the crossgen2 output image. + /// + public string OutputFileExtension { get; init; } = ".dll"; + public void SetOutputDir(string outputDir) { _outputDir = outputDir; From de1beeccb3c7bac0935f3fbeab3b01bbb9490421 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 26 May 2026 15:29:36 -0700 Subject: [PATCH 2/3] Use a distinct wasm machine type for R2R dumps Webcil images currently report Machine.I386 to the ReadyToRun reflection reader, which makes wasm R2R images reuse x86-specific decoding paths. Introduce a wasm machine sentinel and route pointer-size, import-section, transition-block, GC-info, and r2rdump architecture handling through wasm-specific cases instead. Assert the wasm machine type in the Webcil R2R test added by the lower stacked PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TestCases/R2RTestSuites.cs | 10 +++++-- .../Amd64/GcSlotTable.cs | 2 ++ .../Amd64/GcTransition.cs | 3 ++ .../DebugInfo.cs | 2 ++ .../GCInfoTypes.cs | 28 +++++++++++++++++++ .../ReadyToRunReader.cs | 7 +++++ .../TransitionBlock.cs | 20 +++++++++++++ .../WebcilImageReader.cs | 2 +- src/coreclr/tools/r2rdump/CoreDisTools.cs | 3 ++ src/coreclr/tools/r2rdump/Program.cs | 3 +- 10 files changed, 75 insertions(+), 5 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 348319ca5b76d6..d6a717fc67a30f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using ILCompiler.ReadyToRun.Tests.TestCasesRunner; using ILCompiler.Reflection.ReadyToRun; @@ -79,6 +78,7 @@ public void WasmWebcilModule() "wasm", "--targetos", "browser", + "--compile-no-methods", }, Validate = Validate, }, @@ -86,10 +86,14 @@ public void WasmWebcilModule() static void Validate(ReadyToRunReader reader) { + Assert.Equal(WasmMachine.Wasm32, reader.Machine); var webcilReader = Assert.IsType(reader.CompositeReader); + Assert.Equal(WasmMachine.Wasm32, webcilReader.Machine); Assert.True(webcilReader.IsWasmWrapped); - Assert.True(R2RAssert.GetAllMethods(reader).Exists(method => - method.SignatureString.Contains("AddIntegers", StringComparison.Ordinal))); + + var metadataReader = reader.GetGlobalMetadata().MetadataReader; + Assert.Contains(metadataReader.MethodDefinitions, methodHandle => + metadataReader.GetString(metadataReader.GetMethodDefinition(methodHandle).Name) == "AddIntegers"); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs index bd633fbbc9e406..78e9c762b456ab 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs @@ -74,6 +74,8 @@ private static string GetRegisterName(int registerNumber, Machine machine) case Machine.RiscV64: return ((RiscV64.Registers)registerNumber).ToString(); + case WasmMachine.Wasm32: + throw new NotImplementedException("No implementation for machine type Wasm32."); default: throw new NotImplementedException(machine.ToString()); } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcTransition.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcTransition.cs index 749f7e48f22e2f..69d12790f9d951 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcTransition.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcTransition.cs @@ -75,6 +75,9 @@ public string GetSlotState(GcSlotTable slotTable, Machine machine) regType = typeof(RiscV64.Registers); break; + case WasmMachine.Wasm32: + throw new NotImplementedException($"No implementation for machine type Wasm32."); + default: throw new NotImplementedException(); } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs index d2c0e7af16732f..ba486661064e2a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs @@ -86,6 +86,8 @@ public static string GetPlatformSpecificRegister(Machine machine, int regnum) return ((LoongArch64.Registers)regnum).ToString(); case Machine.RiscV64: return ((RiscV64.Registers)regnum).ToString(); + case WasmMachine.Wasm32: + throw new NotImplementedException($"No implementation for machine type {machine}."); default: throw new NotImplementedException($"No implementation for machine type {machine}."); } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs index 40bb78977e71df..b7985983f807fb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs @@ -149,6 +149,34 @@ internal GcInfoTypes(Machine machine, bool denormalizeCodeOffsets) STACK_BASE_REGISTER_ENCBASE = 2; NUM_REGISTERS_ENCBASE = 3; break; + case WasmMachine.Wasm32: + PSP_SYM_STACK_SLOT_ENCBASE = 6; + GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE = 6; + SECURITY_OBJECT_STACK_SLOT_ENCBASE = 6; + GS_COOKIE_STACK_SLOT_ENCBASE = 6; + CODE_LENGTH_ENCBASE = 6; + STACK_BASE_REGISTER_ENCBASE = 3; + SIZE_OF_STACK_AREA_ENCBASE = 6; + SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE = 3; + REVERSE_PINVOKE_FRAME_ENCBASE = 6; + NUM_REGISTERS_ENCBASE = 3; + NUM_STACK_SLOTS_ENCBASE = 5; + NUM_UNTRACKED_SLOTS_ENCBASE = 5; + NORM_PROLOG_SIZE_ENCBASE = 4; + NORM_EPILOG_SIZE_ENCBASE = 3; + INTERRUPTIBLE_RANGE_DELTA1_ENCBASE = 5; + INTERRUPTIBLE_RANGE_DELTA2_ENCBASE = 5; + REGISTER_ENCBASE = 3; + REGISTER_DELTA_ENCBASE = REGISTER_ENCBASE; + STACK_SLOT_ENCBASE = 6; + STACK_SLOT_DELTA_ENCBASE = 4; + NUM_SAFE_POINTS_ENCBASE = 4; + NUM_INTERRUPTIBLE_RANGES_ENCBASE = 1; + POINTER_SIZE_ENCBASE = 3; + LIVESTATE_RLE_RUN_ENCBASE = 2; + LIVESTATE_RLE_SKIP_ENCBASE = 4; + break; + case Machine.I386: CODE_LENGTH_ENCBASE = 6; NORM_PROLOG_SIZE_ENCBASE = 4; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs index 8939b4f3eb4cf9..eefcd036f899ef 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs @@ -33,6 +33,11 @@ public enum OperatingSystem Unknown = -1 } + public static class WasmMachine + { + public const Machine Wasm32 = (Machine)0xFFFE; + } + public struct InstanceMethod { public byte Bucket; @@ -683,6 +688,7 @@ private unsafe void EnsureHeader() case Machine.Arm: case Machine.Thumb: case Machine.ArmThumb2: + case WasmMachine.Wasm32: _pointerSize = 4; break; @@ -1532,6 +1538,7 @@ private void EnsureImportSectionsImpl() { case Machine.I386: case Machine.ArmThumb2: + case WasmMachine.Wasm32: entrySize = 4; break; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/TransitionBlock.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/TransitionBlock.cs index 85d6bda61dbbf2..043b54c8206dce 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/TransitionBlock.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/TransitionBlock.cs @@ -37,6 +37,9 @@ public static TransitionBlock FromReader(ReadyToRunReader reader) case Machine.RiscV64: return RiscV64TransitionBlock.Instance; + case WasmMachine.Wasm32: + return Wasm32TransitionBlock.Instance; + default: throw new NotImplementedException(); } @@ -100,6 +103,23 @@ public override int OffsetFromGCRefMapPos(int pos) } } + private sealed class Wasm32TransitionBlock : TransitionBlock + { + public static readonly TransitionBlock Instance = new Wasm32TransitionBlock(); + + public override int PointerSize => 4; + public override int NumArgumentRegisters => 0; + public override int NumCalleeSavedRegisters => 0; + // Argument registers, callee-save registers, return address + public override int SizeOfTransitionBlock => 8; + public override int OffsetOfArgumentRegisters => 0; + + public override int OffsetFromGCRefMapPos(int pos) + { + return SizeOfTransitionBlock + pos * PointerSize; + } + } + private sealed class X64WindowsTransitionBlock : TransitionBlock { public static readonly TransitionBlock Instance = new X64WindowsTransitionBlock(); diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/WebcilImageReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/WebcilImageReader.cs index 7ced312c9239a5..6c4aea1392d52d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/WebcilImageReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/WebcilImageReader.cs @@ -28,7 +28,7 @@ public class WebcilImageReader : IBinaryImageReader private readonly CorFlags _corFlags; private readonly DirectoryEntry _managedNativeHeaderDirectory; - public Machine Machine => Machine.I386; // Webcil doesn't encode machine type; wasm targets use a placeholder + public Machine Machine => WasmMachine.Wasm32; // Webcil doesn't encode machine type; wasm targets use a placeholder public OperatingSystem OperatingSystem => OperatingSystem.Unknown; public ulong ImageBase => 0; diff --git a/src/coreclr/tools/r2rdump/CoreDisTools.cs b/src/coreclr/tools/r2rdump/CoreDisTools.cs index 5f3e1ed5780b7c..0dd3195faa0f5a 100644 --- a/src/coreclr/tools/r2rdump/CoreDisTools.cs +++ b/src/coreclr/tools/r2rdump/CoreDisTools.cs @@ -78,6 +78,8 @@ public static IntPtr GetDisasm(Machine machine) case Machine.RiscV64: target = TargetArch.Target_RiscV64; break; + case WasmMachine.Wasm32: + return IntPtr.Zero; default: Program.WriteWarning($"{machine} not supported on CoreDisTools"); return IntPtr.Zero; @@ -187,6 +189,7 @@ private void SetIndentations() // to 7 * 3 characters; see https://github.com/dotnet/llilc/blob/master/lib/CoreDisTools/coredistools.cpp. Machine.I386 => 7 * 3, Machine.Amd64 => 7 * 3, + WasmMachine.Wasm32 => 7 * 3, // Instructions are either 2 or 4 bytes long Machine.ArmThumb2 => 4 * 3, diff --git a/src/coreclr/tools/r2rdump/Program.cs b/src/coreclr/tools/r2rdump/Program.cs index deb9dd6c82ebe6..d2ed2cdb89aca4 100644 --- a/src/coreclr/tools/r2rdump/Program.cs +++ b/src/coreclr/tools/r2rdump/Program.cs @@ -212,6 +212,7 @@ public void Dump(ReadyToRunReader r2r) Machine.Arm64 => TargetArchitecture.ARM64, Machine.LoongArch64 => TargetArchitecture.LoongArch64, Machine.RiscV64 => TargetArchitecture.RiscV64, + WasmMachine.Wasm32 => TargetArchitecture.Wasm32, _ => throw new NotImplementedException(r2r.Machine.ToString()), }; TargetOS os = r2r.OperatingSystem switch @@ -466,7 +467,7 @@ public int Run() // parse the ReadyToRun image ReadyToRunReader r2r = new(model, filename); r2r.ValidateDebugInfo = Get(_command.ValidateDebugInfo); - if (disasm && !(r2r.CompositeReader is WebcilImageReader)) + if (disasm) { disassembler = new Disassembler(r2r, model); } From c7f3e37922024bbce222d2a91d2bb88d42dbe6d3 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 26 May 2026 16:32:26 -0700 Subject: [PATCH 3/3] Run Webcil R2R test with method compilation Now that the wasm JIT artifact is current, the Webcil R2R smoke test can exercise normal method compilation while still asserting the wasm machine type reported by the reader. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs | 1 - 1 file changed, 1 deletion(-) 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 d6a717fc67a30f..1c215c908b2e44 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -78,7 +78,6 @@ public void WasmWebcilModule() "wasm", "--targetos", "browser", - "--compile-no-methods", }, Validate = Validate, },