Skip to content

Add ReadyToRun test project and create CrossModuleInliningInfo section in R2R reflection library#126486

Merged
jtschuster merged 33 commits intodotnet:mainfrom
jtschuster:RuntimeAsyncComposite
Apr 17, 2026
Merged

Add ReadyToRun test project and create CrossModuleInliningInfo section in R2R reflection library#126486
jtschuster merged 33 commits intodotnet:mainfrom
jtschuster:RuntimeAsyncComposite

Conversation

@jtschuster
Copy link
Copy Markdown
Member

As part of enabling async thunks to be generated in composite mode, we'll need a number of tests to validate things work as expected. Instead of having to carefully craft coreclr tests and make sure they test what we expect, it would be helpful to have unit tests that can assert things about the output that aren't easily tested in behavioral tests. For example, we can validate that a method was inlined, or certain methods were or weren't compiled.

Create a new ILCompiler.ReadyToRun.Tests.csproj project to more granularly test readytorun outputs. The tests work similarly to ILLink. They compile the source via an in-memory Roslyn compilation, then shells out to crossgen2 to compile the app (since the JIT library cannot be initialized twice in the same process). Then, the ILCompiler.Reflection.ReadyToRun library is used to assert expectations about the final compiled R2R image.

Also add CrossModuleInliningInfoSection to the R2R reflection library and add functionality to InliningInfoSection2 to allow resolving the inliner/inlinee.

Adds a few tests to validate ReadyToRun output related to async codegen and cross-module inlining. Some tests exist that validate async method compilation in composite mode are skipped since that hasn't been implemented yet.

jtschuster and others added 24 commits March 31, 2026 13:25
Add a new test suite at src/tests/readytorun/crossmoduleresolution/ that tests
R2R cross-module reference resolution across different assembly categories:

Assembly graph:
- A (main) - compilation target
- B (version bubble, --inputbubbleref)
- C (cross-module-inlineable only, --opt-cross-module)
- D (external/transitive dependency)
- E (type forwarder to D)

Test scenarios cover:
- TypeRef, MethodCall, FieldAccess across version bubble and cross-module-only
- Transitive dependencies (C → D)
- Nested types, type forwarders, mixed-origin generics
- Interface dispatch with cross-module interfaces

