Skip to content

Commit 8a59078

Browse files
committed
Import some of the zig-js-runtime env tests
- Fix passing nothing into variadic (i.e. slice) parameter - Optimize @sizeof(T) == 0 types by avoiding uncessary allocations (something zig-js-runtime is doing)
1 parent fbacc24 commit 8a59078

File tree

4 files changed

+566
-40
lines changed

4 files changed

+566
-40
lines changed

src/runtime/js.zig

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ const ArenaAllocator = std.heap.ArenaAllocator;
2121

2222
const log = std.log.scoped(.js);
2323

24-
2524
// Global, should only be initialized once.
2625
pub const Platform = struct {
2726
inner: v8.Platform,
@@ -453,7 +452,11 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
453452
}
454453
}.callback);
455454

456-
template.getInstanceTemplate().setInternalFieldCount(1);
455+
if (comptime isEmpty(Receiver(Struct)) == false) {
456+
// If the struct is empty, we won't store a Zig reference inside
457+
// the JS object, so we don't need to set the internal field count
458+
template.getInstanceTemplate().setInternalFieldCount(1);
459+
}
457460

458461
const class_name = v8.String.initUtf8(self.isolate, comptime classNameForStruct(Struct));
459462
template.setClassName(class_name);
@@ -973,19 +976,28 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
973976
else => @compileError("mapZigInstanceToJs requires a v8.Object (constructors) or v8.FunctionTemplate, got: " ++ @typeName(@TypeOf(js_obj_or_template))),
974977
};
975978

976-
// The TAO contains the pointer ot our Zig instance as
977-
// well as any meta data we'll need to use it later.
978-
// See the TaggedAnyOpaque struct for more details.
979-
const tao = try scope_arena.create(TaggedAnyOpaque);
980-
tao.* = .{
981-
.ptr = value,
982-
.index = @field(TYPE_LOOKUP, @typeName(ptr.child)),
983-
.sub_type = if (@hasDecl(ptr.child, "sub_type")) ptr.child.sub_type else null,
984-
.offset = if (@typeInfo(ptr.child) != .@"opaque" and @hasField(ptr.child, "proto")) @offsetOf(ptr.child, "proto") else -1,
985-
};
986-
987979
const isolate = self.isolate;
988-
js_obj.setInternalField(0, v8.External.init(isolate, tao));
980+
981+
if (isEmpty(ptr.child) == false) {
982+
// The TAO contains the pointer ot our Zig instance as
983+
// well as any meta data we'll need to use it later.
984+
// See the TaggedAnyOpaque struct for more details.
985+
const tao = try scope_arena.create(TaggedAnyOpaque);
986+
tao.* = .{
987+
.ptr = value,
988+
.index = @field(TYPE_LOOKUP, @typeName(ptr.child)),
989+
.sub_type = if (@hasDecl(ptr.child, "sub_type")) ptr.child.sub_type else null,
990+
.offset = if (@typeInfo(ptr.child) != .@"opaque" and @hasField(ptr.child, "proto")) @offsetOf(ptr.child, "proto") else -1,
991+
};
992+
993+
js_obj.setInternalField(0, v8.External.init(isolate, tao));
994+
} else {
995+
// If the struct is empty, we don't need to do all
996+
// the TOA stuff and setting the internal data.
997+
// When we try to map this from JS->Zig, in
998+
// typeTaggedAnyOpaque, we'll also know there that
999+
// the type is empty and can create an empty instance.
1000+
}
9891001
const js_persistent = PersistentObject.init(isolate, js_obj);
9901002
gop.value_ptr.* = js_persistent;
9911003
return js_persistent;
@@ -1267,7 +1279,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
12671279

12681280
// Reverses the mapZigInstanceToJs, making sure that our TaggedAnyOpaque
12691281
// contains a ptr to the correct type.
1270-
fn typeTaggedAnyOpaque(comptime named_function: anytype, comptime R: type, op: ?*anyopaque) !R {
1282+
fn typeTaggedAnyOpaque(comptime named_function: anytype, comptime R: type, js_obj: v8.Object) !R {
12711283
const ti = @typeInfo(R);
12721284
if (ti != .pointer) {
12731285
@compileError(std.fmt.comptimePrint(
@@ -1276,16 +1288,25 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
12761288
));
12771289
}
12781290

1279-
const type_name = @typeName(ti.pointer.child);
1291+
const T = ti.pointer.child;
1292+
if (comptime isEmpty(T)) {
1293+
// Empty structs aren't stored as TOAs and there's no data
1294+
// stored in the JSObject's IntenrnalField. Why bother when
1295+
// we can just return an empty struct here?
1296+
return @constCast(@as(*const T, &.{}));
1297+
}
1298+
1299+
const type_name = @typeName(T);
12801300
if (@hasField(TypeLookup, type_name) == false) {
12811301
@compileError(std.fmt.comptimePrint(
12821302
"{s} has an unknown Zig type: {s}",
12831303
.{ named_function.full_name, @typeName(R) },
12841304
));
12851305
}
12861306

1307+
const op = js_obj.getInternalField(0).castTo(v8.External).get();
12871308
const toa: *TaggedAnyOpaque = @alignCast(@ptrCast(op));
1288-
const expected_type_index = @field(TYPE_LOOKUP, @typeName(ti.pointer.child));
1309+
const expected_type_index = @field(TYPE_LOOKUP, type_name);
12891310

12901311
var type_index = toa.index;
12911312
if (type_index == expected_type_index) {
@@ -1319,6 +1340,10 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
13191340
};
13201341
}
13211342

1343+
fn isEmpty(comptime T: type) bool {
1344+
return @typeInfo(T) != .@"opaque" and @sizeOf(T) == 0;
1345+
}
1346+
13221347
// Responsible for calling Zig functions from JS invokations. This could
13231348
// probably just contained in Executor, but having this specific logic, which
13241349
// is somewhat repetitive between constructors, functions, getters, etc contained
@@ -1380,8 +1405,7 @@ fn Caller(comptime E: type) type {
13801405
comptime assertSelfReceiver(named_function);
13811406

13821407
var args = try self.getArgs(named_function, 1, info);
1383-
const external = info.getThis().getInternalField(0).castTo(v8.External);
1384-
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
1408+
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
13851409

13861410
// inject 'self' as the first parameter
13871411
@field(args, "0") = zig_instance;
@@ -1402,8 +1426,7 @@ fn Caller(comptime E: type) type {
14021426
switch (arg_fields.len) {
14031427
0 => {}, // getters _can_ be parameterless
14041428
1, 2 => {
1405-
const external = info.getThis().getInternalField(0).castTo(v8.External);
1406-
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
1429+
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
14071430
comptime assertSelfReceiver(named_function);
14081431
@field(args, "0") = zig_instance;
14091432
if (comptime arg_fields.len == 2) {
@@ -1421,8 +1444,7 @@ fn Caller(comptime E: type) type {
14211444
const S = named_function.S;
14221445
comptime assertSelfReceiver(named_function);
14231446

1424-
const external = info.getThis().getInternalField(0).castTo(v8.External);
1425-
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
1447+
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
14261448

14271449
const Setter = @TypeOf(named_function.func);
14281450
var args: ParamterTypes(Setter) = undefined;
@@ -1464,8 +1486,7 @@ fn Caller(comptime E: type) type {
14641486
switch (arg_fields.len) {
14651487
0, 1, 2 => @compileError(named_function.full_name ++ " must take at least a u32 and *bool parameter"),
14661488
3, 4 => {
1467-
const external = info.getThis().getInternalField(0).castTo(v8.External);
1468-
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
1489+
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
14691490
comptime assertSelfReceiver(named_function);
14701491
@field(args, "0") = zig_instance;
14711492
@field(args, "1") = idx;
@@ -1492,8 +1513,7 @@ fn Caller(comptime E: type) type {
14921513
const S = named_function.S;
14931514
comptime assertSelfReceiver(named_function);
14941515

1495-
const external = info.getThis().getInternalField(0).castTo(v8.External);
1496-
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
1516+
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
14971517

14981518
const IndexedSet = @TypeOf(named_function.func);
14991519
var args: ParamterTypes(IndexedSet) = undefined;
@@ -1537,8 +1557,7 @@ fn Caller(comptime E: type) type {
15371557
switch (arg_fields.len) {
15381558
0, 1, 2 => @compileError(named_function.full_name ++ " must take at least a u32 and *bool parameter"),
15391559
3, 4 => {
1540-
const external = info.getThis().getInternalField(0).castTo(v8.External);
1541-
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
1560+
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
15421561
comptime assertSelfReceiver(named_function);
15431562
@field(args, "0") = zig_instance;
15441563
@field(args, "1") = try self.nameToString(name);
@@ -1565,8 +1584,7 @@ fn Caller(comptime E: type) type {
15651584
const S = named_function.S;
15661585
comptime assertSelfReceiver(named_function);
15671586

1568-
const external = info.getThis().getInternalField(0).castTo(v8.External);
1569-
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
1587+
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
15701588

15711589
const IndexedSet = @TypeOf(named_function.func);
15721590
var args: ParamterTypes(IndexedSet) = undefined;
@@ -1749,14 +1767,17 @@ fn Caller(comptime E: type) type {
17491767
const corresponding_js_index = last_parameter_index - adjusted_offset;
17501768
const corresponding_js_value = info.getArg(@as(u32, @intCast(corresponding_js_index)));
17511769
if (corresponding_js_value.isArray() == false and slice_type != u8) {
1752-
const arr = try self.call_allocator.alloc(last_parameter_type_info.pointer.child, js_parameter_count - expected_js_parameters + 1);
1753-
for (arr, corresponding_js_index..) |*a, i| {
1754-
const js_value = info.getArg(@as(u32, @intCast(i)));
1755-
a.* = try self.jsValueToZig(named_function, slice_type, js_value);
1756-
}
1757-
17581770
is_variadic = true;
1759-
@field(args, tupleFieldName(last_parameter_index)) = arr;
1771+
if (js_parameter_count == 0) {
1772+
@field(args, tupleFieldName(last_parameter_index)) = &.{};
1773+
} else {
1774+
const arr = try self.call_allocator.alloc(last_parameter_type_info.pointer.child, js_parameter_count - expected_js_parameters + 1);
1775+
for (arr, corresponding_js_index..) |*a, i| {
1776+
const js_value = info.getArg(@as(u32, @intCast(i)));
1777+
a.* = try self.jsValueToZig(named_function, slice_type, js_value);
1778+
}
1779+
@field(args, tupleFieldName(last_parameter_index)) = arr;
1780+
}
17601781
}
17611782
}
17621783
}
@@ -1773,7 +1794,7 @@ fn Caller(comptime E: type) type {
17731794
@compileError("State must be the 2nd parameter: " ++ named_function.full_name);
17741795
} else if (i >= js_parameter_count) {
17751796
if (@typeInfo(param.type.?) != .optional) {
1776-
return error.TypeError;
1797+
return error.InvalidArgument;
17771798
}
17781799
@field(args, tupleFieldName(field_index)) = null;
17791800
} else {
@@ -1812,7 +1833,7 @@ fn Caller(comptime E: type) type {
18121833
if (obj.internalFieldCount() == 0) {
18131834
return error.InvalidArgument;
18141835
}
1815-
return E.typeTaggedAnyOpaque(named_function, *Receiver(ptr.child), obj.getInternalField(0).castTo(v8.External).get());
1836+
return E.typeTaggedAnyOpaque(named_function, *Receiver(ptr.child), obj);
18161837
}
18171838
},
18181839
.slice => {
@@ -2271,3 +2292,8 @@ fn getTaggedAnyOpaque(value: v8.Value) ?*TaggedAnyOpaque {
22712292
const external_data = obj.getInternalField(0).castTo(v8.External).get().?;
22722293
return @alignCast(@ptrCast(external_data));
22732294
}
2295+
2296+
test {
2297+
std.testing.refAllDecls(@import("test_primitive_types.zig"));
2298+
std.testing.refAllDecls(@import("test_complex_types.zig"));
2299+
}

0 commit comments

Comments
 (0)