fix(compat): stop describing ArrayList's layout, and read it instead - #876
Merged
Conversation
Zig 0.17.0-dev.1963 appends `pointer_stability: debug.SafetyLock` to
`ArrayList`. Two idioms here assumed the old shape.
**27 struct literals.** `.{ .items = &buf, .capacity = buf.len }` names every
field, so a std with one more field stops compiling. All of them are test
fixtures wrapping a fixed array; `.fromOwnedSlice(&buf)` says the same thing
and does not enumerate fields. It exists on both toolchains.
**One layout assertion, which is the interesting half.** The JIT mirrors the
list as `DenseListLayout` and asserted it `@sizeOf`-equal to the real type.
Measured, `@sizeOf(ArrayListUnmanaged(u64))` on aarch64-macos:
toolchain Debug ReleaseSafe ReleaseFast
dev.1818 24 24 24
dev.1963 32 32 24
`SafetyLock` is zero-sized when runtime safety is off, so on 1963 the
assertion fails in Debug and ReleaseSafe and passes in ReleaseFast — a
compile error that depends on `-O`.
The mirror was never wrong about the part the JIT uses. `@offsetOf(items)` is
0 and `@offsetOf(capacity)` is 16 in all six combinations above, and the
emitted loads only ever touch those two words plus the slice length. It was
the equality that was wrong: it asserted a fact about the tail nothing reads.
So the offsets now come from the real type via `denseListWordOffset`, and
`DenseListLayout` is documented as a description of the prefix rather than a
mirror. A future std that reorders these fields moves the loads with it
instead of silently reading the wrong word — which the old assertion would
not have caught either, since it only compared sizes.
No behaviour change on the current toolchain: same offsets, same loads.
glennmichael123
added a commit
to craft-native/craft
that referenced
this pull request
Sep 3, 2026
…1963 (#106) main has been red on `zig-core` since #104 moved craft to 0.17.0-dev.1963. Every failure was in zig-js at the old pin — that toolchain appends `pointer_stability` to ArrayList, and zig-js had 27 struct literals and one JIT layout assertion that named the old shape. zig-utils/zig-js#876 fixes that upstream, verified there on 1963 (2285 passed, 0 failed) and on the 1818 its own CI pins (53/53 checks). This is the one-line pin to it. Locally, craft's full `zig build` and `zig build test` against this zig-js content on 1963: 153/153 steps, 2519 passed, 0 failed. Co-authored-by: glennmichael123 <gtorregosa@gmail.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What this changes
Zig
0.17.0-dev.1963appendspointer_stability: debug.SafetyLocktoArrayList, and two idioms here assumed the old shape. 27 struct literals of the form.{ .items = &buf, .capacity = buf.len }name every field, so one more field stops them compiling — all are test fixtures wrapping a fixed array, and.fromOwnedSlice(&buf)says the same thing without enumerating fields. The other is a layout assertion in the JIT, which is the part worth reading.emitDirectDenseArrayGuardsmirrors the list asDenseListLayoutand asserted it@sizeOf-equal tostd.ArrayListUnmanaged(Value). Measured,@sizeOf(ArrayListUnmanaged(u64))on aarch64-macos:dev.1818dev.1963SafetyLockis zero-sized when runtime safety is off, so on 1963 that assertion fails in Debug and ReleaseSafe and passes in ReleaseFast — a compile error that depends on-O.The mirror was never wrong about the part the JIT uses.
@offsetOf(items)is 0 and@offsetOf(capacity)is 16 in all six combinations above, and the emitted loads only ever touch those two words plus the slice length. It was the equality that was wrong: it asserted a fact about a tail nothing reads. So the offsets now come from the real type viadenseListWordOffset, andDenseListLayoutis documented as a description of the prefix rather than a mirror.Impact
Flips 0 test262 cases. No behaviour change on the current toolchain — same offsets, same emitted loads;
@offsetOfresolves to the identical constants the literal did. What changes is that a future std reordering these fields moves the loads with it, instead of silently reading the wrong word — which the old assertion would not have caught either, since it only compared sizes.Unblocks a downstream consumer (craft) that had to move off
0.17.0-dev.1509because ziglang.org prunes dev builds: every 1509 tarball now 404s, so the pin could only be satisfied from cache.Verification
zig build teston0.17.0-dev.1963+e00c6c439: 2285 passed, 1 skipped, 0 failed, 0 leaked, exit 0.zig buildon0.17.0-dev.1818+7051f8e73(this repo'sZIG_VERSION): clean, with siblings at upstreammain.The 1963 figure is from a completed run whose log I later clobbered, and two attempts to regenerate it were killed by SIGTERM before finishing (
error: process terminated with signal TERM) rather than failing — so treat it as reported, not attached. CI on this PR is the authoritative measurement and covers the 1818 leg the repo actually pins.