diff --git a/src/engines/v8/callback.zig b/src/engines/v8/callback.zig index d0352e2..cfed8fa 100644 --- a/src/engines/v8/callback.zig +++ b/src/engines/v8/callback.zig @@ -119,11 +119,6 @@ pub const FuncSync = struct { func.index_offset, ) orelse unreachable; - std.debug.print("idx: {d}, offset: {d}, {any}\n", .{ - func.callback_index.?, - idx, - js_func_val, - }); if (!js_func_val.isFunction()) { return error.JSWrongType; } diff --git a/src/interfaces.zig b/src/interfaces.zig deleted file mode 100644 index 3d32f8f..0000000 --- a/src/interfaces.zig +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2023-2024 Lightpanda (Selecy SAS) -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const std = @import("std"); - -const internal = @import("internal_api.zig"); -const refl = internal.refl; - -const public = @import("api.zig"); - -// Interfaces definitions -// ---------------------- - -// NOTE: all thoses interfaces defintions checks must be called comptime - -// TODO: change anyerror to custom error set - -pub fn API(comptime T: type, comptime LoadFnType: type) void { - - // nativeT(), the reflected type of a native struct - assertDecl(T, "nativeT", fn (comptime self: T) callconv(.Inline) refl.Struct); - - // loadFn(), the function loading and binding this native struct into the JS engine - assertDecl(T, "loadFn", fn (comptime self: T) callconv(.Inline) LoadFnType); -} - -pub fn VM(comptime T: type) void { - - // init() - assertDecl(T, "init", fn () T); - - // deinit() - assertDecl(T, "deinit", fn (T) void); -} - -pub fn Env( - comptime T: type, - comptime Inspector_T: type, - comptime JSValue_T: type, - comptime Object_T: type, -) void { - - // engine() - assertDecl(T, "engine", fn () public.EngineType); - - // init() - assertDecl(T, "init", fn (self: *T, alloc: std.mem.Allocator, loop: *public.Loop, userctx: ?public.UserContext) void); - - // deinit() - assertDecl(T, "deinit", fn (self: *T) void); - - // load() native apis into js templates - assertDecl(T, "load", fn (self: *T, js_types: []usize) anyerror!void); - - assertDecl(T, "bindGlobal", fn (self: *T, ob: anytype) anyerror!void); - - assertDecl(T, "setInspector", fn (self: *T, inspector: Inspector_T) void); - assertDecl(T, "getInspector", fn (self: T) callconv(.Inline) ?Inspector_T); - - assertDecl(T, "setUserContext", fn ( - self: *T, - userctx: public.UserContext, - ) anyerror!void); - - // start() - assertDecl(T, "start", fn (self: *T) anyerror!void); - - // stop() - assertDecl(T, "stop", fn (self: *T) void); - - // getGlobal() to retrieve global object from current JS context - assertDecl(T, "getGlobal", fn (self: T) anyerror!Object_T); - - // addObject() from native api into JS - assertDecl(T, "addObject", fn ( - self: *T, - obj: anytype, - name: []const u8, - ) anyerror!void); - - // attachObject() from JS to another JS object - // if to_obj is null, globals is implied - assertDecl(T, "attachObject", fn ( - self: T, - obj: Object_T, - name: []const u8, - to_obj: ?Object_T, - ) anyerror!void); - - // exec() executes script in JS - assertDecl(T, "exec", fn ( - self: T, - script: []const u8, - name: ?[]const u8, - ) anyerror!JSValue_T); - - // wait() all JS callbacks - assertDecl(T, "wait", fn (self: T) anyerror!void); - - // execWait() executes script in JS and waits all JS callbacks - assertDecl(T, "execWait", fn ( - self: T, - script: []const u8, - name: ?[]const u8, - ) anyerror!JSValue_T); -} - -pub fn JSValue(comptime T: type, env: type) void { - - // toString() - assertDecl(T, "toString", fn (self: T, alloc: std.mem.Allocator, env: *const env) anyerror![]const u8); - - // typeOf() - assertDecl(T, "typeOf", fn (self: T, env: env) anyerror!public.JSTypes); -} - -pub fn JSObjectID(comptime T: type) void { - - // get() - assertDecl(T, "get", fn (self: T) usize); -} - -pub fn TryCatch(comptime T: type, comptime env: type) void { - - // init() - assertDecl(T, "init", fn (self: *T, env: *const env) void); - - // deinit() - assertDecl(T, "deinit", fn (self: *T) void); - - // hasCaught() - assertDecl(T, "hasCaught", fn (self: T) bool); - - // exception() - assertDecl(T, "exception", fn ( - self: T, - alloc: std.mem.Allocator, - env: *const env, - ) anyerror!?[]const u8); - - // err() - assertDecl(T, "err", fn ( - self: T, - alloc: std.mem.Allocator, - env: *const env, - ) anyerror!?[]const u8); - - // stack() - assertDecl(T, "stack", fn ( - self: T, - alloc: std.mem.Allocator, - env: *const env, - ) anyerror!?[]const u8); -} - -pub fn Callback(comptime T: type, comptime Res_T: type) void { - - // id() - assertDecl(T, "id", fn (T: T) usize); - - // call() - assertDecl(T, "call", fn (T: T, nat_args: anytype) anyerror!void); - - // trycall() - assertDecl(T, "trycall", fn (T: T, nat_args: anytype, res: *Res_T) anyerror!void); -} - -pub fn CallbackSync(comptime T: type, comptime Res_T: type) void { - // call() - assertDecl(T, "call", fn (T: T, alloc: std.mem.Allocator) anyerror!void); - - // trycall() - assertDecl(T, "trycall", fn (T: T, alloc: std.mem.Allocator, res: *Res_T) anyerror!void); -} - -pub fn CallbackArg(comptime _: type) void {} - -pub fn CallbackResult(comptime T: type) void { - // init() - assertDecl(T, "init", fn (alloc: std.mem.Allocator) T); - - // deinit() - assertDecl(T, "deinit", fn (self: T) void); - - // TODO: how to get the result? -} - -pub fn Inspector(comptime T: type, comptime Env_T: type) void { - - // init() - assertDecl(T, "init", fn ( - alloc: std.mem.Allocator, - env: *const Env_T, - ctx: *anyopaque, - onResp: public.InspectorOnResponseFn, - onEvent: public.InspectorOnEventFn, - ) anyerror!T); - - // deinit() - assertDecl(T, "deinit", fn (self: T, alloc: std.mem.Allocator) void); - - // contextCreated() - assertDecl(T, "contextCreated", fn ( - self: T, - env: *const Env_T, - name: []const u8, - origin: []const u8, - auxData: ?[]const u8, - ) void); - - // send() - assertDecl(T, "send", fn (self: T, env: Env_T, msg: []const u8) void); -} - -// Utils -// ----- - -// from https://github.com/hexops/mach-gpu/blob/main/src/interface.zig -fn assertDecl(comptime T: anytype, comptime name: []const u8, comptime Decl: type) void { - if (!@hasDecl(T, name)) @compileError("Interface missing declaration: " ++ @typeName(Decl)); - // TODO: check if decl is: - // - function - // - pub - const FoundDecl = @TypeOf(@field(T, name)); - if (FoundDecl != Decl) @compileError("Interface field '" ++ name ++ "'\n\texpected type: " ++ @typeName(Decl) ++ "\n\t found type: " ++ @typeName(FoundDecl)); -} diff --git a/src/loop.zig b/src/loop.zig index c9cf26e..83d124c 100644 --- a/src/loop.zig +++ b/src/loop.zig @@ -14,6 +14,7 @@ const std = @import("std"); const builtin = @import("builtin"); +const MemoryPool = std.heap.MemoryPool; pub const IO = @import("tigerbeetle-io").IO; @@ -22,14 +23,6 @@ const JSCallback = public.Callback; const log = std.log.scoped(.loop); -fn report(comptime fmt: []const u8, args: anytype) void { - const max_len = 200; - var buf: [max_len]u8 = undefined; - const s = std.fmt.bufPrint(buf[0..], fmt, args) catch |err| @panic(@errorName(err)); - const report_fmt = "[Thread {d}] {s}\n"; - std.debug.print(report_fmt, .{ std.Thread.getCurrentId(), s }); -} - // SingleThreaded I/O Loop based on Tigerbeetle io_uring loop. // On Linux it's using io_uring. // On MacOS and Windows it's using kqueue/IOCP with a ring design. @@ -56,6 +49,10 @@ pub const SingleThreaded = struct { // This is a weak way to cancel all future Zig callbacks. zig_ctx_id: u32 = 0, + cancel_pool: MemoryPool(ContextCancel), + timeout_pool: MemoryPool(ContextTimeout), + event_callback_pool: MemoryPool(EventCallbackContext), + const Self = @This(); pub const Completion = IO.Completion; @@ -69,6 +66,9 @@ pub const SingleThreaded = struct { .io = try IO.init(32, 0), .js_events_nb = 0, .zig_events_nb = 0, + .cancel_pool = MemoryPool(ContextCancel).init(alloc), + .timeout_pool = MemoryPool(ContextTimeout).init(alloc), + .event_callback_pool = MemoryPool(EventCallbackContext).init(alloc), }; } @@ -83,6 +83,9 @@ pub const SingleThreaded = struct { } self.cancelAll(); self.io.deinit(); + self.cancel_pool.deinit(); + self.timeout_pool.deinit(); + self.event_callback_pool.deinit(); } // Retrieve all registred I/O events completed by OS kernel, @@ -115,12 +118,12 @@ pub const SingleThreaded = struct { // Register events atomically // - add 1 event and return previous value - fn addEvent(self: *Self, comptime event: Event) usize { - return @atomicRmw(usize, self.eventsPtr(event), .Add, 1, .acq_rel); + fn addEvent(self: *Self, comptime event: Event) void { + _ = @atomicRmw(usize, self.eventsPtr(event), .Add, 1, .acq_rel); } // - remove 1 event and return previous value - fn removeEvent(self: *Self, comptime event: Event) usize { - return @atomicRmw(usize, self.eventsPtr(event), .Sub, 1, .acq_rel); + fn removeEvent(self: *Self, comptime event: Event) void { + _ = @atomicRmw(usize, self.eventsPtr(event), .Sub, 1, .acq_rel); } // - get the number of current events fn eventsNb(self: *Self, comptime event: Event) usize { @@ -130,11 +133,6 @@ pub const SingleThreaded = struct { @atomicStore(usize, self.eventsPtr(event), 0, .unordered); } - fn freeCbk(self: *Self, completion: *IO.Completion, ctx: anytype) void { - self.alloc.destroy(completion); - self.alloc.destroy(ctx); - } - // JS callbacks APIs // ----------------- @@ -151,19 +149,17 @@ pub const SingleThreaded = struct { completion: *IO.Completion, result: IO.TimeoutError!void, ) void { + const loop = ctx.loop; defer { - const old_events_nb = ctx.loop.removeEvent(.js); - if (builtin.is_test) { - report("timeout done, remaining events: {d}", .{old_events_nb - 1}); - } - - ctx.loop.freeCbk(completion, ctx); + loop.removeEvent(.js); + loop.timeout_pool.destroy(ctx); + loop.alloc.destroy(completion); } // If the loop's context id has changed, don't call the js callback // function. The callback's memory has already be cleaned and the // events nb reset. - if (ctx.js_ctx_id != ctx.loop.js_ctx_id) return; + if (ctx.js_ctx_id != loop.js_ctx_id) return; // TODO: return the error to the callback result catch |err| { @@ -176,28 +172,28 @@ pub const SingleThreaded = struct { // js callback if (ctx.js_cbk) |*js_cbk| { - defer js_cbk.deinit(ctx.loop.alloc); + defer js_cbk.deinit(loop.alloc); js_cbk.call(null) catch { - ctx.loop.cbk_error = true; + loop.cbk_error = true; }; } } - pub fn timeout(self: *Self, nanoseconds: u63, js_cbk: ?JSCallback) usize { - const completion = self.alloc.create(IO.Completion) catch unreachable; + pub fn timeout(self: *Self, nanoseconds: u63, js_cbk: ?JSCallback) !usize { + const completion = try self.alloc.create(Completion); + errdefer self.alloc.destroy(completion); completion.* = undefined; - const ctx = self.alloc.create(ContextTimeout) catch unreachable; + + const ctx = try self.timeout_pool.create(); + errdefer self.timeout_pool.destroy(ctx); ctx.* = ContextTimeout{ .loop = self, .js_cbk = js_cbk, .js_ctx_id = self.js_ctx_id, }; - const old_events_nb = self.addEvent(.js); - self.io.timeout(*ContextTimeout, ctx, timeoutCallback, completion, nanoseconds); - if (builtin.is_test) { - report("start timeout {d} for {d} nanoseconds", .{ old_events_nb + 1, nanoseconds }); - } + self.addEvent(.js); + self.io.timeout(*ContextTimeout, ctx, timeoutCallback, completion, nanoseconds); return @intFromPtr(completion); } @@ -212,19 +208,18 @@ pub const SingleThreaded = struct { completion: *IO.Completion, result: IO.CancelOneError!void, ) void { - defer { - const old_events_nb = ctx.loop.removeEvent(.js); - if (builtin.is_test) { - report("cancel done, remaining events: {d}", .{old_events_nb - 1}); - } + const loop = ctx.loop; - ctx.loop.freeCbk(completion, ctx); + defer { + loop.removeEvent(.js); + loop.cancel_pool.destroy(ctx); + loop.alloc.destroy(completion); } // If the loop's context id has changed, don't call the js callback // function. The callback's memory has already be cleaned and the // events nb reset. - if (ctx.js_ctx_id != ctx.loop.js_ctx_id) return; + if (ctx.js_ctx_id != loop.js_ctx_id) return; // TODO: return the error to the callback result catch |err| { @@ -237,18 +232,20 @@ pub const SingleThreaded = struct { // js callback if (ctx.js_cbk) |*js_cbk| { - defer js_cbk.deinit(ctx.loop.alloc); + defer js_cbk.deinit(loop.alloc); js_cbk.call(null) catch { - ctx.loop.cbk_error = true; + loop.cbk_error = true; }; } } - pub fn cancel(self: *Self, id: usize, js_cbk: ?JSCallback) void { + pub fn cancel(self: *Self, id: usize, js_cbk: ?JSCallback) !void { const comp_cancel: *IO.Completion = @ptrFromInt(id); - const completion = self.alloc.create(IO.Completion) catch unreachable; + const completion = try self.alloc.create(Completion); + errdefer self.alloc.destroy(completion); completion.* = undefined; + const ctx = self.alloc.create(ContextCancel) catch unreachable; ctx.* = ContextCancel{ .loop = self, @@ -256,11 +253,8 @@ pub const SingleThreaded = struct { .js_ctx_id = self.js_ctx_id, }; - const old_events_nb = self.addEvent(.js); + self.addEvent(.js); self.io.cancel_one(*ContextCancel, ctx, cancelCallback, completion, comp_cancel); - if (builtin.is_test) { - report("cancel {d}", .{old_events_nb + 1}); - } } fn cancelAll(self: *Self) void { @@ -292,19 +286,21 @@ pub const SingleThreaded = struct { comptime cbk: fn (ctx: *Ctx, _: *Completion, res: ConnectError!void) void, socket: std.posix.socket_t, address: std.net.Address, - ) void { - const old_events_nb = self.addEvent(.js); - self.io.connect(*Ctx, ctx, cbk, completion, socket, address); - if (builtin.is_test) { - report("start connect {d}", .{old_events_nb + 1}); - } - } + ) !void { + const onConnect = struct { + fn onConnect(callback: *EventCallbackContext, completion_: *Completion, res: ConnectError!void) void { + defer callback.loop.event_callback_pool.destroy(callback); + callback.loop.removeEvent(.js); + cbk(@alignCast(@ptrCast(callback.ctx)), completion_, res); + } + }.onConnect; - pub fn onConnect(self: *Self, _: ConnectError!void) void { - const old_events_nb = self.removeEvent(.js); - if (builtin.is_test) { - report("connect done, remaining events: {d}", .{old_events_nb - 1}); - } + const callback = try self.event_callback_pool.create(); + errdefer self.event_callback_pool.destroy(callback); + callback.* = .{ .loop = self, .ctx = ctx }; + + self.addEvent(.js); + self.io.connect(*EventCallbackContext, callback, onConnect, completion, socket, address); } // Send @@ -317,19 +313,21 @@ pub const SingleThreaded = struct { comptime cbk: fn (ctx: *Ctx, completion: *Completion, res: SendError!usize) void, socket: std.posix.socket_t, buf: []const u8, - ) void { - const old_events_nb = self.addEvent(.js); - self.io.send(*Ctx, ctx, cbk, completion, socket, buf); - if (builtin.is_test) { - report("start send {d}", .{old_events_nb + 1}); - } - } + ) !void { + const onSend = struct { + fn onSend(callback: *EventCallbackContext, completion_: *Completion, res: SendError!usize) void { + defer callback.loop.event_callback_pool.destroy(callback); + callback.loop.removeEvent(.js); + cbk(@alignCast(@ptrCast(callback.ctx)), completion_, res); + } + }.onSend; - pub fn onSend(self: *Self, _: SendError!usize) void { - const old_events_nb = self.removeEvent(.js); - if (builtin.is_test) { - report("send done, remaining events: {d}", .{old_events_nb - 1}); - } + const callback = try self.event_callback_pool.create(); + errdefer self.event_callback_pool.destroy(callback); + callback.* = .{ .loop = self, .ctx = ctx }; + + self.addEvent(.js); + self.io.send(*EventCallbackContext, callback, onSend, completion, socket, buf); } // Recv @@ -342,19 +340,21 @@ pub const SingleThreaded = struct { comptime cbk: fn (ctx: *Ctx, completion: *Completion, res: RecvError!usize) void, socket: std.posix.socket_t, buf: []u8, - ) void { - const old_events_nb = self.addEvent(.js); - self.io.recv(*Ctx, ctx, cbk, completion, socket, buf); - if (builtin.is_test) { - report("start recv {d}", .{old_events_nb + 1}); - } - } + ) !void { + const onRecv = struct { + fn onRecv(callback: *EventCallbackContext, completion_: *Completion, res: RecvError!usize) void { + defer callback.loop.event_callback_pool.destroy(callback); + callback.loop.removeEvent(.js); + cbk(@alignCast(@ptrCast(callback.ctx)), completion_, res); + } + }.onRecv; - pub fn onRecv(self: *Self, _: RecvError!usize) void { - const old_events_nb = self.removeEvent(.js); - if (builtin.is_test) { - report("recv done, remaining events: {d}", .{old_events_nb - 1}); - } + const callback = try self.event_callback_pool.create(); + errdefer self.event_callback_pool.destroy(callback); + callback.* = .{ .loop = self, .ctx = ctx }; + + self.addEvent(.js); + self.io.recv(*EventCallbackContext, callback, onRecv, completion, socket, buf); } // Zig timeout @@ -374,15 +374,17 @@ pub const SingleThreaded = struct { completion: *IO.Completion, result: IO.TimeoutError!void, ) void { + const loop = ctx.loop; defer { - _ = ctx.loop.removeEvent(.zig); - ctx.loop.freeCbk(completion, ctx); + loop.removeEvent(.zig); + loop.alloc.destroy(ctx); + loop.alloc.destroy(completion); } // If the loop's context id has changed, don't call the js callback // function. The callback's memory has already be cleaned and the // events nb reset. - if (ctx.zig_ctx_id != ctx.loop.zig_ctx_id) return; + if (ctx.zig_ctx_id != loop.zig_ctx_id) return; result catch |err| { switch (err) { @@ -418,8 +420,12 @@ pub const SingleThreaded = struct { }.wrapper, }; - _ = self.addEvent(.zig); - + self.addEvent(.zig); self.io.timeout(*ContextZigTimeout, ctxtimeout, zigTimeoutCallback, completion, nanoseconds); } }; + +const EventCallbackContext = struct { + ctx: *anyopaque, + loop: *SingleThreaded, +}; diff --git a/src/private_api.zig b/src/private_api.zig index 0758f6c..afc2967 100644 --- a/src/private_api.zig +++ b/src/private_api.zig @@ -15,43 +15,10 @@ const std = @import("std"); const build_opts = @import("jsruntime_build_options"); -const interfaces = @import("interfaces.zig"); - -fn checkInterfaces(engine: anytype) void { - - // public api - interfaces.API(engine.API, engine.LoadFnType); - - interfaces.CallbackResult(engine.CallbackResult); - interfaces.Callback(engine.Callback, engine.CallbackResult); - interfaces.CallbackSync(engine.CallbackSync, engine.CallbackResult); - interfaces.CallbackArg(engine.CallbackArg); - - interfaces.JSValue(engine.JSValue, engine.Env); - interfaces.JSObjectID(engine.JSObjectID); - - interfaces.TryCatch(engine.TryCatch, engine.Env); - - interfaces.VM(engine.VM); - interfaces.Env( - engine.Env, - engine.Inspector, - engine.JSValue, - engine.Object, - ); - - interfaces.Inspector(engine.Inspector, engine.Env); - - // private api -} // retrieve JS engine pub const Engine = switch (build_opts.engine) { - .v8 => blk: { - const engine = @import("engines/v8/v8.zig"); - checkInterfaces(engine); - break :blk engine; - }, + .v8 => @import("engines/v8/v8.zig"), }; pub const API = Engine.API; diff --git a/src/tests/cbk_test.zig b/src/tests/cbk_test.zig index 443e84c..c3c0626 100644 --- a/src/tests/cbk_test.zig +++ b/src/tests/cbk_test.zig @@ -55,9 +55,9 @@ pub const Window = struct { loop: *jsruntime.Loop, callback: Callback, milliseconds: u32, - ) u32 { + ) !u32 { const n: u63 = @intCast(milliseconds); - const id = loop.timeout(n * std.time.ns_per_ms, callback); + const id = try loop.timeout(n * std.time.ns_per_ms, callback); defer self.timeoutid += 1; self.timeoutids[self.timeoutid] = id; @@ -71,9 +71,9 @@ pub const Window = struct { callback: Callback, milliseconds: u32, _: CallbackArg, - ) u32 { + ) !u32 { const n: u63 = @intCast(milliseconds); - const id = loop.timeout(n * std.time.ns_per_ms, callback); + const id = try loop.timeout(n * std.time.ns_per_ms, callback); defer self.timeoutid += 1; self.timeoutids[self.timeoutid] = id; @@ -81,9 +81,9 @@ pub const Window = struct { return self.timeoutid; } - pub fn _cancel(self: Window, loop: *jsruntime.Loop, id: u32) void { + pub fn _cancel(self: Window, loop: *jsruntime.Loop, id: u32) !void { if (id >= self.timeoutid) return; - loop.cancel(self.timeoutids[id], null); + try loop.cancel(self.timeoutids[id], null); } pub fn _cbkAsyncWithNatArg(_: Window, callback: Callback) !void {