Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 12 additions & 19 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("src/lib/libansi.zig"),
});

const wren = b.addModule("wren", .{
const fiberscript_mod = b.addModule("fiberscript", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("src/fiberscript/vm.zig"),
.root_source_file = b.path("src/fiberscript.zig"),
});

wren.addImport("ansi", ansi);
fiberscript_mod.addImport("ansi", ansi);

wren.addIncludePath(b.path("deps/wren/src/include"));
wren.addIncludePath(b.path("deps/wren/src/vm"));
wren.addIncludePath(b.path("deps/wren/src/optional"));
wren.addCSourceFiles(.{
fiberscript_mod.addIncludePath(b.path("deps/wren/src/include"));
fiberscript_mod.addIncludePath(b.path("deps/wren/src/vm"));
fiberscript_mod.addIncludePath(b.path("deps/wren/src/optional"));
fiberscript_mod.addCSourceFiles(.{
.files = &.{
"deps/wren/src/vm/wren_compiler.c",
"deps/wren/src/vm/wren_core.c",
Expand All @@ -43,15 +43,7 @@ pub fn build(b: *std.Build) void {
"-DDEBUG",
},
});
wren.addCMacro("abort", "zig_abort");

const libwren = b.addLibrary(.{
.linkage = .static,
.name = "wren",
.root_module = wren,
});

libwren.linkLibC();
fiberscript_mod.addCMacro("abort", "zig_abort");

// Create WASM-specific Wren library
const libwren_wasm = b.addStaticLibrary(.{
Expand Down Expand Up @@ -98,6 +90,7 @@ pub fn build(b: *std.Build) void {
});

xtc.addImport("ansi", ansi);
xtc.addImport("fiberscript", fiberscript_mod);

// xtc.addImport("code_point", zg.module("code_point"));
xtc.addImport("Graphemes", zg.module("Graphemes"));
Expand All @@ -110,7 +103,7 @@ pub fn build(b: *std.Build) void {
});

exe.linkSystemLibrary("m");
exe.linkLibrary(libwren);
exe.linkLibC();

b.installArtifact(exe);

Expand All @@ -120,7 +113,7 @@ pub fn build(b: *std.Build) void {
});

exe_check.linkSystemLibrary("m");
exe_check.linkLibrary(libwren);
exe_check.linkLibC();

const check_step = b.step("check", "Check the xtc executable");
check_step.dependOn(&exe_check.step);
Expand All @@ -134,7 +127,7 @@ pub fn build(b: *std.Build) void {
},
});

unit_tests.linkLibrary(libwren);
unit_tests.linkLibC();

b.installArtifact(unit_tests);

Expand Down
5 changes: 5 additions & 0 deletions src/fiberscript.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub usingnamespace @import("fiberscript/vm.zig");

pub const syscalls = @import("fiberscript/syscalls.zig");
pub const allocator = @import("fiberscript/allocator.zig");
pub const TrackingAllocator = @import("fiberscript/tracking_allocator.zig");
2 changes: 1 addition & 1 deletion src/fiberscript/allocator.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const std = @import("std");
const c = @import("wren.zig");
const TrackingAllocator = @import("../lib/TrackingAllocator.zig");
const TrackingAllocator = @import("tracking_allocator.zig");

/// Wren allocator wrapper that integrates with Zig's allocator system.
///
Expand Down
30 changes: 20 additions & 10 deletions src/fiberscript/syscalls.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub fn RequestUnion(comptime Syscalls: type) type {
const value_type = @TypeOf(value);
if (@typeInfo(value_type) == .@"fn") {
const fn_info = @typeInfo(value_type).@"fn";
const PayloadType = fn_info.params[1].type.?;
const PayloadType = fn_info.params[2].type.?;
union_fields[idx] = .{
.name = decl.name,
.type = PayloadType,
Expand Down Expand Up @@ -112,7 +112,7 @@ pub fn generateSlotParser(comptime Request: type, comptime Syscalls: type) type
const value = @field(Syscalls, decl.name);
const value_type = @TypeOf(value);
if (@typeInfo(value_type) == .@"fn" and std.mem.eql(u8, operation, decl.name)) {
const PayloadType = @typeInfo(value_type).@"fn".params[1].type.?;
const PayloadType = @typeInfo(value_type).@"fn".params[2].type.?;
const payload_fields = std.meta.fields(PayloadType);
var payload: PayloadType = undefined;
inline for (payload_fields) |pf| {
Expand All @@ -127,12 +127,17 @@ pub fn generateSlotParser(comptime Request: type, comptime Syscalls: type) type
}

/// Generate a trampoline dispatcher that bridges Wren fiber yields to syscall implementations.
pub fn generateTrampoline(comptime Syscalls: type, comptime Context: type) type {
pub fn generateTrampoline(
comptime Syscalls: type,
comptime Engine: type,
comptime Context: type,
) type {
return struct {
const RequestType = RequestUnion(Syscalls);
const ResultType = ResultUnion(Syscalls);
const DispatchResult = SyscallResult(Syscalls);

engine: *Engine,
context: *Context,

/// Process a single request from a yielding fiber
Expand All @@ -147,13 +152,13 @@ pub fn generateTrampoline(comptime Syscalls: type, comptime Context: type) type
const fn_info = @typeInfo(value_type).@"fn";
const ReturnType = fn_info.return_type.?;
if (ReturnType == Pending) {
try value(self.context, payload);
try value(self.engine, self.context, payload);
return .{ .pending = {} };
} else if (ReturnType == void) {
try value(self.context, payload);
try value(self.engine, self.context, payload);
return .{ .immediate = @unionInit(ResultType, decl.name, {}) };
} else {
const val = try value(self.context, payload);
const val = try value(self.engine, self.context, payload);
return .{ .immediate = @unionInit(ResultType, decl.name, val) };
}
}
Expand All @@ -170,6 +175,8 @@ pub fn generateTrampoline(comptime Syscalls: type, comptime Context: type) type

const testing = std.testing;

const TestEngine = struct {};

const TestContext = struct {
output_buffer: std.ArrayList(u8),

Expand All @@ -183,23 +190,26 @@ const TestContext = struct {
};

const TestSyscalls = struct {
pub fn print(context: *TestContext, payload: struct { message: []const u8 }) anyerror![]const u8 {
pub fn print(engine: *TestEngine, context: *TestContext, payload: struct { message: []const u8 }) anyerror![]const u8 {
_ = engine;
try context.output_buffer.appendSlice(payload.message);
return payload.message;
}

pub fn sleep(context: *TestContext, payload: struct { duration_ms: u64 }) anyerror!void {
pub fn sleep(engine: *TestEngine, context: *TestContext, payload: struct { duration_ms: u64 }) anyerror!void {
_ = engine;
_ = context;
_ = payload;
}
};

test "can generate trampoline dispatcher" {
const Trampoline = generateTrampoline(TestSyscalls, TestContext);
const Trampoline = generateTrampoline(TestSyscalls, TestEngine, TestContext);
var engine = TestEngine{};
var context = TestContext.init(testing.allocator);
defer context.deinit();

var trampoline = Trampoline{ .context = &context };
var trampoline = Trampoline{ .engine = &engine, .context = &context };
const request = Trampoline.RequestType{ .print = .{ .message = "hello" } };
const result = try trampoline.dispatch(request);
switch (result) {
Expand Down
File renamed without changes.
123 changes: 37 additions & 86 deletions src/fiberscript/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,105 +5,41 @@ const ErrorHandler = @import("error_handler.zig").ErrorHandler;
const slots_api = @import("slots.zig");
const OutputHandler = @import("output.zig").OutputHandler;
const syscalls = @import("syscalls.zig");
const dom = @import("../dom.zig");
const TrackingAllocator = @import("../lib/TrackingAllocator.zig");
const TrackingAllocator = @import("tracking_allocator.zig");

const ansi = @import("ansi");
const tree = ansi.nest;

pub fn Syscalls(comptime Self: type) type {
return struct {
pub fn createElement(self: *Self, args: struct { style: []const u8 }) anyerror!dom.DomNodeId {
return self.document.addElement(args.style);
}

pub fn updateText(self: *Self, args: struct { nodeId: u32, text: []const u8 }) anyerror!void {
try self.document.updateText(args.nodeId, args.text);
}

pub fn updateClass(self: *Self, args: struct { nodeId: u32, className: []const u8 }) anyerror!void {
try self.document.updateClass(args.nodeId, args.className);
}

pub fn appendChild(self: *Self, args: struct { parentId: u32, childId: u32 }) anyerror!void {
try self.document.appendChild(args.parentId, args.childId);
}

pub fn removeChild(self: *Self, args: struct { parentId: u32, childId: u32 }) anyerror!void {
try self.document.removeChild(args.parentId, args.childId);
}

pub fn requestRender(self: *Self, args: struct {}) anyerror!void {
_ = self;
_ = args;
@panic("requestRender not implemented");
}

pub fn clearScreen(self: *Self, args: struct {}) anyerror!void {
_ = self;
_ = args;
@panic("clearScreen not implemented");
}

pub fn requestAnimationFrame(self: *Self, args: struct {}) anyerror!void {
_ = self;
_ = args;
@panic("requestAnimationFrame not implemented");
}

pub fn setTimeout(self: *Self, args: struct { delayMs: f64 }) anyerror!void {
_ = self;
_ = args;
@panic("setTimeout not implemented");
}

pub fn addEventListener(self: *Self, args: struct { eventType: []const u8 }) anyerror!void {
_ = self;
_ = args;
@panic("addEventListener not implemented");
}

pub fn getViewportSize(self: *Self, args: struct {}) anyerror!void {
_ = self;
_ = args;
@panic("getViewportSize not implemented");
}

pub fn setViewportSize(self: *Self, args: struct { width: u32, height: u32 }) anyerror!void {
_ = self;
_ = args;
@panic("setViewportSize not implemented");
}
};
}

pub const Configuration = struct {
Syscalls: fn (comptime Self: type) type = Syscalls,
Syscalls: fn (comptime EngineType: type, comptime Context: type) type,
Context: type,
};

pub const ErrorReport = ErrorHandler.ErrorReport;
pub const StackTraceLine = ErrorHandler.StackTraceLine;

pub fn Engine(configuration: Configuration) type {
pub fn Engine(comptime configuration: Configuration) type {
return struct {
allocator: std.mem.Allocator,
output_handler: OutputHandler,
error_handler: ErrorHandler,
fiber_queue: std.ArrayList(*c.Handle),
trampoliner: Trampoline,
document: *dom.Dom,
syscall_context: *Context,
vm: *c.VM,

const Self = @This();

const SyscallsType = configuration.Syscalls(Self);
const Context = configuration.Context;
const SyscallsType = configuration.Syscalls(Self, Context);
const Request = syscalls.RequestUnion(SyscallsType);
const Trampoline = syscalls.generateTrampoline(SyscallsType, Self);
const Trampoline = syscalls.generateTrampoline(SyscallsType, Self, Context);
const SlotParser = syscalls.generateSlotParser(Request, SyscallsType);

pub const Options = struct {
output_buffer_size: usize = 1024 * 32,
error_buffer_size: usize = 1024 * 32,
syscall_context: *Context,
};

pub fn init(base_allocator: std.mem.Allocator, options: Options) !*Self {
Expand Down Expand Up @@ -133,7 +69,8 @@ pub fn Engine(configuration: Configuration) type {

self.fiber_queue = std.ArrayList(*c.Handle).init(allocator);

self.trampoliner = Trampoline{ .context = self };
self.syscall_context = options.syscall_context;
self.trampoliner = Trampoline{ .engine = self, .context = self.syscall_context };

var vmconf = c.Configuration{};
c.wrenInitConfiguration(&vmconf);
Expand Down Expand Up @@ -372,10 +309,19 @@ pub fn Engine(configuration: Configuration) type {
};
}

fn NoSyscalls(comptime EngineType: type, comptime Context: type) type {
_ = EngineType;
_ = Context;
return struct {};
}

const TestContext = struct {};

test "we can create and destroy a VM" {
const allocator = std.testing.allocator;

var vm = try Engine(.{}).init(allocator, .{});
const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext });
var context = TestContext{};
var vm = try EngineType.init(allocator, .{ .syscall_context = &context });
defer vm.deinit();

const output = try vm.takeOutput(allocator);
Expand All @@ -387,8 +333,9 @@ test "we can create and destroy a VM" {

test "we can run a simple script" {
const allocator = std.testing.allocator;

var engine = try Engine(.{}).init(allocator, .{});
const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext });
var context = TestContext{};
var engine = try EngineType.init(allocator, .{ .syscall_context = &context });
defer engine.deinit();

try engine.runTopLevel("foo",
Expand All @@ -404,8 +351,9 @@ test "we can run a simple script" {

test "we can call Core.spawn" {
const allocator = std.testing.allocator;

var engine = try Engine(.{}).init(allocator, .{});
const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext });
var context = TestContext{};
var engine = try EngineType.init(allocator, .{ .syscall_context = &context });
defer engine.deinit();

engine.runTopLevel("main",
Expand Down Expand Up @@ -450,8 +398,9 @@ test "we can call Core.spawn" {

test "slots API - simple method call" {
const allocator = std.testing.allocator;

var engine = try Engine(.{}).init(allocator, .{});
const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext });
var context = TestContext{};
var engine = try EngineType.init(allocator, .{ .syscall_context = &context });
defer engine.deinit();

try engine.runTopLevel("test",
Expand Down Expand Up @@ -482,8 +431,9 @@ test "slots API - simple method call" {

test "slots API - working with strings" {
const allocator = std.testing.allocator;

var engine = try Engine(.{}).init(allocator, .{});
const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext });
var context = TestContext{};
var engine = try EngineType.init(allocator, .{ .syscall_context = &context });
defer engine.deinit();

try engine.runTopLevel("test",
Expand All @@ -507,8 +457,9 @@ test "slots API - working with strings" {

test "slots API - list operations" {
const allocator = std.testing.allocator;

var engine = try Engine(.{}).init(allocator, .{});
const EngineType = Engine(.{ .Syscalls = NoSyscalls, .Context = TestContext });
var context = TestContext{};
var engine = try EngineType.init(allocator, .{ .syscall_context = &context });
defer engine.deinit();

try engine.runTopLevel("test",
Expand Down
Loading