Skip to content

Fix profiler ReJIT of Runtime Async method bodies - #131681

Open
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/rejit-runtime-async-variant
Open

Fix profiler ReJIT of Runtime Async method bodies#131681
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/rejit-runtime-async-variant

Conversation

@tommcdon

Copy link
Copy Markdown
Member

The methodDef of a Runtime Async method resolves (via Module::LookupMethodDef) to the Task-returning thunk, not the async variant that owns the user IL and actually executes. CodeVersionManager::SetActiveILCodeVersions enumerated and republished native code only for that thunk, so the async variant kept running its original code. As a result, profiler ReJIT of an already-jitted async method silently had no effect: RequestReJITWithInliners returned S_OK, GetReJITParameters/ReJITCompilationStarted were never called for the executing method, and the rewritten IL never ran.

Redirect the resolved MethodDesc to the async variant when it is a thunk, so the variant's native code is republished for the new IL version. This mirrors the existing handling in MethodDesc::ResetCodeEntryPointForEnC. The variant is already created at this point, so the no-create/no-GC lookup is safe in this GC_NOTRIGGER path.

Validated with a profiler that ReJITs an already-jitted async method and rewrites the string it returns: before the fix rejitStarts=0 and the original value is returned; after the fix the ReJIT compiles and the rewritten value is returned. Non-async ReJIT (RequestReJITWithInliners on a regular method) is unaffected and continues to pass.

Fixes #128944

The methodDef of a Runtime Async method resolves (via Module::LookupMethodDef)
to the Task-returning thunk, not the async variant that owns the user IL and
actually executes. CodeVersionManager::SetActiveILCodeVersions enumerated and
republished native code only for that thunk, so the async variant kept running
its original code. As a result, profiler ReJIT of an already-jitted async
method silently had no effect: RequestReJITWithInliners returned S_OK,
GetReJITParameters/ReJITCompilationStarted were never called for the executing
method, and the rewritten IL never ran.

Redirect the resolved MethodDesc to the async variant when it is a thunk, so
the variant's native code is republished for the new IL version. This mirrors
the existing handling in MethodDesc::ResetCodeEntryPointForEnC. The variant is
already created at this point, so the no-create/no-GC lookup is safe in this
GC_NOTRIGGER path.

Validated with a profiler that ReJITs an already-jitted async method and
rewrites the string it returns: before the fix rejitStarts=0 and the original
value is returned; after the fix the ReJIT compiles and the rewritten value is
returned. Non-async ReJIT (RequestReJITWithInliners on a regular method) is
unaffected and continues to pass.

Fixes dotnet#128944

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tommcdon tommcdon added this to the 11.0.0 milestone Jul 31, 2026
@tommcdon tommcdon self-assigned this Jul 31, 2026
Copilot AI review requested due to automatic review settings July 31, 2026 21:59
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates CoreCLR’s code versioning publish path so that when an ILCodeVersion targets a runtime-async method whose mdMethodDef resolves to an async thunk, the runtime redirects to the async variant MethodDesc before enumerating/publishing native code versions. This ensures ReJIT updates apply to the actually-executing async body rather than only the Task-returning thunk.

Changes:

  • In CodeVersionManager::SetActiveILCodeVersions, detect async thunk MethodDescs and redirect to the async variant prior to enumerating closed MethodDescs for publishing.
  • Add inline rationale explaining why the redirect is needed for runtime-async ReJIT scenarios.

Comment thread src/coreclr/vm/codeversion.cpp Outdated
Comment on lines +1633 to +1640
if (pLoadedMethodDesc != NULL && pLoadedMethodDesc->IsAsyncThunkMethod())
{
MethodDesc* pAsyncVariant = pLoadedMethodDesc->GetAsyncVariantNoCreate();
if (pAsyncVariant != NULL)
{
pLoadedMethodDesc = pAsyncVariant;
}
}
Comment thread src/coreclr/vm/codeversion.cpp Outdated
// MethodDesc::ResetCodeEntryPointForEnC. The variant is already created at this point (the
// method has native code being versioned), so the no-create/no-GC lookup is safe in this
// GC_NOTRIGGER path.
if (pLoadedMethodDesc != NULL && pLoadedMethodDesc->IsAsyncThunkMethod())
Comment thread src/coreclr/vm/codeversion.cpp Outdated
@@ -1663,6 +1621,24 @@ HRESULT CodeVersionManager::SetActiveILCodeVersions(ILCodeVersion* pActiveVersio
*pMethodDescs = CDynArray<MethodDesc*>();

MethodDesc* pLoadedMethodDesc = pActiveVersions[i].GetModule()->LookupMethodDef(pActiveVersions[i].GetMethodDef());

// The methodDef of a Runtime Async method resolves to the Task-returning thunk, but the
// async variant is the MethodDesc that owns the user IL and actually executes. Redirect to

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this handle the case where the async v1 IL is used to compile both runtime-async and task-returning native code?

#128384

MethodDesc that owns the user IL

MethodDescs do not really own the user IL

Comment thread src/coreclr/vm/codeversion.cpp Outdated
// async variant is the MethodDesc that owns the user IL and actually executes. Redirect to
// the variant so its native code (not the thunk's) is republished for the new IL version.
// Without this, ReJIT of an already-jitted async method silently has no effect
// (https://github.com/dotnet/runtime/issues/128944). This mirrors the handling in

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We do not need a link to the issue here. The comment explains what this code fairly well.

Republish every loaded Task-returning and runtime-async native variant for an active IL version, preserving variant identity when enumerating generic instantiations and using only NOTHROW lookups. Add profiler coverage that warms both generic async-v1 forms, ReJITs with inliners, rewrites the IL, and verifies both forms execute the new behavior.

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

Copilot-Session: a05f6e13-8be4-43e0-b655-1cdeb687d825
Copilot AI review requested due to automatic review settings August 1, 2026 17:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 8 out of 8 changed files in this pull request and generated 1 comment.

Comment on lines +386 to +388
String functionName = methodId == _runtimeAsyncTargetMethodDef
? RuntimeAsyncTargetMethodName
: GetFunctionIDName(GetFunctionIDFromToken(moduleId, methodId, false));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Is profiler ReJIT of runtime-async method bodies intended to be supported on .NET 11 (preview 4)?

3 participants