haku2: make server use haku2 (and make it work!)
This commit is contained in:
parent
c5e2892def
commit
48d03699bd
16 changed files with 329 additions and 174 deletions
|
@ -10,7 +10,7 @@ const value = @import("value.zig");
|
|||
const Vm = @import("vm.zig");
|
||||
const log = @import("log.zig");
|
||||
|
||||
const vm_trace = true;
|
||||
const debug_logs = false;
|
||||
|
||||
pub const allocator =
|
||||
if (builtin.cpu.arch == .wasm32)
|
||||
|
@ -19,7 +19,7 @@ pub const allocator =
|
|||
@import("allocator.zig").hostAllocator;
|
||||
|
||||
pub const std_options: std.Options = .{
|
||||
.log_level = if (vm_trace) .debug else .info,
|
||||
.log_level = if (debug_logs) .debug else .info,
|
||||
.logFn = log.logFn,
|
||||
};
|
||||
|
||||
|
@ -57,10 +57,6 @@ export fn haku2_limits_set_call_stack_capacity(limits: *Vm.Limits, new: usize) v
|
|||
limits.call_stack_capacity = new;
|
||||
}
|
||||
|
||||
export fn haku2_limits_set_fuel(limits: *Vm.Limits, new: u32) void {
|
||||
limits.fuel = new;
|
||||
}
|
||||
|
||||
// Defs
|
||||
|
||||
export fn haku2_defs_parse(
|
||||
|
@ -95,6 +91,10 @@ export fn haku2_vm_destroy(vm: *Vm) void {
|
|||
allocator.destroy(vm);
|
||||
}
|
||||
|
||||
export fn haku2_vm_reset(vm: *Vm, fuel: u32) void {
|
||||
vm.reset(fuel);
|
||||
}
|
||||
|
||||
export fn haku2_vm_run_main(
|
||||
vm: *Vm,
|
||||
scratch: *Scratch,
|
||||
|
@ -102,11 +102,15 @@ export fn haku2_vm_run_main(
|
|||
code_len: usize,
|
||||
local_count: u8,
|
||||
) bool {
|
||||
const chunk = bytecode.Chunk{
|
||||
const chunk = scratch.allocator().create(bytecode.Chunk) catch {
|
||||
vm.outOfMemory() catch {};
|
||||
return false;
|
||||
};
|
||||
chunk.* = bytecode.Chunk{
|
||||
.bytecode = code[0..code_len],
|
||||
};
|
||||
const closure = value.Closure{
|
||||
.chunk = &chunk,
|
||||
.chunk = chunk,
|
||||
.start = 0,
|
||||
.param_count = 0,
|
||||
.local_count = local_count,
|
||||
|
|
|
@ -7,6 +7,8 @@ use std::{
|
|||
slice,
|
||||
};
|
||||
|
||||
use log::{debug, trace};
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
unsafe extern "C" fn __haku2_alloc(size: usize, align: usize) -> *mut u8 {
|
||||
if let Ok(layout) = Layout::from_size_align(size, align) {
|
||||
|
@ -121,7 +123,6 @@ extern "C" {
|
|||
fn haku2_limits_destroy(limits: *mut LimitsC);
|
||||
fn haku2_limits_set_stack_capacity(limits: *mut LimitsC, new: usize);
|
||||
fn haku2_limits_set_call_stack_capacity(limits: *mut LimitsC, new: usize);
|
||||
fn haku2_limits_set_fuel(limits: *mut LimitsC, new: u32);
|
||||
|
||||
fn haku2_defs_parse(
|
||||
defs_string: *const u8,
|
||||
|
@ -133,6 +134,7 @@ extern "C" {
|
|||
|
||||
fn haku2_vm_new(s: *mut ScratchC, defs: *const DefsC, limits: *const LimitsC) -> *mut VmC;
|
||||
fn haku2_vm_destroy(vm: *mut VmC);
|
||||
fn haku2_vm_reset(vm: *mut VmC, fuel: u32);
|
||||
fn haku2_vm_run_main(
|
||||
vm: *mut VmC,
|
||||
scratch: *mut ScratchC,
|
||||
|
@ -167,13 +169,14 @@ pub struct Scratch {
|
|||
|
||||
impl Scratch {
|
||||
pub fn new(max: usize) -> Scratch {
|
||||
Scratch {
|
||||
// SAFETY: haku2_scratch_new does not have any safety invariants.
|
||||
raw: NonNull::new(unsafe { haku2_scratch_new(max) }).expect("out of memory"),
|
||||
}
|
||||
// SAFETY: haku2_scratch_new does not have any safety invariants.
|
||||
let raw = NonNull::new(unsafe { haku2_scratch_new(max) }).expect("out of memory");
|
||||
trace!("Scratch::new -> {raw:?}");
|
||||
Scratch { raw }
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
trace!("Scratch::reset({:?})", self.raw);
|
||||
// SAFETY: The pointer passed is non-null.
|
||||
unsafe {
|
||||
haku2_scratch_reset(self.raw.as_ptr());
|
||||
|
@ -183,6 +186,7 @@ impl Scratch {
|
|||
|
||||
impl Drop for Scratch {
|
||||
fn drop(&mut self) {
|
||||
trace!("Scratch::drop({:?})", self.raw);
|
||||
// SAFETY: The pointer passed is non-null.
|
||||
unsafe {
|
||||
haku2_scratch_destroy(self.raw.as_ptr());
|
||||
|
@ -194,7 +198,6 @@ impl Drop for Scratch {
|
|||
pub struct LimitsSpec {
|
||||
pub stack_capacity: usize,
|
||||
pub call_stack_capacity: usize,
|
||||
pub fuel: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -216,7 +219,6 @@ impl Limits {
|
|||
unsafe {
|
||||
haku2_limits_set_stack_capacity(limits.as_ptr(), spec.stack_capacity);
|
||||
haku2_limits_set_call_stack_capacity(limits.as_ptr(), spec.call_stack_capacity);
|
||||
haku2_limits_set_fuel(limits.as_ptr(), spec.fuel);
|
||||
}
|
||||
|
||||
Self { raw: limits }
|
||||
|
@ -315,21 +317,21 @@ pub struct Dotter {
|
|||
|
||||
impl Vm {
|
||||
pub fn new(scratch: Scratch, code: Code, limits: &Limits) -> Self {
|
||||
let raw = NonNull::new(unsafe {
|
||||
haku2_vm_new(
|
||||
scratch.raw.as_ptr(),
|
||||
code.defs.raw.as_ptr(),
|
||||
limits.raw.as_ptr(),
|
||||
)
|
||||
})
|
||||
.expect("out of memory");
|
||||
trace!("Vm::new({scratch:?}, {code:?}, {limits:?}) -> {raw:?}");
|
||||
Self {
|
||||
// SAFETY:
|
||||
// - Ownership of scratch is passed to the VM, so the VM cannot outlive the scratch space.
|
||||
// - The VM never gives you any references back, so this is safe to do.
|
||||
// - The other arguments are only borrowed immutably for construction.
|
||||
inner: VmInner {
|
||||
raw: NonNull::new(unsafe {
|
||||
haku2_vm_new(
|
||||
scratch.raw.as_ptr(),
|
||||
code.defs.raw.as_ptr(),
|
||||
limits.raw.as_ptr(),
|
||||
)
|
||||
})
|
||||
.expect("out of memory"),
|
||||
},
|
||||
inner: VmInner { raw },
|
||||
scratch,
|
||||
code,
|
||||
}
|
||||
|
@ -340,8 +342,10 @@ impl Vm {
|
|||
///
|
||||
/// Calling `begin` again during this process will work correctly, and result in another
|
||||
/// continuation being stack on top of the old one---at the expense of a stack slot.
|
||||
pub fn begin(&mut self) -> Result<(), Exception> {
|
||||
pub fn begin(&mut self, fuel: u32) -> Result<(), Exception> {
|
||||
trace!("Vm::begin({self:?}, {fuel})");
|
||||
let ok = unsafe {
|
||||
haku2_vm_reset(self.inner.raw.as_ptr(), fuel);
|
||||
haku2_vm_run_main(
|
||||
self.inner.raw.as_ptr(),
|
||||
self.scratch.raw.as_ptr(),
|
||||
|
@ -411,6 +415,7 @@ impl Vm {
|
|||
/// Take the `Scratch` out of the VM for reuse in another one.
|
||||
/// The scratch memory will be reset (no bytes will be consumed.)
|
||||
pub fn into_scratch(self) -> Scratch {
|
||||
trace!("Vm::into_scratch({self:?})");
|
||||
let Vm {
|
||||
mut scratch,
|
||||
code: _,
|
||||
|
@ -423,6 +428,8 @@ impl Vm {
|
|||
|
||||
impl ContDotter<'_> {
|
||||
pub fn run(self, dotter: &Dotter) -> Result<(), Exception> {
|
||||
trace!("ContDotter::run({self:?}, {dotter:?})");
|
||||
|
||||
let Dotter {
|
||||
from: (from_x, from_y),
|
||||
to: (to_x, to_y),
|
||||
|
@ -455,6 +462,7 @@ struct VmInner {
|
|||
|
||||
impl Drop for VmInner {
|
||||
fn drop(&mut self) {
|
||||
trace!("VmInner::drop({:?})", self.raw);
|
||||
// SAFETY: The pointer passed is non-null.
|
||||
unsafe {
|
||||
haku2_vm_destroy(self.raw.as_ptr());
|
||||
|
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
|||
const mem = std.mem;
|
||||
const meta = std.meta;
|
||||
const math = std.math;
|
||||
const log = std.log.scoped(.system);
|
||||
|
||||
const bytecode = @import("bytecode.zig");
|
||||
const Opcode = bytecode.Opcode;
|
||||
|
@ -112,10 +113,13 @@ fn fromArgument(cx: Context, comptime T: type, i: usize) Vm.Error!T {
|
|||
if (val != .ref or val.ref.* != .list) return typeError(cx.vm, val, i, "list");
|
||||
return val.ref.list;
|
||||
},
|
||||
*const value.Shape => {
|
||||
value.Shape => {
|
||||
const val = cx.args[i];
|
||||
if (val != .ref or val.ref.* != .shape) return typeError(cx.vm, val, i, "shape");
|
||||
return &val.ref.shape;
|
||||
if (toShape(val)) |shape| {
|
||||
return shape;
|
||||
} else {
|
||||
return typeError(cx.vm, val, i, "shape");
|
||||
}
|
||||
},
|
||||
*const value.Closure => {
|
||||
const val = cx.args[i];
|
||||
|
@ -756,9 +760,9 @@ fn circle(center: Vec4, radius: f32) value.Ref {
|
|||
} } };
|
||||
}
|
||||
|
||||
fn stroke(thickness: f32, color: Rgba, shape: *const value.Shape) value.Ref {
|
||||
fn stroke(thickness: f32, color: Rgba, shape: value.Shape) value.Ref {
|
||||
return .{ .scribble = .{
|
||||
.shape = shape.*,
|
||||
.shape = shape,
|
||||
.action = .{ .stroke = .{
|
||||
.thickness = thickness,
|
||||
.color = color.value,
|
||||
|
@ -766,9 +770,9 @@ fn stroke(thickness: f32, color: Rgba, shape: *const value.Shape) value.Ref {
|
|||
} };
|
||||
}
|
||||
|
||||
fn fill(color: Rgba, shape: *const value.Shape) value.Ref {
|
||||
fn fill(color: Rgba, shape: value.Shape) value.Ref {
|
||||
return .{ .scribble = .{
|
||||
.shape = shape.*,
|
||||
.shape = shape,
|
||||
.action = .{ .fill = .{
|
||||
.color = color.value,
|
||||
} },
|
||||
|
@ -776,6 +780,7 @@ fn fill(color: Rgba, shape: *const value.Shape) value.Ref {
|
|||
}
|
||||
|
||||
fn withDotter(cont: *const value.Closure, cx: Context) Vm.Error!value.Ref {
|
||||
log.debug("withDotter({})", .{cont});
|
||||
if (cont.param_count != 1) {
|
||||
return cx.vm.throw("function passed to withDotter must have a single parameter (\\d -> _), but it has {}", .{cont.param_count});
|
||||
}
|
||||
|
|
|
@ -68,6 +68,28 @@ pub const Value = union(enum) {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(value: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
switch (value) {
|
||||
.nil => try std.fmt.formatBuf("Nil", options, writer),
|
||||
.false => try std.fmt.formatBuf("False", options, writer),
|
||||
.true => try std.fmt.formatBuf("True", options, writer),
|
||||
inline .tag, .number => |x| try std.fmt.format(writer, "{d}", .{x}),
|
||||
inline .vec4, .rgba => |x| try std.fmt.format(writer, "{s}{d}", .{ @tagName(value), x }),
|
||||
.ref => |ref| switch (ref.*) {
|
||||
.closure => |c| try std.fmt.format(writer, "function({})", .{c.param_count}),
|
||||
.list => |l| {
|
||||
try std.fmt.formatBuf("[", options, writer);
|
||||
for (l, 0..) |elem, i| {
|
||||
if (i != 0) try std.fmt.formatBuf(", ", options, writer);
|
||||
try elem.format(fmt, options, writer);
|
||||
}
|
||||
try std.fmt.formatBuf("]", options, writer);
|
||||
},
|
||||
inline .shape, .scribble, .reticle => |x| try std.fmt.format(writer, "{}", .{x}),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const TagId = enum(u16) {
|
||||
|
|
|
@ -17,13 +17,12 @@ stack_top: u32 = 0,
|
|||
call_stack: []CallFrame,
|
||||
call_stack_top: u32 = 0,
|
||||
defs: []Value,
|
||||
fuel: u32,
|
||||
fuel: u32 = 0, // NOTE: VM must be refueled via reset() before running code
|
||||
exception: ?Exception = null,
|
||||
|
||||
pub const Limits = struct {
|
||||
stack_capacity: usize = 256,
|
||||
call_stack_capacity: usize = 256,
|
||||
fuel: u32 = 63336,
|
||||
};
|
||||
|
||||
pub const CallFrame = struct {
|
||||
|
@ -50,10 +49,16 @@ pub fn init(a: mem.Allocator, defs: *const bytecode.Defs, limits: *const Limits)
|
|||
.stack = try a.alloc(Value, limits.stack_capacity),
|
||||
.call_stack = try a.alloc(CallFrame, limits.call_stack_capacity),
|
||||
.defs = try a.alloc(Value, defs.num_defs),
|
||||
.fuel = limits.fuel,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reset(vm: *Vm, fuel: u32) void {
|
||||
vm.stack_top = 0;
|
||||
vm.call_stack_top = 0;
|
||||
vm.fuel = fuel;
|
||||
vm.exception = null;
|
||||
}
|
||||
|
||||
pub fn throw(vm: *Vm, comptime fmt: []const u8, args: anytype) Error {
|
||||
log.debug("throw: fmt={s}", .{fmt});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue