Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
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
27 changes: 27 additions & 0 deletions src/coreclr/inc/cordebuginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -431,4 +431,31 @@ class ICorDebugInfo
// Source information about the IL instruction in the inlinee
SourceTypes Source;
};

struct AsyncContinuationVarInfo
{
// IL number of variable (or one of the special IL numbers, like UNKNOWN_ILNUM)
uint32_t VarNumber;
// Offset in continuation's data where this variable is stored
uint32_t Offset;
Comment on lines +439 to +440
Copy link
Member Author

Choose a reason for hiding this comment

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

We probably need to include GCData offset here too, but I expect that we will end up with customized continuation layouts during .NET 11 that gets rid of the Data/GCData split, so I designed some of these types around that expectation. It also should be possible to derive the GCData offset by iterating the vars linearly, so we can probably make do with that for now.

Copy link
Member

Choose a reason for hiding this comment

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

Storing variables in arrays may work well for some scenarios, like AOT, which is sensitive to generation of types or GC layouts.

I think uint32_t Offset; may still work if it means "if Offset is greater than GCData length, subtract the length of GCData and continue in non-GC data"

Just in case we may end up using more than one shape, perhaps add some versioning/flavor marker in the AsyncSuspensionPoint?

Copy link
Member Author

@jakobbotsch jakobbotsch Oct 3, 2025

Choose a reason for hiding this comment

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

I think uint32_t Offset; may still work if it means "if Offset is greater than GCData length, subtract the length of GCData and continue in non-GC data"

For some values we have data stored in both Data and GCData arrays, and to reconstruct the value we need both offsets. Perhaps we can just do 2x 16-bit offsets in this field until we get tailored continuation layouts. I think it's something to investigate further once we look at actually implementing the diagnostics that access the continuation values.

Just in case we may end up using more than one shape, perhaps add some versioning/flavor marker in the AsyncSuspensionPoint?

I expect we stabilize on something during .NET 11, so if we change it after I think we can take an R2R version update at the same time as well. If we do it like that we can version based on R2R module version (it's already what we do in DebugInfo.cs).

};

struct AsyncSuspensionPoint
{
// IL offset in the root method that resulted in the creation of this suspension point.
uint32_t RootILOffset;
// Index of inline tree node containing the IL offset (0 for root)
uint32_t Inlinee;
// IL offset that resulted in the creation of the suspension point.
uint32_t ILOffset;
Copy link
Member

Choose a reason for hiding this comment

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

if we care about size, can IL offsets inside a method be uint16_t ?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think there is a 2^16 size limit on IL, but in any case we do not care about size in these data types. The actual storage of these is compressed in the VM, so when these values are small, they consume little space.

// Count of AsyncContinuationVarInfo in array of locals starting where
// the previous suspension point's locals end.
uint32_t NumContinuationVars;
};

struct AsyncInfo
{
// Number of suspension points in the method.
uint32_t NumSuspensionPoints;
Copy link
Member

Choose a reason for hiding this comment

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

can this be uint16_t?

Copy link
Member Author

Choose a reason for hiding this comment

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

Same reason as above I don't think we need to limit this.

};
};
10 changes: 10 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -2912,6 +2912,16 @@ class ICorStaticInfo
uint32_t numMappings // [IN] Number of rich mappings
) = 0;

// Report async debug information to EE.
// The arrays are expected to be allocated with allocateArray
// and ownership is transferred to the EE with this call.
virtual void reportAsyncDebugInfo(
ICorDebugInfo::AsyncInfo* asyncInfo, // [IN] Async method information
ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, // [IN] Array of async suspension points, indexed by state number
ICorDebugInfo::AsyncContinuationVarInfo* vars, // [IN] Array of async continuation variable info
uint32_t numVars // [IN] Number of entries in the async vars array
) = 0;

// Report back some metadata about the compilation to the EE -- for
// example, metrics about the compilation.
virtual void reportMetadata(
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/inc/icorjitinfoimpl_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,12 @@ void reportRichMappings(
ICorDebugInfo::RichOffsetMapping* mappings,
uint32_t numMappings) override;

void reportAsyncDebugInfo(
ICorDebugInfo::AsyncInfo* asyncInfo,
ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints,
ICorDebugInfo::AsyncContinuationVarInfo* vars,
uint32_t numVars) override;

void reportMetadata(
const char* key,
const void* value,
Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@

#include <minipal/guid.h>

constexpr GUID JITEEVersionIdentifier = { /* 3d2bdd20-eced-4a07-b9fb-227ce7f55fcd */
0x3d2bdd20,
0xeced,
0x4a07,
{0xb9, 0xfb, 0x22, 0x7c, 0xe7, 0xf5, 0x5f, 0xcd}
constexpr GUID JITEEVersionIdentifier = { /* e1c4ef4f-62a1-4055-813e-23837fdfdffa */
0xe1c4ef4f,
0x62a1,
0x4055,
{0x81, 0x3e, 0x23, 0x83, 0x7f, 0xdf, 0xdf, 0xfa}
};

#endif // JIT_EE_VERSIONING_GUID_H
5 changes: 3 additions & 2 deletions src/coreclr/inc/readytorun.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
// src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h
// If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION`
// and handle pending work.
#define READYTORUN_MAJOR_VERSION 16
#define READYTORUN_MAJOR_VERSION 17
#define READYTORUN_MINOR_VERSION 0x0000

#define MINIMUM_READYTORUN_MAJOR_VERSION 16
#define MINIMUM_READYTORUN_MAJOR_VERSION 17

// R2R Version 2.1 adds the InliningInfo section
// R2R Version 2.2 adds the ProfileDataInfo section
Expand All @@ -47,6 +47,7 @@
// R2R Version 14 changed x86 code generation to use funclets
// R2R Version 15 removes double to int/uint helper calls
// R2R Version 16 replaces the compression format for debug boundaries with a new format that is smaller and more efficient to parse
// R2R Version 17 adds support for producing "fat" debug information (that e.g. can include async debug info)

struct READYTORUN_CORE_HEADER
{
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/ICorJitInfo_names_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ DEF_CLR_API(setBoundaries)
DEF_CLR_API(getVars)
DEF_CLR_API(setVars)
DEF_CLR_API(reportRichMappings)
DEF_CLR_API(reportAsyncDebugInfo)
DEF_CLR_API(reportMetadata)
DEF_CLR_API(allocateArray)
DEF_CLR_API(freeArray)
Expand Down
11 changes: 11 additions & 0 deletions src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,17 @@ void WrapICorJitInfo::reportRichMappings(
API_LEAVE(reportRichMappings);
}

void WrapICorJitInfo::reportAsyncDebugInfo(
ICorDebugInfo::AsyncInfo* asyncInfo,
ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints,
ICorDebugInfo::AsyncContinuationVarInfo* vars,
uint32_t numVars)
{
API_ENTER(reportAsyncDebugInfo);
wrapHnd->reportAsyncDebugInfo(asyncInfo, suspensionPoints, vars, numVars);
API_LEAVE(reportAsyncDebugInfo);
}

void WrapICorJitInfo::reportMetadata(
const char* key,
const void* value,
Expand Down
76 changes: 76 additions & 0 deletions src/coreclr/jit/async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,8 @@ PhaseStatus AsyncTransformation::Run()

m_comp->fgInvalidateDfsTree();

ReportDebugInfo();

return PhaseStatus::MODIFIED_EVERYTHING;
}

Expand Down Expand Up @@ -818,6 +820,8 @@ void AsyncTransformation::Transform(
BasicBlock* resumeBB = CreateResumption(block, *remainder, call, callDefInfo, stateNum, layout);

m_resumptionBBs.push_back(resumeBB);

CreateDebugInfoForSuspensionPoint(call, layout);
}

//------------------------------------------------------------------------
Expand Down Expand Up @@ -2202,6 +2206,78 @@ GenTreeStoreInd* AsyncTransformation::StoreAtOffset(GenTree* base, unsigned offs
return store;
}

//------------------------------------------------------------------------
// AsyncTransformation::CreateDebugInfoForSuspensionPoint:
// Create debug info for the specific suspension point we just created.
//
// Parameters:
// asyncCall - Call node resulting in the suspension point
// stateNum - State number that was assigned to the suspension point
// layout - Layout of continuation
//
void AsyncTransformation::CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, const ContinuationLayout& layout)
{
if (!m_comp->opts.compDbgInfo)
{
return;
}

uint32_t numLocals = 0;
for (const LiveLocalInfo& local : layout.Locals)
{
unsigned ilVarNum = m_comp->compMap2ILvarNum(local.LclNum);
if (ilVarNum == (unsigned)ICorDebugInfo::UNKNOWN_ILNUM)
{
continue;
}

ICorDebugInfo::AsyncContinuationVarInfo varInf;
varInf.VarNumber = ilVarNum;
varInf.Offset = local.DataOffset;
m_dbgContinuationVars.push_back(varInf);
numLocals++;
}

ICorDebugInfo::AsyncSuspensionPoint suspensionPoint;
const DebugInfo& di = asyncCall->GetAsyncInfo().DebugInfo;
suspensionPoint.RootILOffset = di.GetRoot().GetLocation().GetOffset();
suspensionPoint.Inlinee = di.GetInlineContext()->GetOrdinal();
suspensionPoint.ILOffset = di.GetLocation().GetOffset();
suspensionPoint.NumContinuationVars = numLocals;

m_dbgSuspensionPoints.push_back(suspensionPoint);
}

//------------------------------------------------------------------------
// AsyncTransformation::ReportDebugInfo:
// Report debug info back to EE.
//
void AsyncTransformation::ReportDebugInfo()
{
if (!m_comp->opts.compDbgInfo)
{
return;
}

ICorDebugInfo::AsyncInfo asyncInfo;
asyncInfo.NumSuspensionPoints = static_cast<uint32_t>(m_dbgSuspensionPoints.size());

ICorDebugInfo::AsyncSuspensionPoint* hostSuspensionPoints =
static_cast<ICorDebugInfo::AsyncSuspensionPoint*>(m_comp->info.compCompHnd->allocateArray(
m_dbgSuspensionPoints.size() * sizeof(ICorDebugInfo::AsyncSuspensionPoint)));
for (size_t i = 0; i < m_dbgSuspensionPoints.size(); i++)
hostSuspensionPoints[i] = m_dbgSuspensionPoints[i];

ICorDebugInfo::AsyncContinuationVarInfo* hostVars =
static_cast<ICorDebugInfo::AsyncContinuationVarInfo*>(m_comp->info.compCompHnd->allocateArray(
m_dbgContinuationVars.size() * sizeof(ICorDebugInfo::AsyncContinuationVarInfo)));
for (size_t i = 0; i < m_dbgContinuationVars.size(); i++)
hostVars[i] = m_dbgContinuationVars[i];

m_comp->info.compCompHnd->reportAsyncDebugInfo(&asyncInfo, hostSuspensionPoints, hostVars,
static_cast<uint32_t>(m_dbgContinuationVars.size()));
}

//------------------------------------------------------------------------
// AsyncTransformation::GetDataArrayVar:
// Create a new local to hold the data array of the continuation object. This
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/jit/async.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ class AsyncTransformation
BasicBlock* m_lastResumptionBB = nullptr;
BasicBlock* m_sharedReturnBB = nullptr;

// Debug info
jitstd::vector<ICorDebugInfo::AsyncContinuationVarInfo> m_dbgContinuationVars;
jitstd::vector<ICorDebugInfo::AsyncSuspensionPoint> m_dbgSuspensionPoints;

bool IsLive(unsigned lclNum);
void Transform(BasicBlock* block,
GenTreeCall* call,
Expand Down Expand Up @@ -132,6 +136,9 @@ class AsyncTransformation
GenTreeFlags indirFlags = GTF_IND_NONFAULTING);
GenTreeStoreInd* StoreAtOffset(GenTree* base, unsigned offset, GenTree* value, var_types storeType);

void CreateDebugInfoForSuspensionPoint(GenTreeCall* asyncCall, const ContinuationLayout& layout);
void ReportDebugInfo();

unsigned GetDataArrayVar();
unsigned GetGCDataArrayVar();
unsigned GetResultBaseVar();
Expand All @@ -147,6 +154,8 @@ class AsyncTransformation
: m_comp(comp)
, m_liveLocalsScratch(comp->getAllocator(CMK_Async))
, m_resumptionBBs(comp->getAllocator(CMK_Async))
, m_dbgContinuationVars(comp->getAllocator(CMK_Async))
, m_dbgSuspensionPoints(comp->getAllocator(CMK_Async))
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -4601,7 +4601,7 @@ class Compiler
CORINFO_CALL_INFO* callInfo,
IL_OFFSET rawILOffset);

void impSetupAndSpillForAsyncCall(GenTreeCall* call, OPCODE opcode, unsigned prefixFlags);
void impSetupAndSpillForAsyncCall(GenTreeCall* call, OPCODE opcode, unsigned prefixFlags, const DebugInfo& callInstDI);

void impInsertAsyncContinuationForLdvirtftnCall(GenTreeCall* call);

Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -4400,6 +4400,9 @@ struct AsyncCallInfo
bool SaveAndRestoreSynchronizationContextField = false;
bool HasSuspensionIndicatorDef = false;
unsigned SynchronizationContextLclNum = BAD_VAR_NUM;

// Exact debug info of call IL instruction
DebugInfo DebugInfo;
};

// Return type descriptor of a GT_CALL node.
Expand Down
11 changes: 8 additions & 3 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ var_types Compiler::impImportCall(OPCODE opcode,

if (sig->isAsyncCall())
{
impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags);
impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags, di);
}

