mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-28 08:36:17 +08:00
discrete type for fast functions
This commit is contained in:
3 files changed
+233
-55
No files matched your search
@@ -1024,6 +1024,169 @@ pub const Function = struct {
|
||||
};
|
||||
};
|
||||
|
||||
/// V8 Fast API integration.
|
||||
pub const FastFunction = struct {
|
||||
pub const Options = struct {
|
||||
static: bool,
|
||||
exposed: Function.Opts.Exposed,
|
||||
};
|
||||
|
||||
inline fn isPointer(comptime T: type) bool {
|
||||
const Inner = if (@typeInfo(T) == .optional) @typeInfo(T).optional.child else T;
|
||||
return @typeInfo(Inner) == .pointer;
|
||||
}
|
||||
|
||||
/// Only primitives and V8 values are allowed.
|
||||
fn Arg(comptime T: type) type {
|
||||
// Treat pointers as V8 values; we can change this in the future.
|
||||
if (comptime isPointer(T)) {
|
||||
return ?*const v8.Value;
|
||||
}
|
||||
// Primitives.
|
||||
return switch (comptime T) {
|
||||
void, bool, i32, u32, i64, u64, f32, f64 => T,
|
||||
else => @compileError("type '" ++ @typeName(T) ++ "' is not allowed in Fast Functions"),
|
||||
};
|
||||
}
|
||||
|
||||
fn toCTypeEnum(comptime T: type) v8.CTypeInfo {
|
||||
// Treat pointers as V8 values; we can change this in the future.
|
||||
if (comptime isPointer(T)) {
|
||||
return .{ .type = v8.kCTypeInfoType_V8Value };
|
||||
}
|
||||
// Primitives.
|
||||
return switch (T) {
|
||||
void => .{ .type = v8.kCTypeInfoType_Void },
|
||||
bool => .{ .type = v8.kCTypeInfoType_Bool },
|
||||
u8 => unreachable, // u8 return crashes TurboFan.
|
||||
i32 => .{ .type = v8.kCTypeInfoType_Int32 },
|
||||
u32 => .{ .type = v8.kCTypeInfoType_Uint32 },
|
||||
i64 => .{ .type = v8.kCTypeInfoType_Int64 },
|
||||
u64 => .{ .type = v8.kCTypeInfoType_Uint64 },
|
||||
f32 => .{ .type = v8.kCTypeInfoType_Float32 },
|
||||
f64 => .{ .type = v8.kCTypeInfoType_Float64 },
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Mirrors `.optional` / `.pointer` branches from `Local.jsValueToZig`.
|
||||
fn toZigObject(comptime T: type, handle: ?*const v8.Value) error{InvalidArgument}!T {
|
||||
const value = handle.?;
|
||||
if (@typeInfo(T) == .optional) {
|
||||
if (v8.v8__Value__IsNullOrUndefined(value)) return null;
|
||||
}
|
||||
if (!v8.v8__Value__IsObject(value)) return error.InvalidArgument;
|
||||
const Pointer = if (@typeInfo(T) == .optional) @typeInfo(T).optional.child else T;
|
||||
return TaggedOpaque.fromJS(Pointer, @ptrCast(value));
|
||||
}
|
||||
|
||||
fn canReturnErr(comptime T: type) struct { type, bool } {
|
||||
return switch (@typeInfo(T)) {
|
||||
.error_union => |info| .{ info.payload, true },
|
||||
.error_set => unreachable,
|
||||
else => .{ T, false },
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a constant `v8.CFunction`.
|
||||
pub fn create(comptime function: anytype, comptime options: Options) *const v8.CFunction {
|
||||
// Bunch of comptime-known stuff.
|
||||
const Fn = @TypeOf(function);
|
||||
const info = @typeInfo(Fn).@"fn";
|
||||
const P = info.param_types;
|
||||
// TODO: Check if last argument is `*js.Execution` or `*const js.Execution`.
|
||||
if (comptime !options.static and P.len == 0) {
|
||||
@compileError("non-static fast functions need a receiver parameter");
|
||||
}
|
||||
// Parameters bound from JS arguments; a non-static function's `self`
|
||||
// comes from the receiver instead.
|
||||
const offset = if (options.static) 0 else 1;
|
||||
const JsP = P[offset..];
|
||||
const R, const can_return_err = canReturnErr(info.return_type orelse {
|
||||
@compileError("fast functions cannot return generic types");
|
||||
});
|
||||
// TODO: Support this in the future.
|
||||
if (comptime can_return_err) {
|
||||
@compileError("fast functions cannot return errors currently");
|
||||
}
|
||||
const Opts = *const v8.FastApiCallbackOptions;
|
||||
|
||||
const type_safe_call = struct {
|
||||
inline fn wrap(c_args: anytype, opts: Opts) R {
|
||||
var args: ParameterTypes(Fn) = undefined;
|
||||
if (comptime !options.static) {
|
||||
// Cannot fail.
|
||||
args[0] = TaggedOpaque.fromJS(P[0].?, @ptrCast(c_args[0].?)) catch unreachable;
|
||||
}
|
||||
inline for (JsP, 0..) |param_type, i| {
|
||||
const c_arg = c_args[i + 1];
|
||||
args[i + offset] = blk: {
|
||||
const ParamType = param_type.?;
|
||||
if (comptime isPointer(ParamType)) {
|
||||
break :blk toZigObject(ParamType, c_arg) catch {
|
||||
// On failure, we get into handle scope, this
|
||||
// should only be done with `opts.isolate`.
|
||||
const isolate_handle = opts.isolate.?;
|
||||
const isolate: js.Isolate = .{ .handle = isolate_handle };
|
||||
var scope: js.HandleScope = undefined;
|
||||
scope.initWithIsolateHandle(isolate_handle);
|
||||
defer scope.deinit();
|
||||
_ = isolate.throwException(isolate.createTypeError("invalid argument"));
|
||||
return std.mem.zeroes(R);
|
||||
};
|
||||
}
|
||||
// Primitive.
|
||||
break :blk c_arg;
|
||||
};
|
||||
}
|
||||
return @call(.auto, function, args);
|
||||
}
|
||||
}.wrap;
|
||||
|
||||
// Zig doesn't allow us to build parameter lists in comptime. So we
|
||||
// switch on the JS argument count and prefer a wrapper depending on it.
|
||||
const func = switch (comptime JsP.len) {
|
||||
0 => struct {
|
||||
fn wrap(recv: ?*const v8.Value, opts: Opts) callconv(.c) R {
|
||||
return type_safe_call(.{recv}, opts);
|
||||
}
|
||||
}.wrap,
|
||||
1 => struct {
|
||||
fn wrap(recv: ?*const v8.Value, a0: Arg(JsP[0].?), opts: Opts) callconv(.c) R {
|
||||
return type_safe_call(.{ recv, a0 }, opts);
|
||||
}
|
||||
}.wrap,
|
||||
2 => struct {
|
||||
fn wrap(recv: ?*const v8.Value, a0: Arg(JsP[0].?), a1: Arg(JsP[1].?), opts: Opts) callconv(.c) R {
|
||||
return type_safe_call(.{ recv, a0, a1 }, opts);
|
||||
}
|
||||
}.wrap,
|
||||
3 => struct {
|
||||
fn wrap(recv: ?*const v8.Value, a0: Arg(JsP[0].?), a1: Arg(JsP[1].?), a2: Arg(JsP[2].?), opts: Opts) callconv(.c) R {
|
||||
return type_safe_call(.{ recv, a0, a1, a2 }, opts);
|
||||
}
|
||||
}.wrap,
|
||||
else => @compileError("exceeded argument limit for fast functions"),
|
||||
};
|
||||
|
||||
const args = comptime blk: {
|
||||
// [receiver] + [JS arguments] + [v8.FastApiCallbackOptions].
|
||||
var list: [JsP.len + 2]v8.CTypeInfo = undefined;
|
||||
list[0] = .{ .type = v8.kCTypeInfoType_V8Value };
|
||||
for (JsP, 1..) |t, i| list[i] = toCTypeEnum(t.?);
|
||||
list[list.len - 1] = .{ .type = v8.kCTypeInfoType_CallbackOptions };
|
||||
break :blk list;
|
||||
};
|
||||
const func_info: *const v8.CFunctionInfo = &.{
|
||||
.return_info = toCTypeEnum(R),
|
||||
.arg_count = args.len,
|
||||
.arg_info = &args,
|
||||
};
|
||||
|
||||
return &.{ .address = @ptrCast(&func), .type_info = func_info };
|
||||
}
|
||||
};
|
||||
|
||||
// If we call a method in javascript: cat.lives('nine');
|
||||
//
|
||||
// Then we'd expect a Zig function with 2 parameters: a self and the string.
|
||||
|
||||
@@ -462,6 +462,9 @@ fn countExternalReferences() comptime_int {
|
||||
continue;
|
||||
}
|
||||
count += 1;
|
||||
} else if (T == bridge.FastFunction) {
|
||||
// slow callback + v8.CFunction
|
||||
count += 2;
|
||||
} else if (T == bridge.Iterator) {
|
||||
count += 1;
|
||||
} else if (T == bridge.Indexed) {
|
||||
@@ -546,7 +549,6 @@ fn collectExternalReferences() *const ExternalReferences {
|
||||
references[idx] = @bitCast(@intFromPtr(value.getter));
|
||||
idx += 1;
|
||||
if (value.fast_getter) |fast_getter| {
|
||||
// V8 stores a pointer to the CFunction struct itself, not to fast_method_fn.
|
||||
references[idx] = @bitCast(@intFromPtr(fast_getter));
|
||||
idx += 1;
|
||||
}
|
||||
@@ -560,6 +562,13 @@ fn collectExternalReferences() *const ExternalReferences {
|
||||
}
|
||||
references[idx] = @bitCast(@intFromPtr(value.func));
|
||||
idx += 1;
|
||||
} else if (T == bridge.FastFunction) {
|
||||
references[idx] = @bitCast(@intFromPtr(value.slow));
|
||||
idx += 1;
|
||||
// V8 stores a pointer to the v8.CFunction itself (not its
|
||||
// .address) as a Foreign in the template's C function overloads.
|
||||
references[idx] = @bitCast(@intFromPtr(value.fast));
|
||||
idx += 1;
|
||||
} else if (T == bridge.Iterator) {
|
||||
references[idx] = @bitCast(@intFromPtr(value.func));
|
||||
idx += 1;
|
||||
@@ -880,6 +889,25 @@ fn attachClass(comptime JsApi: type, comptime flatten: bool, isolate: *v8.Isolat
|
||||
v8.v8__Template__Set(@ptrCast(define_on orelse member_template), js_name, @ptrCast(function_template), v8.None);
|
||||
}
|
||||
},
|
||||
bridge.FastFunction => {
|
||||
const func_signature = if (value.static) null else signature;
|
||||
const function_template = v8.v8__FunctionTemplate__New__CFunction(isolate, &.{
|
||||
.callback = value.slow,
|
||||
.signature = func_signature,
|
||||
.length = @intCast(value.arity()),
|
||||
.behavior = v8.kConstructorBehavior_Throw,
|
||||
.side_effect_type = v8.kSideEffectType_HasSideEffect,
|
||||
}, value.fast, 0, 0, 0).?;
|
||||
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
||||
v8.v8__FunctionTemplate__SetClassName(function_template, js_name);
|
||||
if (value.static and !own_properties) {
|
||||
v8.v8__Template__Set(@ptrCast(template), js_name, @ptrCast(function_template), v8.None);
|
||||
} else {
|
||||
// Web IDL: operations on the interface prototype object
|
||||
// (and mirrored onto [Global] instances) are enumerable.
|
||||
v8.v8__Template__Set(@ptrCast(define_on orelse member_template), js_name, @ptrCast(function_template), v8.None);
|
||||
}
|
||||
},
|
||||
bridge.Indexed => {
|
||||
var configuration: v8.IndexedPropertyHandlerConfiguration = .{
|
||||
.getter = value.getter,
|
||||
@@ -1033,9 +1061,11 @@ fn attachAccessorProperty(comptime name: [:0]const u8, value: bridge.Accessor, i
|
||||
|
||||
const getter_callback: *const v8.FunctionTemplate = blk: {
|
||||
if (value.fast_getter) |fast_getter| {
|
||||
// You can pass `data` through this constructor:
|
||||
// https://v8docs.nodesource.com/node-25.0/d5/dfd/structv8_1_1_fast_api_callback_options.html#ad3be03826a4a0cfbf72eab7eac9cc3db
|
||||
// For our use cases, this make no sense though.
|
||||
break :blk v8.v8__FunctionTemplate__New__CFunction(isolate, &.{
|
||||
.callback = value.getter,
|
||||
.data = null,
|
||||
.signature = getter_signature,
|
||||
.length = 0,
|
||||
.behavior = v8.kConstructorBehavior_Throw, // Required with a fast function.
|
||||
|
||||
+38
-53
@@ -46,6 +46,10 @@ pub fn Builder(comptime T: type) type {
|
||||
return Function.init(T, func, opts);
|
||||
}
|
||||
|
||||
pub fn fastFunction(comptime func: anytype, comptime opts: Caller.FastFunction.Options) FastFunction {
|
||||
return FastFunction.init(T, func, opts);
|
||||
}
|
||||
|
||||
pub fn indexed(comptime getter_func: anytype, comptime enumerator_func: anytype, comptime opts: Indexed.Opts) Indexed {
|
||||
return Indexed.init(T, getter_func, null, null, null, null, enumerator_func, opts);
|
||||
}
|
||||
@@ -241,7 +245,40 @@ pub const Function = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const TaggedOpaque = @import("TaggedOpaque.zig");
|
||||
pub const FastFunction = struct {
|
||||
static: bool,
|
||||
exposed: Caller.Function.Opts.Exposed,
|
||||
/// V8 still requires us to create a slow variant.
|
||||
slow: *const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void,
|
||||
fast: *const v8.CFunction,
|
||||
|
||||
fn init(
|
||||
comptime T: type,
|
||||
comptime func: anytype,
|
||||
comptime opts: Caller.FastFunction.Options,
|
||||
) FastFunction {
|
||||
return .{
|
||||
.static = opts.static,
|
||||
.exposed = opts.exposed,
|
||||
.slow = struct {
|
||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||
return Caller.Function.call(T, handle.?, func, .{
|
||||
.static = opts.static,
|
||||
.exposed = opts.exposed,
|
||||
});
|
||||
}
|
||||
}.wrap,
|
||||
.fast = Caller.FastFunction.create(func, .{
|
||||
.static = opts.static,
|
||||
.exposed = opts.exposed,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn arity(self: FastFunction) c_uint {
|
||||
return self.fast.type_info.*.arg_count - 2;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Accessor = struct {
|
||||
static: bool = false,
|
||||
@@ -281,58 +318,6 @@ pub const Accessor = struct {
|
||||
Caller.Function.call(T, handle.?, getter, getter_opts);
|
||||
}
|
||||
}.wrap;
|
||||
|
||||
const can_be_fast = blk: {
|
||||
switch (@typeInfo(@TypeOf(getter))) {
|
||||
.@"fn" => |info| {
|
||||
const param_types = info.param_types;
|
||||
break :blk param_types.len == 1 and
|
||||
opts.static == false and
|
||||
opts.cache == null and
|
||||
opts.noop == false and
|
||||
opts.embedded_receiver == false;
|
||||
},
|
||||
.null => false,
|
||||
else => unreachable,
|
||||
}
|
||||
};
|
||||
|
||||
fast_getter: {
|
||||
if (comptime can_be_fast) {
|
||||
const R = @typeInfo(@TypeOf(getter)).@"fn".return_type.?;
|
||||
|
||||
const host_to_v8_type = switch (comptime R) {
|
||||
void => v8.kCTypeInfoType_Void,
|
||||
bool => v8.kCTypeInfoType_Bool,
|
||||
u8 => break :fast_getter, // u8 return crashes TurboFan.
|
||||
i32 => v8.kCTypeInfoType_Int32,
|
||||
u32 => v8.kCTypeInfoType_Uint32,
|
||||
i64 => v8.kCTypeInfoType_Int64,
|
||||
u64 => v8.kCTypeInfoType_Uint64,
|
||||
f32 => v8.kCTypeInfoType_Float32,
|
||||
f64 => v8.kCTypeInfoType_Float64,
|
||||
else => break :fast_getter,
|
||||
};
|
||||
|
||||
const fast_fn = struct {
|
||||
fn wrap(handle: ?*const v8.Value) callconv(.c) R {
|
||||
const self = TaggedOpaque.fromJS(*T, handle.?) catch unreachable;
|
||||
return @call(.auto, getter, .{self});
|
||||
}
|
||||
}.wrap;
|
||||
|
||||
const fast_method_info: *const v8.CFunctionInfo = &.{
|
||||
.return_info = .{ .type = host_to_v8_type },
|
||||
.arg_count = fast_method_args.len,
|
||||
.arg_info = &fast_method_args,
|
||||
};
|
||||
|
||||
accessor.fast_getter = &.{
|
||||
.address = @ptrCast(&fast_fn),
|
||||
.type_info = fast_method_info,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (@typeInfo(@TypeOf(setter)) != .null) {
|
||||
|
||||
Reference in new issue
Block a user