Skip to content

JIT: Avoid TLS access when restoring async contexts#127889

Draft
jakobbotsch wants to merge 3 commits intodotnet:mainfrom
jakobbotsch:async-save-thread-object
Draft

JIT: Avoid TLS access when restoring async contexts#127889
jakobbotsch wants to merge 3 commits intodotnet:mainfrom
jakobbotsch:async-save-thread-object

Conversation

@jakobbotsch
Copy link
Copy Markdown
Member

@jakobbotsch jakobbotsch commented May 6, 2026

Avoid TLS access when restoring async contexts in the synchronous case at the end of runtime async functions by saving the Thread object used when we captured them in a local.

Copilot AI review requested due to automatic review settings May 6, 2026 21:42
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 6, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

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 updates CoreCLR’s async context save/restore plumbing so the synchronous restore path can use a captured Thread instance instead of re-reading Thread.CurrentThreadAssumedInitialized (TLS) at the end of runtime async functions.

Changes:

  • Extend AsyncHelpers.CaptureContexts / AsyncHelpers.RestoreContexts to include the current Thread as an explicit argument for the synchronous restore path.
  • Teach the JIT to allocate/track a new async Thread local (including OSR/patchpoint offset plumbing) and pass it through to the restore helper.
  • Update the interpreter to allocate the extra local and adjust its async helper call setup (but there are currently critical argument-passing issues; see stored PR comments).

Reviewed changes

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

Show a summary per file
File Description
src/coreclr/vm/interpexec.cpp Adjusts interpreter execution of async restore-on-suspend opcode to account for an additional local (currently mis-passing args to the on-suspension helper).
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs Adds Thread to CaptureContexts outputs and RestoreContexts inputs to avoid TLS access on synchronous restore.
src/coreclr/jit/lclvars.cpp Allocates stack home for a new async thread local and excludes it from certain frame offset assignments.
src/coreclr/jit/compiler.h Introduces lvaAsyncThreadObjectVar local tracking in the JIT.
src/coreclr/jit/compiler.cpp Adds OSR patchpoint offset reporting/mapping for the async thread local.
src/coreclr/jit/codegenxarch.cpp Updates preserved-area size accounting/assertions for the new async thread local.
src/coreclr/jit/codegencommon.cpp Updates tracked stack pointer contiguity logic to treat the new async thread local as a special case.
src/coreclr/jit/codegenarmarch.cpp Updates preserved-area size accounting/assertions for the new async thread local.
src/coreclr/jit/codegenarm64.cpp Updates funclet prolog/epilog size capture to account for the new async thread local.
src/coreclr/jit/codegenarm.cpp Updates funclet prolog/epilog size capture to account for the new async thread local.
src/coreclr/jit/async.cpp Creates/passes the new async thread local through capture/restore calls and excludes it from suspension live sets.
src/coreclr/interpreter/compiler.h Tracks a new interpreter local index for the async thread local.
src/coreclr/interpreter/compiler.cpp Allocates the new interpreter local and wires it into async helper calls (currently has critical call-arg construction and on-suspend base-local issues).
src/coreclr/inc/patchpointinfo.h Extends patchpoint metadata to include async thread local FP-relative offset and associated accessors.

Comment on lines +8277 to 8283
int32_t numArgs = 3;
int32_t *callArgs = getAllocator(IMK_CallInfo).allocate<int32_t>(numArgs + 1);
callArgs[0] = execContextAddressVar;
callArgs[1] = syncContextAddressVar;
callArgs[2] = CALL_ARGS_TERMINATOR;
callArgs[1] = threadAddressVar;
callArgs[1] = execContextAddressVar;
callArgs[2] = syncContextAddressVar;
callArgs[3] = CALL_ARGS_TERMINATOR;
m_pLastNewIns->info.pCallInfo->pCallArgs = callArgs;
Comment thread src/coreclr/interpreter/compiler.cpp Outdated
AddIns(INTOP_RESTORE_CONTEXTS_ON_SUSPEND);
m_pLastNewIns->data[0] = suspendDataIndex;
m_pLastNewIns->SetSVars3(varAllocatedContinuation, m_continuationArgIndex, m_execContextVarIndex /* We know the sync context immediately follows */);
m_pLastNewIns->SetSVars3(varAllocatedContinuation, m_continuationArgIndex, m_threadObjVarIndex /* We know the contexts immediately follow */);
Comment on lines +4452 to 4466
OBJECTREF thread = LOCAL_VAR(ip[4], OBJECTREF);
OBJECTREF executionContext = LOCAL_VAR(ip[4] + INTERP_STACK_SLOT_SIZE, OBJECTREF);
OBJECTREF syncContext = LOCAL_VAR(ip[4] + 2 * INTERP_STACK_SLOT_SIZE, OBJECTREF);

InterpAsyncSuspendData *pAsyncSuspendData = (InterpAsyncSuspendData*)pMethod->pDataItems[ip[5]];
MethodDesc *restoreContextsMethod = pAsyncSuspendData->restoreContextsOnSuspensionMethod;

returnOffset = ip[1];
callArgsOffset = pMethod->allocaSize;
// Pass argument to the target method
LOCAL_VAR(callArgsOffset, int32_t) = resumed;
LOCAL_VAR(callArgsOffset + INTERP_STACK_SLOT_SIZE, OBJECTREF) = executionContext;
LOCAL_VAR(callArgsOffset + INTERP_STACK_SLOT_SIZE * 2, OBJECTREF) = syncContext;
LOCAL_VAR(callArgsOffset + INTERP_STACK_SLOT_SIZE * 1, OBJECTREF) = thread;
LOCAL_VAR(callArgsOffset + INTERP_STACK_SLOT_SIZE * 2, OBJECTREF) = executionContext;
LOCAL_VAR(callArgsOffset + INTERP_STACK_SLOT_SIZE * 3, OBJECTREF) = syncContext;
targetMethod = restoreContextsMethod;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants