Route async resumption stub fixups through precode fixup handling#127523
Route async resumption stub fixups through precode fixup handling#127523
Conversation
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ff3b55f4-0aee-4788-a560-906f45812f1d Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
|
/azp run runtime-coreclr crossgen2 |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
Failures in crossgen outerloops appear to preexisting. |
|
@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. |
There was a problem hiding this comment.
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
ResumptionStubEntryPointfixups viaAddPrecodeFixupinstead of directly mutating_methodCodeNode.Fixups. - Ensure resumption stub fixups participate in the existing deduplication logic and are only committed during successful publish.
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/28262f27-27b1-45af-b60b-c45d3bf5d90c Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
…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>
Note AI-generated content by Copilot coding agent — may contain mistakes. Added in 05cdd60: a new However, I want to flag that I couldn't get the test to fail when reverting the fix on top of this, even with The reason: Happy to take a different approach if you'd prefer something that does fail without the fix — possible options I considered:
Let me know which you'd like. |
| // Force each method to be compiled multiple times so that | ||
| // any non-deduplicated fixup additions become observable. | ||
| AdditionalArgs = { "--determinism-stress=2" }, |
There was a problem hiding this comment.
The test comment claims compilation retries will make duplicate ResumptionStubEntryPoint fixups observable in the emitted R2R image, but MethodWithGCInfo.GetFixupBlob deduplicates fixup cells by (TableIndex, ImportOffset), and ResumptionStubEntryPoint appears to reuse the same Import cell across retries. This means the assertion may pass even if duplicates were added during retries (and the PR description notes it doesn’t fail when the fix is reverted). Consider adjusting the test to validate something that changes without this fix, or update the comment/intent so it doesn't suggest this is a regression guard when it isn't.
| R2RAssert.HasAsyncVariant(reader, "MultipleAwaits"); | ||
| R2RAssert.HasAsyncVariant(reader, "MultipleAwaitsWithRefs"); | ||
| R2RAssert.HasResumptionStubFixup(reader, "MultipleAwaits"); | ||
| R2RAssert.HasResumptionStubFixup(reader, "MultipleAwaitsWithRefs"); | ||
| R2RAssert.HasFixupKindCountOnMethod(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, "MultipleAwaits", 1); | ||
| R2RAssert.HasFixupKindCountOnMethod(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, "MultipleAwaitsWithRefs", 1); |
There was a problem hiding this comment.
Passing "MultipleAwaits" here will also match "MultipleAwaitsWithRefs" (substring match), so this assertion is not scoped to a single method and can be misleading if additional similarly-named methods are added. Consider using a more specific match token (e.g., including "::MultipleAwaits(" or similar) or enhancing the helper to match method names more precisely to avoid accidental cross-matches.
| R2RAssert.HasAsyncVariant(reader, "MultipleAwaits"); | |
| R2RAssert.HasAsyncVariant(reader, "MultipleAwaitsWithRefs"); | |
| R2RAssert.HasResumptionStubFixup(reader, "MultipleAwaits"); | |
| R2RAssert.HasResumptionStubFixup(reader, "MultipleAwaitsWithRefs"); | |
| R2RAssert.HasFixupKindCountOnMethod(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, "MultipleAwaits", 1); | |
| R2RAssert.HasFixupKindCountOnMethod(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, "MultipleAwaitsWithRefs", 1); | |
| R2RAssert.HasAsyncVariant(reader, "::MultipleAwaits("); | |
| R2RAssert.HasAsyncVariant(reader, "::MultipleAwaitsWithRefs("); | |
| R2RAssert.HasResumptionStubFixup(reader, "::MultipleAwaits("); | |
| R2RAssert.HasResumptionStubFixup(reader, "::MultipleAwaitsWithRefs("); | |
| R2RAssert.HasFixupKindCountOnMethod(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, "::MultipleAwaits(", 1); | |
| R2RAssert.HasFixupKindCountOnMethod(reader, ReadyToRunFixupKind.ResumptionStubEntryPoint, "::MultipleAwaitsWithRefs(", 1); |
There was a problem hiding this comment.
@copilot this is a good point, make the assertion fail if more than one method matches the search name, and fix these calls to match only one method.
There was a problem hiding this comment.
Done in cb97167. Updated HasAsyncVariant, HasFixupKindOnMethod, and HasFixupKindCountOnMethod to fail if more than one method matches the search name. Fixed the calls to use ::MultipleAwaits( and ::MultipleAwaitsWithRefs( as precise tokens. Also updated RuntimeAsyncDevirtualize to use OpenImpl::GetValueAsync( and SealedImpl::GetValueAsync( since that file defines GetValueAsync on both classes and the strict check would otherwise fail.
There was a problem hiding this comment.
@copilot The method search strings should not use :: as the separator. See this error:
Expected [ASYNC] variant for 'OpenImpl::GetValueAsync(' not found. Async methods: [[ASYNC] System.Threading.Tasks.Task`1<int> OpenImpl.GetValueAsync(), [ASYNC] System.Threading.Tasks.Task`1<int> SealedImpl.GetValueAsync(), [ASYNC] System.Threading.Tasks.Task`1<int> AsyncDevirtualize.CallGenericConstrained<__Canon>(__Canon), [ASYNC] System.Threading.Tasks.Task`1<int> AsyncDevirtualize.CallOnNewOpen(), [ASYNC] System.Threading.Tasks.Task`1<int> AsyncDevirtualize.CallOnSealed(SealedImpl)]
Fix the search strings to use . and the delimiter, not ::
There was a problem hiding this comment.
Fixed in 68cb13e. Changed all search tokens to use . as the separator (OpenImpl.GetValueAsync(, SealedImpl.GetValueAsync(, .MultipleAwaits(, .MultipleAwaitsWithRefs() and updated the doc comment examples in the helpers accordingly.
… 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>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/50e62b65-3704-4410-8f4a-6658ce495f75 Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
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
ResumptionStubEntryPointfixups throughAddPrecodeFixup.Behavioral impact
RuntimeAsyncResumptionStubFixupDeduptoILCompiler.ReadyToRun.Testswith a newAsyncMultipleSuspensionPointssource containing async methods that have multipleawait 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 oneResumptionStubEntryPointfixup.HasFixupKindCountOnMethodhelper toR2RAssertfor asserting an exact fixup count per method (scoped to methods that actually carry that fixup kind, to avoid false-flagging co-named sync thunks).AdditionalArgsplumbing onCrossgenCompilationso tests can pass value-bearing crossgen2 flags (e.g.--determinism-stress=N) not modeled by the existingCrossgen2Optionenum.R2RAsserthelpers (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.RuntimeAsyncDevirtualizetest to useOpenImpl.GetValueAsync(andSealedImpl.GetValueAsync(as distinct search tokens, since both classes define an asyncGetValueAsyncand the stricter helper would otherwise report an ambiguous match.MethodWithGCInfo.EncodeFixupCellsalready deduplicates the emitted fixup blob by(TableIndex, ImportOffset), so any in-memory duplicates are squashed at encoding time and are not observable fromReadyToRunReader.