From 2426abd17a327b29c80220bf886bcf8d5e4418b4 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 17 Feb 2026 16:35:12 +0300 Subject: [PATCH 1/3] introduce persisted typed arrays --- src/browser/js/Context.zig | 8 +++++++- src/browser/js/Local.zig | 11 ++++++----- src/browser/js/js.zig | 33 ++++++++++++++++++++++++++++---- src/browser/webapi/ImageData.zig | 6 +++--- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index ca4e83d4..7a8ef1f5 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -1046,8 +1046,14 @@ pub const FinalizerCallback = struct { }; /// Creates a new typed array. Memory is owned by JS context. +/// If storing the type in a Zig type is desired, prefer `.global` state to persist. /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Typed_arrays -pub fn createTypedArray(self: *Context, comptime array_type: js.ArrayType, size: usize) js.ArrayBufferRef(array_type) { +pub fn createTypedArray( + self: *const Context, + comptime array_type: js.ArrayType, + comptime state: js.ArrayBufferState, + size: usize, +) js.ArrayBufferRef(array_type, state) { return .init(self.isolate, size); } diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index 7beab5c9..156c2214 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -306,12 +306,13 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts) js.Value => return value, js.Exception => return .{ .local = self, .handle = isolate.throwException(value.handle) }, - js.ArrayBufferRef(.int8), js.ArrayBufferRef(.uint8), js.ArrayBufferRef(.uint8_clamped), - js.ArrayBufferRef(.int16), js.ArrayBufferRef(.uint16), - js.ArrayBufferRef(.int32), js.ArrayBufferRef(.uint32), - js.ArrayBufferRef(.float16), js.ArrayBufferRef(.float32), js.ArrayBufferRef(.float64), + js.ArrayBufferRef(.int8, .global), js.ArrayBufferRef(.uint8, .global), + js.ArrayBufferRef(.uint8_clamped, .global),js.ArrayBufferRef(.int16, .global), + js.ArrayBufferRef(.uint16, .global), js.ArrayBufferRef(.int32, .global), + js.ArrayBufferRef(.uint32, .global), js.ArrayBufferRef(.float16, .global), + js.ArrayBufferRef(.float32, .global), js.ArrayBufferRef(.float64, .global), => { - return .{ .local = self, .handle = value.handle }; + return .{ .local = self, .handle = value.localHandle(self) }; }, inline diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index acb58a27..252d0a3d 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -90,7 +90,13 @@ pub const ArrayType = enum(u8) { float64, }; -pub fn ArrayBufferRef(comptime kind: ArrayType) type { +pub const ArrayBufferState = enum(u1) { local, global }; + +/// If `state` is `global`; a persisted, global typed array is created. +pub fn ArrayBufferRef( + comptime kind: ArrayType, + comptime state: ArrayBufferState, +) type { return struct { const Self = @This(); @@ -106,7 +112,10 @@ pub fn ArrayBufferRef(comptime kind: ArrayType) type { .float64 => f64, }; - handle: *const v8.Value, + handle: switch (state) { + .local => *const v8.Value, + .global => v8.Global, + }, pub fn init(isolate: Isolate, size: usize) Self { const bits = switch (@typeInfo(BackingInt)) { @@ -125,7 +134,7 @@ pub fn ArrayBufferRef(comptime kind: ArrayType) type { array_buffer = v8.v8__ArrayBuffer__New2(isolate.handle, &backing_store_ptr).?; } - const handle: *const v8.Value = switch (comptime kind) { + const local_handle: *const v8.Value = switch (comptime kind) { .int8 => @ptrCast(v8.v8__Int8Array__New(array_buffer, 0, size).?), .uint8 => @ptrCast(v8.v8__Uint8Array__New(array_buffer, 0, size).?), .uint8_clamped => @ptrCast(v8.v8__Uint8ClampedArray__New(array_buffer, 0, size).?), @@ -138,7 +147,23 @@ pub fn ArrayBufferRef(comptime kind: ArrayType) type { .float64 => @ptrCast(v8.v8__Float64Array__New(array_buffer, 0, size).?), }; - return .{ .handle = handle }; + switch (comptime state) { + .local => return .{ .handle = local_handle }, + .global => { + // We need a global handle if state is `global`. + var global_handle: v8.Global = undefined; + v8.v8__Global__New(isolate.handle, local_handle, &global_handle); + return .{ .handle = global_handle }; + }, + } + } + + /// Returns appropriate local handle. + pub fn localHandle(self: *const Self, scope: *const Local) *const v8.Value { + return switch (comptime state) { + .local => return self.handle, + .global => v8.v8__Global__Get(&self.handle, scope.isolate.handle).?, + }; } }; } diff --git a/src/browser/webapi/ImageData.zig b/src/browser/webapi/ImageData.zig index eed265fc..153130e5 100644 --- a/src/browser/webapi/ImageData.zig +++ b/src/browser/webapi/ImageData.zig @@ -29,7 +29,7 @@ const Page = @import("../Page.zig"); const ImageData = @This(); _width: u32, _height: u32, -_data: js.ArrayBufferRef(.uint8_clamped), +_data: js.ArrayBufferRef(.uint8_clamped, .global), pub const ConstructorSettings = struct { /// Specifies the color space of the image data. @@ -74,7 +74,7 @@ pub fn constructor( return page._factory.create(ImageData{ ._width = width, ._height = height, - ._data = page.js.createTypedArray(.uint8_clamped, size), + ._data = page.js.createTypedArray(.uint8_clamped, .global, size), }); } @@ -94,7 +94,7 @@ pub fn getColorSpace(_: *const ImageData) String { return comptime .wrap("srgb"); } -pub fn getData(self: *const ImageData) js.ArrayBufferRef(.uint8_clamped) { +pub fn getData(self: *const ImageData) js.ArrayBufferRef(.uint8_clamped, .global) { return self._data; } From 95920bf207c4437197eb83b00e03d11448a30073 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Wed, 18 Feb 2026 21:43:19 +0300 Subject: [PATCH 2/3] `ArrayBufferRef(...).Global`: consistent, persisted typed arrays --- src/browser/js/Context.zig | 9 ++--- src/browser/js/Local.zig | 12 +++---- src/browser/js/js.zig | 56 ++++++++++++++++---------------- src/browser/webapi/ImageData.zig | 6 ++-- 4 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 7a8ef1f5..f2b65ce3 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -1048,13 +1048,8 @@ pub const FinalizerCallback = struct { /// Creates a new typed array. Memory is owned by JS context. /// If storing the type in a Zig type is desired, prefer `.global` state to persist. /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Typed_arrays -pub fn createTypedArray( - self: *const Context, - comptime array_type: js.ArrayType, - comptime state: js.ArrayBufferState, - size: usize, -) js.ArrayBufferRef(array_type, state) { - return .init(self.isolate, size); +pub fn createTypedArray(self: *const Context, comptime array_type: js.ArrayType, size: usize) js.ArrayBufferRef(array_type) { + return .init(self.local.?, size); } // == Profiler == diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index 156c2214..b06e0600 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -306,13 +306,13 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts) js.Value => return value, js.Exception => return .{ .local = self, .handle = isolate.throwException(value.handle) }, - js.ArrayBufferRef(.int8, .global), js.ArrayBufferRef(.uint8, .global), - js.ArrayBufferRef(.uint8_clamped, .global),js.ArrayBufferRef(.int16, .global), - js.ArrayBufferRef(.uint16, .global), js.ArrayBufferRef(.int32, .global), - js.ArrayBufferRef(.uint32, .global), js.ArrayBufferRef(.float16, .global), - js.ArrayBufferRef(.float32, .global), js.ArrayBufferRef(.float64, .global), + js.ArrayBufferRef(.int8).Global, js.ArrayBufferRef(.uint8).Global, + js.ArrayBufferRef(.uint8_clamped).Global, js.ArrayBufferRef(.int16).Global, + js.ArrayBufferRef(.uint16).Global, js.ArrayBufferRef(.int32).Global, + js.ArrayBufferRef(.uint32).Global, js.ArrayBufferRef(.float16).Global, + js.ArrayBufferRef(.float32).Global, js.ArrayBufferRef(.float64).Global, => { - return .{ .local = self, .handle = value.localHandle(self) }; + return .{ .local = self, .handle = value.local(self).handle }; }, inline diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 252d0a3d..112cc596 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -90,13 +90,7 @@ pub const ArrayType = enum(u8) { float64, }; -pub const ArrayBufferState = enum(u1) { local, global }; - -/// If `state` is `global`; a persisted, global typed array is created. -pub fn ArrayBufferRef( - comptime kind: ArrayType, - comptime state: ArrayBufferState, -) type { +pub fn ArrayBufferRef(comptime kind: ArrayType) type { return struct { const Self = @This(); @@ -112,12 +106,25 @@ pub fn ArrayBufferRef( .float64 => f64, }; - handle: switch (state) { - .local => *const v8.Value, - .global => v8.Global, - }, + local: *const Local, + handle: *const v8.Value, - pub fn init(isolate: Isolate, size: usize) Self { + /// Persisted typed array. + pub const Global = struct { + handle: v8.Global, + + pub fn deinit(self: *Global) void { + v8.v8__Global__Reset(&self.handle); + } + + pub fn local(self: *const Global, l: *const Local) Self { + return .{ .local = l, .handle = v8.v8__Global__Get(&self.handle, l.isolate.handle).? }; + } + }; + + pub fn init(local: *const Local, size: usize) Self { + const ctx = local.ctx; + const isolate = ctx.isolate; const bits = switch (@typeInfo(BackingInt)) { .int => |n| n.bits, .float => |f| f.bits, @@ -134,7 +141,7 @@ pub fn ArrayBufferRef( array_buffer = v8.v8__ArrayBuffer__New2(isolate.handle, &backing_store_ptr).?; } - const local_handle: *const v8.Value = switch (comptime kind) { + const handle: *const v8.Value = switch (comptime kind) { .int8 => @ptrCast(v8.v8__Int8Array__New(array_buffer, 0, size).?), .uint8 => @ptrCast(v8.v8__Uint8Array__New(array_buffer, 0, size).?), .uint8_clamped => @ptrCast(v8.v8__Uint8ClampedArray__New(array_buffer, 0, size).?), @@ -147,23 +154,16 @@ pub fn ArrayBufferRef( .float64 => @ptrCast(v8.v8__Float64Array__New(array_buffer, 0, size).?), }; - switch (comptime state) { - .local => return .{ .handle = local_handle }, - .global => { - // We need a global handle if state is `global`. - var global_handle: v8.Global = undefined; - v8.v8__Global__New(isolate.handle, local_handle, &global_handle); - return .{ .handle = global_handle }; - }, - } + return .{ .local = local, .handle = handle }; } - /// Returns appropriate local handle. - pub fn localHandle(self: *const Self, scope: *const Local) *const v8.Value { - return switch (comptime state) { - .local => return self.handle, - .global => v8.v8__Global__Get(&self.handle, scope.isolate.handle).?, - }; + pub fn persist(self: *const Self) !Global { + var ctx = self.local.ctx; + var global: v8.Global = undefined; + v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); + try ctx.global_values.append(ctx.arena, global); + + return .{ .handle = global }; } }; } diff --git a/src/browser/webapi/ImageData.zig b/src/browser/webapi/ImageData.zig index 153130e5..39c7c241 100644 --- a/src/browser/webapi/ImageData.zig +++ b/src/browser/webapi/ImageData.zig @@ -29,7 +29,7 @@ const Page = @import("../Page.zig"); const ImageData = @This(); _width: u32, _height: u32, -_data: js.ArrayBufferRef(.uint8_clamped, .global), +_data: js.ArrayBufferRef(.uint8_clamped).Global, pub const ConstructorSettings = struct { /// Specifies the color space of the image data. @@ -74,7 +74,7 @@ pub fn constructor( return page._factory.create(ImageData{ ._width = width, ._height = height, - ._data = page.js.createTypedArray(.uint8_clamped, .global, size), + ._data = try page.js.createTypedArray(.uint8_clamped, size).persist(), }); } @@ -94,7 +94,7 @@ pub fn getColorSpace(_: *const ImageData) String { return comptime .wrap("srgb"); } -pub fn getData(self: *const ImageData) js.ArrayBufferRef(.uint8_clamped, .global) { +pub fn getData(self: *const ImageData) js.ArrayBufferRef(.uint8_clamped).Global { return self._data; } From abab10b2ccded70c7807a24e0b2f88a9fbef57ff Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 19 Feb 2026 10:03:04 +0300 Subject: [PATCH 3/3] move `createTypedArray` to `Local` --- src/browser/js/Context.zig | 7 ------- src/browser/js/Local.zig | 6 ++++++ src/browser/webapi/ImageData.zig | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index f2b65ce3..de9c4849 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -1045,13 +1045,6 @@ pub const FinalizerCallback = struct { } }; -/// Creates a new typed array. Memory is owned by JS context. -/// If storing the type in a Zig type is desired, prefer `.global` state to persist. -/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Typed_arrays -pub fn createTypedArray(self: *const Context, comptime array_type: js.ArrayType, size: usize) js.ArrayBufferRef(array_type) { - return .init(self.local.?, size); -} - // == Profiler == pub fn startCpuProfiler(self: *Context) void { if (comptime !IS_DEBUG) { diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index b06e0600..e9d9d9d9 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -75,6 +75,12 @@ pub fn newArray(self: *const Local, len: u32) js.Array { }; } +/// Creates a new typed array. Memory is owned by JS context. +/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Typed_arrays +pub fn createTypedArray(self: *const Local, comptime array_type: js.ArrayType, size: usize) js.ArrayBufferRef(array_type) { + return .init(self, size); +} + pub fn runMicrotasks(self: *const Local) void { self.isolate.performMicrotasksCheckpoint(); } diff --git a/src/browser/webapi/ImageData.zig b/src/browser/webapi/ImageData.zig index 39c7c241..05fcae6c 100644 --- a/src/browser/webapi/ImageData.zig +++ b/src/browser/webapi/ImageData.zig @@ -74,7 +74,7 @@ pub fn constructor( return page._factory.create(ImageData{ ._width = width, ._height = height, - ._data = try page.js.createTypedArray(.uint8_clamped, size).persist(), + ._data = try page.js.local.?.createTypedArray(.uint8_clamped, size).persist(), }); }