Two test modes via separate .csproj files:
- main_crossmodule: --opt-cross-module:assemblyC (MutableModule #:N resolution)
- main_bubble: --inputbubbleref:assemblyB.dll (MODULE_ZAPSIG encoding)

Also adds CrossModuleResolutionTestPlan.md documenting all 5 planned phases
including composite mode, ALC, and programmatic R2R validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add async Task<T> and async Task variants of each cross-module
inlineable method in assemblyC, with corresponding test methods in
main.cs. Enable runtime-async compilation via Features=runtime-async=on
and --opt-async-methods crossgen2 flag in both test projects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add r2rvalidate tool using ILCompiler.Reflection.ReadyToRun to
programmatically verify R2R compilation artifacts:
- Assembly references (MSIL + manifest metadata AssemblyRef tables)
- CHECK_IL_BODY fixups proving cross-module inlining occurred
- RuntimeFunction counts confirming async thunk generation (3+)

Integrate validator into test precommands for both main_crossmodule
and main_bubble test variants. Validator runs after crossgen2 and
before the actual test execution.

For non-composite images, reads MSIL AssemblyRefs via
GetGlobalMetadata().MetadataReader (not ManifestReferenceAssemblies
which only holds extra manifest-level refs).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace ~160 lines of inline bash/batch in each csproj with a shared
crossmoduleresolution.targets file that generates scripts from MSBuild
items and properties.

Each csproj now declaratively specifies:
- Crossgen2Step items (dependency assemblies with extra args)
- Crossgen2MainRef items (references for main assembly)
- R2RExpect* items (validation expectations)
- Crossgen2CommonArgs/MainExtraArgs properties

The targets file generates:
- A __crossgen2() helper function (bash) / :__cg2_invoke subroutine (batch)
- IL_DLLS copy loop from Crossgen2Step + main assembly names
- Per-step crossgen2 calls via @() item transforms
- Main assembly compilation with extra args and --map
- R2R validation invocation with args from R2RExpect* items

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace inline bash/batch precommands with a C# RunCrossgen tool
invoked at MSBuild build time via corerun. The targets file now uses
an AfterTargets=Build Exec target that runs runcrossgen.dll to:
- Copy IL assemblies before crossgen2 overwrites them
- Run crossgen2 on each dependency in declared order
- Run crossgen2 on the main assembly with refs and flags
- Run r2rvalidate for R2R image validation

This moves all crossgen2 work from test execution time to build time,
making the generated test scripts clean (no precommands).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Set OutputPath to $(CORE_ROOT)/runcrossgen/ so the tool lives alongside
crossgen2 and corerun. The targets file now references it from CORE_ROOT
instead of navigating relative to the test output directory.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Set RuntimeIdentifier=$(NETCoreSdkRuntimeIdentifier) since runcrossgen
runs at build time on the build machine, not at test time on the target.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Roslyn-based test framework for crossgen2 R2R validation:
- Compiles test assemblies with Roslyn at test time
- Invokes crossgen2 out-of-process via dotnet exec
- Validates R2R output using ILCompiler.Reflection.ReadyToRun
- 4 test cases: basic inlining, transitive refs, async, composite
- Integrated into crossgen2.slnx and clr.toolstests subset

Status: crossgen2 invocation works, fixup validation WIP.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use FixupKind enum + ToString(SignatureFormattingOptions) instead of
  ToString() which returned the class name for TodoSignature fixups
- Add System.Private.CoreLib from native/ dir to crossgen2 references
- Use dotnet exec instead of corerun to invoke crossgen2
- Remove opt-async-methods plumbing (unrelated to runtime-async)
- Rename AsyncMethodThunks to AsyncCrossModuleInlining with correct
  expectations (CHECK_IL_BODY fixups, not RuntimeFunction counts)
- Remove unused CoreRun property from TestPaths
- Add KEEP_R2R_TESTS env var to preserve temp dirs for debugging

All 4 tests pass: BasicCrossModuleInlining, TransitiveReferences,
AsyncCrossModuleInlining, CompositeBasic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 5 new tests covering key runtime-async crossgen2 PRs:

- RuntimeAsyncMethodEmission (dotnet#124203): Validates [ASYNC] variant
  entries for Task/ValueTask-returning methods
- RuntimeAsyncContinuationLayout (dotnet#123643): Validates ContinuationLayout
  and ResumptionStubEntryPoint fixups for methods with GC refs across awaits
- RuntimeAsyncDevirtualize (dotnet#125420): Validates async virtual method
  devirtualization produces [ASYNC] entries for sealed/interface dispatch
- RuntimeAsyncNoYield (dotnet#124203): Validates async methods without await
  still produce [ASYNC] variants
- RuntimeAsyncCrossModule (dotnet#121679): Validates MutableModule async
  references work with cross-module inlining of runtime-async methods

Infrastructure changes:
- Add Roslyn feature flag support (runtime-async=on) to R2RTestCaseCompiler
- Add R2RExpectations for async variants, resumption stubs,
  continuation layouts, and arbitrary fixup kinds
- Add MainExtraSourceResourceNames to R2RTestCase for shared source files
- Add null guard for method.Fixups in CheckFixupKinds

All 9 tests pass.

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

- Rename DependencyInfo -> CompiledAssembly with IsCrossgenInput property
- Remove AdditionalReferences: compile dependencies in listed order, each
  referencing all previously compiled assemblies
- Add Crossgen2OptionKind enum and Crossgen2Option record replacing raw
  string CLI args
- Remove unused CrossgenOptions from CompiledAssembly
- Remove dead ReadEmbeddedSource locals in async tests
- Fix Crossgen2Dir fallback to check for crossgen2.dll file instead of
  just directory existence (empty Debug dir was preventing Checked fallback)

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

- R2RTestCase now takes an Action<ReadyToRunReader> Validate callback
  instead of an R2RExpectations data object
- Move CompositeMode, Crossgen2Options, Features to R2RTestCase directly
- Convert R2RResultChecker to R2RAssert static helper class with methods:
  HasManifestRef, HasInlinedMethod, HasAsyncVariant, HasResumptionStub,
  HasContinuationLayout, HasResumptionStubFixup, HasFixupKind
- Delete unused Expectations/R2RExpectationAttributes.cs (attribute-based
  design replaced by inline test code)
- Each test now owns its assertions directly, making them more readable
  and easier to extend with custom checks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix --opt-cross-module arg emission: use space-separated args without
  .dll suffix instead of colon-joined format
- Add -composite suffix to composite output FilePath to prevent component
  stubs from overwriting the composite image
- Add IsComposite property to CrossgenCompilation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
InliningInfoSection2:
- Add InliningEntry struct, GetEntries(), GetInliningPairs()
- Method name resolution via local method map and OpenReferenceAssembly

R2RResultChecker:
- HasInlinedMethod checks both CrossModuleInlineInfo and all per-assembly
  InliningInfo2 sections (needed for composite images)
- Add HasContinuationLayout(reader, methodName) overload
- Add HasResumptionStubFixup(reader, methodName) overload
- Add HasFixupKindOnMethod generic helper
- GetAllInliningInfo2Sections iterates global + all assembly headers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New tests covering the intersection of cross-module inlining,
runtime-async, and composite mode:
- CompositeCrossModuleInlining, CompositeTransitive
- CompositeAsync, CompositeAsyncCrossModuleInlining,
  CompositeAsyncTransitive (skipped: async not in composite yet)
- AsyncCrossModuleContinuation, AsyncCrossModuleTransitive
- MultiStepCompositeConsumer, CompositeAsyncDevirt, RuntimeAsyncNoYield

Fix existing tests: BasicCrossModuleInlining (InlineableLib as
Reference), TransitiveReferences (CrossModuleOptimization on lib).
Use method-targeted HasContinuationLayout/HasResumptionStubFixup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add C# source files for CompositeAsync, MultiStepConsumer,
AsyncCrossModuleContinuation, AsyncTransitiveMain, CompositeAsyncDevirt
test cases and their dependencies.

Add CrossModuleInliningInfoSection parser for ILCompiler.Reflection.

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

In composite mode, crossgen2 skips the DebuggableAttribute-based
optimization detection (Program.cs:588), defaulting to
OptimizationMode.None which sets CORJIT_FLAG_DEBUG_CODE and disables
all inlining. Add Crossgen2Option.Optimize to all composite
compilations.

Add HasInlinedMethod assertion to CompositeCrossModuleInlining test
to verify cross-assembly inlining actually occurs in composite mode.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CrossModuleInliningInfoSection: Fix cross-module inliner index parsing.
  The writer emits absolute ILBody indices (baseIndex is never updated),
  and the runtime reads them as absolute. The parser was incorrectly
  accumulating them as deltas, which would break for inlinees with 2+
  cross-module inliners.

- R2RDriver: Read stdout/stderr concurrently via ReadToEndAsync to avoid
  the classic redirected-stdio deadlock when crossgen2 fills one pipe
  buffer while we block reading the other.

- R2RResultChecker: Load PE images into memory via File.ReadAllBytes
  instead of holding FileStream handles open. IAssemblyMetadata has no
  disposal contract and ReadyToRunReader caches instances indefinitely,
  so stream-backed implementations leak file handles.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a test that exercises the cross-module INLINER parsing path in
CrossModuleInliningInfoSection, where multiple cross-module inliner
entries exist for the same inlinee.

The test uses two generic types (GenericWrapperA<T>, GenericWrapperB<T>)
from a --opt-cross-module reference library, each with a virtual method
that inlines the same Utility.GetValue(). Value type instantiations
(LocalStruct) are required because CrossModuleCompileable discovery uses
CanonicalFormKind.Specific, which preserves value types (unlike reference
types that canonicalize to __Canon, losing alternate location info).

This produces two distinct cross-module inliner MethodDefs for the same
inlinee, validating that the parser correctly reads absolute (not
delta-accumulated) inliner indices.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename HasMultipleCrossModuleInliners to HasCrossModuleInliners and
accept expected inliner method names instead of just a count. This
ensures the absolute-encoded ILBody import indices actually resolve
to the correct methods (GenericWrapperA, GenericWrapperB).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These are local session artifacts that should not be in the repository.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These tests were superseded by the ILCompiler.ReadyToRun.Tests test suite
which compiles and validates R2R images programmatically.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- R2RResultChecker: fix broken see-cref (R2RTestCase.Validate -> CrossgenCompilation.Validate)
- R2RTestRunner: fix comment on BuildReferencePaths (runtime pack only, not compiled assemblies)
- CrossModuleInliningInfoSection: fix version to v6.2 per readytorun.h
- AsyncMethods.cs: describe actual test behavior (cross-module inlining, not RuntimeFunction layout)
- BasicAsyncEmission.cs: remove false claim about resumption stubs
- AsyncDevirtualize.cs: describe actual validation ([ASYNC] variants, not devirtualized calls)
- AsyncCrossModule.cs: describe actual validation (manifest refs + [ASYNC] variants)
- AsyncCrossModuleContinuation.cs: remove false claim about ContinuationLayout fixups
- R2RTestSuites: fix 'and' -> 'or' for inlining info sections, fix A+B -> A ref, clarify devirt test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jtschuster jtschuster added this to the 11.0.0 milestone Apr 2, 2026
@jtschuster jtschuster self-assigned this Apr 2, 2026
jtschuster and others added 2 commits April 3, 2026 16:47
Copilot AI review requested due to automatic review settings April 8, 2026 22:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 42 out of 42 changed files in this pull request and generated 7 comments.

Address review feedback on CrossModuleInlineInfo and InliningInfo2 parsers:

- Fix inliner module default: use inlinee's module index (not 0) when
  InlinerRidHasModule flag is absent, matching writer encoding
- Fix TryGetModuleName: module index 0 means owner module in both
  composite and non-composite images (remove CompositeBuildMode=1 ./src/tests/run.sh --runcrossgen2tests guard)
- Key _localMethodMap by (moduleIndex, rid) tuple to avoid RID collisions
  across modules in composite images
- Refactor InliningInfoSection2.ToString() to use TryGetModuleName
  instead of inline bounds-check with incorrect offset
- Fix version comment: CrossModuleInlineInfo is v6.3, not v6.2
- Add NativeAotSupported condition to test project in Subsets.props
- Exclude TestCases subdirectory sources from compilation (compiled
  in-memory by R2RTestCaseCompiler)
- Add missing 'using System' in RuntimeAsyncMethodGenerationAttribute.cs
- Remove unused usings, rename PascalCase local variable

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The crossgen2 test infrastructure was not passing System.Private.CoreLib
to crossgen2 as a reference, causing all tests to fail with:
  'Failed to load assembly System.Private.CoreLib'

SPCL lives in the runtime pack's native/ directory, not the lib/tfm/
directory where other managed references are found. The existing code
attempted a relative path walk-up to find it but silently skipped when
the directory was missing.

Changes:
- Add R2RTest.RuntimePackNativeDir config option plumbed from MSBuild
  property MicrosoftNetCoreAppRuntimePackNativeDir
- Make TestPaths non-static to accept ITestOutputHelper for logging
- Add Regex-based config fallback (Checked→Release/Debug) with logging
  when the exact config directory is not found
- Thread TestPaths instance through R2RTestRunner, R2RDriver,
  R2RTestCaseCompiler, and SimpleAssemblyResolver
- Assert with clear message if SPCL is not found after all fallbacks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 15, 2026 18:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 42 out of 42 changed files in this pull request and generated 1 comment.

jtschuster and others added 2 commits April 15, 2026 12:25
The R2R tests need System.Private.CoreLib.dll in the runtime pack's
native/ directory, but the CLR_Tools_Tests CI job didn't include
clr.corelib in its build subsets. Without it, libs.sfx never copies
SPCL into the runtime pack, causing all crossgen2 tests to fail with
'System.Private.CoreLib.dll not found'.

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

In partial CI builds (e.g. CLR_Tools_Tests) that include clr.nativecorelib
but skip libs.pretest, the runtime pack native/ layout is not populated.
SPCL is still available in the CoreCLR artifacts directory. Add a fallback
so tests find it in either location.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 15, 2026 23:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 43 out of 43 changed files in this pull request and generated 3 comments.

@jtschuster jtschuster merged commit b145d33 into dotnet:main Apr 17, 2026
181 of 188 checks passed
@github-project-automation github-project-automation Bot moved this to Done in AppModel Apr 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants