镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b0bfb82
Compact Wasm ReadyToRun GC and unwind metadata
davidwrighton Oct 14, 2026
7a25adf
Exclude tracked-root decoding from Wasm ReadyToRun
davidwrighton Oct 14, 2026
6148d5d
Version standalone Wasm GC and unwind metadata encoding
davidwrighton Oct 14, 2026
d640af8
Merge upstream/main into split/wasm-gc-metadata
davidwrighton Oct 20, 2026
b897307
Compact shared GC slot encoding and tune Wasm metadata for Brotli
davidwrighton Oct 21, 2026
b1b2a87
Merge upstream/main into split/wasm-gc-metadata
davidwrighton Oct 21, 2026
b7b45d8
Require current GCInfo in the runtime and remove legacy Wasm decoding
davidwrighton Oct 21, 2026
0d5a38e
Optimize Wasm GC slot encoding for pinned object roots
davidwrighton Oct 22, 2026
3b4daf9
Use interpreter GC decoding when inspecting interpreted frames
davidwrighton Oct 27, 2026
d2c9728
Initialize the implicit GC frame register for frameless Wasm methods
davidwrighton Oct 27, 2026
763652b
Merge upstream/main into split/wasm-gc-metadata
davidwrighton Oct 27, 2026
6e79f6d
Merge upstream main and move compact GCInfo to ReadyToRun 34.0
davidwrighton Nov 3, 2026
eec9271
Implement cDAC ISOSDacInterface18 GCInfo inspection
davidwrighton Nov 4, 2026
51a58db
Normalize cDAC GCInfo inspection stack bases
davidwrighton Nov 4, 2026
c23c10a
Merge branch 'main' into split/wasm-gc-metadata
davidwrighton Nov 4, 2026
5b29fbf
Fix packed Wasm unwind decoding in the NativeAOT object writer
davidwrighton Nov 4, 2026
1b65956
Preserve the register-presence bit in legacy interpreter GCInfo
davidwrighton Nov 4, 2026
31e4b57
Denormalize reverse-PInvoke frame offsets in the R2R GCInfo reader
davidwrighton Nov 4, 2026
ddee9c7
Document the GCInfo v6 slot-table encoding
davidwrighton Nov 4, 2026
ac165a3
Identify the GCInfo v6 decoder helpers
davidwrighton Nov 4, 2026
534f2f3
Select compact stack-slot decoding by GCInfo version
davidwrighton Nov 4, 2026
367e299
Map .NET 12 to GCInfo v6 in SOS
davidwrighton Nov 4, 2026
76899d0
Fix pre-existing GCInfo decoder correctness issues
davidwrighton Nov 4, 2026
e496a22
Preserve the cDAC reverse-PInvoke decoding boundary
davidwrighton Nov 4, 2026
498ab2d
Merge branch 'main' of https://github.com/dotnet/runtime into split/w…
davidwrighton Nov 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 86 additions & 7 deletions docs/design/coreclr/botr/readytorun-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,19 +507,98 @@ value containing the minimum WebAssembly function table index for the image.

### UnwindInfo (wasm)

On WebAssembly, the unwind info blob associated with each `RUNTIME_FUNCTION` entry is encoded as
two consecutive ULEB128 values:

| Order | Encoding | Value
|------:|:---------|:-----
| 1 | ULEB128 | Frame size in bytes (the number of bytes to unwind from the stack)
| 2 | ULEB128 | Virtual IP count divided by 2 (the number of virtual IPs logically present in the function, halved)
On WebAssembly, the unwind info blob associated with each `RUNTIME_FUNCTION` entry is a single
ULEB128-encoded unsigned 64-bit integer. It interleaves the frame size divided by 16 and the
virtual IP count divided by 2:

| Encoded bits | Value
|-------------:|:-----
| 3:0 | Frame size in 16-byte units, bits 3:0
| 7:4 | Virtual IP count in 2-IP units, bits 3:0
| 11:8 | Frame size in 16-byte units, bits 7:4
| 15:12 | Virtual IP count in 2-IP units, bits 7:4
| 31:16 | Frame size in 16-byte units, bits 23:8
| 47:32 | Virtual IP count in 2-IP units, bits 23:8

