ReviewOS

also looking at this

zig-utils/zig-js

fix(compat): stop describing ArrayList's layout, and read it instead

#876
Merged glennmichael123 wants to merge fix/zig-1963-arraylist-compat into main
5 files +88 -42
src/jit/optimizer_compiler.zigmodified+41-3
Changes to src/jit/optimizer_compiler.zig
@@ -3576,12 +3576,47 @@ fn emitDirectNamedPropertyWrite(
35763576 return direct;
35773577}
35783578
3579/// The three words the JIT reads out of an `ObjectElementsState.list`, named
3580/// for the loads that use them.
3581///
3582/// A description of the prefix, not a mirror of the type.
3583///
3584/// This used to be asserted `@sizeOf`-equal to `std.ArrayListUnmanaged(Value)`,
3585/// which stopped being true when 0.17.0-dev.1963 appended `pointer_stability`.
3586/// Measured, `@sizeOf(ArrayListUnmanaged(u64))`:
3587///
3588/// toolchain Debug ReleaseSafe ReleaseFast
3589/// dev.1818 24 24 24
3590/// dev.1963 32 32 24
3591///
3592/// So the equality held everywhere on 1818, and on 1963 it fails in two build
3593/// modes and passes in the third — a compile error that depends on `-O`. The
3594/// JIT never read that tail: `@offsetOf(items)` is 0 and `@offsetOf(capacity)`
3595/// is 16 in all six combinations above. Only the assertion was wrong.
3596///
3597/// The offsets the loads use come from the real type now
3598/// (`denseListWordOffset`); this struct only bounds them.
35793599const DenseListLayout = extern struct {
35803600 items: [*]Value,
35813601 len: usize,
35823602 capacity: usize,
35833603};
35843604
3605const DenseList = std.ArrayListUnmanaged(Value);
3606
3607/// Byte offset of one of the words above, inside the real list type.
3608///
3609/// `@offsetOf` on `DenseList`, not on `DenseListLayout`: a std that reorders
3610/// or extends the struct moves the loads with it instead of silently reading
3611/// the wrong word. `len` is the second word of the `items` slice — pointer,
3612/// then length — which is the one layout fact here the language does fix.
3613fn denseListWordOffset(comptime field: []const u8) usize {
3614 if (comptime std.mem.eql(u8, field, "items")) return @offsetOf(DenseList, "items");
3615 if (comptime std.mem.eql(u8, field, "len")) return @offsetOf(DenseList, "items") + @sizeOf([*]Value);
3616 if (comptime std.mem.eql(u8, field, "capacity")) return @offsetOf(DenseList, "capacity");
3617 @compileError("not a word the JIT reads: " ++ field);
3618}
3619
35853620fn objectStorageByteOffset(comptime field: []const u8) !u15 {
35863621 return std.math.cast(u15, @offsetOf(ObjectStorageState, field)) orelse error.UnsupportedChunk;
35873622}
@@ -3589,7 +3624,7 @@ fn objectStorageByteOffset(comptime field: []const u8) !u15 {
35893624fn objectElementsByteOffset(comptime field: []const u8) !u15 {
35903625 return std.math.cast(
35913626 u15,
3592 @offsetOf(ObjectElementsState, "list") + @offsetOf(DenseListLayout, field),
3627 @offsetOf(ObjectElementsState, "list") + denseListWordOffset(field),
35933628 ) orelse error.UnsupportedChunk;
35943629}
35953630
@@ -3601,8 +3636,11 @@ fn emitDirectDenseArrayGuards(
36013636 comptime has_index: bool,
36023637) !void {
36033638 comptime {
3604 std.debug.assert(@sizeOf(DenseListLayout) == @sizeOf(std.ArrayListUnmanaged(Value)));
3605 std.debug.assert(@alignOf(DenseListLayout) == @alignOf(std.ArrayListUnmanaged(Value)));
3639 // The prefix the JIT reads, not the whole type — see `DenseListLayout`.
3640 std.debug.assert(@sizeOf([]Value) == 2 * @sizeOf(usize)); // pointer, then length
3641 std.debug.assert(@sizeOf(DenseListLayout) <= @sizeOf(DenseList));
3642 std.debug.assert(@alignOf(DenseListLayout) <= @alignOf(DenseList));
3643 std.debug.assert(denseListWordOffset("capacity") + @sizeOf(usize) <= @sizeOf(DenseList));
36063644 }
36073645
36083646 // Shared-realm mutation requires the element lock. The same global gate