impPopCallArgs(sig, call->AsCall());
Expand Down Expand Up @@ -691,7 +691,7 @@ var_types Compiler::impImportCall(OPCODE opcode,

if (sig->isAsyncCall())
{
impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags);
impSetupAndSpillForAsyncCall(call->AsCall(), opcode, prefixFlags, di);
}

// Now create the argument list.
Expand Down Expand Up @@ -6800,10 +6800,15 @@ void Compiler::impCheckForPInvokeCall(
// call - The call
// opcode - The IL opcode for the call
// prefixFlags - Flags containing context handling information from IL
// callInstDI - Debug info for the exact call instruction
//
void Compiler::impSetupAndSpillForAsyncCall(GenTreeCall* call, OPCODE opcode, unsigned prefixFlags)
void Compiler::impSetupAndSpillForAsyncCall(GenTreeCall* call,
OPCODE opcode,
unsigned prefixFlags,
const DebugInfo& callInstDI)
{
AsyncCallInfo asyncInfo;
asyncInfo.DebugInfo = callInstDI;

if ((prefixFlags & PREFIX_IS_TASK_AWAIT) != 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal struct ReadyToRunHeaderConstants
{
public const uint Signature = 0x00525452; // 'RTR'

public const ushort CurrentMajorVersion = 16;
public const ushort CurrentMajorVersion = 17;
public const ushort CurrentMinorVersion = 0;
}
#if READYTORUN
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3215,6 +3215,15 @@ private void reportRichMappings(InlineTreeNode* inlineTree, uint numInlineTree,
Marshal.FreeHGlobal((IntPtr)mappings);
}

#pragma warning disable CA1822 // Mark members as static
private void reportAsyncDebugInfo(AsyncInfo* asyncInfo, AsyncSuspensionPoint* suspensionPoints, AsyncContinuationVarInfo* vars, uint numVars)
#pragma warning restore CA1822 // Mark members as static
{
Marshal.FreeHGlobal((IntPtr)asyncInfo);
Marshal.FreeHGlobal((IntPtr)suspensionPoints);
Marshal.FreeHGlobal((IntPtr)vars);
}

#pragma warning disable CA1822 // Mark members as static
private void reportMetadata(byte* key, void* value, nuint length)
#pragma warning restore CA1822 // Mark members as static
Expand Down
Loading
Loading