Skip to content

Commit e0837be

Browse files
authored
Add debug information for runtime async methods (#120303)
- Add new JIT-EE API to report back debug information about the generated state machine and continuations - Refactor debug info storage on VM side to be more easily extensible. The new format has either a thin or fat header. The fat header is used when we have either uninstrumented bounds, patchpoint info, rich debug info or async debug info, and stores the blob sizes of all of those components in addition to the bounds and vars. It is indicated by the first field (size of bounds) having value 0, which is an uncommon value for this field. - Add new async debug information to the storage on the VM side - Implement new format in R2R as well, bump R2R major version (might as well do this now as we expect to need to store async debug info in R2R during .NET 11 anyway) - Remove `Continuation.Resume` in favor of a `Continuation.ResumeInfo` pointer that gives both the resumption stub and a `DiagnosticIP`. The `DiagnosticIP` can be used for async stackwalking and for keying into the async debug info - Teach the JIT to emit "async resumption info tables" and update the async transformation to point `Continuation.ResumeInfo` at this table's entries on suspension - Add a new `ICorDebugInfo::ASYNC` source type; emit mappings with this source type for generated suspension and resumption code
1 parent d7fba07 commit e0837be

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1685
-698
lines changed

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,27 @@ internal enum ContinuationFlags
7878
ContinueOnCapturedTaskScheduler = 64,
7979
}
8080

81+
// Keep in sync with dataAsyncResumeInfo in the JIT
82+
internal unsafe struct ResumeInfo
83+
{
84+
public delegate*<Continuation, ref byte, Continuation?> Resume;
85+
// IP to use for diagnostics. Points into the jitted suspension code.
86+
// For debug codegen the IP resolves via an ASYNC native->IL mapping to
87+
// the IL AsyncHelpers.Await (or other async function) call which
88+
// caused the suspension.
89+
// For optimized codegen the mapping into the root method may be more
90+
// approximate (e.g. because of inlining).
91+
// For all codegens the offset of DiagnosticsIP matches
92+
// DiagnosticNativeOffset for the corresponding AsyncSuspensionPoint in
93+
// the debug info.
94+
public void* DiagnosticIP;
95+
}
96+
8197
#pragma warning disable CA1852 // "Type can be sealed" -- no it cannot because the runtime constructs subtypes dynamically
8298
internal unsafe class Continuation
8399
{
84100
public Continuation? Next;
85-
public delegate*<Continuation, ref byte, Continuation?> Resume;
101+
public ResumeInfo* ResumeInfo;
86102
public ContinuationFlags Flags;
87103
public int State;
88104

@@ -195,9 +211,8 @@ private interface IRuntimeAsyncTaskOps<T>
195211
static abstract ref byte GetResultStorage(T task);
196212
}
197213

198-
/// <summary>
199-
/// Represents a wrapped runtime async operation.
200-
/// </summary>
214+
// Represents execution of a chain of suspended and resuming runtime
215+
// async functions.
201216
private sealed class RuntimeAsyncTask<T> : Task<T>, ITaskCompletionAction
202217
{
203218
public RuntimeAsyncTask()
@@ -261,9 +276,8 @@ public static void PostToSyncContext(RuntimeAsyncTask<T> task, SynchronizationCo
261276
}
262277
}
263278

