Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 20 additions & 20 deletions src/gc.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1888,11 +1888,11 @@ test "Function marking and relocation cover every managed field" {
var generator_constants = [_]Value{Value.obj(&old_objects[11])};
var async_constants = [_]Value{Value.obj(&old_objects[12])};
var plain_chunk = bytecode.Chunk.init(std.testing.allocator);
plain_chunk.consts = .{ .items = &plain_constants, .capacity = plain_constants.len };
plain_chunk.consts = .fromOwnedSlice(&plain_constants);
var generator_chunk = bytecode.Chunk.init(std.testing.allocator);
generator_chunk.consts = .{ .items = &generator_constants, .capacity = generator_constants.len };
generator_chunk.consts = .fromOwnedSlice(&generator_constants);
var async_chunk = bytecode.Chunk.init(std.testing.allocator);
async_chunk.consts = .{ .items = &async_constants, .capacity = async_constants.len };
async_chunk.consts = .fromOwnedSlice(&async_constants);
var parent_frame_slots = [_]Value{Value.obj(&old_objects[13])};
var parent_frame = vm.Frame{ .slots = &parent_frame_slots, .parent = null };
var captured_frame_slots = [_]Value{Value.obj(&old_objects[14])};
Expand Down Expand Up @@ -2092,8 +2092,8 @@ test "Promise relocation rewrites inline and overflow reaction graphs" {
.resolve = Value.obj(&old_objects[6]),
.reject = Value.obj(&old_objects[7]),
},
.on_fulfill = .{ .items = &fulfill_overflow, .capacity = fulfill_overflow.len },
.on_reject = .{ .items = &reject_overflow, .capacity = reject_overflow.len },
.on_fulfill = .fromOwnedSlice(&fulfill_overflow),
.on_reject = .fromOwnedSlice(&reject_overflow),
};

