Skip to content

format, pointers: define segment offset carry semantics and resolve multi-slot regions - #284

Open
gnidan wants to merge 6 commits into
mainfrom
architect-segment-carry
Open

format, pointers: define segment offset carry semantics and resolve multi-slot regions#284
gnidan wants to merge 6 commits into
mainfrom
architect-segment-carry

Conversation

@gnidan

@gnidan gnidan commented Aug 6, 2026

Copy link
Copy Markdown
Member

Two pointer-side fixes from the design rulings, with the reference implementation brought along so the schema does not outrun it.

Segment offset carry. The offset field in pointer/scheme/segment required its value n to satisfy 0 ≤ n < $wordsize ("must begin inside the slot"). That contradicts the schema's own multi-slot note — which already says byte { "offset": "$wordsize" } of a slot is byte 0 of the next — and the flagship packed-struct pointer example, which places a sentinel region at offset: $wordsize. The bound was the bug. offset is now an unbounded non-negative value with full carry: for a slot value p and an offset value n, the segment begins at byte n mod $wordsize of slot p + floor(n / $wordsize). Emitters may chain byte sums across slot boundaries; resolvers recover slot and byte by division and remainder against $wordsize. The multi-slot note and the packed-struct example are unchanged.

Length default under carry. The old default $wordsize − .offset clamps to zero once offset carries. The default is now $wordsize − (.offset mod $wordsize): the segment ends at the end of the slot in which it begins.

Region self-reference. The packed-array example computed a region's own offset from .length: "struct-pointer" — a reference to the very region being declared. The reference-resolution rules only resolve previously-declared names, so on the first list iteration there is no earlier struct-pointer to resolve to. It now uses the built-in self-reference .length: $this, and the $this reference gains a guard: a property lookup via $this must not be circular. The pointers implementation already detects this and throws rather than looping; a test now pins that.

Byte-chaining example. A companion to the string storage example expresses the long-string body as a single region whose length runs across slots — { name: "string", slot: "start-slot", offset: 0, length: "string-length" } — instead of the per-slot list + last-slot-trim machinery. A minimal carry example is also added to the segment scheme's own examples. The per-slot list form stays primary: it exercises list / conditional / define and yields a distinct region per slot, which a consumer may want for display. The companion models only solc's long form.

Reference implementation. @ethdebug/pointers now resolves all of the above. Reads against word-addressed locations (stack, storage, transient) go through a shared readSegment that applies the carry, reads as many consecutive words as length spans, and concatenates them. The Machine.State.Words interface is unchanged, so @ethdebug/evm needs no changes. Unit tests cover carry at and past the word boundary, spans across two and three slots, the default length under a carried offset, and stack/transient carry. A new integration case observes the companion example against the same StringStorage contract as the existing test and decodes the multi-slot string end to end. Along the way the ganache test adapter now reads slots absent from the struct log as zero instead of garbage; that corrects the struct-storage case's initial salt expectation from 0x to 0x00000000.

…erence

Two pointer-side fixes from the design rulings.

Segment offset (ruling 5): the offset field prose required a value n with
0 <= n < $wordsize, contradicting the schema's own multi-slot note and
the packed-struct pointer example, both of which use offsets at or beyond
a word boundary. The offset is now defined as an unbounded non-negative
value with full carry: for slot p and offset n, the segment begins at
byte (n mod $wordsize) of slot (p + floor(n / $wordsize)). The multi-slot
note and the example are unchanged; only the erroneous bound is removed.

Region self-reference (ruling 6): the packed-array example computed a
region's own offset from '.length: "struct-pointer"' — a reference to the
region being declared, which the name-resolution rules (previously-declared
names only) cannot satisfy on the first iteration. It now uses the built-in
'.length: $this'. A guard is added to the $this reference: a property
lookup via $this must not be circular.
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://ethdebug.github.io/format/pr-preview/pr-284/

Built to branch gh-pages at 2026-09-03 04:22 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

gnidan added 3 commits August 5, 2026 21:27
Adds a companion to the string-storage pointer example that expresses the
long-string body as one region whose length runs across slots, instead of
a per-slot list — the byte chaining the carry semantics enable. The
existing per-slot list form is kept as the primary example: it stays
resolvable by the reference implementation and yields a distinct region
per slot, which a consumer may want. Also adds a minimal carry example to
the segment scheme's own examples (offset at a word boundary addressing
the next slot).

Note: a single storage region spanning slots is not yet resolved by the
pointers reference implementation (flagged to debugger as a tracked
follow-up); the primary per-slot form remains the resolvable one.
The companion example shared the string example's
'string-storage-contract-variable-slot' identifier, which the pointers
integration tests use with findExamplePointer (first substring match).
First-match already selects the resolvable per-slot form, but renaming the
companion's variable to 'string-storage-slot' removes the shared lookup
substring so the companion can never be selected even if examples are
reordered.
Segment regions (stack, storage, transient) now follow the addressing
scheme's carry semantics: an offset at or beyond $wordsize addresses a
later slot, a length may run across slots (concatenating sequentially
addressed slots), and an omitted length ends at the end of the slot in
which the segment begins. The Machine.State interface is unchanged;
read() assembles the bytes from per-slot reads.

Also:

- express the segment `length` default with $remainder so that it is
  consistent with offset carry (it previously clamped to 0 for offsets
  at or beyond $wordsize)
- add an integration test selecting the single-region `string storage`
  companion example against the same StringStorage contract
- treat storage slots absent from ganache struct logs as zero (they
  previously decoded as garbage); this corrects the struct storage
  test's expected initial `salt` to 0x00000000

Claude-Session: https://claude.ai/code/session_01RJFyifZxcSXZchLFNTNPuT
@gnidan gnidan changed the title format: define segment offset carry semantics and fix region self-reference format, pointers: define segment offset carry semantics and resolve multi-slot regions Sep 3, 2026
…guide

The slot-based locations paragraph in the pointers regions guide still
described offset/length as sub-slot positioning only. Restate it against
the segment scheme's semantics: offsets carry past $wordsize into later
slots, lengths may span consecutive slots, and an omitted length ends at
the end of the starting slot. Points at the multi-slot string storage
example as the canonical use.

Claude-Session: https://claude.ai/code/session_014otYPQPP9pvQabmY58Fyom
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant