Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
74e89c5
Optimize runtime async OSR transitions
jakobbotsch Jan 29, 2026
51a9384
Misalign OSR function stack on entry
jakobbotsch Jan 29, 2026
92bfbf5
Fix
jakobbotsch Jan 29, 2026
e1c9e53
Rename
jakobbotsch Jan 29, 2026
8d48da0
WIP
jakobbotsch Apr 14, 2026
f1d98d1
WIP
jakobbotsch Apr 14, 2026
e3d29e7
WIP
jakobbotsch Apr 14, 2026
2179ae7
WIP
jakobbotsch Apr 14, 2026
448d7cd
Run jit-format
jakobbotsch Apr 14, 2026
10cff17
Fix
jakobbotsch Apr 14, 2026
27db1a7
Fix again
jakobbotsch Apr 14, 2026
05131c3
More hacking
jakobbotsch Apr 14, 2026
480d01e
Build break
jakobbotsch Apr 14, 2026
771df5f
Switch to restoring callee saves instead
jakobbotsch Apr 15, 2026
3474bd1
Clean up
jakobbotsch Apr 15, 2026
60aa1f8
Run jit-format
jakobbotsch Apr 15, 2026
ec1a088
Fix
jakobbotsch Apr 15, 2026
df8a012
Set FP for arm64
jakobbotsch Apr 15, 2026
a1e0380
Remove assert
jakobbotsch Apr 15, 2026
dbc2d70
Fix overriding FP during restore
jakobbotsch Apr 15, 2026
4410ba3
Clean up
jakobbotsch Apr 15, 2026
6dce58b
Implement for LA64/RV64
jakobbotsch Apr 15, 2026
d8bddb7
Clean up
jakobbotsch Apr 15, 2026
b7d705e
AltJit based fixes
jakobbotsch Apr 15, 2026
b18b0fc
Fix arm build
jakobbotsch Apr 15, 2026
20d1190
Feedback
jakobbotsch Apr 15, 2026
1e4df82
Fix build
jakobbotsch Apr 15, 2026
06e9e7f
Remove unnecessary phantom unwind
jakobbotsch Apr 16, 2026
05ffa2d
Partial compilation patchpoints
jakobbotsch Apr 16, 2026
bbde19c
Merge branch 'main' into osr-arm64-restore-from-tier0
jakobbotsch Apr 16, 2026
82ad389
Support frames without fp/lr saved with callee saves
jakobbotsch Apr 17, 2026
6d784e9
Fixes
jakobbotsch Apr 17, 2026
bd302fb
Remove SPMI hack
jakobbotsch Apr 17, 2026
87e6ce5
Feedback
jakobbotsch Apr 17, 2026
bd0cc58
Merge remote-tracking branch 'origin/async-fast-osr-resume' into osr-…
jakobbotsch Apr 17, 2026
776b074
Run jit-format
jakobbotsch Apr 17, 2026
b008162
Fixes after merge, implement support for rest of targets
jakobbotsch Apr 17, 2026
4c13bd5
Fix patchpoint info
jakobbotsch Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/coreclr/inc/patchpointinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ struct PatchpointInfo
m_keptAliveThisOffset = -1;
m_securityCookieOffset = -1;
m_monitorAcquiredOffset = -1;
m_asyncExecutionContextOffset = -1;
m_asyncSynchronizationContextOffset = -1;
}

// Copy
Expand All @@ -58,6 +60,8 @@ struct PatchpointInfo
m_keptAliveThisOffset = original->m_keptAliveThisOffset;
m_securityCookieOffset = original->m_securityCookieOffset;
m_monitorAcquiredOffset = original->m_monitorAcquiredOffset;
m_asyncExecutionContextOffset = original->m_asyncExecutionContextOffset;
m_asyncSynchronizationContextOffset = original->m_asyncSynchronizationContextOffset;

