-
Notifications
You must be signed in to change notification settings - Fork 5.5k
[cdac] x86: implement IGCInfoDecoder.EnumerateLiveSlots; unblock GCRoots stackref tests #129547
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
Closed
Closed
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
1692fbe
[cdac] x86: implement IGCInfoDecoder.EnumerateLiveSlots; unblock GCRo…
687b4f6
docs: move GCInfo.md x86 details to dedicated section
4fdd75d
[cdac] x86 GetInterruptibleRanges: implement properly + correct doc
cb82b21
[cdac] Rename x86 GCInfo.cs to X86GCInfo.cs to match the class name
e93d40b
[cdac] x86 VarPtr-tracked locals: pass through both interior and pinn…
ed26cee
[cdac] x86 GCTransition: store isThis/iptr in transition ctors
5ec697e
[cdac] x86 GcArgTable: fix curOffs scope in GetTransitionsEbpFrame
53be78a
[cdac] x86 EnumerateLiveSlots: stress-test correctness fixes
a899035
[cdac] x86 EnumerateLiveSlots: handle ParentOfFuncletStackFrame and A…
be6863e
[cdac] x86 GCInfo: trim native code references and document Enumerate…
a11403b
[cdac] x86 GCInfo: keep file name as GCInfo.cs
b1efe0b
[cdac] GCInfo.md: x86 EnumerateLiveSlots and GetInterruptibleRanges a…
90fc47c
[cdac] GCInfo.md: keep x86 specifics confined to the x86 specifics se…
03af067
[cdac] GCInfo.md: drop x86 Supported APIs table
f895e3c
[cdac] x86 GcArgTable: drop redundant curOffs scope comment
9b4a17b
[cdac] DumpTests.targets: drop local-dev DebuggeeFilter property
eb136dc
[cdac] x86 GCInfo: address PR review feedback
49fd924
[cdac] x86 GcArgTable: emit negative stack-depth delta at partial-int…
9da24a4
[cdac] x86 ApplyPointerTransition: respect IsPtr=false on non-pointer…
a434af6
Merge branch 'main' of https://github.com/dotnet/runtime into cdac-x8…
1e9044e
[cdac] runtime-diagnostics pipeline: add windows_x86 to cDAC stress t…
4912495
[cdac] x86 EnumerateLiveSlots: bias ESP-frame untracked/VarPtr slots …
67213d8
[cdac] x86 ScanDynamicHelperFrame: swap ObjectArg / ObjectArg2 offsets
1326374
[cdac] x86 GCInfo: address PR review feedback round 2
c9534e5
[cdac] x86 stress fixes uncovered by Helix CI
bc1e143
[cdac] x86 GCRefMap: use OffsetOfArgs for stack-arg positions
017b1fc
[cdac] Document known x86 stress flake (#129545/#129546 GC hole)
9f800b0
[cdac] x86 stress flake doc: correct attribution (not #129545/#129546)
a941c32
[cdac] x86 GCArgTable: case 0xFB code-delta is += not =
18efd15
[cdac] x86 GCArgTable: don't emit spurious call for 'this-pointer' tag
c0d21cc
[cdac] GCInfo.md: refresh x86 specifics for recent decoder fixes
31a6f43
[cdac] x86 stack-walker: compute cbStackPop in transition Frames
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,12 +40,9 @@ uint GetSizeOfStackParameterArea(IGCInfoHandle handle); | |
| uint GetCalleePoppedArgumentsSize(IGCInfoHandle handle); | ||
|
|
||
| // Returns the list of interruptible code offset ranges from the GCInfo | ||
| // (not implemented for x86 — x86 encodes per-offset transitions rather than explicit ranges). | ||
| IReadOnlyList<InterruptibleRange> GetInterruptibleRanges(IGCInfoHandle handle); | ||
|
|
||
| // Returns all live GC slots at the given instruction offset | ||
| // (not implemented for x86 — see X86GCInfo for the underlying transition data; the cDAC | ||
| // adapter is future work). | ||
| IReadOnlyList<LiveSlot> EnumerateLiveSlots(IGCInfoHandle handle, uint instructionOffset, GcSlotEnumerationOptions options); | ||
| ``` | ||
|
|
||
|
|
@@ -603,3 +600,51 @@ IReadOnlyList<LiveSlot> EnumerateLiveSlots(IGCInfoHandle handle, | |
| // Collect each live slot into a list and return it. | ||
| } | ||
| ``` | ||
|
|
||
|
|
||
| ## x86 specifics | ||
|
|
||
| x86 uses the legacy bit-packed `InfoHdr` byte-stream encoding (`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`) rather than the modern `GcInfoDecoder` shared by all other architectures. The cDAC decoder lives at `src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/` and is shared with the x86 stack walker. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some of this looks like cDAC implementation details which should move to cDAC docs or code. |
||
|
|
||
| A few API behaviors are worth calling out: | ||
|
|
||
| - `GetSizeOfStackParameterArea` always returns 0 -- x86 has no separate outgoing-argument scratch area; pushed args are reported directly through the per-offset transition stream. | ||
| - `GetInterruptibleRanges` reports one range covering the post-prolog body for fully-interruptible methods, or a single-byte range per call site for partially-interruptible methods. Consumed by `StackWalk_1.WalkStackReferences` for the catch-handler PC override (x86 uses the funclet EH model, see PR [#115957](https://github.com/dotnet/runtime/pull/115957)). | ||
| - `EnumerateLiveSlots` mirrors `EnumGcRefsX86`; see below. | ||
|
|
||
| ### `EnumerateLiveSlots` behavior | ||
|
|
||
| Mirrors `EnumGcRefsX86` (gc_unwind_x86.inl), `scanArgRegTableI` (fully-interruptible) and `scanArgRegTable` (partially-interruptible). | ||
|
|
||
| Early-return gates (no slots reported): | ||
| - `IsParentOfFuncletStackFrame` — the funclet sharing this parent's locals will report them itself. | ||
| - Code offset is in the prolog or any epilog (only reachable via `IsExecutionAborted`; GC info does not describe these regions). | ||
| - `IsExecutionAborted` and the method is not fully interruptible. | ||
|
|
||
| Filter-funclet `SuppressUntrackedSlots` is honored within the untracked-locals step (the parent frame already reported them) but does not gate the rest of the walk. | ||
|
|
||
| Sources of live slots: | ||
| - **Untracked frame locals** — always live for the entire method body. Encoded as signed delta-from-previous offsets in the untracked table. On EBP frames the encoded value resolves directly to `EBP + stkOffs` (`FRAMEREG_REL`). On ESP frames the encoded value is `argBase`-relative where `argBase = ESP + pushedSize` (mirrors `EnumGcRefsX86`); the decoder rebases to a true `SP_REL` offset by adding the pushed-arg size computed at the queried instruction offset. | ||
| - **VarPtr-tracked locals** — live when the lifetime-check offset is within `[BeginOffset, EndOffset)`. EBP-frame offsets are stored as their negated form (mirrors `if (info.ebpFrame) stkOffs = -stkOffs`); ESP-frame offsets receive the same `pushedSize` bias as untracked locals. Non-active frames evaluate the lifetime at `instructionOffset - 1` because a variable can be dead at the return address (e.g. when the call is the last instruction of a try and the return is the catch-block jump target). | ||
| - **Live registers** — accumulated from the LIVE/DEAD transition stream up to the queried offset. Callee-saved registers (EBX/EBP/ESI/EDI) are reported when execution will continue; callee-trashed scratch (EAX/ECX/EDX) is reported only on the active leaf frame. On non-leaf frames register liveness is evaluated at the instruction *before* the call (`regOffset = instructionOffset - 1`) since liveness can change across calls. | ||
| - **Pushed pointer args** — for fully-interruptible code, accumulated from the PUSH/POP transition stream. Non-pointer pushes (`IsPtr = false`) still bump the stack depth (so subsequent pushed-ptr indices stay aligned) but do not contribute a slot. At emit time, once `finalDepth` is known, each tracked push is reported as a positive SP-relative offset: `addr = ESP_call + (finalDepth - 1 - pushIndex) * sizeof(DWORD)` (mirrors `pPendingArgFirst - i * sizeof(DWORD)` in `EnumGcRefsX86`). The translation must be deferred because subsequent pushes/pops change `finalDepth`. For partially-interruptible call sites, slots come from the matching `GcTransitionCall` instead: explicit per-pointer offsets in the huge (0xFB) encoding, or a uint32 `ArgMask` / `IArgs` bitmap walked low-to-high with `addr = ESP + i * sizeof(DWORD)` for the tiny / small / medium / large encodings. | ||
| - **`IsParentOfFuncletStackFrame`** suppresses all reporting from the parent: the funclet itself reports the shared locals via `EnumerateLiveSlots` on the funclet frame. | ||
|
|
||
| When `ReportFPBasedSlotsOnly` is set, the result list is filtered to drop register slots and any stack slot whose base is not the frame register (matching `GCInfoDecoder.ReportSlot`). | ||
|
|
||
| ### Encoding correctness notes (x86) | ||
|
|
||
| A few subtleties of the legacy byte-stream encoding caught during stress validation, mirrored from native and worth remembering when modifying the decoder: | ||
|
|
||
| - **Huge call-site (`0xFB`) code-delta is cumulative.** The uint32 code delta is `curOffs += delta`, not `curOffs = delta`. Assigning loses all preceding offset accumulation and corrupts every subsequent call site. | ||
| - **Partial-EBP this-pointer tag (`val & 0x80 == 0 && val & 0x0F == 0`).** This encodes the callee-saved register holding `this` at the next call site; native (`gc_unwind_x86.inl` ~line 970) sets `thisPtrReg` only and does *not* record a call entry. Emitting a `GcTransitionCall` at the current offset would overwrite the real call site's `CallRegisters` when both fall at the same `curOffs`. | ||
| - **Partial-interrupt EBP-less call sites emit a negative stack-depth delta** (callee-popped args reverse the prior pushes); the transition stream is generated accordingly. | ||
| - **`0xC0..0xCF` partial-interruptible byte range** in the huge encoding is a call entry that names only callee-saved registers (no pointer args), distinct from `0xFD..0xFF`. | ||
|
|
||
| ### Deferred edges | ||
|
|
||
| These do not affect the GC root scan / `WalkStackReferences` path validated by the cDAC stress suite, but are noted for future work: | ||
|
|
||
| - `info.thisPtrResult` reporting for synchronized methods on the `!willContinueExecution` path (the regular live-register report covers `willContinueExecution`, which is what stress exercises). | ||
| - VarPtr `0x2` legacy-encoder "this" bit (the modern x86 JIT uses `0x2` only for pinned, which we already pass through; the legacy "this" interpretation never appears in code from the current JIT). | ||
| - `IPtrMask` (`0xF0`) interior-pointer bitmaps for pushed args — accepted by the decoder as informational, but the bitmap is not yet applied to pushed-arg slots. Only relevant on the partial-interruptible ESP-frame path; in practice the current x86 JIT rarely emits these. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This doesn't appear detailed enough that someone could reproduce a parser solely by reading the docs. If the doc gets really big because it needs to describe complex formats that should serve as a gut-check for us on whether we are comfortable with this level of complexity.