Skip to content

Conversation

@jakobbotsch
Copy link
Member

@jakobbotsch jakobbotsch commented Oct 1, 2025

  • 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

- 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.
- Add new async debug information to the storage on the VM side
- Set get target method desc for async resumption stubs, to be used for
  mapping from continuations back to the async IL function that it will
  resume.
@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 Oct 1, 2025
@jakobbotsch jakobbotsch changed the title Add debug information for runtime async information Add debug information for runtime async methods Oct 1, 2025
@jakobbotsch
Copy link
Member Author

@AndyAyersMS Any other comments on the JIT part?

@lateralusX
Copy link
Member

lateralusX commented Nov 4, 2025

I ran through the latest with EventPipe and was able to capture the stack trace in dotnet-stack as well as sample profile in dotnet-trace and load it in PerfView:

image

Level9Async and below are coming in from the continuation chain, Level10Async is the current dispatched async frame.

I also loaded the source code location, and it looks like it correctly matches the diagnostic IP through native <-> IL -> Source:

image

I also looked at the location of diagnostic IP in generated assembler:

image

So, it ends up at 00007FFD259C9B7F that is before the call, expected?

@jakobbotsch
Copy link
Member Author

So, it ends up at 00007FFD259C9B7F that is before the call, expected?

Yes, the DiagnosticIP is pointing at the beginning of the suspension code for the suspension point. The call you see in the codegen is actually the helper call to allocate a new continuation instance, so not the call to Level7Async.

Copy link
Member

@AndyAyersMS AndyAyersMS left a comment

Choose a reason for hiding this comment

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

JIT changes LGTM, one small formatting issue.

@davidwrighton
Copy link
Member

This seems like its probably good. One question I have is what is the impact on the file size of System.Private.CoreLib. The cost of adding a new SourceType bit is probably not that bad, but I would like it measured before we merge. We recently went through a fair amount of effort to make it more efficient to read the compressed format (which is why it doesn't use compressed nibbles for most of the data anymore), but part of the sacrifice there was to reduce the number of bits available for SourceType. It turns out that reading this sort of debug data is on the critical path for applications which are experiencing high failure rates, so we have to be quite careful to avoid too much usage of decompressing nibbles, as that is an incredibly inefficient codepath.

Copy link
Member

@lateralusX lateralusX left a comment

Choose a reason for hiding this comment

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

DiagnosticIP on ResumeInfo and usage of it LGTM! Ability to go from continuation -> native IP looks good and can be used in regular EventPipe stack-walks + resolve to user code through the IL<->native mappings emitted in rundown. Captured samples are correctly translated to matching await calls in user code into PerfView. With this PR we are able to use our internal EventPipe sample profiler and profile AsyncV2 continuation chains with low overhead.

@jakobbotsch
Copy link
Member Author

This seems like its probably good. One question I have is what is the impact on the file size of System.Private.CoreLib. The cost of adding a new SourceType bit is probably not that bad, but I would like it measured before we merge. We recently went through a fair amount of effort to make it more efficient to read the compressed format (which is why it doesn't use compressed nibbles for most of the data anymore), but part of the sacrifice there was to reduce the number of bits available for SourceType. It turns out that reading this sort of debug data is on the critical path for applications which are experiencing high failure rates, so we have to be quite careful to avoid too much usage of decompressing nibbles, as that is an incredibly inefficient codepath.

Measured this, SPC grows from 16244 KB to 16272 KB with the extra bit to encode the new source type.

@jakobbotsch
Copy link
Member Author

/ba-g Android timeout

@jakobbotsch jakobbotsch merged commit e0837be into dotnet:main Nov 6, 2025
108 of 113 checks passed
@jakobbotsch jakobbotsch deleted the jit-async-diagnostics branch November 6, 2025 09:39
@jakobbotsch
Copy link
Member Author

Thanks for the reviews everyone!

jakobbotsch added a commit that referenced this pull request Nov 11, 2025
In debug codegen we expect basic blocks to have valid start IL offsets,
potentially to an invalid IL end offset.

When we split a block at a node, we find the end offset by looking
backwards from that node for an `IL_OFFSET` node. If we find such a
node, we use it as the split point IL offset. Otherwise we use
`BAD_IL_OFFSET` and start the next block at the end offset (making it
have a 0 byte range).

This latter behavior is problematic if the bottom portion of the split
has its own `IL_OFFSET` nodes. In that case we can end up with
`IL_OFFSET` nodes pointing outside the region of the basic block. If we
later split again, then we can end up creating illegal IL offset ranges
for unoptimized code. In an example case we first ended up with the
bottom block having an IL offset range of `[0aa..0aa)` but still
containing an `IL_OFFSET` with an offset of `092`. When we later split
the bottom block again we ended up with `[0aa..0aa), [092..0aa)` and hit
an assert.

To address the problem change the behavior in the following way:
* If we find no `IL_OFFSET` node in the upper block, then look for one
in the lower block, and use its value as the split point
* If we find no `IL_OFFSET` in both blocks then consider the lower block
empty and the upper block to have everything (same behavior as before).
* One exception to above: if the end was already `BAD_IL_OFFSET` then we
have to consider the upper block to be empty as we cannot represent the
other case

This fixes #119616. However for that specific case, which is inside
async code, we will have the exact IL offset of the async call we are
splitting at after #120303, so we will be able to do better. I'll make
that change as part of that PR.
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 runtime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.