for (uint32_t i = 0; i < original->m_numberOfLocals; i++)
{
Expand Down
148 changes: 73 additions & 75 deletions src/coreclr/jit/async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1201,16 +1201,15 @@ void AsyncTransformation::BuildContinuation(BasicBlock* block,
JITDUMP(" Call has return; continuation will have return value\n");
}

// For OSR, we store the IL offset that inspired the OSR method at the
// beginning of the data (and store -1 in the tier0 version). This must be
// at the beginning because the tier0 and OSR versions need to agree on
// this.
// For OSR, we store the address of the OSR function at the beginning of
// the data (and store 0 in the tier0 version). This must be at the
// beginning because the tier0 and OSR versions need to agree on this.
if (m_compiler->doesMethodHavePatchpoints() || m_compiler->opts.IsOSR())
{
JITDUMP(" Method %s; keeping IL offset that inspired OSR method at the beginning of non-GC data\n",
JITDUMP(" Method %s; keeping OSR address at the beginning of non-GC data\n",
m_compiler->doesMethodHavePatchpoints() ? "has patchpoints" : "is an OSR method");
// Must be pointer sized for compatibility with Continuation methods that access fields
layoutBuilder->SetNeedsOSRILOffset();
layoutBuilder->SetNeedsOSRAddress();
}

if (HasNonContextRestoreExceptionalFlow(block))
Expand Down Expand Up @@ -1252,9 +1251,9 @@ void AsyncTransformation::BuildContinuation(BasicBlock* block,
void ContinuationLayout::Dump(int indent)
{
printf("%*sContinuation layout (%u bytes):\n", indent, "", Size);
if (OSRILOffset != UINT_MAX)
if (OSRAddress != UINT_MAX)
{
printf("%*s +%03u OSR IL offset\n", indent, "", OSRILOffset);
printf("%*s +%03u OSR address\n", indent, "", OSRAddress);
}

if (ExceptionOffset != UINT_MAX)
Expand Down Expand Up @@ -1415,10 +1414,10 @@ ContinuationLayout* ContinuationLayoutBuilder::Create()
return offset;
};

if (m_needsOSRILOffset)
if (m_needsOSRAddress)
{
// Must be pointer sized for compatibility with Continuation methods that access fields
layout->OSRILOffset = allocLayout(TARGET_POINTER_SIZE, TARGET_POINTER_SIZE);
layout->OSRAddress = allocLayout(TARGET_POINTER_SIZE, TARGET_POINTER_SIZE);
}

if (m_needsException)
Expand Down Expand Up @@ -1876,7 +1875,7 @@ GenTreeCall* AsyncTransformation::CreateAllocContinuationCall(bool
//------------------------------------------------------------------------
// AsyncTransformation::FillInDataOnSuspension:
// Create IR that fills the data array of the continuation object with
// live local values, OSR IL offset, continuation context, and execution
// live local values, OSR address, continuation context, and execution
// context.
//
// Parameters:
Expand All @@ -1894,19 +1893,23 @@ void AsyncTransformation::FillInDataOnSuspension(GenTreeCall*
{
if ((saveSet != SaveSet::MutatedLocals) && (m_compiler->doesMethodHavePatchpoints() || m_compiler->opts.IsOSR()))
{
GenTree* ilOffsetToStore;
GenTree* osrAddressToStore;
if (m_compiler->doesMethodHavePatchpoints())
ilOffsetToStore = m_compiler->gtNewIconNode(-1);
{
osrAddressToStore = m_compiler->gtNewIconNode(0, TYP_I_IMPL);
}
else
ilOffsetToStore = m_compiler->gtNewIconNode((int)m_compiler->info.compILEntry);
{
osrAddressToStore = new (m_compiler, GT_FTN_ENTRY) GenTree(GT_FTN_ENTRY, TYP_I_IMPL);
}

// OSR IL offset needs to be at offset 0 because OSR and tier0 methods
// OSR address needs to be at offset 0 because OSR and tier0 methods
// need to agree on that.
assert(layout.OSRILOffset == 0);
GenTree* newContinuation = m_compiler->gtNewLclvNode(GetNewContinuationVar(), TYP_REF);
unsigned offset = OFFSETOF__CORINFO_Continuation__data;
GenTree* storePatchpointOffset = StoreAtOffset(newContinuation, offset, ilOffsetToStore, TYP_INT);
LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_compiler, storePatchpointOffset));
assert(layout.OSRAddress == 0);
GenTree* newContinuation = m_compiler->gtNewLclvNode(GetNewContinuationVar(), TYP_REF);
unsigned offset = OFFSETOF__CORINFO_Continuation__data;
GenTree* storeOSRAddress = StoreAtOffset(newContinuation, offset, osrAddressToStore, TYP_I_IMPL);
LIR::AsRange(suspendBB).InsertAtEnd(LIR::SeqTree(m_compiler, storeOSRAddress));
}

// Fill in data
Expand Down Expand Up @@ -2871,7 +2874,7 @@ ContinuationLayoutBuilder* ContinuationLayoutBuilder::CreateSharedLayout(Compile
for (const AsyncState& state : states)
{
ContinuationLayoutBuilder* layout = state.Layout;
sharedLayout->m_needsOSRILOffset |= layout->m_needsOSRILOffset;
sharedLayout->m_needsOSRAddress |= layout->m_needsOSRAddress;
sharedLayout->m_needsException |= layout->m_needsException;
sharedLayout->m_needsContinuationContext |= layout->m_needsContinuationContext;
sharedLayout->m_needsKeepAlive |= layout->m_needsKeepAlive;
Expand Down Expand Up @@ -3004,55 +3007,50 @@ void AsyncTransformation::CreateResumptionSwitch()
{
JITDUMP(" Method has patch points...\n");
// If we have patchpoints then first check if we need to resume in the OSR version.
BasicBlock* callHelperBB = m_compiler->fgNewBBafter(BBJ_THROW, m_compiler->fgLastBBInMainFunction(), false);
callHelperBB->bbSetRunRarely();
callHelperBB->clearTryIndex();
callHelperBB->clearHndIndex();
BasicBlock* jmpOSR = m_compiler->fgNewBBafter(BBJ_THROW, m_compiler->fgLastBBInMainFunction(), false);
jmpOSR->bbSetRunRarely();
jmpOSR->clearTryIndex();
jmpOSR->clearHndIndex();

JITDUMP(" Created " FMT_BB " for transitions back into OSR method\n", callHelperBB->bbNum);
JITDUMP(" Created " FMT_BB " for transitions back into OSR method\n", jmpOSR->bbNum);

BasicBlock* onContinuationBB = newEntryBB->GetTrueTarget();
BasicBlock* checkILOffsetBB = m_compiler->fgNewBBbefore(BBJ_COND, onContinuationBB, true);
BasicBlock* onContinuationBB = newEntryBB->GetTrueTarget();
BasicBlock* checkOSRAddressBB = m_compiler->fgNewBBbefore(BBJ_COND, onContinuationBB, true);

JITDUMP(" Created " FMT_BB " to check whether we should transition immediately to OSR\n",
checkILOffsetBB->bbNum);
checkOSRAddressBB->bbNum);

// Redirect newEntryBB -> onContinuationBB into newEntryBB -> checkILOffsetBB -> onContinuationBB
m_compiler->fgRedirectEdge(newEntryBB->TrueEdgeRef(), checkILOffsetBB);
// Redirect newEntryBB -> onContinuationBB into newEntryBB -> checkOSRAddressBB -> onContinuationBB
m_compiler->fgRedirectEdge(newEntryBB->TrueEdgeRef(), checkOSRAddressBB);
newEntryBB->GetTrueEdge()->setLikelihood(0);
checkILOffsetBB->inheritWeightPercentage(newEntryBB, 0);
checkOSRAddressBB->inheritWeightPercentage(newEntryBB, 0);

FlowEdge* toOnContinuationBB = m_compiler->fgAddRefPred(onContinuationBB, checkILOffsetBB);
FlowEdge* toCallHelperBB = m_compiler->fgAddRefPred(callHelperBB, checkILOffsetBB);
checkILOffsetBB->SetCond(toCallHelperBB, toOnContinuationBB);
toCallHelperBB->setLikelihood(0);
FlowEdge* toOnContinuationBB = m_compiler->fgAddRefPred(onContinuationBB, checkOSRAddressBB);
FlowEdge* toJmpOSRBB = m_compiler->fgAddRefPred(jmpOSR, checkOSRAddressBB);
checkOSRAddressBB->SetCond(toJmpOSRBB, toOnContinuationBB);
toJmpOSRBB->setLikelihood(0);
toOnContinuationBB->setLikelihood(1);
callHelperBB->inheritWeightPercentage(checkILOffsetBB, 0);

// We need to dispatch to the OSR version if the IL offset is non-negative.
continuationArg = m_compiler->gtNewLclvNode(m_compiler->lvaAsyncContinuationArg, TYP_REF);
unsigned offsetOfIlOffset = OFFSETOF__CORINFO_Continuation__data;
GenTree* ilOffset = LoadFromOffset(continuationArg, offsetOfIlOffset, TYP_INT);
unsigned ilOffsetLclNum = m_compiler->lvaGrabTemp(false DEBUGARG("IL offset for tier0 OSR method"));
m_compiler->lvaGetDesc(ilOffsetLclNum)->lvType = TYP_INT;
GenTree* storeIlOffset = m_compiler->gtNewStoreLclVarNode(ilOffsetLclNum, ilOffset);
LIR::AsRange(checkILOffsetBB).InsertAtEnd(LIR::SeqTree(m_compiler, storeIlOffset));

ilOffset = m_compiler->gtNewLclvNode(ilOffsetLclNum, TYP_INT);
GenTree* zero = m_compiler->gtNewIconNode(0);
GenTree* geZero = m_compiler->gtNewOperNode(GT_GE, TYP_INT, ilOffset, zero);
GenTree* jtrue = m_compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, geZero);
LIR::AsRange(checkILOffsetBB).InsertAtEnd(ilOffset, zero, geZero, jtrue);
jmpOSR->inheritWeightPercentage(checkOSRAddressBB, 0);

ilOffset = m_compiler->gtNewLclvNode(ilOffsetLclNum, TYP_INT);
// We need to dispatch to the OSR version if the OSR address is non-zero.
continuationArg = m_compiler->gtNewLclvNode(m_compiler->lvaAsyncContinuationArg, TYP_REF);
unsigned offsetOfOSRAddress = OFFSETOF__CORINFO_Continuation__data;
GenTree* osrAddress = LoadFromOffset(continuationArg, offsetOfOSRAddress, TYP_I_IMPL);
unsigned osrAddressLclNum = m_compiler->lvaGrabTemp(false DEBUGARG("OSR address for tier0 OSR method"));
m_compiler->lvaGetDesc(osrAddressLclNum)->lvType = TYP_I_IMPL;
GenTree* storeOsrAddress = m_compiler->gtNewStoreLclVarNode(osrAddressLclNum, osrAddress);
LIR::AsRange(checkOSRAddressBB).InsertAtEnd(LIR::SeqTree(m_compiler, storeOsrAddress));

GenTreeCall* callHelper = m_compiler->gtNewHelperCallNode(CORINFO_HELP_PATCHPOINT_FORCED, TYP_VOID, ilOffset);
callHelper->gtCallMoreFlags |= GTF_CALL_M_DOES_NOT_RETURN;
osrAddress = m_compiler->gtNewLclvNode(osrAddressLclNum, TYP_I_IMPL);
GenTree* zero = m_compiler->gtNewIconNode(0, TYP_I_IMPL);
GenTree* neZero = m_compiler->gtNewOperNode(GT_NE, TYP_INT, osrAddress, zero);
GenTree* jtrue = m_compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, neZero);
LIR::AsRange(checkOSRAddressBB).InsertAtEnd(osrAddress, zero, neZero, jtrue);

m_compiler->compCurBB = callHelperBB;
m_compiler->fgMorphTree(callHelper);
osrAddress = m_compiler->gtNewLclvNode(osrAddressLclNum, TYP_I_IMPL);

LIR::AsRange(callHelperBB).InsertAtEnd(LIR::SeqTree(m_compiler, callHelper));
GenTree* jmpOsr = m_compiler->gtNewOperNode(GT_NONLOCAL_JMP, TYP_VOID, osrAddress);
LIR::AsRange(jmpOSR).InsertAtEnd(LIR::SeqTree(m_compiler, jmpOsr));
}
else if (m_compiler->opts.IsOSR())
{
Expand All @@ -3063,31 +3061,31 @@ void AsyncTransformation::CreateResumptionSwitch()
// ignore it, so create a BB that jumps back.
BasicBlock* onContinuationBB = newEntryBB->GetTrueTarget();
BasicBlock* onNoContinuationBB = newEntryBB->GetFalseTarget();
BasicBlock* checkILOffsetBB = m_compiler->fgNewBBbefore(BBJ_COND, onContinuationBB, true);
BasicBlock* checkOSRAddressBB = m_compiler->fgNewBBbefore(BBJ_COND, onContinuationBB, true);

// Switch newEntryBB -> onContinuationBB into newEntryBB -> checkILOffsetBB
m_compiler->fgRedirectEdge(newEntryBB->TrueEdgeRef(), checkILOffsetBB);
// Switch newEntryBB -> onContinuationBB into newEntryBB -> checkOSRAddressBB
m_compiler->fgRedirectEdge(newEntryBB->TrueEdgeRef(), checkOSRAddressBB);
newEntryBB->GetTrueEdge()->setLikelihood(0);
checkILOffsetBB->inheritWeightPercentage(newEntryBB, 0);
checkOSRAddressBB->inheritWeightPercentage(newEntryBB, 0);

// Make checkILOffsetBB ->(true) onNoContinuationBB
// ->(false) onContinuationBB
// Make checkOSRAddressBB ->(true) onNoContinuationBB
// ->(false) onContinuationBB

FlowEdge* toOnContinuationBB = m_compiler->fgAddRefPred(onContinuationBB, checkILOffsetBB);
FlowEdge* toOnNoContinuationBB = m_compiler->fgAddRefPred(onNoContinuationBB, checkILOffsetBB);
checkILOffsetBB->SetCond(toOnNoContinuationBB, toOnContinuationBB);
FlowEdge* toOnContinuationBB = m_compiler->fgAddRefPred(onContinuationBB, checkOSRAddressBB);
FlowEdge* toOnNoContinuationBB = m_compiler->fgAddRefPred(onNoContinuationBB, checkOSRAddressBB);
checkOSRAddressBB->SetCond(toOnNoContinuationBB, toOnContinuationBB);
toOnContinuationBB->setLikelihood(0);
toOnNoContinuationBB->setLikelihood(1);

JITDUMP(" Created " FMT_BB " to check for Tier-0 continuations\n", checkILOffsetBB->bbNum);
JITDUMP(" Created " FMT_BB " to check for Tier-0 continuations\n", checkOSRAddressBB->bbNum);

continuationArg = m_compiler->gtNewLclvNode(m_compiler->lvaAsyncContinuationArg, TYP_REF);
unsigned offsetOfIlOffset = OFFSETOF__CORINFO_Continuation__data;
GenTree* ilOffset = LoadFromOffset(continuationArg, offsetOfIlOffset, TYP_INT);
GenTree* zero = m_compiler->gtNewIconNode(0);
GenTree* ltZero = m_compiler->gtNewOperNode(GT_LT, TYP_INT, ilOffset, zero);
GenTree* jtrue = m_compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, ltZero);
LIR::AsRange(checkILOffsetBB).InsertAtEnd(LIR::SeqTree(m_compiler, jtrue));
continuationArg = m_compiler->gtNewLclvNode(m_compiler->lvaAsyncContinuationArg, TYP_REF);
unsigned offsetOfOSRAddress = OFFSETOF__CORINFO_Continuation__data;
GenTree* osrAddress = LoadFromOffset(continuationArg, offsetOfOSRAddress, TYP_I_IMPL);
GenTree* zero = m_compiler->gtNewIconNode(0, TYP_I_IMPL);
GenTree* eqZero = m_compiler->gtNewOperNode(GT_EQ, TYP_INT, osrAddress, zero);
GenTree* jtrue = m_compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, eqZero);
LIR::AsRange(checkOSRAddressBB).InsertAtEnd(LIR::SeqTree(m_compiler, jtrue));

if (ReuseContinuations())
{
Expand Down
12 changes: 6 additions & 6 deletions src/coreclr/jit/async.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct ContinuationLayoutBuilder
{
private:
Compiler* m_compiler;
bool m_needsOSRILOffset = false;
bool m_needsOSRAddress = false;
bool m_needsException = false;
bool m_needsContinuationContext = false;
bool m_needsKeepAlive = false;
Expand All @@ -70,13 +70,13 @@ struct ContinuationLayoutBuilder
{
}

void SetNeedsOSRILOffset()
void SetNeedsOSRAddress()
{
m_needsOSRILOffset = true;
m_needsOSRAddress = true;
}
bool NeedsOSRILOffset() const
bool NeedsOSRAddress() const
{
return m_needsOSRILOffset;
return m_needsOSRAddress;
}
void SetNeedsException()
{
Expand Down Expand Up @@ -128,7 +128,7 @@ struct ContinuationLayoutBuilder
struct ContinuationLayout
{
unsigned Size = 0;
unsigned OSRILOffset = UINT_MAX;
unsigned OSRAddress = UINT_MAX;
unsigned ExceptionOffset = UINT_MAX;
unsigned ContinuationContextOffset = UINT_MAX;
unsigned KeepAliveOffset = UINT_MAX;
Expand Down
Loading
Loading