haku2: rest of functionality (hopefully) & Rust->Zig FFI

This commit is contained in:
りき萌 2025-06-03 21:53:21 +02:00
parent 01d4514a65
commit 550227da34
7 changed files with 716 additions and 38 deletions

View file

@ -1,8 +1,10 @@
const std = @import("std");
const mem = std.mem;
const debug = std.debug;
const testAllocator = std.testing.allocator;
const bytecode = @import("bytecode.zig");
const Canvas = @import("canvas.zig");
const system = @import("system.zig");
const value = @import("value.zig");
const Value = value.Value;
@ -15,7 +17,6 @@ call_stack: []CallFrame,
call_stack_top: u32 = 0,
defs: []Value,
fuel: u32,
exception_buffer: [1024]u8 = [_]u8{0} ** 1024, // buffer for exception message
exception: ?Exception = null,
pub const Limits = struct {
@ -31,7 +32,9 @@ pub const CallFrame = struct {
};
pub const Exception = struct {
message: []const u8,
len: usize,
format: *const fn (buf: []u8, args: *align(64) const anyopaque) []u8,
args: [40]u8 align(64), // increase the size if we ever throw a larger exception
};
/// All errors coming from inside the VM get turned into a single Exception type, which signals
@ -51,11 +54,28 @@ pub fn init(a: mem.Allocator, defs: *const bytecode.Defs, limits: *const Limits)
}
pub fn throw(vm: *Vm, comptime fmt: []const u8, args: anytype) Error {
const message = std.fmt.bufPrint(vm.exception_buffer[0..], fmt, args) catch {
vm.exception = .{ .message = "[exception message is too long; format string: " ++ fmt ++ "]" };
return error.Exception;
const Args = @TypeOf(args);
const max_args_size = @sizeOf(@TypeOf(vm.exception.?.args));
if (@sizeOf(Args) > max_args_size) {
@compileError(std.fmt.comptimePrint(
"format arguments are too large; size={}, max={}",
.{ @sizeOf(Args), max_args_size },
));
}
const Formatter = struct {
fn format(buf: []u8, erased_args: *align(64) const anyopaque) []u8 {
return std.fmt.bufPrint(buf, fmt, @as(*const Args, @ptrCast(erased_args)).*) catch unreachable;
}
};
vm.exception = .{ .message = message };
var exn = Exception{
.len = @truncate(std.fmt.count(fmt, args)),
.format = Formatter.format,
.args = undefined,
};
@memcpy(exn.args[0..@sizeOf(Args)], mem.asBytes(&args));
return error.Exception;
}
@ -452,7 +472,43 @@ pub fn run(
try vm.consumeFuel(&fuel, 1);
// NOTE: Not a validateBytecode call because this is zero-cost on the happy path,
// so we don't need to disable it on release builds.
return vm.throw("corrupted bytecode (invalid opcode {})", .{invalid_opcode});
return vm.throw("corrupted bytecode: invalid opcode {}", .{invalid_opcode});
},
}
}
pub const Dotter = struct {
from: value.Vec4,
to: value.Vec4,
num: f32,
};
/// NOTE: Assumes the value at the top is a dotter reticle.
pub fn runDotter(
vm: *Vm,
allocator: mem.Allocator,
from: value.Vec4,
to: value.Vec4,
num: f32,
) Error!void {
const reticle = try vm.pop();
const draw = reticle.ref.reticle.dotter.draw; // parameter count checked on construction
const data = allocator.dupe(Value, &[_]Value{
.{ .vec4 = from },
.{ .vec4 = to },
.{ .number = num },
}) catch return vm.outOfMemory();
const ref = allocator.create(value.Ref) catch return vm.outOfMemory();
ref.* = value.Ref{ .closure = .{
.chunk = &system.record_dotter,
.start = 0,
.param_count = 1,
.local_count = 0,
.captures = data,
} };
const bottom = vm.stack_top;
try vm.push(.{ .ref = ref });
try vm.run(allocator, draw, bottom);
}