The largest representable frame size is `2^28 - 16` bytes; the largest virtual IP count is
`2^25 - 2`. The encoded value requires at most seven bytes, and one byte when the frame size
is below 256 bytes and the virtual IP count is below 16.

ReadyToRun format 34.0 uses this packed unwind data and the compact Wasm GC encoding described
below. The runtime requires ReadyToRun format 34.0 or later on every architecture and only
decodes GCInfo v6. Earlier images must be regenerated. Inspection tools retain older non-Wasm
format support, but reject earlier Wasm images before interpreting their unwind or GC metadata.

The virtual IP count (after multiplying by 2) gives the span of virtual IPs covered by this
function or funclet. All virtual IPs are forced to even numbers so that the runtime can force all virtual
ips to have odd numbers and fit into the address space in a manner which cannot conflict with either interpreter
IPs or PortableEntryPoint structures.

### GC info

ReadyToRun format 34.0 uses GCInfo v6. On non-Wasm platforms using the shared GCInfo encoding,
tracked and untracked stack slots are each sorted in descending flag order:
pinned interior pointers, pinned object references, interior pointers, then ordinary object
references.

Register slots encode their interior/pinned flags before the register number. Slots are sorted
in descending flag order, so all pinned slots precede unpinned slots. The first register carries
both flag bits. Subsequent registers carry both bits while the preceding slot is pinned, only
the interior bit after an unpinned interior slot, and no flag bits after an ordinary reference.
Once an unpinned slot has been seen, the pinned bit is therefore implicitly zero.
The first register in each flag group uses an absolute register number; subsequent registers
with the same flags use the difference from the preceding register number minus one.

Register numbers and deltas use at most two chunks. The first chunk contains `ENCBASE` low
payload bits followed by one continuation bit. If set, the second chunk contains all remaining
bits of the target register number, without another continuation bit:

| Target | Register bits | Absolute ENCBASE / total bits | Delta ENCBASE / total bits |
| --- | --- | --- | --- |
| x64 (including APX R16-R31), Arm64, LoongArch64, RISC-V64 | 5 | 3 / 4 or 6 | 2 / 3 or 6 |
| Arm32 | 4 | 2 / 3 or 5 | 1 / 2 or 5 |
| Shared-format x86 | 3 | 3 / 4 | 3 / 4 |

This reserves x64 encoding space for APX roots without changing register allocation or
runtime context support for them. Legacy x86 GCInfo uses its separate, unchanged format.

Stack slots use the same shrinking flag bits as registers: two bits initially and after a
pinned slot, only the interior bit after an unpinned interior slot, and no flag bits after an
ordinary reference. Within each flag group, slots are sorted by base, SP before Frame, then
by ascending offset. Each non-Wasm slot has one base bit: `0` for SP-relative and `1` for
frame-relative. Caller-SP-relative slots are not represented in v6.

The first slot of each flags/base combination has a signed absolute stack offset, followed
by unsigned offset deltas for the remaining slots in that group. Flags, base, and offset state
reset at the tracked/untracked boundary. Platform-specific offset normalization is unchanged.

### GC info (wasm)

Wasm GC slot tables omit the register-presence bit and per-slot stack base: roots are implicitly
frame-relative, with offsets stored in 4-byte pointer units. Both header forms implicitly use
normalized zero for the default frame register: neither its presence bit nor its register
number is encoded. Fat headers therefore contain nine flag bits instead of ten. The prolog
size is also implicit at one virtual-IP unit; its encoded size-minus-one field is omitted.
Wasm XORs the pinned flag (`0x2`) before sorting and encoding stack slot flags. The descending
encoded order is therefore interior pointers (`11`), ordinary object references (`10`),
pinned interior pointers (`01`), then pinned object references (`00`). Pinned object references
are the most common Wasm roots. The first slot carries two flag bits; subsequent slots carry
two bits after an unpinned slot, one bit after a pinned interior pointer, and no flag bits after
a pinned object reference. Decoders XOR `0x2` back to recover the semantic flags.
Offset grouping and tracked/untracked state resets are unchanged.