const Plan = struct {
Expand Down Expand Up @@ -2353,7 +2353,7 @@ test "Generator and IteratorHelper marking and relocation cover every managed sl
.stack = .{ .items = &stack, .capacity = stack.len },
.acc = Value.obj(&old_objects[2]),
.frame = &child_frame,
.handlers = .{ .items = &handlers, .capacity = handlers.len },
.handlers = .fromOwnedSlice(&handlers),
},
.env = &old_environment,
.this_value = Value.obj(&old_objects[5]),
Expand All @@ -2362,7 +2362,7 @@ test "Generator and IteratorHelper marking and relocation cover every managed sl
.import_meta_slot = &import_meta,
.result = &old_objects[9],
.async_parent_promise = &old_promise,
.requests = .{ .items = &requests, .capacity = requests.len },
.requests = .fromOwnedSlice(&requests),
};
var helper = value.IterHelper{
.src = Value.obj(&old_objects[14]),
Expand Down Expand Up @@ -3395,9 +3395,9 @@ test "realm root relocation rewrites Context registries and embedder handles" {
context.next_ticks.items = saved_next_ticks;
context.next_ticks.head = saved_next_tick_head;
}
context.microtasks.items = .{ .items = &microtask_items, .capacity = microtask_items.len };
context.microtasks.items = .fromOwnedSlice(&microtask_items);
context.microtasks.head = 0;
context.next_ticks.items = .{ .items = &next_tick_items, .capacity = next_tick_items.len };
context.next_ticks.items = .fromOwnedSlice(&next_tick_items);
context.next_ticks.head = 0;

var unhandled = [_]*promise.Promise{&old_promises[0]};
Expand Down Expand Up @@ -3453,15 +3453,15 @@ test "realm root relocation rewrites Context registries and embedder handles" {
context.private_strong_roots = saved_strong_roots;
context.private_weak_roots = saved_weak_roots;
}
context.unhandled_rejections = .{ .items = &unhandled, .capacity = unhandled.len };
context.handled_rejections = .{ .items = &handled, .capacity = handled.len };
context.async_waiters = .{ .items = &async_waiters, .capacity = async_waiters.len };
context.timers = .{ .items = &timers, .capacity = timers.len };
context.finalization_cleanup_jobs = .{ .items = &finalization_jobs, .capacity = finalization_jobs.len };
context.c_api_class_prototypes = .{ .items = &prototypes, .capacity = prototypes.len };
context.c_api_handles = .{ .items = &handles, .capacity = handles.len };
context.private_strong_roots = .{ .items = &strong_roots, .capacity = strong_roots.len };
context.private_weak_roots = .{ .items = &weak_roots, .capacity = weak_roots.len };
context.unhandled_rejections = .fromOwnedSlice(&unhandled);
context.handled_rejections = .fromOwnedSlice(&handled);
context.async_waiters = .fromOwnedSlice(&async_waiters);
context.timers = .fromOwnedSlice(&timers);
context.finalization_cleanup_jobs = .fromOwnedSlice(&finalization_jobs);
context.c_api_class_prototypes = .fromOwnedSlice(&prototypes);
context.c_api_handles = .fromOwnedSlice(&handles);
context.private_strong_roots = .fromOwnedSlice(&strong_roots);
context.private_weak_roots = .fromOwnedSlice(&weak_roots);

const Plan = struct {
old_objects: *[18]Object,
Expand Down Expand Up @@ -4511,7 +4511,7 @@ test "precise heap realm registry traces relocates and retires one sibling exact
const saved_cleanup_jobs = sibling.finalization_cleanup_jobs;
defer sibling.finalization_cleanup_jobs = saved_cleanup_jobs;
var pending_cleanup = [_]*Object{&relocated_sibling_root};
sibling.finalization_cleanup_jobs = .{ .items = &pending_cleanup, .capacity = pending_cleanup.len };
sibling.finalization_cleanup_jobs = .fromOwnedSlice(&pending_cleanup);
try std.testing.expectError(error.RealmNotQuiescent, state.realms.retireQuiescent(sibling));
sibling.finalization_cleanup_jobs = .empty;
try std.testing.expectError(error.OwnerCannotRetire, state.realms.retireQuiescent(owner));
Expand Down Expand Up @@ -4700,7 +4700,7 @@ test "precise sibling realm shares one heap and retires only after cross-realm h
const moving_handle = try sibling.protectValue(moving_value);
const saved_cleanup_jobs = sibling.finalization_cleanup_jobs;
var pending_cleanup = [_]*Object{moving_handle.get().asObj()};
sibling.finalization_cleanup_jobs = .{ .items = &pending_cleanup, .capacity = pending_cleanup.len };
sibling.finalization_cleanup_jobs = .fromOwnedSlice(&pending_cleanup);
try std.testing.expectEqual(.unsupported, owner.compactGarbage().status);
sibling.finalization_cleanup_jobs = saved_cleanup_jobs;
const compaction = owner.compactGarbage();
Expand Down
44 changes: 41 additions & 3 deletions src/jit/optimizer_compiler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3576,20 +3576,55 @@ fn emitDirectNamedPropertyWrite(
return direct;
}

/// The three words the JIT reads out of an `ObjectElementsState.list`, named
/// for the loads that use them.
///
/// A description of the prefix, not a mirror of the type.
///
/// This used to be asserted `@sizeOf`-equal to `std.ArrayListUnmanaged(Value)`,
/// which stopped being true when 0.17.0-dev.1963 appended `pointer_stability`.
/// Measured, `@sizeOf(ArrayListUnmanaged(u64))`:
///
/// toolchain Debug ReleaseSafe ReleaseFast
/// dev.1818 24 24 24
/// dev.1963 32 32 24
///
/// So the equality held everywhere on 1818, and on 1963 it fails in two build
/// modes and passes in the third — a compile error that depends on `-O`. The
/// JIT never read that tail: `@offsetOf(items)` is 0 and `@offsetOf(capacity)`
/// is 16 in all six combinations above. Only the assertion was wrong.
///
/// The offsets the loads use come from the real type now
/// (`denseListWordOffset`); this struct only bounds them.
const DenseListLayout = extern struct {
items: [*]Value,
len: usize,
capacity: usize,
};

const DenseList = std.ArrayListUnmanaged(Value);

/// Byte offset of one of the words above, inside the real list type.
///
/// `@offsetOf` on `DenseList`, not on `DenseListLayout`: a std that reorders
/// or extends the struct moves the loads with it instead of silently reading
/// the wrong word. `len` is the second word of the `items` slice — pointer,
/// then length — which is the one layout fact here the language does fix.
fn denseListWordOffset(comptime field: []const u8) usize {
if (comptime std.mem.eql(u8, field, "items")) return @offsetOf(DenseList, "items");
if (comptime std.mem.eql(u8, field, "len")) return @offsetOf(DenseList, "items") + @sizeOf([*]Value);
if (comptime std.mem.eql(u8, field, "capacity")) return @offsetOf(DenseList, "capacity");
@compileError("not a word the JIT reads: " ++ field);
}

fn objectStorageByteOffset(comptime field: []const u8) !u15 {
return std.math.cast(u15, @offsetOf(ObjectStorageState, field)) orelse error.UnsupportedChunk;
}

fn objectElementsByteOffset(comptime field: []const u8) !u15 {
return std.math.cast(
u15,
@offsetOf(ObjectElementsState, "list") + @offsetOf(DenseListLayout, field),
@offsetOf(ObjectElementsState, "list") + denseListWordOffset(field),
) orelse error.UnsupportedChunk;
}

Expand All @@ -3601,8 +3636,11 @@ fn emitDirectDenseArrayGuards(
comptime has_index: bool,
) !void {
comptime {
std.debug.assert(@sizeOf(DenseListLayout) == @sizeOf(std.ArrayListUnmanaged(Value)));
std.debug.assert(@alignOf(DenseListLayout) == @alignOf(std.ArrayListUnmanaged(Value)));
// The prefix the JIT reads, not the whole type — see `DenseListLayout`.
std.debug.assert(@sizeOf([]Value) == 2 * @sizeOf(usize)); // pointer, then length
std.debug.assert(@sizeOf(DenseListLayout) <= @sizeOf(DenseList));
std.debug.assert(@alignOf(DenseListLayout) <= @alignOf(DenseList));
std.debug.assert(denseListWordOffset("capacity") + @sizeOf(usize) <= @sizeOf(DenseList));
}

// Shared-realm mutation requires the element lock. The same global gate
Expand Down
10 changes: 5 additions & 5 deletions src/jsthread.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1774,15 +1774,15 @@ test "jsthread native private relocation mirrors every traced payload" {
.release_state = .{ .lock = &lock },
};
var pending = [_]*HoldJob{&hold};
lock.pending = .{ .items = &pending, .capacity = pending.len };
lock.pending = .fromOwnedSlice(&pending);

var waiter_lock = LockRecord{ .gil = undefined, .owner = &old_objects[4] };
var waiter = AsyncCondWaiter{ .lock = &waiter_lock, .outer = &old_objects[5] };
var queue = [_]CondEntry{.{ .asynchronous = &waiter }};
var condition = CondRecord{
.gil = undefined,
.owner = &old_objects[3],
.queue = .{ .items = &queue, .capacity = queue.len },
.queue = .fromOwnedSlice(&queue),
};

var thread_local = TLRecord{
Expand Down Expand Up @@ -1905,7 +1905,7 @@ test "realm root relocation rewrites GIL tasks thread records and property waite
.release_state = .{ .lock = &lock },
};
var task_items = [_]*anyopaque{@ptrCast(&job)};
g.tasks = .{ .items = &task_items, .capacity = task_items.len };
g.tasks = .fromOwnedSlice(&task_items);

var queue = promise.MicrotaskQueue{};
var pending_joins = [_]PendingJoin{.{ .promise = &old_objects[5], .microtasks = &queue }};
Expand All @@ -1916,8 +1916,8 @@ test "realm root relocation rewrites GIL tasks thread records and property waite
.ctx = undefined,
.result = Value.obj(&old_objects[3]),
.js_obj = &old_objects[4],
.pending_joins = .{ .items = &pending_joins, .capacity = pending_joins.len },
.settling_joins = .{ .items = &settling_joins, .capacity = settling_joins.len },
.pending_joins = .fromOwnedSlice(&pending_joins),
.settling_joins = .fromOwnedSlice(&settling_joins),
};
var ticket = PropAsyncTicket{
.obj = &old_objects[7],
Expand Down
5 changes: 4 additions & 1 deletion src/value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4396,7 +4396,10 @@ pub const Object = struct {
return err;
};
@memcpy(values[0..old_slots.len], old_slots);
state.* = .{ .list = .{ .items = values[0..old_slots.len], .capacity = values.len } };
// `initBuffer`, not a literal: 0.17.0-dev.1963 added a field to the
// list, and a literal that names the old fields no longer compiles.
state.* = .{ .list = .initBuffer(values) };
state.list.items.len = old_slots.len;
state.list.appendAssumeCapacity(value_);
storage.state.slots.store(state, .release);
}
Expand Down
31 changes: 18 additions & 13 deletions src/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -214,22 +214,30 @@ const OperandStack = struct {
self.items[self.items.len - 1] = item;
}

/// The stack as the `ArrayListUnmanaged` it is a view of, so the list can
/// grow and free the buffer on the stack's behalf.
///
/// Built with `initBuffer` rather than a struct literal. A literal names
/// every field, and Zig 0.17.0-dev.1963 added `pointer_stability` to the
/// list — so a literal that was complete on 1818 is a compile error on
/// 1963. `initBuffer` fills whatever fields the current std has, and
/// `items.len` is the only thing this type adds on top.
fn asList(self: *const OperandStack) std.ArrayListUnmanaged(Value) {
var list: std.ArrayListUnmanaged(Value) = .initBuffer(self.items.ptr[0..self.capacity]);
list.items.len = self.items.len;
return list;
}

pub fn ensureTotalCapacity(self: *OperandStack, allocator: std.mem.Allocator, new_capacity: usize) std.mem.Allocator.Error!void {
if (new_capacity <= self.capacity) return;
var list: std.ArrayListUnmanaged(Value) = .{
.items = self.items,
.capacity = self.capacity,
};
var list = self.asList();
try list.ensureTotalCapacity(allocator, new_capacity);
self.items = list.items;
self.capacity = list.capacity;
}

noinline fn grow(self: *OperandStack, allocator: std.mem.Allocator) std.mem.Allocator.Error!void {
var list: std.ArrayListUnmanaged(Value) = .{
.items = self.items,
.capacity = self.capacity,
};
var list = self.asList();
try list.ensureTotalCapacity(allocator, self.items.len + 1);
self.items = list.items;
self.capacity = list.capacity;
Expand All @@ -252,10 +260,7 @@ const OperandStack = struct {
}

pub fn deinit(self: *OperandStack, allocator: std.mem.Allocator) void {
var list: std.ArrayListUnmanaged(Value) = .{
.items = self.items,
.capacity = self.capacity,
};
var list = self.asList();
list.deinit(allocator);
self.* = .empty;
}
Expand Down Expand Up @@ -10044,7 +10049,7 @@ fn runChunk(
try vm.notifyDebuggerException(false);
return error.Throw;
},
.throw_not_a_reference => return vm.throwNotAReference(@enumFromInt(inst.a)),
.throw_not_a_reference => return vm.throwNotAReference(@fromBackingInt(@intCast(inst.a))),
.push_handler, .push_handler_catch, .push_handler_outer => {
const outer = inst.op == .push_handler_outer;
if (outer and (exec.environment_depth == 0 or vm.env.parent == null)) return error.OutOfMemory;
Expand Down
Loading