-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Add debug information for runtime async methods #120303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 38 commits
b7bb968
d5d0864
d7277fb
6addd0e
6a5bf19
262260a
4e2a829
820afa9
3621eae
f597424
6bb6cee
f580e3f
55b9522
c9c64be
db77446
c808390
3ec5160
bb12c77
bf3364b
b49f38f
d1bf49f
579714e
c056793
9679f91
7a34475
e839409
d8ad0c1
9fdd8a7
69e02db
68c245b
2031f08
b7576b2
03f85ec
06fea81
29d8998
cb7a943
879ec4f
0749688
6bcc004
286baf9
8cf3a72
3096aa8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -431,4 +431,33 @@ class ICorDebugInfo | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Source information about the IL instruction in the inlinee | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SourceTypes Source; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
noahfalk marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
struct AsyncContinuationVarInfo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// IL number of variable (or one of the special IL numbers, like TYPECTXT_ILNUM) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uint32_t VarNumber; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Index in continuation's byte[] data where this variable is stored, or 0xFFFFFFFF if the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// variable does not have any byte[] data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uint32_t Offset; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Index in continuation's object[] data where this variable's GC pointers are stored, or 0xFFFFFFFF | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// if the variable does not have any GC pointers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uint32_t GCIndex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
internal sealed unsafe class Continuation | |
{ | |
public Continuation? Next; | |
public delegate*<Continuation, Continuation?> Resume; | |
public uint State; | |
public CorInfoContinuationFlags Flags; | |
// Data and GCData contain the state of the continuation. | |
// Note: The JIT is ultimately responsible for laying out these arrays. | |
// However, other parts of the system depend on the layout to | |
// know where to locate or place various pieces of data: | |
// | |
// 1. Resumption stubs need to know where to place the return value | |
// inside the next continuation. If the return value has GC references | |
// then it is boxed and placed at GCData[0]; otherwise, it is placed | |
// inside Data at offset 0 if | |
// CORINFO_CONTINUATION_OSR_IL_OFFSET_IN_DATA is NOT set and otherwise | |
// at offset 4. | |
// | |
// 2. Likewise, Finalize[Value]TaskReturningThunk needs to know from | |
// where to extract the return value. | |
// | |
// 3. The dispatcher needs to know where to place the exception inside | |
// the next continuation with a handler. Continuations with handlers | |
// have CORINFO_CONTINUATION_NEEDS_EXCEPTION set. The exception is | |
// placed at GCData[0] if CORINFO_CONTINUATION_RESULT_IN_GCDATA is NOT | |
// set, and otherwise at GCData[1]. | |
// | |
public byte[]? Data; | |
public object?[]? GCData; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we done any modeling whether it is worth it to have the data in separate blocks from the continuation? The extra object headers and indirections are not free.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The design makes Continuation shape mostly opaque. Most helpers and the infrastructure do not dig into internals of Continuation. As long as JIT is self-consistent in terms of serialization/deserialization of locals, most other things are not concerned with the shape thus JIT can change the continuation shape, modulo R2R and debug interfaces.
There are just a few things that are meaningful to the infrastructure - the link to the Next
continuation, the flags, the locations of the return value or an exception when infrastructure, upon completion of the calee, needs to place results into the caller's continuation before resuming it.
How most of the locals are stored is an opaque agreement between the method who serializes locals into a continuation and the method who deserializes - that is the same method.
Currently we use two arrays (array of bytes + array of objects) to serialize/deserialize locals. The biggest advantage is "time-to-market", obviously.
There are other advantages - like there is no type or GC layout generation. The shape of continuation is different for every await
, so it is useful to only emit site-specific code, as we must do anyways, but not types/layouts. There could be more than one await
per method so it could add up. We optimize for suspensions never happening though, statistically speaking, so slightly less efficient format which has fewer upfront/static requirements is attractive.
There are disadvantages:
- it is not the most compact format. Think of capturing just one
int
and oneobject
. - return values that happen to be object-containing structs, need to be boxed.
- Also it could be difficult to external introspection like a debugger.
For example structs containing a mix ofint
andobject
fields get their fields stored in different arrays accordingly. It is not a problem for JIT to reconstitute such struct, but it could be an inconvenience for other observers.
Anyways. The API here tries to support the current format, but leave the door open for future changes.
I think it is good to not have too many parts changing at once, unless it is blocking or costly to change later, so I think it is a good approach even if we think of tweaking the continuation format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are emitting custom methodtables for the continuations, should we rather make the field layout flat and avoid these extra layers?
Yes, once/if #120411 makes it in, we only need Offset
here.
Originally I didn't add GCIndex
for precisely the reason that I expected we would do that. However @tommcdon was playing around with the debug info earlier and wondered about the data he was seeing, and since it's not a large change I decided to just add it for now, with the catch that it might change in the future.
I agree with all of @VSadov's points, but we probably need to measure it. I also am somewhat worried about creating custom MethodTable
for every continuation up front. Although the actual creation in #120411 is lightweight so maybe it will not be a problem.
In the end we could even take a hybrid approach with the byte[]
/object[]
version used for tier0/debug and the flat versions used for tier1. Of course that makes it more complicated for everyone since now they need to be aware of two possible formats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example structs containing a mix of int and object fields get their fields stored in different arrays accordingly. It is not a problem for JIT to reconstitute such struct, but it could be an inconvenience for other observers.
Is there a design for how the debugger could reconstitute such a struct? It doesn't seem like the current debug info is sufficiently powerful to represent that.
The API here tries to support the current format, but leave the door open for future changes.
If you'd like to leave the door open to store locals inline within the Continuation rather than indirected, perhaps represent the variable location as:
enum Base
{
ContinuationObj = 1, // variable stored at continuation + offset
DataArray = 2, // variable stored at continuation->Data + offset
GCDataArray = 3 // variable stored at continuation->GCData + offset
}
Base OffsetBase;
uint32_t Offset;
Alternately changing the contract sometime in the next 6 months probably isn't too costly. Doing it close to .NET 11 ship or after .NET 11 ship would have additional burdens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a design for how the debugger could reconstitute such a struct? It doesn't seem like the current debug info is sufficiently powerful to represent that.
The replicate the way the JIT currently stores/restores these values, for a type of size S
:
- If
Offset != UINT_MAX
, take S bytes fromData
starting fromOffset
- If
GCIndex != UINT_MAX
, fill in the GC pointers in ascending order of offset in the type, starting with the GC pointer at indexGCIndex
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If debuggers hard-code that algorithm it becomes a breaking change if the JIT ever wants to lay out the data differently. Is this an algorithm we want to set in stone or just a stop-gap for now? I haven't had a chance to get a good look at #120411 yet but it suggests our plans for field layout are still in flux.
In that algorithm above, do we have to worry about alignment padding or all the fields will be packed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this an algorithm we want to set in stone or just a stop-gap for now?
At this point I am expecting/hopeful that #120411 makes it in. It makes things simpler -- a single offset and the type is just stored in the normal way at that offset in the continuation. But it has other implications for various components as @davidwrighton pointed out in that PR.
The current storage mechanism exists almost unchanged since the original prototype in 2023. It was the simplest thing I could think of that didn't require boxing all structs with object fields in them.
In that algorithm above, do we have to worry about alignment padding or all the fields will be packed?
Do you mean for step 2? The GCIndex
encodes an index into the GCData
array. If the value has N object refs in it, then GCData
will store N object refs starting at that index. However, it will be up to the reconstruction code to figure out where those N object refs are stored in the value and to copy the GC refs from GCData
across.
There can be alignment padding in Data
to make sure value types are aligned properly but reconstruction doesn't need to worry about that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still have a little confusion about the current algorithm, but since it doesn't look like we'll be keeping it much longer no need for me to suss out the details :) I'm assuming the new continuation types will trigger a new round of updates here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updating these tools based on @EgorBo's instructions (I used the prompt to add the JIT-EE boilerplate, but the agent was not making the changes automatically without allowing it access to these tools)