264-
/// <summary>
265-
/// Represents a wrapped runtime async operation.
266-
/// </summary>
279+
// Represents execution of a chain of suspended and resuming runtime
280+
// async functions.
267281
private sealed class RuntimeAsyncTask : Task, ITaskCompletionAction
268282
{
269283
public RuntimeAsyncTask()
@@ -329,35 +343,63 @@ public static void PostToSyncContext(RuntimeAsyncTask task, SynchronizationConte
329343

330344
private static class RuntimeAsyncTaskCore
331345
{
346+
[StructLayout(LayoutKind.Explicit)]
347+
private unsafe ref struct DispatcherInfo
348+
{
349+
// Dispatcher info for next dispatcher present on stack, or
350+
// null if none.
351+
[FieldOffset(0)]
352+
public DispatcherInfo* Next;
353+
354+
// Next continuation the dispatcher will process.
355+
#if TARGET_64BIT
356+
[FieldOffset(8)]
357+
#else
358+
[FieldOffset(4)]
359+
#endif
360+
public Continuation? NextContinuation;
361+
}
362+
363+
// Information about current task dispatching, to be used for async
364+
// stackwalking.
365+
[ThreadStatic]
366+
private static unsafe DispatcherInfo* t_dispatcherInfo;
367+
332368
public static unsafe void DispatchContinuations<T, TOps>(T task) where T : Task, ITaskCompletionAction where TOps : IRuntimeAsyncTaskOps<T>
333369
{
334370
ExecutionAndSyncBlockStore contexts = default;
335371
contexts.Push();
336-
Continuation? continuation = TOps.GetContinuationState(task);
372+
373+
DispatcherInfo dispatcherInfo;
374+
dispatcherInfo.Next = t_dispatcherInfo;
375+
dispatcherInfo.NextContinuation = TOps.GetContinuationState(task);
376+
t_dispatcherInfo = &dispatcherInfo;
337377

338378
while (true)
339379
{
340-
Debug.Assert(continuation != null);
380+
Debug.Assert(dispatcherInfo.NextContinuation != null);
341381
try
342382
{
343-
ref byte resultLoc = ref continuation.Next != null ? ref continuation.Next.GetResultStorageOrNull() : ref TOps.GetResultStorage(task);
344-
Continuation? newContinuation = continuation.Resume(continuation, ref resultLoc);
383+
Continuation curContinuation = dispatcherInfo.NextContinuation;
384+
Continuation? nextContinuation = curContinuation.Next;
385+
dispatcherInfo.NextContinuation = nextContinuation;
386+
387+
ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref TOps.GetResultStorage(task);
388+
Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc);
345389

346390
if (newContinuation != null)
347391
{
348-
newContinuation.Next = continuation.Next;
392+
newContinuation.Next = nextContinuation;
349393
HandleSuspended<T, TOps>(task);
350394
contexts.Pop();
395+
t_dispatcherInfo = dispatcherInfo.Next;
351396
return;
352397
}
353-
354-
continuation = continuation.Next;
355398
}
356399
catch (Exception ex)
357400
{
358-
Debug.Assert(continuation != null);
359-
Continuation? nextContinuation = UnwindToPossibleHandler(continuation);
360-
if (nextContinuation == null)
401+
Continuation? handlerContinuation = UnwindToPossibleHandler(dispatcherInfo.NextContinuation);
402+
if (handlerContinuation == null)
361403
{
362404
// Tail of AsyncTaskMethodBuilderT.SetException
363405
bool successfullySet = ex is OperationCanceledException oce ?
@@ -366,6 +408,8 @@ public static unsafe void DispatchContinuations<T, TOps>(T task) where T : Task,
366408

367409
contexts.Pop();
368410

411+
t_dispatcherInfo = dispatcherInfo.Next;
412+
369413
if (!successfullySet)
370414
{
371415
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
@@ -374,17 +418,18 @@ public static unsafe void DispatchContinuations<T, TOps>(T task) where T : Task,
374418
return;
375419
}
376420

377-
nextContinuation.SetException(ex);
378-
379-
continuation = nextContinuation;
421+
handlerContinuation.SetException(ex);
422+
dispatcherInfo.NextContinuation = handlerContinuation;
380423
}
381424

382-
if (continuation == null)
425+
if (dispatcherInfo.NextContinuation == null)
383426
{
384427
bool successfullySet = TOps.SetCompleted(task);
385428

386429
contexts.Pop();
387430

431+
t_dispatcherInfo = dispatcherInfo.Next;
432+
388433
if (!successfullySet)
389434
{
390435
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
@@ -393,26 +438,23 @@ public static unsafe void DispatchContinuations<T, TOps>(T task) where T : Task,
393438
return;
394439
}
395440

396-
if (QueueContinuationFollowUpActionIfNecessary<T, TOps>(task, continuation))
441+
if (QueueContinuationFollowUpActionIfNecessary<T, TOps>(task, dispatcherInfo.NextContinuation))
397442
{
398443
contexts.Pop();
444+
t_dispatcherInfo = dispatcherInfo.Next;
399445
return;
400446
}
401447
}
402448
}
403449

404-
private static Continuation? UnwindToPossibleHandler(Continuation continuation)
450+
private static Continuation? UnwindToPossibleHandler(Continuation? continuation)
405451
{
406452
while (true)
407453
{
408-
Continuation? nextContinuation = continuation.Next;
409-
if (nextContinuation == null)
410-
return null;
411-
412-
if ((nextContinuation.Flags & ContinuationFlags.HasException) != 0)
413-
return nextContinuation;
454+
if (continuation == null || (continuation.Flags & ContinuationFlags.HasException) != 0)
455+
return continuation;
414456

415-
continuation = nextContinuation;
457+
continuation = continuation.Next;
416458
}
417459
}
418460

src/coreclr/inc/cordebuginfo.h

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ class ICorDebugInfo
4545
STACK_EMPTY = 0x02, // The stack is empty here
4646
CALL_SITE = 0x04, // This is a call site.
4747
NATIVE_END_OFFSET_UNKNOWN = 0x08, // Indicates a epilog endpoint
48-
CALL_INSTRUCTION = 0x10 // The actual instruction of a call.
48+
CALL_INSTRUCTION = 0x10, // The actual instruction of a call.
49+
ASYNC = 0x20, // Indicates suspension/resumption for an async call
4950

5051
};
5152

@@ -431,4 +432,30 @@ class ICorDebugInfo
431432
// Source information about the IL instruction in the inlinee
432433
SourceTypes Source;
433434
};
435+
436+
struct AsyncContinuationVarInfo
437+
{
438+
// IL number of variable (or one of the special IL numbers, like TYPECTXT_ILNUM)
439+
uint32_t VarNumber;
440+
// Offset in continuation object where this variable is stored
441+
uint32_t Offset;
442+
};
443+
444+
struct AsyncSuspensionPoint
445+
{
446+
// Offset of IP stored in ResumeInfo.DiagnosticIP. This offset maps to
447+
// the IL call that resulted in the suspension point through an ASYNC
448+
// mapping. Also used as a unique key for debug information about the
449+
// suspension point. See ResumeInfo.DiagnosticIP in SPC for more info.
450+
uint32_t DiagnosticNativeOffset;
451+
// Count of AsyncContinuationVarInfo in array of locals starting where
452+
// the previous suspension point's locals end.
453+
uint32_t NumContinuationVars;
454+
};
455+
456+
struct AsyncInfo
457+
{
458+
// Number of suspension points in the method.
459+
uint32_t NumSuspensionPoints;
460+
};
434461
};

src/coreclr/inc/corinfo.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,8 +1735,8 @@ struct CORINFO_ASYNC_INFO
17351735
CORINFO_CLASS_HANDLE continuationClsHnd;
17361736
// 'Next' field
17371737
CORINFO_FIELD_HANDLE continuationNextFldHnd;
1738-
// 'Resume' field
1739-
CORINFO_FIELD_HANDLE continuationResumeFldHnd;
1738+
// 'ResumeInfo' field
1739+
CORINFO_FIELD_HANDLE continuationResumeInfoFldHnd;
17401740
// 'State' field
17411741
CORINFO_FIELD_HANDLE continuationStateFldHnd;
17421742
// 'Flags' field
@@ -2913,6 +2913,16 @@ class ICorStaticInfo
29132913
uint32_t numMappings // [IN] Number of rich mappings
29142914
) = 0;
29152915