Wasm slim headers imply zero safe points and omit the count. Methods with nonzero safe-point
counts must use fat headers, which retain the count and safe-point data. Non-Wasm slim headers
retain their safe-point counts.

Wasm v6 uses ENCBASE 5 for code lengths, 7 for untracked-slot counts, 3 for safe-point counts,
and 4 for reverse-P/Invoke frame offsets. These values favor whole-image Brotli compression
rather than the smallest uncompressed GC payload.

Wasm ReadyToRun reports only untracked roots. The zero tracked-slot count remains in the
stream, but its runtime decoder omits tracked-slot descriptor and lifetime parsing.
The interpreter retains tracked-root support and its own register-free encoding.

## ReadyToRunSectionType.MethodDefEntryPoints

This section contains a [native format sparse array](#sparse-array) that maps methoddef rows to
Expand Down
5 changes: 4 additions & 1 deletion docs/design/datacontracts/ExecutionManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,10 @@ For R2R images, `hasFlagByte` is always `false`.

* For R2R code (`ReadyToRunJitManager`), the `GCInfo` is stored directly after the `UnwindData`. This in turn is found by looking up the `UnwindInfo` (`RUNTIME_FUNCTION`) and reading the `UnwindData` offset. We find the `UnwindInfo` as described above in `IExecutionManager.GetUnwindInfo`. Once we have the relevant unwind data, we calculate the size of the unwind data and return a pointer to the following byte (first byte of the GCInfo). The size of the unwind data is a platform specific. See src/coreclr/vm/codeman.cpp GetUnwindDataBlob for more details.
* The `GCInfoVersion` of R2R code is mapped from the R2R MajorVersion and MinorVersion which is read from the ReadyToRunHeader which itself is read from the ReadyToRunInfo (can be found as in GetMethodInfo). The current GCInfoVersion mapping is:
* MajorVersion >= 11 and MajorVersion < 15 => 4
* MajorVersion < 11 => 3
* MajorVersion >= 11 and MajorVersion < 21 => 4
* MajorVersion >= 21 and MajorVersion < 34 => 5
* MajorVersion >= 34 => 6

* For interpreted code (`InterpreterJitManager`), a pointer to the `GCInfo` is stored on the `InterpreterRealCodeHeader`, accessed via nibble map lookup as with the EE JitManager. The `GCInfoVersion` is defined by the runtime global `GCInfoVersion`. The GC info is decoded using interpreter-specific decoding (`DecodeInterpreterGCInfo`).

Expand Down
157 changes: 142 additions & 15 deletions docs/design/datacontracts/GCInfo.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,28 @@ This contract is for fetching information related to GCInfo associated with nati
The GCInfo contract has platform specific implementations as GCInfo differs per architecture. With the exception of x86, all platforms have a common encoding scheme with different encoding lengths and normalization functions for data. x86 uses an entirely different scheme which is partially supported by this contract.

WebAssembly uses the common decoder with `Wasm32GcInfoEncoding` traits from
`src/coreclr/inc/gcinfotypes.h`: code lengths/offsets and stack offsets are
unscaled, the code-length encoding base is 6, and there is no fixed stack
`src/coreclr/inc/gcinfotypes.h`: code lengths/offsets are unscaled, stack offsets
are in four-byte pointer units, the code-length encoding base is 5, and there is no fixed stack
parameter scratch area. The platform encoding has no interruptible-range
count in its fat header (`HAS_INTERRUPTIBLE_RANGES` is false).
count in its fat header (`HAS_INTERRUPTIBLE_RANGES` is false). Both header forms omit the
frame-register presence bit and register value,
implicitly using normalized register zero (physical register 2). The prolog-size-minus-one
field is omitted and its normalized prolog size is implicitly one when needed for
GS-cookie or generic-context validity. Slim headers imply zero safe points and omit the
count; nonzero safe-point counts require fat headers and remain explicitly encoded.
Wasm stack-slot flags are sorted and encoded after XOR with the pinned bit (`0x2`):
interior, ordinary object, pinned interior, then pinned object. The first slot has two
flag bits; later slots have two after an unpinned root, one after a pinned interior root,
and none after a pinned object root. Decoding restores the semantic pinned bit with
the same XOR. Tracked and untracked sections reset their flag and offset state independently.
`DecodeInterpreterGCInfo` uses the separate interpreter traits even on
WebAssembly, including its code-length encoding base of 8 and its
interruptible-range count field.

The runtime uses GCInfo v6 exclusively. This contract also retains non-Wasm GCInfo v5
decoding to support older runtimes. WebAssembly has only the v6 platform encoding;
there is no legacy WebAssembly decoding path.

## APIs of contract

```csharp
Expand Down Expand Up @@ -198,6 +212,22 @@ Constants:

The GCInfo format consists of a header structure and following data types. The header is either 'slim' for simple methods that can use the compact encoding scheme or a 'fat' header containing more details.

The tables and decoding pseudocode below describe the common layout. Wasm platform GCInfo
version 6 omits `UsingStackBaseRegister`, `StackBaseRegister`, and `PrologSize` as described
above, and omits `NumSafePoints` only for slim headers. Its fat `HeaderFlags` occupy nine
Comment on lines +215 to +217
bits: bit `0x40` is omitted from the stream, higher flag bits shift down one position, and
the decoder restores `GC_INFO_HAS_STACK_BASE_REGISTER` implicitly. Interpreter headers
retain the common layout on every architecture.

Wasm uses the following tuned variable-length integer bases.

| Constant | Wasm v6 ENCBASE |
| --- | --- |
| `CODE_LENGTH_ENCBASE` | 5 |
| `NUM_UNTRACKED_SLOTS_ENCBASE` | 7 |
| `NUM_SAFE_POINTS_ENCBASE` | 3 |
| `REVERSE_PINVOKE_FRAME_ENCBASE` | 4 |

#### GCInfo Header

##### Slim Header
Expand Down Expand Up @@ -573,16 +603,121 @@ for each range:

The slot table describes all GC-tracked locations used by the method. It has three sections decoded in order: register slots, tracked stack slots, and untracked stack slots.

**Slot counts** are encoded with presence bits:
**Slot counts** are encoded with presence bits. GCInfo v6 omits the
register-presence bit for the interpreter and Wasm platform encoding, which
cannot contain register slots. Non-Wasm v5 retains that bit, including the
always-zero bit in interpreter data.

```
if ReadBits(1): // has register slots
numRegisters = DecodeVarLengthUnsigned(NUM_REGISTERS_ENCBASE)
numRegisters = 0
numStackSlots = 0
numUntrackedSlots = 0
if gcVersion < 6 or HAS_REGISTER_SLOTS:
if ReadBits(1): // has register slots
numRegisters = DecodeVarLengthUnsigned(NUM_REGISTERS_ENCBASE)
if ReadBits(1): // has stack/untracked slots
numStackSlots = DecodeVarLengthUnsigned(NUM_STACK_SLOTS_ENCBASE)
numUntrackedSlots = DecodeVarLengthUnsigned(NUM_UNTRACKED_SLOTS_ENCBASE)
```

The semantic slot flags are:

| Flag | Value | Meaning |
| --- | --- | --- |
| `GC_SLOT_BASE` | 0x0 | Normal object reference |
| `GC_SLOT_INTERIOR` | 0x1 | Interior pointer (points inside an object) |
| `GC_SLOT_PINNED` | 0x2 | Pinned object reference |

###### GCInfo v6 slot descriptors

Flags precede locations. Each section is ordered by descending encoded flags.
For Wasm platform slots only, the encoded flags are the semantic flags XOR
`GC_SLOT_PINNED`; decoding reverses that transformation. This makes pinned
object roots the implicit-zero group on Wasm.

The first descriptor in each section has two flag bits. Subsequent descriptors
have two bits after encoded flags with the pinned bit set, one bit after encoded
interior-only flags, and no bits after encoded flags zero. Sorting guarantees
that a later descriptor cannot need a flag bit that has become implicit.

```
ReadSlotFlags(previousFlags, first):
flagsXor = USES_COMPACT_WASM_HEADER ? GC_SLOT_PINNED : 0
previousEncodedFlags = previousFlags XOR flagsXor
if first or previousEncodedFlags != 0:
numBits = (first or (previousEncodedFlags & GC_SLOT_PINNED) != 0) ? 2 : 1
encodedFlags = ReadBits(numBits)
else:
encodedFlags = 0
return encodedFlags XOR flagsXor
```

**Register slots** use an absolute register number at the start of each flag
group, followed by gap-minus-one deltas within that group. Register integers
use a bounded encoding rather than the ordinary variable-length encoding:
read `base + 1` bits, then, if the high bit is set, read exactly
`REGISTER_BITS - base` more bits. `REGISTER_BITS` is four on ARM and five on
the other register-bearing platforms supported by this decoder.

```
ReadRegisterValue(base):
chunk = ReadBits(base + 1)
value = chunk & ((1 << base) - 1)
if (chunk & (1 << base)) != 0:
value |= ReadBits(REGISTER_BITS - base) << base
return value

previousFlags = 0
regNum = 0
for i = 0; i < numRegisters; i++:
flags = ReadSlotFlags(previousFlags, i == 0)
absolute = i == 0 or flags != previousFlags
base = absolute ? REGISTER_ENCBASE : REGISTER_DELTA_ENCBASE
value = ReadRegisterValue(base)
regNum = absolute ? value : regNum + value + 1
emit register slot(regNum, flags)
previousFlags = flags
```

**Stack slots** are ordered by flags, then base (SP before frame register), then
signed normalized offset. A single bit selects the base: zero for SP-relative
and one for frame-register-relative. Caller-SP-relative slots are not encoded.
Wasm platform slots omit this bit and are implicitly frame-relative.

Offsets are signed absolute values at the first slot or any flag/base change;
within a flag/base group, offsets use unsigned deltas without a bias.
Decode the tracked and untracked sections separately, resetting flags, base,
and offset state at the beginning of each section.

```
DecodeStackSlots(count, isUntracked):
previousFlags = 0
previousBase = SP_REL
normOffset = 0
for i = 0; i < count; i++:
flags = ReadSlotFlags(previousFlags, i == 0)
slotBase = (HAS_STACK_SLOT_BASE and ReadBits(1) == 0) ? SP_REL : FRAMEREG_REL
if i == 0 or flags != previousFlags or slotBase != previousBase:
normOffset = DecodeVarLengthSigned(STACK_SLOT_ENCBASE)
else:
normOffset += DecodeVarLengthUnsigned(STACK_SLOT_DELTA_ENCBASE)
emit stack slot(DenormalizeStackSlot(normOffset), slotBase, flags, isUntracked)
previousFlags = flags
previousBase = slotBase

DecodeStackSlots(numStackSlots, false)
DecodeStackSlots(numUntrackedSlots, true)
```

`HAS_STACK_SLOT_BASE` is false only for Wasm platform slots. Stack-slot
denormalization converts Wasm offsets from four-byte pointer units to bytes;
the interpreter retains byte offsets on every architecture.

###### Legacy non-Wasm GCInfo v5 slot descriptors

The following algorithms apply only to the retained non-Wasm v5 decoding path,
not to current v6 data. Locations precede flags and stack bases occupy two bits.

**Register slots** use delta encoding when consecutive slots share the same flags:

```
Expand Down Expand Up @@ -618,15 +753,7 @@ else:
// flags inherited from previous
```

Untracked slots use the same encoding as tracked stack slots.

The 2-bit slot flags are:

| Flag | Value | Meaning |
| --- | --- | --- |
| `GC_SLOT_BASE` | 0x0 | Normal object reference |
| `GC_SLOT_INTERIOR` | 0x1 | Interior pointer (points inside an object) |
| `GC_SLOT_PINNED` | 0x2 | Pinned object reference |
Untracked slots use the same legacy encoding as tracked stack slots.

##### 4. Live State Data

Expand Down
Loading
Loading