Skip to content

Route async resumption stub fixups through precode fixup handling#127523

Open
Copilot wants to merge 6 commits intomainfrom
copilot/implement-discussed-change
Open

Route async resumption stub fixups through precode fixup handling#127523
Copilot wants to merge 6 commits intomainfrom
copilot/implement-discussed-change

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 28, 2026

Async resumption stub fixups were added directly to the compiled method node, bypassing the existing precode fixup path that deduplicates fixups and only commits them after a successful compile.

  • Fixup handling

    • Route ResumptionStubEntryPoint fixups through AddPrecodeFixup.
    • Preserve the existing resumption stub signature generation while reusing the standard deferred fixup flow.
  • Behavioral impact

    • Avoids adding duplicate resumption stub fixups.
    • Prevents fixups discovered during failed compilation paths from being committed directly to the method node.
private void AddResumptionStubFixup(MethodWithGCInfo compiledStubNode)
{
    AddPrecodeFixup(_compilation.SymbolNodeFactory.ResumptionStubEntryPoint(compiledStubNode));
}
  • Tests
    • Added RuntimeAsyncResumptionStubFixupDedup to ILCompiler.ReadyToRun.Tests with a new AsyncMultipleSuspensionPoints source containing async methods that have multiple await Task.Yield() suspension points. The test runs crossgen2 with --determinism-stress=2 (forcing multiple compile attempts per method) and asserts each [ASYNC] variant has exactly one ResumptionStubEntryPoint fixup.
    • Added a HasFixupKindCountOnMethod helper to R2RAssert for asserting an exact fixup count per method (scoped to methods that actually carry that fixup kind, to avoid false-flagging co-named sync thunks).
    • Added an AdditionalArgs plumbing on CrossgenCompilation so tests can pass value-bearing crossgen2 flags (e.g. --determinism-stress=N) not modeled by the existing Crossgen2Option enum.
    • Updated R2RAssert helpers (HasAsyncVariant, HasFixupKindOnMethod, HasFixupKindCountOnMethod) to enforce an exactly-one-match invariant: each helper now fails if more than one method matches the search token, preventing ambiguous substring matches from silently passing. Call sites updated to use precise tokens using . as the type member separator to match R2R signature format (e.g. .MultipleAwaits(, .MultipleAwaitsWithRefs() that uniquely identify a single method.
    • Fixed RuntimeAsyncDevirtualize test to use OpenImpl.GetValueAsync( and SealedImpl.GetValueAsync( as distinct search tokens, since both classes define an async GetValueAsync and the stricter helper would otherwise report an ambiguous match.
    • Note: the new test acts as a property guard for "exactly one resumption stub fixup per async method". It does not fail when the fix is reverted because MethodWithGCInfo.EncodeFixupCells already deduplicates the emitted fixup blob by (TableIndex, ImportOffset), so any in-memory duplicates are squashed at encoding time and are not observable from ReadyToRunReader.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ff3b55f4-0aee-4788-a560-906f45812f1d

Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
@jtschuster
Copy link
Copy Markdown
Member

/azp run runtime-coreclr crossgen2

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@jtschuster
Copy link
Copy Markdown
Member

Failures in crossgen outerloops appear to preexisting.

@jtschuster jtschuster marked this pull request as ready for review April 29, 2026 09:39
@jtschuster jtschuster marked this pull request as draft April 29, 2026 09:39
@jtschuster
Copy link
Copy Markdown
Member

@copilot can you add tests to the ILCompiler.ReadyToRun.Tests project to validate that a method with multiple suspension points only has one ResumptionStub fixup? And make sure the test fails when crossgen doesn't have this change.

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

This PR fixes how async resumption stub fixups are recorded during ReadyToRun compilation by routing them through the existing precode fixup pipeline, rather than adding them directly to the compiled method’s fixup list. This aligns resumption stub fixups with the established deferred + deduplicated fixup commit flow (only committed when compilation successfully publishes code).

Changes:

  • Route ResumptionStubEntryPoint fixups via AddPrecodeFixup instead of directly mutating _methodCodeNode.Fixups.
  • Ensure resumption stub fixups participate in the existing deduplication logic and are only committed during successful publish.

Copilot AI and others added 2 commits April 29, 2026 10:14
…f the kind

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/28262f27-27b1-45af-b60b-c45d3bf5d90c

Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 29, 2026 10:21
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 29, 2026

@copilot can you add tests to the ILCompiler.ReadyToRun.Tests project to validate that a method with multiple suspension points only has one ResumptionStub fixup? And make sure the tes...

Note

AI-generated content by Copilot coding agent — may contain mistakes.

Added in 05cdd60: a new RuntimeAsyncResumptionStubFixupDedup test plus a HasFixupKindCountOnMethod helper. The test compiles a method with multiple await Task.Yield() suspension points and asserts there is exactly one ResumptionStubEntryPoint fixup per [ASYNC] variant.

However, I want to flag that I couldn't get the test to fail when reverting the fix on top of this, even with --determinism-stress=2 (which I plumbed in via a new AdditionalArgs on CrossgenCompilation to force 3 compile attempts per method, including the [ASYNC] variant — confirmed via --verbose "Processing N recompiles" output).

The reason: MethodWithGCInfo.EncodeFixupCells (lines 192–213) sorts and deduplicates the emitted fixup blob by (TableIndex, ImportOffset). Because every getAsyncResumptionStub call returns the same cached Import (same table/offset), any duplicates that the unfixed code adds directly to _methodCodeNode.Fixups across retries get collapsed at encoding time and are not observable from ReadyToRunReader. So the new test doesn't act as a direct catch for the original bug — it's a regression guard for the "exactly one per method" property.

Happy to take a different approach if you'd prefer something that does fail without the fix — possible options I considered:

  1. Drop the new test entirely if a sanity/property test isn't useful.
  2. Add a debug-only counter on MethodWithGCInfo (e.g., a RawFixupAddCount exposed under DEBUG) and assert on that — invasive but would directly catch the bug.
  3. Keep the property test as-is for future regression coverage on the dedup behavior.

Let me know which you'd like.

Copilot AI requested a review from jtschuster April 29, 2026 10:23
@jtschuster jtschuster marked this pull request as ready for review April 29, 2026 14:27
Copilot AI review requested due to automatic review settings April 29, 2026 14:27
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 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs Outdated
… call sites

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/6ff5c01d-ee5a-429c-8f70-1d3c05424c3b

Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 29, 2026 19:07
Copilot AI review requested due to automatic review settings May 1, 2026 12:49
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 5 out of 5 changed files in this pull request and generated 3 comments.

Comment on lines +313 to +344
/// <summary>
/// Validates that ResumptionStubEntryPoint fixups are deduplicated for a method:
/// even with multiple suspension points and forced compilation retries (via
/// --determinism-stress), each compiled method should have exactly one
/// ResumptionStubEntryPoint fixup.
///
/// Without routing the fixup through the precode-fixup path, retried
/// compilations would each add a duplicate fixup directly to the method node,
/// producing more than one ResumptionStubEntryPoint fixup per method.
/// </summary>
[Fact]
public void RuntimeAsyncResumptionStubFixupDedup()
{
var asm = new CompiledAssembly
{
AssemblyName = nameof(RuntimeAsyncResumptionStubFixupDedup),
SourceResourceNames =
[
"RuntimeAsync/AsyncMultipleSuspensionPoints.cs",
"RuntimeAsync/RuntimeAsyncMethodGenerationAttribute.cs",
],
Features = { RuntimeAsyncFeature },
};

new R2RTestRunner(_output).Run(new R2RTestCase(
nameof(RuntimeAsyncResumptionStubFixupDedup),
[
new(nameof(RuntimeAsyncResumptionStubFixupDedup), [new CrossgenAssembly(asm)])
{
// Force each method to be compiled multiple times so that
// any non-deduplicated fixup additions become observable.
AdditionalArgs = { "--determinism-stress=2" },
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

The new test’s doc comment / rationale implies that duplicate ResumptionStubEntryPoint fixups would become observable in the emitted R2R image (and that this test would fail without routing through the precode-fixup path). However, MethodWithGCInfo.GetFixupBlob deduplicates fixup cells by (TableIndex, ImportOffset) before encoding, so in-memory duplicates on the method node won’t show up via ReadyToRunReader fixups. As written, this test is unlikely to regress the specific bug being fixed. Consider rewording the comment (and the inline note about “observable”) to reflect that the test validates the emitted image invariant only, or adjust the test strategy to observe the pre-encoding method-node fixup list / retry behavior more directly (e.g., via an in-proc compilation harness or additional crossgen2 diagnostics).

Copilot uses AI. Check for mistakes.
Comment on lines +2 to +3
// Used to validate that ResumptionStubEntryPoint fixups are deduplicated
// across compilation retries (only one fixup per compiled method).
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

The header comment says this source is used to validate ResumptionStubEntryPoint fixup deduplication across compilation retries. Since the emitted fixup blob is already deduplicated at encoding time (by import cell coordinates), this file isn’t actually able to validate the in-memory duplication issue described in the PR. Consider rewording this comment to avoid implying the emitted R2R fixup list will reflect retry-time duplicates, or add a more direct validation mechanism if the intent is to cover the underlying bug.

Suggested change
// Used to validate that ResumptionStubEntryPoint fixups are deduplicated
// across compilation retries (only one fixup per compiled method).
// Exercises async state machines that may require ResumptionStubEntryPoint
// fixups, without asserting how fixups are deduplicated across retries.

Copilot uses AI. Check for mistakes.
Comment on lines +326 to +330
/// <paramref name="expectedCount"/> fixups of that kind.
/// Fails if no match is found, if more than one method matches the search token, or if
/// the fixup count differs from <paramref name="expectedCount"/>.
/// Use a precise token (e.g. <c>".MethodName("</c>) to avoid unintended substring matches.
/// Useful for ensuring fixups are properly deduplicated.
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

The doc comment suggests this helper is “useful for ensuring fixups are properly deduplicated,” but it’s only validating the emitted fixup-cell list from ReadyToRunReader. Since the fixup blob encoding deduplicates cells before emission (by import cell coordinates), this helper can’t detect duplicate additions to the in-memory method-node fixup list. Consider clarifying the wording so readers don’t assume it validates compilation-time fixup deduplication, or note that it asserts the post-encoding invariant in the final R2R image.

Suggested change
/// <paramref name="expectedCount"/> fixups of that kind.
/// Fails if no match is found, if more than one method matches the search token, or if
/// the fixup count differs from <paramref name="expectedCount"/>.
/// Use a precise token (e.g. <c>".MethodName("</c>) to avoid unintended substring matches.
/// Useful for ensuring fixups are properly deduplicated.
/// <paramref name="expectedCount"/> emitted fixups of that kind.
/// Fails if no match is found, if more than one method matches the search token, or if
/// the emitted fixup count differs from <paramref name="expectedCount"/>.
/// Use a precise token (e.g. <c>".MethodName("</c>) to avoid unintended substring matches.
/// Useful for asserting the post-encoding fixup-cell count in the final R2R image; this
/// validates emitted fixups, not compilation-time deduplication of in-memory fixup lists.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Test comment

Hello

Generated by Code Review for issue #127523 ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-crossgen2-coreclr only use for closed issues

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants