[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals - #127757

Merged
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals
May 13, 2026
Merged

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals#127757
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals

Conversation

@kotlarmilos

@kotlarmiloskotlarmilos commented May 4, 2026

Copy link
Copy Markdown
Member

Description

When a managed async method is suspended at an await, the right-side debugger asks for the live values of the user's locals and arguments at that suspension point so they can be displayed on the call stack. The JIT path supplies this through ICorJitInfo::reportAsyncDebugInfo. The interpreter compiler never hits that path, so ICorDebugAsyncFrame::GetArgument returned CORDBG_E_IL_VAR_NOT_AVAILABLE for every IL variable on every interpreted async frame.

This adds a small parallel pipeline for the interpreter. InterpAsyncSuspendData now carries a per suspension point table of {ilVarNum, dataOffset} entries. On the DAC side, GetAsyncLocals detects an interpreter method and decodes the bytecode at startIp + state.

This fixes the following interpreter debugger test failures:

  • Async.AsyncSimple
  • Async.AsyncBreakpoint
  • Async.AsyncBreakpointJmc
  • Async.AsyncGeneric
  • Async.AsyncRecursive
  • Async.AsyncSharedGeneric
  • Async.AsyncV2CallingAsyncV1Iterator
  • Async.AsyncGenericStepInto

CopilotAI review requested due to automatic review settings May 4, 2026 14:54
@kotlarmilos
kotlarmilos marked this pull request as draft May 4, 2026 14:55
@kotlarmiloskotlarmilos changed the title [interp] Report async-suspension locals to the debugger via GetAsyncLocals[interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos changed the title [interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos added this to the 11.0.0 milestone May 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch 3 times, most recently from 35b435b to d41eea4CompareMay 4, 2026 14:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends CoreCLR’s interpreter async-debugging support so the right-side debugger can retrieve live async locals/arguments for interpreted async frames via DacDbiInterfaceImpl::GetAsyncLocals, analogous to the existing JIT DebugInfoStore pipeline.

Changes:

  • Introduces InterpAsyncDebugVar and adds a per-suspension-point { ilVarNum, dataOffset } table to InterpAsyncSuspendData.
  • Emits and persists interpreter async-local debug metadata during EmitSuspend, storing the debug-var tables in the finalized method data.
  • Updates the DAC GetAsyncLocals implementation to detect interpreted methods, decode the suspension-point bytecode via state, and surface interpreter async locals to the debugger.
Show a summary per file
FileDescription
src/coreclr/interpreter/inc/interpretershared.hAdds interpreter async debug-var metadata types/fields carried per suspension point.
src/coreclr/interpreter/compiler.cppCaptures IL var → continuation offset mappings at suspend points and persists them into the unified method allocation.
src/coreclr/debug/daccess/dacdbiimpl.cppImplements interpreter-aware GetAsyncLocals decoding to return async locals for interpreted frames.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 3

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
@kotlarmiloskotlarmilos changed the title [clr-interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from d41eea4 to c873f5aCompareMay 4, 2026 15:19
CopilotAI review requested due to automatic review settings May 4, 2026 15:19
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from c873f5a to 59cef97CompareMay 4, 2026 15:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/interpreter/inc/interpretershared.h Outdated
@kotlarmiloskotlarmilos self-assigned this May 5, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 59cef97 to 8207ffcCompareMay 5, 2026 08:46
CopilotAI review requested due to automatic review settings May 5, 2026 12:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 8207ffc to a275e72CompareMay 5, 2026 14:09
CopilotAI review requested due to automatic review settings May 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from a275e72 to 1f75069CompareMay 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 1f75069 to f1be050CompareMay 5, 2026 14:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new

@noahfalknoahfalk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me modulo maybe special variables are missing from the encoding.

Comment threadsrc/coreclr/interpreter/compiler.cpp
Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
kotlarmilosand others added 2 commits May 12, 2026 16:17
…ocals
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bug info
In InterpCompiler::EmitSuspend, the AsyncContinuationVarInfo emission was guarded
by var < m_numILVars, which excluded the hidden generics-context arg stored at
m_paramArgIndex. Shared-generic async methods would therefore miss the type context
and the debugger could fail to resolve generic types in suspended frames.
Match the JIT's compMap2ILvarNum behavior by emitting an entry for the param arg
with VarNumber = ICorDebugInfo::TYPECTXT_ILNUM when m_methodInfo->args.hasTypeArg().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 593d29b to 38ef4deCompareMay 12, 2026 14:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
…ying on emission order
EmitCode reorders BBs in waves by overlappingEHClauseCount, so the order
INTOP_HANDLE_CONTINUATION_SUSPEND opcodes are emitted does not match the
order EmitSuspend assigned suspensionPointIndex. The previous code appended
to m_suspensionPointIPOffsets via Add() in emission order, which produced
a mismatched index->IP table whenever a method had two awaits at different
EH wave depths. In checked builds the assert caught it; in release the
runtime would resume at the wrong bytecode offset.
Pre-size the table in EmitCode and write entries by suspensionPointIndex.
Add an explicit size assert in FinalizeMethodData.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilos merged commit edcc3af into dotnet:mainMay 13, 2026
112 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@kotlarmilos@BrzVlad@noahfalk@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals - #127757

Merged
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals
May 13, 2026
Merged

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals#127757
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals

Conversation

@kotlarmilos

@kotlarmiloskotlarmilos commented May 4, 2026

Copy link
Copy Markdown
Member

Description

When a managed async method is suspended at an await, the right-side debugger asks for the live values of the user's locals and arguments at that suspension point so they can be displayed on the call stack. The JIT path supplies this through ICorJitInfo::reportAsyncDebugInfo. The interpreter compiler never hits that path, so ICorDebugAsyncFrame::GetArgument returned CORDBG_E_IL_VAR_NOT_AVAILABLE for every IL variable on every interpreted async frame.

This adds a small parallel pipeline for the interpreter. InterpAsyncSuspendData now carries a per suspension point table of {ilVarNum, dataOffset} entries. On the DAC side, GetAsyncLocals detects an interpreter method and decodes the bytecode at startIp + state.

This fixes the following interpreter debugger test failures:

  • Async.AsyncSimple
  • Async.AsyncBreakpoint
  • Async.AsyncBreakpointJmc
  • Async.AsyncGeneric
  • Async.AsyncRecursive
  • Async.AsyncSharedGeneric
  • Async.AsyncV2CallingAsyncV1Iterator
  • Async.AsyncGenericStepInto

CopilotAI review requested due to automatic review settings May 4, 2026 14:54
@kotlarmilos
kotlarmilos marked this pull request as draft May 4, 2026 14:55
@kotlarmiloskotlarmilos changed the title [interp] Report async-suspension locals to the debugger via GetAsyncLocals[interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos changed the title [interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos added this to the 11.0.0 milestone May 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch 3 times, most recently from 35b435b to d41eea4CompareMay 4, 2026 14:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends CoreCLR’s interpreter async-debugging support so the right-side debugger can retrieve live async locals/arguments for interpreted async frames via DacDbiInterfaceImpl::GetAsyncLocals, analogous to the existing JIT DebugInfoStore pipeline.

Changes:

  • Introduces InterpAsyncDebugVar and adds a per-suspension-point { ilVarNum, dataOffset } table to InterpAsyncSuspendData.
  • Emits and persists interpreter async-local debug metadata during EmitSuspend, storing the debug-var tables in the finalized method data.
  • Updates the DAC GetAsyncLocals implementation to detect interpreted methods, decode the suspension-point bytecode via state, and surface interpreter async locals to the debugger.
Show a summary per file
FileDescription
src/coreclr/interpreter/inc/interpretershared.hAdds interpreter async debug-var metadata types/fields carried per suspension point.
src/coreclr/interpreter/compiler.cppCaptures IL var → continuation offset mappings at suspend points and persists them into the unified method allocation.
src/coreclr/debug/daccess/dacdbiimpl.cppImplements interpreter-aware GetAsyncLocals decoding to return async locals for interpreted frames.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 3

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
@kotlarmiloskotlarmilos changed the title [clr-interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from d41eea4 to c873f5aCompareMay 4, 2026 15:19
CopilotAI review requested due to automatic review settings May 4, 2026 15:19
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from c873f5a to 59cef97CompareMay 4, 2026 15:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/interpreter/inc/interpretershared.h Outdated
@kotlarmiloskotlarmilos self-assigned this May 5, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 59cef97 to 8207ffcCompareMay 5, 2026 08:46
CopilotAI review requested due to automatic review settings May 5, 2026 12:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 8207ffc to a275e72CompareMay 5, 2026 14:09
CopilotAI review requested due to automatic review settings May 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from a275e72 to 1f75069CompareMay 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 1f75069 to f1be050CompareMay 5, 2026 14:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new

@noahfalknoahfalk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me modulo maybe special variables are missing from the encoding.

Comment threadsrc/coreclr/interpreter/compiler.cpp
Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
kotlarmilosand others added 2 commits May 12, 2026 16:17
…ocals
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bug info
In InterpCompiler::EmitSuspend, the AsyncContinuationVarInfo emission was guarded
by var < m_numILVars, which excluded the hidden generics-context arg stored at
m_paramArgIndex. Shared-generic async methods would therefore miss the type context
and the debugger could fail to resolve generic types in suspended frames.
Match the JIT's compMap2ILvarNum behavior by emitting an entry for the param arg
with VarNumber = ICorDebugInfo::TYPECTXT_ILNUM when m_methodInfo->args.hasTypeArg().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 593d29b to 38ef4deCompareMay 12, 2026 14:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
…ying on emission order
EmitCode reorders BBs in waves by overlappingEHClauseCount, so the order
INTOP_HANDLE_CONTINUATION_SUSPEND opcodes are emitted does not match the
order EmitSuspend assigned suspensionPointIndex. The previous code appended
to m_suspensionPointIPOffsets via Add() in emission order, which produced
a mismatched index->IP table whenever a method had two awaits at different
EH wave depths. In checked builds the assert caught it; in release the
runtime would resume at the wrong bytecode offset.
Pre-size the table in EmitCode and write entries by suspensionPointIndex.
Add an explicit size assert in FinalizeMethodData.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilos merged commit edcc3af into dotnet:mainMay 13, 2026
112 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@kotlarmilos@BrzVlad@noahfalk@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals - #127757

Merged
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals
May 13, 2026
Merged

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals#127757
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals

Conversation

@kotlarmilos

@kotlarmiloskotlarmilos commented May 4, 2026

Copy link
Copy Markdown
Member

Description

When a managed async method is suspended at an await, the right-side debugger asks for the live values of the user's locals and arguments at that suspension point so they can be displayed on the call stack. The JIT path supplies this through ICorJitInfo::reportAsyncDebugInfo. The interpreter compiler never hits that path, so ICorDebugAsyncFrame::GetArgument returned CORDBG_E_IL_VAR_NOT_AVAILABLE for every IL variable on every interpreted async frame.

This adds a small parallel pipeline for the interpreter. InterpAsyncSuspendData now carries a per suspension point table of {ilVarNum, dataOffset} entries. On the DAC side, GetAsyncLocals detects an interpreter method and decodes the bytecode at startIp + state.

This fixes the following interpreter debugger test failures:

  • Async.AsyncSimple
  • Async.AsyncBreakpoint
  • Async.AsyncBreakpointJmc
  • Async.AsyncGeneric
  • Async.AsyncRecursive
  • Async.AsyncSharedGeneric
  • Async.AsyncV2CallingAsyncV1Iterator
  • Async.AsyncGenericStepInto

CopilotAI review requested due to automatic review settings May 4, 2026 14:54
@kotlarmilos
kotlarmilos marked this pull request as draft May 4, 2026 14:55
@kotlarmiloskotlarmilos changed the title [interp] Report async-suspension locals to the debugger via GetAsyncLocals[interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos changed the title [interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos added this to the 11.0.0 milestone May 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch 3 times, most recently from 35b435b to d41eea4CompareMay 4, 2026 14:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends CoreCLR’s interpreter async-debugging support so the right-side debugger can retrieve live async locals/arguments for interpreted async frames via DacDbiInterfaceImpl::GetAsyncLocals, analogous to the existing JIT DebugInfoStore pipeline.

Changes:

  • Introduces InterpAsyncDebugVar and adds a per-suspension-point { ilVarNum, dataOffset } table to InterpAsyncSuspendData.
  • Emits and persists interpreter async-local debug metadata during EmitSuspend, storing the debug-var tables in the finalized method data.
  • Updates the DAC GetAsyncLocals implementation to detect interpreted methods, decode the suspension-point bytecode via state, and surface interpreter async locals to the debugger.
Show a summary per file
FileDescription
src/coreclr/interpreter/inc/interpretershared.hAdds interpreter async debug-var metadata types/fields carried per suspension point.
src/coreclr/interpreter/compiler.cppCaptures IL var → continuation offset mappings at suspend points and persists them into the unified method allocation.
src/coreclr/debug/daccess/dacdbiimpl.cppImplements interpreter-aware GetAsyncLocals decoding to return async locals for interpreted frames.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 3

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
@kotlarmiloskotlarmilos changed the title [clr-interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from d41eea4 to c873f5aCompareMay 4, 2026 15:19
CopilotAI review requested due to automatic review settings May 4, 2026 15:19
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from c873f5a to 59cef97CompareMay 4, 2026 15:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/interpreter/inc/interpretershared.h Outdated
@kotlarmiloskotlarmilos self-assigned this May 5, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 59cef97 to 8207ffcCompareMay 5, 2026 08:46
CopilotAI review requested due to automatic review settings May 5, 2026 12:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 8207ffc to a275e72CompareMay 5, 2026 14:09
CopilotAI review requested due to automatic review settings May 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from a275e72 to 1f75069CompareMay 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 1f75069 to f1be050CompareMay 5, 2026 14:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new

@noahfalknoahfalk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me modulo maybe special variables are missing from the encoding.

Comment threadsrc/coreclr/interpreter/compiler.cpp
Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
kotlarmilosand others added 2 commits May 12, 2026 16:17
…ocals
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bug info
In InterpCompiler::EmitSuspend, the AsyncContinuationVarInfo emission was guarded
by var < m_numILVars, which excluded the hidden generics-context arg stored at
m_paramArgIndex. Shared-generic async methods would therefore miss the type context
and the debugger could fail to resolve generic types in suspended frames.
Match the JIT's compMap2ILvarNum behavior by emitting an entry for the param arg
with VarNumber = ICorDebugInfo::TYPECTXT_ILNUM when m_methodInfo->args.hasTypeArg().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 593d29b to 38ef4deCompareMay 12, 2026 14:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
…ying on emission order
EmitCode reorders BBs in waves by overlappingEHClauseCount, so the order
INTOP_HANDLE_CONTINUATION_SUSPEND opcodes are emitted does not match the
order EmitSuspend assigned suspensionPointIndex. The previous code appended
to m_suspensionPointIPOffsets via Add() in emission order, which produced
a mismatched index->IP table whenever a method had two awaits at different
EH wave depths. In checked builds the assert caught it; in release the
runtime would resume at the wrong bytecode offset.
Pre-size the table in EmitCode and write entries by suspensionPointIndex.
Add an explicit size assert in FinalizeMethodData.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilos merged commit edcc3af into dotnet:mainMay 13, 2026
112 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@kotlarmilos@BrzVlad@noahfalk@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals - #127757

Merged
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals
May 13, 2026
Merged

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals#127757
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals

Conversation

@kotlarmilos

@kotlarmiloskotlarmilos commented May 4, 2026

Copy link
Copy Markdown
Member

Description

When a managed async method is suspended at an await, the right-side debugger asks for the live values of the user's locals and arguments at that suspension point so they can be displayed on the call stack. The JIT path supplies this through ICorJitInfo::reportAsyncDebugInfo. The interpreter compiler never hits that path, so ICorDebugAsyncFrame::GetArgument returned CORDBG_E_IL_VAR_NOT_AVAILABLE for every IL variable on every interpreted async frame.

This adds a small parallel pipeline for the interpreter. InterpAsyncSuspendData now carries a per suspension point table of {ilVarNum, dataOffset} entries. On the DAC side, GetAsyncLocals detects an interpreter method and decodes the bytecode at startIp + state.

This fixes the following interpreter debugger test failures:

  • Async.AsyncSimple
  • Async.AsyncBreakpoint
  • Async.AsyncBreakpointJmc
  • Async.AsyncGeneric
  • Async.AsyncRecursive
  • Async.AsyncSharedGeneric
  • Async.AsyncV2CallingAsyncV1Iterator
  • Async.AsyncGenericStepInto

CopilotAI review requested due to automatic review settings May 4, 2026 14:54
@kotlarmilos
kotlarmilos marked this pull request as draft May 4, 2026 14:55
@kotlarmiloskotlarmilos changed the title [interp] Report async-suspension locals to the debugger via GetAsyncLocals[interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos changed the title [interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos added this to the 11.0.0 milestone May 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch 3 times, most recently from 35b435b to d41eea4CompareMay 4, 2026 14:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends CoreCLR’s interpreter async-debugging support so the right-side debugger can retrieve live async locals/arguments for interpreted async frames via DacDbiInterfaceImpl::GetAsyncLocals, analogous to the existing JIT DebugInfoStore pipeline.

Changes:

  • Introduces InterpAsyncDebugVar and adds a per-suspension-point { ilVarNum, dataOffset } table to InterpAsyncSuspendData.
  • Emits and persists interpreter async-local debug metadata during EmitSuspend, storing the debug-var tables in the finalized method data.
  • Updates the DAC GetAsyncLocals implementation to detect interpreted methods, decode the suspension-point bytecode via state, and surface interpreter async locals to the debugger.
Show a summary per file
FileDescription
src/coreclr/interpreter/inc/interpretershared.hAdds interpreter async debug-var metadata types/fields carried per suspension point.
src/coreclr/interpreter/compiler.cppCaptures IL var → continuation offset mappings at suspend points and persists them into the unified method allocation.
src/coreclr/debug/daccess/dacdbiimpl.cppImplements interpreter-aware GetAsyncLocals decoding to return async locals for interpreted frames.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 3

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
@kotlarmiloskotlarmilos changed the title [clr-interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from d41eea4 to c873f5aCompareMay 4, 2026 15:19
CopilotAI review requested due to automatic review settings May 4, 2026 15:19
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from c873f5a to 59cef97CompareMay 4, 2026 15:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/interpreter/inc/interpretershared.h Outdated
@kotlarmiloskotlarmilos self-assigned this May 5, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 59cef97 to 8207ffcCompareMay 5, 2026 08:46
CopilotAI review requested due to automatic review settings May 5, 2026 12:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 8207ffc to a275e72CompareMay 5, 2026 14:09
CopilotAI review requested due to automatic review settings May 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from a275e72 to 1f75069CompareMay 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 1f75069 to f1be050CompareMay 5, 2026 14:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new

@noahfalknoahfalk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me modulo maybe special variables are missing from the encoding.

Comment threadsrc/coreclr/interpreter/compiler.cpp
Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
kotlarmilosand others added 2 commits May 12, 2026 16:17
…ocals
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bug info
In InterpCompiler::EmitSuspend, the AsyncContinuationVarInfo emission was guarded
by var < m_numILVars, which excluded the hidden generics-context arg stored at
m_paramArgIndex. Shared-generic async methods would therefore miss the type context
and the debugger could fail to resolve generic types in suspended frames.
Match the JIT's compMap2ILvarNum behavior by emitting an entry for the param arg
with VarNumber = ICorDebugInfo::TYPECTXT_ILNUM when m_methodInfo->args.hasTypeArg().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 593d29b to 38ef4deCompareMay 12, 2026 14:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
…ying on emission order
EmitCode reorders BBs in waves by overlappingEHClauseCount, so the order
INTOP_HANDLE_CONTINUATION_SUSPEND opcodes are emitted does not match the
order EmitSuspend assigned suspensionPointIndex. The previous code appended
to m_suspensionPointIPOffsets via Add() in emission order, which produced
a mismatched index->IP table whenever a method had two awaits at different
EH wave depths. In checked builds the assert caught it; in release the
runtime would resume at the wrong bytecode offset.
Pre-size the table in EmitCode and write entries by suspensionPointIndex.
Add an explicit size assert in FinalizeMethodData.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilos merged commit edcc3af into dotnet:mainMay 13, 2026
112 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@kotlarmilos@BrzVlad@noahfalk@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals - #127757

Merged
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals
May 13, 2026
Merged

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals#127757
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals

Conversation

@kotlarmilos

@kotlarmiloskotlarmilos commented May 4, 2026

Copy link
Copy Markdown
Member

Description

When a managed async method is suspended at an await, the right-side debugger asks for the live values of the user's locals and arguments at that suspension point so they can be displayed on the call stack. The JIT path supplies this through ICorJitInfo::reportAsyncDebugInfo. The interpreter compiler never hits that path, so ICorDebugAsyncFrame::GetArgument returned CORDBG_E_IL_VAR_NOT_AVAILABLE for every IL variable on every interpreted async frame.

This adds a small parallel pipeline for the interpreter. InterpAsyncSuspendData now carries a per suspension point table of {ilVarNum, dataOffset} entries. On the DAC side, GetAsyncLocals detects an interpreter method and decodes the bytecode at startIp + state.

This fixes the following interpreter debugger test failures:

  • Async.AsyncSimple
  • Async.AsyncBreakpoint
  • Async.AsyncBreakpointJmc
  • Async.AsyncGeneric
  • Async.AsyncRecursive
  • Async.AsyncSharedGeneric
  • Async.AsyncV2CallingAsyncV1Iterator
  • Async.AsyncGenericStepInto

CopilotAI review requested due to automatic review settings May 4, 2026 14:54
@kotlarmilos
kotlarmilos marked this pull request as draft May 4, 2026 14:55
@kotlarmiloskotlarmilos changed the title [interp] Report async-suspension locals to the debugger via GetAsyncLocals[interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos changed the title [interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos added this to the 11.0.0 milestone May 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch 3 times, most recently from 35b435b to d41eea4CompareMay 4, 2026 14:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends CoreCLR’s interpreter async-debugging support so the right-side debugger can retrieve live async locals/arguments for interpreted async frames via DacDbiInterfaceImpl::GetAsyncLocals, analogous to the existing JIT DebugInfoStore pipeline.

Changes:

  • Introduces InterpAsyncDebugVar and adds a per-suspension-point { ilVarNum, dataOffset } table to InterpAsyncSuspendData.
  • Emits and persists interpreter async-local debug metadata during EmitSuspend, storing the debug-var tables in the finalized method data.
  • Updates the DAC GetAsyncLocals implementation to detect interpreted methods, decode the suspension-point bytecode via state, and surface interpreter async locals to the debugger.
Show a summary per file
FileDescription
src/coreclr/interpreter/inc/interpretershared.hAdds interpreter async debug-var metadata types/fields carried per suspension point.
src/coreclr/interpreter/compiler.cppCaptures IL var → continuation offset mappings at suspend points and persists them into the unified method allocation.
src/coreclr/debug/daccess/dacdbiimpl.cppImplements interpreter-aware GetAsyncLocals decoding to return async locals for interpreted frames.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 3

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
@kotlarmiloskotlarmilos changed the title [clr-interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from d41eea4 to c873f5aCompareMay 4, 2026 15:19
CopilotAI review requested due to automatic review settings May 4, 2026 15:19
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from c873f5a to 59cef97CompareMay 4, 2026 15:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/interpreter/inc/interpretershared.h Outdated
@kotlarmiloskotlarmilos self-assigned this May 5, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 59cef97 to 8207ffcCompareMay 5, 2026 08:46
CopilotAI review requested due to automatic review settings May 5, 2026 12:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 8207ffc to a275e72CompareMay 5, 2026 14:09
CopilotAI review requested due to automatic review settings May 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from a275e72 to 1f75069CompareMay 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 1f75069 to f1be050CompareMay 5, 2026 14:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new

@noahfalknoahfalk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me modulo maybe special variables are missing from the encoding.

Comment threadsrc/coreclr/interpreter/compiler.cpp
Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
kotlarmilosand others added 2 commits May 12, 2026 16:17
…ocals
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bug info
In InterpCompiler::EmitSuspend, the AsyncContinuationVarInfo emission was guarded
by var < m_numILVars, which excluded the hidden generics-context arg stored at
m_paramArgIndex. Shared-generic async methods would therefore miss the type context
and the debugger could fail to resolve generic types in suspended frames.
Match the JIT's compMap2ILvarNum behavior by emitting an entry for the param arg
with VarNumber = ICorDebugInfo::TYPECTXT_ILNUM when m_methodInfo->args.hasTypeArg().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 593d29b to 38ef4deCompareMay 12, 2026 14:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
…ying on emission order
EmitCode reorders BBs in waves by overlappingEHClauseCount, so the order
INTOP_HANDLE_CONTINUATION_SUSPEND opcodes are emitted does not match the
order EmitSuspend assigned suspensionPointIndex. The previous code appended
to m_suspensionPointIPOffsets via Add() in emission order, which produced
a mismatched index->IP table whenever a method had two awaits at different
EH wave depths. In checked builds the assert caught it; in release the
runtime would resume at the wrong bytecode offset.
Pre-size the table in EmitCode and write entries by suspensionPointIndex.
Add an explicit size assert in FinalizeMethodData.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilos merged commit edcc3af into dotnet:mainMay 13, 2026
112 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@kotlarmilos@BrzVlad@noahfalk@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals - #127757

Merged
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals
May 13, 2026
Merged

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals#127757
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals

Conversation

@kotlarmilos

@kotlarmiloskotlarmilos commented May 4, 2026

Copy link
Copy Markdown
Member

Description

When a managed async method is suspended at an await, the right-side debugger asks for the live values of the user's locals and arguments at that suspension point so they can be displayed on the call stack. The JIT path supplies this through ICorJitInfo::reportAsyncDebugInfo. The interpreter compiler never hits that path, so ICorDebugAsyncFrame::GetArgument returned CORDBG_E_IL_VAR_NOT_AVAILABLE for every IL variable on every interpreted async frame.

This adds a small parallel pipeline for the interpreter. InterpAsyncSuspendData now carries a per suspension point table of {ilVarNum, dataOffset} entries. On the DAC side, GetAsyncLocals detects an interpreter method and decodes the bytecode at startIp + state.

This fixes the following interpreter debugger test failures:

  • Async.AsyncSimple
  • Async.AsyncBreakpoint
  • Async.AsyncBreakpointJmc
  • Async.AsyncGeneric
  • Async.AsyncRecursive
  • Async.AsyncSharedGeneric
  • Async.AsyncV2CallingAsyncV1Iterator
  • Async.AsyncGenericStepInto

CopilotAI review requested due to automatic review settings May 4, 2026 14:54
@kotlarmilos
kotlarmilos marked this pull request as draft May 4, 2026 14:55
@kotlarmiloskotlarmilos changed the title [interp] Report async-suspension locals to the debugger via GetAsyncLocals[interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos changed the title [interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos added this to the 11.0.0 milestone May 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch 3 times, most recently from 35b435b to d41eea4CompareMay 4, 2026 14:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends CoreCLR’s interpreter async-debugging support so the right-side debugger can retrieve live async locals/arguments for interpreted async frames via DacDbiInterfaceImpl::GetAsyncLocals, analogous to the existing JIT DebugInfoStore pipeline.

Changes:

  • Introduces InterpAsyncDebugVar and adds a per-suspension-point { ilVarNum, dataOffset } table to InterpAsyncSuspendData.
  • Emits and persists interpreter async-local debug metadata during EmitSuspend, storing the debug-var tables in the finalized method data.
  • Updates the DAC GetAsyncLocals implementation to detect interpreted methods, decode the suspension-point bytecode via state, and surface interpreter async locals to the debugger.
Show a summary per file
FileDescription
src/coreclr/interpreter/inc/interpretershared.hAdds interpreter async debug-var metadata types/fields carried per suspension point.
src/coreclr/interpreter/compiler.cppCaptures IL var → continuation offset mappings at suspend points and persists them into the unified method allocation.
src/coreclr/debug/daccess/dacdbiimpl.cppImplements interpreter-aware GetAsyncLocals decoding to return async locals for interpreted frames.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 3

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
@kotlarmiloskotlarmilos changed the title [clr-interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from d41eea4 to c873f5aCompareMay 4, 2026 15:19
CopilotAI review requested due to automatic review settings May 4, 2026 15:19
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from c873f5a to 59cef97CompareMay 4, 2026 15:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/interpreter/inc/interpretershared.h Outdated
@kotlarmiloskotlarmilos self-assigned this May 5, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 59cef97 to 8207ffcCompareMay 5, 2026 08:46
CopilotAI review requested due to automatic review settings May 5, 2026 12:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 8207ffc to a275e72CompareMay 5, 2026 14:09
CopilotAI review requested due to automatic review settings May 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from a275e72 to 1f75069CompareMay 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 1f75069 to f1be050CompareMay 5, 2026 14:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new

@noahfalknoahfalk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me modulo maybe special variables are missing from the encoding.

Comment threadsrc/coreclr/interpreter/compiler.cpp
Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
kotlarmilosand others added 2 commits May 12, 2026 16:17
…ocals
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bug info
In InterpCompiler::EmitSuspend, the AsyncContinuationVarInfo emission was guarded
by var < m_numILVars, which excluded the hidden generics-context arg stored at
m_paramArgIndex. Shared-generic async methods would therefore miss the type context
and the debugger could fail to resolve generic types in suspended frames.
Match the JIT's compMap2ILvarNum behavior by emitting an entry for the param arg
with VarNumber = ICorDebugInfo::TYPECTXT_ILNUM when m_methodInfo->args.hasTypeArg().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 593d29b to 38ef4deCompareMay 12, 2026 14:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
…ying on emission order
EmitCode reorders BBs in waves by overlappingEHClauseCount, so the order
INTOP_HANDLE_CONTINUATION_SUSPEND opcodes are emitted does not match the
order EmitSuspend assigned suspensionPointIndex. The previous code appended
to m_suspensionPointIPOffsets via Add() in emission order, which produced
a mismatched index->IP table whenever a method had two awaits at different
EH wave depths. In checked builds the assert caught it; in release the
runtime would resume at the wrong bytecode offset.
Pre-size the table in EmitCode and write entries by suspensionPointIndex.
Add an explicit size assert in FinalizeMethodData.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilos merged commit edcc3af into dotnet:mainMay 13, 2026
112 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@kotlarmilos@BrzVlad@noahfalk@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals - #127757

Merged
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals
May 13, 2026
Merged

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals#127757
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals

Conversation

@kotlarmilos

@kotlarmiloskotlarmilos commented May 4, 2026

Copy link
Copy Markdown
Member

Description

When a managed async method is suspended at an await, the right-side debugger asks for the live values of the user's locals and arguments at that suspension point so they can be displayed on the call stack. The JIT path supplies this through ICorJitInfo::reportAsyncDebugInfo. The interpreter compiler never hits that path, so ICorDebugAsyncFrame::GetArgument returned CORDBG_E_IL_VAR_NOT_AVAILABLE for every IL variable on every interpreted async frame.

This adds a small parallel pipeline for the interpreter. InterpAsyncSuspendData now carries a per suspension point table of {ilVarNum, dataOffset} entries. On the DAC side, GetAsyncLocals detects an interpreter method and decodes the bytecode at startIp + state.

This fixes the following interpreter debugger test failures:

  • Async.AsyncSimple
  • Async.AsyncBreakpoint
  • Async.AsyncBreakpointJmc
  • Async.AsyncGeneric
  • Async.AsyncRecursive
  • Async.AsyncSharedGeneric
  • Async.AsyncV2CallingAsyncV1Iterator
  • Async.AsyncGenericStepInto

CopilotAI review requested due to automatic review settings May 4, 2026 14:54
@kotlarmilos
kotlarmilos marked this pull request as draft May 4, 2026 14:55
@kotlarmiloskotlarmilos changed the title [interp] Report async-suspension locals to the debugger via GetAsyncLocals[interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos changed the title [interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos added this to the 11.0.0 milestone May 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch 3 times, most recently from 35b435b to d41eea4CompareMay 4, 2026 14:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends CoreCLR’s interpreter async-debugging support so the right-side debugger can retrieve live async locals/arguments for interpreted async frames via DacDbiInterfaceImpl::GetAsyncLocals, analogous to the existing JIT DebugInfoStore pipeline.

Changes:

  • Introduces InterpAsyncDebugVar and adds a per-suspension-point { ilVarNum, dataOffset } table to InterpAsyncSuspendData.
  • Emits and persists interpreter async-local debug metadata during EmitSuspend, storing the debug-var tables in the finalized method data.
  • Updates the DAC GetAsyncLocals implementation to detect interpreted methods, decode the suspension-point bytecode via state, and surface interpreter async locals to the debugger.
Show a summary per file
FileDescription
src/coreclr/interpreter/inc/interpretershared.hAdds interpreter async debug-var metadata types/fields carried per suspension point.
src/coreclr/interpreter/compiler.cppCaptures IL var → continuation offset mappings at suspend points and persists them into the unified method allocation.
src/coreclr/debug/daccess/dacdbiimpl.cppImplements interpreter-aware GetAsyncLocals decoding to return async locals for interpreted frames.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 3

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
@kotlarmiloskotlarmilos changed the title [clr-interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from d41eea4 to c873f5aCompareMay 4, 2026 15:19
CopilotAI review requested due to automatic review settings May 4, 2026 15:19
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from c873f5a to 59cef97CompareMay 4, 2026 15:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/interpreter/inc/interpretershared.h Outdated
@kotlarmiloskotlarmilos self-assigned this May 5, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 59cef97 to 8207ffcCompareMay 5, 2026 08:46
CopilotAI review requested due to automatic review settings May 5, 2026 12:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 8207ffc to a275e72CompareMay 5, 2026 14:09
CopilotAI review requested due to automatic review settings May 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from a275e72 to 1f75069CompareMay 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 1f75069 to f1be050CompareMay 5, 2026 14:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new

@noahfalknoahfalk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me modulo maybe special variables are missing from the encoding.

Comment threadsrc/coreclr/interpreter/compiler.cpp
Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
kotlarmilosand others added 2 commits May 12, 2026 16:17
…ocals
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bug info
In InterpCompiler::EmitSuspend, the AsyncContinuationVarInfo emission was guarded
by var < m_numILVars, which excluded the hidden generics-context arg stored at
m_paramArgIndex. Shared-generic async methods would therefore miss the type context
and the debugger could fail to resolve generic types in suspended frames.
Match the JIT's compMap2ILvarNum behavior by emitting an entry for the param arg
with VarNumber = ICorDebugInfo::TYPECTXT_ILNUM when m_methodInfo->args.hasTypeArg().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 593d29b to 38ef4deCompareMay 12, 2026 14:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
…ying on emission order
EmitCode reorders BBs in waves by overlappingEHClauseCount, so the order
INTOP_HANDLE_CONTINUATION_SUSPEND opcodes are emitted does not match the
order EmitSuspend assigned suspensionPointIndex. The previous code appended
to m_suspensionPointIPOffsets via Add() in emission order, which produced
a mismatched index->IP table whenever a method had two awaits at different
EH wave depths. In checked builds the assert caught it; in release the
runtime would resume at the wrong bytecode offset.
Pre-size the table in EmitCode and write entries by suspensionPointIndex.
Add an explicit size assert in FinalizeMethodData.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilos merged commit edcc3af into dotnet:mainMay 13, 2026
112 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@kotlarmilos@BrzVlad@noahfalk@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals - #127757

Merged
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals
May 13, 2026
Merged

[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocals#127757
kotlarmilos merged 3 commits into
dotnet:mainfrom
kotlarmilos:interp-async-debug-locals

Conversation

@kotlarmilos

@kotlarmiloskotlarmilos commented May 4, 2026

Copy link
Copy Markdown
Member

Description

When a managed async method is suspended at an await, the right-side debugger asks for the live values of the user's locals and arguments at that suspension point so they can be displayed on the call stack. The JIT path supplies this through ICorJitInfo::reportAsyncDebugInfo. The interpreter compiler never hits that path, so ICorDebugAsyncFrame::GetArgument returned CORDBG_E_IL_VAR_NOT_AVAILABLE for every IL variable on every interpreted async frame.

This adds a small parallel pipeline for the interpreter. InterpAsyncSuspendData now carries a per suspension point table of {ilVarNum, dataOffset} entries. On the DAC side, GetAsyncLocals detects an interpreter method and decodes the bytecode at startIp + state.

This fixes the following interpreter debugger test failures:

  • Async.AsyncSimple
  • Async.AsyncBreakpoint
  • Async.AsyncBreakpointJmc
  • Async.AsyncGeneric
  • Async.AsyncRecursive
  • Async.AsyncSharedGeneric
  • Async.AsyncV2CallingAsyncV1Iterator
  • Async.AsyncGenericStepInto

CopilotAI review requested due to automatic review settings May 4, 2026 14:54
@kotlarmilos
kotlarmilos marked this pull request as draft May 4, 2026 14:55
@kotlarmiloskotlarmilos changed the title [interp] Report async-suspension locals to the debugger via GetAsyncLocals[interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos changed the title [interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmiloskotlarmilos added this to the 11.0.0 milestone May 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch 3 times, most recently from 35b435b to d41eea4CompareMay 4, 2026 14:59

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends CoreCLR’s interpreter async-debugging support so the right-side debugger can retrieve live async locals/arguments for interpreted async frames via DacDbiInterfaceImpl::GetAsyncLocals, analogous to the existing JIT DebugInfoStore pipeline.

Changes:

  • Introduces InterpAsyncDebugVar and adds a per-suspension-point { ilVarNum, dataOffset } table to InterpAsyncSuspendData.
  • Emits and persists interpreter async-local debug metadata during EmitSuspend, storing the debug-var tables in the finalized method data.
  • Updates the DAC GetAsyncLocals implementation to detect interpreted methods, decode the suspension-point bytecode via state, and surface interpreter async locals to the debugger.
Show a summary per file
FileDescription
src/coreclr/interpreter/inc/interpretershared.hAdds interpreter async debug-var metadata types/fields carried per suspension point.
src/coreclr/interpreter/compiler.cppCaptures IL var → continuation offset mappings at suspend points and persists them into the unified method allocation.
src/coreclr/debug/daccess/dacdbiimpl.cppImplements interpreter-aware GetAsyncLocals decoding to return async locals for interpreted frames.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 3

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
@kotlarmiloskotlarmilos changed the title [clr-interp] Report async locals to the debugger via GetAsyncLocals[clr-interp] Report async-suspension locals to the debugger via GetAsyncLocalsMay 4, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from d41eea4 to c873f5aCompareMay 4, 2026 15:19
CopilotAI review requested due to automatic review settings May 4, 2026 15:19
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from c873f5a to 59cef97CompareMay 4, 2026 15:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment threadsrc/coreclr/interpreter/inc/interpretershared.h Outdated
@kotlarmiloskotlarmilos self-assigned this May 5, 2026
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 59cef97 to 8207ffcCompareMay 5, 2026 08:46
CopilotAI review requested due to automatic review settings May 5, 2026 12:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 8207ffc to a275e72CompareMay 5, 2026 14:09
CopilotAI review requested due to automatic review settings May 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from a275e72 to 1f75069CompareMay 5, 2026 14:10
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 1f75069 to f1be050CompareMay 5, 2026 14:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new

@noahfalknoahfalk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me modulo maybe special variables are missing from the encoding.

Comment threadsrc/coreclr/interpreter/compiler.cpp
Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
kotlarmilosand others added 2 commits May 12, 2026 16:17
…ocals
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bug info
In InterpCompiler::EmitSuspend, the AsyncContinuationVarInfo emission was guarded
by var < m_numILVars, which excluded the hidden generics-context arg stored at
m_paramArgIndex. Shared-generic async methods would therefore miss the type context
and the debugger could fail to resolve generic types in suspended frames.
Match the JIT's compMap2ILvarNum behavior by emitting an entry for the param arg
with VarNumber = ICorDebugInfo::TYPECTXT_ILNUM when m_methodInfo->args.hasTypeArg().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilosforce-pushed the interp-async-debug-locals branch from 593d29b to 38ef4deCompareMay 12, 2026 14:19

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 2

Comment threadsrc/coreclr/interpreter/compiler.cpp Outdated
Comment threadsrc/coreclr/interpreter/compiler.cpp
…ying on emission order
EmitCode reorders BBs in waves by overlappingEHClauseCount, so the order
INTOP_HANDLE_CONTINUATION_SUSPEND opcodes are emitted does not match the
order EmitSuspend assigned suspensionPointIndex. The previous code appended
to m_suspensionPointIPOffsets via Add() in emission order, which produced
a mismatched index->IP table whenever a method had two awaits at different
EH wave depths. In checked builds the assert caught it; in release the
runtime would resume at the wrong bytecode offset.
Pre-size the table in EmitCode and write entries by suspensionPointIndex.
Add an explicit size assert in FinalizeMethodData.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos
kotlarmilos merged commit edcc3af into dotnet:mainMay 13, 2026
112 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@kotlarmilos@BrzVlad@noahfalk@jakobbotsch