2916+
// Report async debug information to EE.
2917+
// The arrays are expected to be allocated with allocateArray
2918+
// and ownership is transferred to the EE with this call.
2919+
virtual void reportAsyncDebugInfo(
2920+
ICorDebugInfo::AsyncInfo* asyncInfo, // [IN] Async method information
2921+
ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints, // [IN] Array of async suspension points, indexed by state number
2922+
ICorDebugInfo::AsyncContinuationVarInfo* vars, // [IN] Array of async continuation variable info
2923+
uint32_t numVars // [IN] Number of entries in the async vars array
2924+
) = 0;
2925+
29162926
// Report back some metadata about the compilation to the EE -- for
29172927
// example, metrics about the compilation.
29182928
virtual void reportMetadata(
@@ -3341,7 +3351,7 @@ class ICorDynamicInfo : public ICorStaticInfo
33413351
CORINFO_TAILCALL_HELPERS* pResult
33423352
) = 0;
33433353

3344-
virtual CORINFO_METHOD_HANDLE getAsyncResumptionStub() = 0;
3354+
virtual CORINFO_METHOD_HANDLE getAsyncResumptionStub(void** entryPoint) = 0;
33453355

33463356
virtual CORINFO_CLASS_HANDLE getContinuationType(
33473357
size_t dataSize,

src/coreclr/inc/icorjitinfoimpl_generated.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,12 @@ void reportRichMappings(
453453
ICorDebugInfo::RichOffsetMapping* mappings,
454454
uint32_t numMappings) override;
455455

456+
void reportAsyncDebugInfo(
457+
ICorDebugInfo::AsyncInfo* asyncInfo,
458+
ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints,
459+
ICorDebugInfo::AsyncContinuationVarInfo* vars,
460+
uint32_t numVars) override;
461+
456462
void reportMetadata(
457463
const char* key,
458464
const void* value,
@@ -660,7 +666,8 @@ CORINFO_CLASS_HANDLE getContinuationType(
660666
bool* objRefs,
661667
size_t objRefsSize) override;
662668

663-
CORINFO_METHOD_HANDLE getAsyncResumptionStub() override;
669+
CORINFO_METHOD_HANDLE getAsyncResumptionStub(
670+
void** entryPoint) override;
664671

665672
bool convertPInvokeCalliToCall(
666673
CORINFO_RESOLVED_TOKEN* pResolvedToken,

src/coreclr/inc/jiteeversionguid.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@
3737

3838
#include <minipal/guid.h>
3939

40-
constexpr GUID JITEEVersionIdentifier = { /* 68e93e9d-dd28-49ca-9ebc-a01a54532bb3 */
41-
0x68e93e9d,
42-
0xdd28,
43-
0x49ca,
44-
{0x9e, 0xbc, 0xa0, 0x1a, 0x54, 0x53, 0x2b, 0xb3}
40+
constexpr GUID JITEEVersionIdentifier = { /* a802fbbf-3e14-4b34-a348-5fba9fd756d4 */
41+
0xa802fbbf,
42+
0x3e14,
43+
0x4b34,
44+
{0xa3, 0x48, 0x5f, 0xba, 0x9f, 0xd7, 0x56, 0xd4}
4545
};
4646

4747
#endif // JIT_EE_VERSIONING_GUID_H

src/coreclr/inc/readytorun.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
// src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h
2020
// If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION`
2121
// and handle pending work.
22-
#define READYTORUN_MAJOR_VERSION 16
22+
#define READYTORUN_MAJOR_VERSION 17
2323
#define READYTORUN_MINOR_VERSION 0x0000
2424

25-
#define MINIMUM_READYTORUN_MAJOR_VERSION 16
25+
#define MINIMUM_READYTORUN_MAJOR_VERSION 17
2626

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

5152
struct READYTORUN_CORE_HEADER
5253
{

src/coreclr/jit/ICorJitInfo_names_generated.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ DEF_CLR_API(setBoundaries)
112112
DEF_CLR_API(getVars)
113113
DEF_CLR_API(setVars)
114114
DEF_CLR_API(reportRichMappings)
115+
DEF_CLR_API(reportAsyncDebugInfo)
115116
DEF_CLR_API(reportMetadata)
116117
DEF_CLR_API(allocateArray)
117118
DEF_CLR_API(freeArray)

src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,17 @@ void WrapICorJitInfo::reportRichMappings(
10671067
API_LEAVE(reportRichMappings);
10681068
}
10691069

1070+
void WrapICorJitInfo::reportAsyncDebugInfo(
1071+
ICorDebugInfo::AsyncInfo* asyncInfo,
1072+
ICorDebugInfo::AsyncSuspensionPoint* suspensionPoints,
1073+
ICorDebugInfo::AsyncContinuationVarInfo* vars,
1074+
uint32_t numVars)
1075+
{
1076+
API_ENTER(reportAsyncDebugInfo);
1077+
wrapHnd->reportAsyncDebugInfo(asyncInfo, suspensionPoints, vars, numVars);
1078+
API_LEAVE(reportAsyncDebugInfo);
1079+
}
1080+
10701081
void WrapICorJitInfo::reportMetadata(
10711082
const char* key,
10721083
const void* value,
@@ -1547,10 +1558,11 @@ CORINFO_CLASS_HANDLE WrapICorJitInfo::getContinuationType(
15471558
return temp;
15481559
}
15491560

1550-
CORINFO_METHOD_HANDLE WrapICorJitInfo::getAsyncResumptionStub()
1561+
CORINFO_METHOD_HANDLE WrapICorJitInfo::getAsyncResumptionStub(
1562+
void** entryPoint)
15511563
{
15521564
API_ENTER(getAsyncResumptionStub);
1553-
CORINFO_METHOD_HANDLE temp = wrapHnd->getAsyncResumptionStub();
1565+
CORINFO_METHOD_HANDLE temp = wrapHnd->getAsyncResumptionStub(entryPoint);
15541566
API_LEAVE(getAsyncResumptionStub);
15551567
return temp;
15561568
}

0 commit comments

Comments
 (0)