diff --git a/build.zig.zon b/build.zig.zon index ed2c3251..e5e4b8f5 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -13,8 +13,8 @@ .hash = "tigerbeetle_io-0.0.0-ViLgxpyRBAB5BMfIcj3KMXfbJzwARs9uSl8aRy2OXULd", }, .v8 = .{ - .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/f0c7eaaffe39f2f1a224fbe97e550daca0ca1801.tar.gz", - .hash = "v8-0.0.0-xddH62T4IADchAHFgo4nx79w1VedNDhIVErtSNgup-Tk", + .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/fc764e7d29bc1514924e8df09255a057e03d453a.tar.gz", + .hash = "v8-0.0.0-xddH6zUZIQBJf109L94sC-mWH1NJXWCnOJGJttKtfasI", }, //.v8 = .{ .path = "../zig-v8-fork" }, //.tigerbeetle_io = .{ .path = "../tigerbeetle-io" }, diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 765016ec..86d4cb30 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -1012,6 +1012,22 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { } }; + // If a function returns a []i32, should that map to a plain-old + // JavaScript array, or a Int32Array? It's ambiguous. By default, we'll + // map arrays/slices to the JavaScript arrays. If you want a TypedArray + // wrap it in this. + // Also, this type has nothing to do with the Env. But we place it here + // for consistency. Want a callback? Env.Callback. Want a JsObject? + // Env.JsObject. Want a TypedArray? Env.TypedArray. + pub fn TypedArray(comptime T: type) type { + return struct { + // See Callback._CALLBACK_ID_KLUDGE + const _TYPED_ARRAY_ID_KLUDGE = true; + + values: []const T, + }; + } + pub const Inspector = struct { isolate: v8.Isolate, inner: *v8.Inspector, @@ -2632,6 +2648,53 @@ fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bool) } return v8.initNull(isolate).toValue(); }, + .@"struct" => { + const T = @TypeOf(value); + if (@hasDecl(T, "_TYPED_ARRAY_ID_KLUDGE")) { + const values = value.values; + const value_type = @typeInfo(@TypeOf(values)).pointer.child; + const len = values.len; + const bits = switch (@typeInfo(value_type)) { + .int => |n| n.bits, + .float => |f| f.bits, + else => @compileError("Invalid TypeArray type: " ++ @typeName(value_type)), + }; + + const buffer_len = len * bits / 8; + const backing_store = v8.BackingStore.init(isolate, buffer_len); + const data: [*]u8 = @alignCast(@ptrCast(backing_store.getData())); + @memcpy(data[0..buffer_len], @as([]const u8, @ptrCast(values))[0..buffer_len]); + const array_buffer = v8.ArrayBuffer.initWithBackingStore(isolate, &backing_store.toSharedPtr()); + + switch (@typeInfo(value_type)) { + .int => |n| switch (n.signedness) { + .unsigned => switch (n.bits) { + 8 => return v8.Uint8Array.init(array_buffer, 0, len).toValue(), + 16 => return v8.Uint16Array.init(array_buffer, 0, len).toValue(), + 32 => return v8.Uint32Array.init(array_buffer, 0, len).toValue(), + 64 => return v8.BigUint64Array.init(array_buffer, 0, len).toValue(), + else => {}, + }, + .signed => switch (n.bits) { + 8 => return v8.Int8Array.init(array_buffer, 0, len).toValue(), + 16 => return v8.Int16Array.init(array_buffer, 0, len).toValue(), + 32 => return v8.Int32Array.init(array_buffer, 0, len).toValue(), + 64 => return v8.BigInt64Array.init(array_buffer, 0, len).toValue(), + else => {}, + }, + }, + .float => |f| switch (f.bits) { + 32 => return v8.Float32Array.init(array_buffer, 0, len).toValue(), + 64 => return v8.Float64Array.init(array_buffer, 0, len).toValue(), + else => {}, + }, + else => {}, + } + // We normally don't fail in this function unless fail == true + // but this can never be valid. + @compileError("Invalid TypeArray type: " ++ @typeName(value_type)); + } + }, .@"union" => return simpleZigValueToJs(isolate, std.meta.activeTag(value), fail), else => {}, } diff --git a/src/runtime/test_primitive_types.zig b/src/runtime/test_primitive_types.zig index 5c003481..e2a2673c 100644 --- a/src/runtime/test_primitive_types.zig +++ b/src/runtime/test_primitive_types.zig @@ -17,6 +17,10 @@ // along with this program. If not, see . // TODO: use functions instead of "fake" struct once we handle function API generation + +const Runner = testing.Runner(void, void, .{Primitives}); +const Env = Runner.Env; + const Primitives = struct { pub fn constructor() Primitives { return .{}; @@ -114,6 +118,46 @@ const Primitives = struct { } } + pub fn _returnUint8(_: *const Primitives) Env.TypedArray(u8) { + return .{ .values = &.{ 10, 20, 250 } }; + } + + pub fn _returnInt8(_: *const Primitives) Env.TypedArray(i8) { + return .{ .values = &.{ 10, -20, -120 } }; + } + + pub fn _returnUint16(_: *const Primitives) Env.TypedArray(u16) { + return .{ .values = &.{ 10, 200, 2050 } }; + } + + pub fn _returnInt16(_: *const Primitives) Env.TypedArray(i16) { + return .{ .values = &.{ 10, -420, 0 } }; + } + + pub fn _returnUint32(_: *const Primitives) Env.TypedArray(u32) { + return .{ .values = &.{ 10, 2444343, 43432432 } }; + } + + pub fn _returnInt32(_: *const Primitives) Env.TypedArray(i32) { + return .{ .values = &.{ 10, -20, -495929123 } }; + } + + pub fn _returnUint64(_: *const Primitives) Env.TypedArray(u64) { + return .{ .values = &.{ 10, 495812375924, 0 } }; + } + + pub fn _returnInt64(_: *const Primitives) Env.TypedArray(i64) { + return .{ .values = &.{ 10, -49283838122, -2 } }; + } + + pub fn _returnFloat32(_: *const Primitives) Env.TypedArray(f32) { + return .{ .values = &.{ 1.1, -200.035, 0.0003 } }; + } + + pub fn _returnFloat64(_: *const Primitives) Env.TypedArray(f64) { + return .{ .values = &.{ 8881.22284, -4928.3838122, -0.00004 } }; + } + pub fn _int16(_: *const Primitives, arr: []i16) void { for (arr) |*a| { a.* -= @intCast(arr.len); @@ -153,7 +197,7 @@ const Primitives = struct { const testing = @import("testing.zig"); test "JS: primitive types" { - var runner = try testing.Runner(void, void, .{Primitives}).init({}, {}); + var runner = try Runner.init({}, {}); defer runner.deinit(); // constructor @@ -280,5 +324,16 @@ test "JS: primitive types" { .{ "try { p.int64(arr_u64) } catch(e) { e instanceof TypeError; }", "true" }, .{ "try { p.intu64(arr_i64) } catch(e) { e instanceof TypeError; }", "true" }, .{ "try { p.intu64(arr_u32) } catch(e) { e instanceof TypeError; }", "true" }, + + .{ "p.returnUint8()", "10,20,250" }, + .{ "p.returnInt8()", "10,-20,-120" }, + .{ "p.returnUint16()", "10,200,2050" }, + .{ "p.returnInt16()", "10,-420,0" }, + .{ "p.returnUint32()", "10,2444343,43432432" }, + .{ "p.returnInt32()", "10,-20,-495929123" }, + .{ "p.returnUint64()", "10,495812375924,0" }, + .{ "p.returnInt64()", "10,-49283838122,-2" }, + .{ "p.returnFloat32()", "1.100000023841858,-200.03500366210938,0.0003000000142492354" }, + .{ "p.returnFloat64()", "8881.22284,-4928.3838122,-0.00004" }, }, .{}); } diff --git a/src/runtime/testing.zig b/src/runtime/testing.zig index a7524509..dc4fc4e2 100644 --- a/src/runtime/testing.zig +++ b/src/runtime/testing.zig @@ -26,15 +26,16 @@ pub const allocator = std.testing.allocator; // browser.Env or the browser.SessionState pub fn Runner(comptime State: type, comptime Global: type, comptime types: anytype) type { const AdjustedTypes = if (Global == void) generate.Tuple(.{ types, DefaultGlobal }) else types; - const Env = js.Env(State, struct { - pub const Interfaces = AdjustedTypes; - }); return struct { env: *Env, scope: *Env.Scope, executor: Env.Executor, + pub const Env = js.Env(State, struct { + pub const Interfaces = AdjustedTypes; + }); + const Self = @This(); pub fn init(state: State, global: Global) !*Self {