haku2: add stats + fix crash w uninit vm

This commit is contained in:
りき萌 2025-06-16 16:39:54 +02:00
parent e667c6336a
commit 00a48527ca
12 changed files with 88 additions and 71 deletions

View file

@ -544,3 +544,8 @@ unsafe extern "C" fn haku_bytecode2(instance: *const Instance) -> *const u8 {
unsafe extern "C" fn haku_local_count2(instance: *const Instance) -> u8 { unsafe extern "C" fn haku_local_count2(instance: *const Instance) -> u8 {
compile_result(instance).closure_spec.local_count compile_result(instance).closure_spec.local_count
} }
#[unsafe(no_mangle)]
unsafe extern "C" fn haku_stat_ast_size(instance: *const Instance) -> usize {
compile_result(instance).stats.ast_size
}

View file

@ -1,5 +1,6 @@
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const log = std.log.scoped(.bytecode);
/// NOTE: This must be mirrored in bytecode.rs. /// NOTE: This must be mirrored in bytecode.rs.
pub const Opcode = enum(u8) { pub const Opcode = enum(u8) {
@ -70,6 +71,8 @@ pub const Defs = struct {
.num_tags = mem.count(u8, tags_string, "\n"), .num_tags = mem.count(u8, tags_string, "\n"),
}; };
log.debug("parsed defs {*} tags {s}: {}", .{ defs_string, tags_string, d });
return d; return d;
} }

View file

@ -10,8 +10,6 @@ const value = @import("value.zig");
const Vm = @import("vm.zig"); const Vm = @import("vm.zig");
const log = @import("log.zig"); const log = @import("log.zig");
const debug_logs = true;
pub const allocator = pub const allocator =
if (builtin.cpu.arch == .wasm32) if (builtin.cpu.arch == .wasm32)
std.heap.wasm_allocator std.heap.wasm_allocator
@ -19,10 +17,14 @@ pub const allocator =
@import("allocator.zig").hostAllocator; @import("allocator.zig").hostAllocator;
pub const std_options: std.Options = .{ pub const std_options: std.Options = .{
.log_level = if (debug_logs) .debug else .info, .log_level = .debug,
.logFn = log.logFn, .logFn = log.logFn,
}; };
pub fn enableLogScope(scope: @TypeOf(.enum_literal)) bool {
if (scope == .vm) return false else return true;
}
// Allocator // Allocator
export fn haku2_alloc(size: usize, alignment: usize) ?[*]u8 { export fn haku2_alloc(size: usize, alignment: usize) ?[*]u8 {
@ -47,6 +49,10 @@ export fn haku2_scratch_reset(scratch: *Scratch) void {
scratch.fixedBuffer = std.heap.FixedBufferAllocator.init(scratch.buffer); scratch.fixedBuffer = std.heap.FixedBufferAllocator.init(scratch.buffer);
} }
export fn haku2_scratch_used(scratch: *const Scratch) usize {
return scratch.fixedBuffer.end_index;
}
// Limits // Limits
export fn haku2_limits_new() ?*Vm.Limits { export fn haku2_limits_new() ?*Vm.Limits {
@ -88,12 +94,9 @@ export fn haku2_defs_destroy(defs: *bytecode.Defs) void {
// VM // VM
export fn haku2_vm_new(s: *Scratch, defs: *const bytecode.Defs, limits: *const Vm.Limits) ?*Vm { export fn haku2_vm_new() ?*Vm {
const vm = allocator.create(Vm) catch return null; const vm = allocator.create(Vm) catch return null;
errdefer allocator.destroy(vm); vm.* = .{};
vm.* = Vm.init(s.allocator(), defs, limits) catch return null;
return vm; return vm;
} }
@ -101,8 +104,10 @@ export fn haku2_vm_destroy(vm: *Vm) void {
allocator.destroy(vm); allocator.destroy(vm);
} }
export fn haku2_vm_reset(vm: *Vm, fuel: u32) void { export fn haku2_vm_reset(vm: *Vm, s: *Scratch, defs: *const bytecode.Defs, limits: *const Vm.Limits, fuel: u32) bool {
vm.* = Vm.init(s.allocator(), defs, limits) catch return false;
vm.reset(fuel); vm.reset(fuel);
return true;
} }
export fn haku2_vm_run_main( export fn haku2_vm_run_main(
@ -160,6 +165,10 @@ export fn haku2_vm_run_dotter(
return true; return true;
} }
export fn haku2_vm_fuel(vm: *const Vm) usize {
return vm.fuel;
}
export fn haku2_vm_exception_len(vm: *const Vm) usize { export fn haku2_vm_exception_len(vm: *const Vm) usize {
if (vm.exception) |exn| { if (vm.exception) |exn| {
return exn.len; return exn.len;

View file

@ -134,9 +134,15 @@ extern "C" {
) -> *mut DefsC; ) -> *mut DefsC;
fn haku2_defs_destroy(defs: *mut DefsC); fn haku2_defs_destroy(defs: *mut DefsC);
fn haku2_vm_new(s: *mut ScratchC, defs: *const DefsC, limits: *const LimitsC) -> *mut VmC; fn haku2_vm_new() -> *mut VmC;
fn haku2_vm_destroy(vm: *mut VmC); fn haku2_vm_destroy(vm: *mut VmC);
fn haku2_vm_reset(vm: *mut VmC, fuel: u32); fn haku2_vm_reset(
vm: *mut VmC,
s: *mut ScratchC,
defs: *const DefsC,
limits: *const LimitsC,
fuel: u32,
);
fn haku2_vm_run_main( fn haku2_vm_run_main(
vm: *mut VmC, vm: *mut VmC,
scratch: *mut ScratchC, scratch: *mut ScratchC,
@ -296,6 +302,7 @@ impl Code {
pub struct Vm { pub struct Vm {
scratch: Scratch, scratch: Scratch,
code: Code, code: Code,
limits: Limits,
inner: VmInner, inner: VmInner,
} }
@ -318,15 +325,10 @@ pub struct Dotter {
} }
impl Vm { impl Vm {
pub fn new(scratch: Scratch, code: Code, limits: &Limits) -> Self { pub fn new(scratch: Scratch, code: Code, limits: Limits) -> Self {
let raw = NonNull::new(unsafe { // SAFETY: haku2_vm_new cannot fail.
haku2_vm_new( // Do note that this returns an uninitialized VM, which must be reset before use.
scratch.raw.as_ptr(), let raw = NonNull::new(unsafe { haku2_vm_new() }).expect("out of memory");
code.defs.raw.as_ptr(),
limits.raw.as_ptr(),
)
})
.expect("out of memory");
trace!("Vm::new({scratch:?}, {code:?}, {limits:?}) -> {raw:?}"); trace!("Vm::new({scratch:?}, {code:?}, {limits:?}) -> {raw:?}");
Self { Self {
// SAFETY: // SAFETY:
@ -336,6 +338,7 @@ impl Vm {
inner: VmInner { raw }, inner: VmInner { raw },
scratch, scratch,
code, code,
limits,
} }
} }
@ -346,8 +349,15 @@ impl Vm {
/// continuation being stack on top of the old one---at the expense of a stack slot. /// continuation being stack on top of the old one---at the expense of a stack slot.
pub fn begin(&mut self, fuel: u32) -> Result<(), Exception> { pub fn begin(&mut self, fuel: u32) -> Result<(), Exception> {
trace!("Vm::begin({self:?}, {fuel})"); trace!("Vm::begin({self:?}, {fuel})");
self.scratch.reset();
let ok = unsafe { let ok = unsafe {
haku2_vm_reset(self.inner.raw.as_ptr(), fuel); haku2_vm_reset(
self.inner.raw.as_ptr(),
self.scratch.raw.as_ptr(),
self.code.defs.raw.as_ptr(),
self.limits.raw.as_ptr(),
fuel,
);
haku2_vm_run_main( haku2_vm_run_main(
self.inner.raw.as_ptr(), self.inner.raw.as_ptr(),
self.scratch.raw.as_ptr(), self.scratch.raw.as_ptr(),
@ -422,6 +432,7 @@ impl Vm {
mut scratch, mut scratch,
code: _, code: _,
inner: _, inner: _,
limits: _,
} = self; } = self;
scratch.reset(); scratch.reset();
scratch scratch

View file

@ -9,6 +9,8 @@ pub fn logFn(
comptime fmt: []const u8, comptime fmt: []const u8,
args: anytype, args: anytype,
) void { ) void {
if (!root.enableLogScope(scope)) return;
const stack_buf_size = 1024; const stack_buf_size = 1024;
var stack_buf: [stack_buf_size]u8 = undefined; var stack_buf: [stack_buf_size]u8 = undefined;
var heap_buf: ?[]u8 = null; var heap_buf: ?[]u8 = null;

View file

@ -54,6 +54,6 @@ fn renderRec(vm: *Vm, canvas: *Canvas, val: Value, depth: usize, max_depth: usiz
} }
pub fn render(vm: *Vm, canvas: *Canvas, max_depth: usize) !void { pub fn render(vm: *Vm, canvas: *Canvas, max_depth: usize) !void {
const val = vm.stack[vm.stack_top - 1]; const val = vm.top();
try renderRec(vm, canvas, val, 0, max_depth); try renderRec(vm, canvas, val, 0, max_depth);
} }

View file

@ -780,7 +780,6 @@ fn fill(color: Rgba, shape: value.Shape) value.Ref {
} }
fn withDotter(cont: *const value.Closure, cx: Context) Vm.Error!value.Ref { fn withDotter(cont: *const value.Closure, cx: Context) Vm.Error!value.Ref {
log.debug("withDotter({})", .{cont});
if (cont.param_count != 1) { 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}); return cx.vm.throw("function passed to withDotter must have a single parameter (\\d -> _), but it has {}", .{cont.param_count});
} }

View file

@ -12,11 +12,11 @@ const Value = value.Value;
const Vm = @This(); const Vm = @This();
stack: []Value, stack: []Value = &.{},
stack_top: u32 = 0, stack_top: u32 = 0,
call_stack: []CallFrame, call_stack: []CallFrame = &.{},
call_stack_top: u32 = 0, call_stack_top: u32 = 0,
defs: []Value, defs: []Value = &.{},
fuel: u32 = 0, // NOTE: VM must be refueled via reset() before running code fuel: u32 = 0, // NOTE: VM must be refueled via reset() before running code
exception: ?Exception = null, exception: ?Exception = null,

View file

@ -127,23 +127,20 @@ impl Haku {
.context("too many chunks")?; .context("too many chunks")?;
self.brush = Some((chunk_id, closure_spec)); self.brush = Some((chunk_id, closure_spec));
// haku2 setup let scratch = self
{ .vm2
let scratch = self .take()
.vm2 .map(|vm| vm.into_scratch())
.take() .unwrap_or_else(|| haku2::Scratch::new(self.limits.memory));
.map(|vm| vm.into_scratch()) let defs = haku2::Defs::parse(&self.defs.serialize_defs(), &self.defs.serialize_tags());
.unwrap_or_else(|| haku2::Scratch::new(self.limits.memory)); // SAFETY: The code is fresh out of the compiler oven, so it is guaranteed to be valid.
let defs = haku2::Defs::parse(&self.defs.serialize_defs(), &self.defs.serialize_tags()); // Well, more or less. There may lurk bugs.
// SAFETY: The code is fresh out of the compiler oven, so it is guaranteed to be valid. let code = unsafe { haku2::Code::new(defs, chunk.bytecode, closure_spec.local_count) };
// Well, more or less. There may lurk bugs. let limits = haku2::Limits::new(haku2::LimitsSpec {
let code = unsafe { haku2::Code::new(defs, chunk.bytecode, closure_spec.local_count) }; stack_capacity: self.limits.stack_capacity,
let limits = haku2::Limits::new(haku2::LimitsSpec { call_stack_capacity: self.limits.call_stack_capacity,
stack_capacity: self.limits.stack_capacity, });
call_stack_capacity: self.limits.call_stack_capacity, self.vm2 = Some(haku2::Vm::new(scratch, code, limits));
});
self.vm2 = Some(haku2::Vm::new(scratch, code, &limits))
}
info!("brush set successfully"); info!("brush set successfully");

View file

@ -43,31 +43,22 @@ export class BrushCostGauges extends HTMLElement {
label: "Fuel", label: "Fuel",
}), }),
); );
this.objectsGauge = this.appendChild(
createGauge({
className: "objects",
iconName: "object",
label: "Objects",
}),
);
this.memoryGauge = this.appendChild( this.memoryGauge = this.appendChild(
createGauge({ createGauge({
className: "memory", className: "memory",
iconName: "memory", iconName: "memory",
label: "Bulk memory", label: "Memory",
}), }),
); );
this.codeSizeGauge.setValue(0); this.codeSizeGauge.setValue(0);
this.fuelGauge.setValue(0); this.fuelGauge.setValue(0);
this.objectsGauge.setValue(0);
this.memoryGauge.setValue(0); this.memoryGauge.setValue(0);
} }
update(stats) { update(stats) {
this.codeSizeGauge.setValue(stats.astSize, stats.astSizeMax); this.codeSizeGauge.setValue(stats.astSize, stats.astSizeMax);
this.fuelGauge.setValue(stats.fuel, stats.fuelMax); this.fuelGauge.setValue(stats.fuel, stats.fuelMax);
this.objectsGauge.setValue(stats.numRefs, stats.numRefsMax);
this.memoryGauge.setValue(stats.memory, stats.memoryMax); this.memoryGauge.setValue(stats.memory, stats.memoryMax);
} }
} }

View file

@ -47,7 +47,6 @@ let [hakuWasm, haku2Wasm] = await Promise.all([
__haku2_log_info: makeLogFunction2("info"), __haku2_log_info: makeLogFunction2("info"),
__haku2_log_debug: makeLogFunction2("debug"), __haku2_log_debug: makeLogFunction2("debug"),
// TODO: Renderer
__haku2_canvas_begin: (c) => canvasBeginImpl(c), __haku2_canvas_begin: (c) => canvasBeginImpl(c),
__haku2_canvas_line: (c, x1, y1, x2, y2) => canvasLineImpl(c, x1, y1, x2, y2), __haku2_canvas_line: (c, x1, y1, x2, y2) => canvasLineImpl(c, x1, y1, x2, y2),
__haku2_canvas_rectangle: (c, x, y, width, height) => __haku2_canvas_rectangle: (c, x, y, width, height) =>
@ -205,6 +204,7 @@ export class Haku {
#pLimits2 = 0; #pLimits2 = 0;
#pScratch2 = 0; #pScratch2 = 0;
#pDefs2 = 0;
#pVm2 = 0; #pVm2 = 0;
#bytecode2 = null; #bytecode2 = null;
@ -242,11 +242,14 @@ export class Haku {
w2.haku2_scratch_destroy(this.#pScratch2); w2.haku2_scratch_destroy(this.#pScratch2);
w2.haku2_vm_destroy(this.#pVm2); w2.haku2_vm_destroy(this.#pVm2);
w2.haku2_defs_destroy(this.#pDefs2);
w2.haku2_limits_destroy(this.#pLimits2); w2.haku2_limits_destroy(this.#pLimits2);
w2.haku_dealloc(this.#bytecode2.ptr); w2.haku_dealloc(this.#bytecode2.ptr);
} }
setBrush(code) { setBrush(code) {
w.haku_reset(this.#pInstance);
let brushCode = allocString(code); let brushCode = allocString(code);
let statusCode = w.haku_compile_brush2(this.#pInstance, brushCode.length, brushCode.ptr); let statusCode = w.haku_compile_brush2(this.#pInstance, brushCode.length, brushCode.ptr);
freeString(brushCode); freeString(brushCode);
@ -279,8 +282,11 @@ export class Haku {
} }
if (this.#pVm2 != 0) w2.haku2_vm_destroy(this.#pVm2); if (this.#pVm2 != 0) w2.haku2_vm_destroy(this.#pVm2);
if (this.#pDefs2 != 0) w2.haku2_defs_destroy(this.#pDefs2);
if (this.#bytecode2 != null) freeString2(this.#bytecode2); if (this.#bytecode2 != null) freeString2(this.#bytecode2);
console.log(w.haku_defs2(this.#pInstance), w.haku_defs_len2(this.#pInstance));
let pDefsString = dup1to2( let pDefsString = dup1to2(
w.haku_defs2(this.#pInstance), w.haku_defs2(this.#pInstance),
w.haku_defs_len2(this.#pInstance), w.haku_defs_len2(this.#pInstance),
@ -291,17 +297,17 @@ export class Haku {
w.haku_tags_len2(this.#pInstance), w.haku_tags_len2(this.#pInstance),
1, 1,
); );
let defs = allocCheck( this.#pDefs2 = allocCheck(
w2.haku2_defs_parse( w2.haku2_defs_parse(
pDefsString, pDefsString.ptr,
w.haku_defs_len2(this.#pInstance), w.haku_defs_len2(this.#pInstance),
pTagsString, pTagsString.ptr,
w.haku_tags_len2(this.#pInstance), w.haku_tags_len2(this.#pInstance),
), ),
); );
w2.haku2_scratch_reset(this.#pScratch2); w2.haku2_scratch_reset(this.#pScratch2);
this.#pVm2 = allocCheck(w2.haku2_vm_new(this.#pScratch2, defs, this.#pLimits2)); this.#pVm2 = allocCheck(w2.haku2_vm_new());
this.#bytecode2 = dup1to2( this.#bytecode2 = dup1to2(
w.haku_bytecode2(this.#pInstance), w.haku_bytecode2(this.#pInstance),
@ -310,7 +316,6 @@ export class Haku {
); );
this.#localCount = w.haku_local_count2(this.#pInstance); this.#localCount = w.haku_local_count2(this.#pInstance);
w2.haku2_defs_destroy(defs);
freeString2(pDefsString); freeString2(pDefsString);
freeString2(pTagsString); freeString2(pTagsString);
@ -341,7 +346,8 @@ export class Haku {
return; return;
} }
w2.haku2_vm_reset(this.#pVm2, this.#fuel); w2.haku2_scratch_reset(this.#pScratch2);
w2.haku2_vm_reset(this.#pVm2, this.#pScratch2, this.#pDefs2, this.#pLimits2, this.#fuel);
let ok = w2.haku2_vm_run_main( let ok = w2.haku2_vm_run_main(
this.#pVm2, this.#pVm2,
this.#pScratch2, this.#pScratch2,
@ -405,18 +411,14 @@ export class Haku {
} }
get astSize() { get astSize() {
return 0; // TODO return w.haku_stat_ast_size(this.#pInstance);
}
get numRefs() {
return 0; // TODO
} }
get remainingFuel() { get remainingFuel() {
return 0; // TODO return w2.haku2_vm_fuel(this.#pVm2);
} }
get remainingMemory() { get usedMemory() {
return 0; // TODO return w2.haku2_scratch_used(this.#pScratch2);
} }
} }

View file

@ -94,11 +94,9 @@ export class User {
return { return {
astSize: this.haku.astSize, astSize: this.haku.astSize,
astSizeMax: wallInfo.hakuLimits.ast_capacity, astSizeMax: wallInfo.hakuLimits.ast_capacity,
numRefs: this.haku.numRefs,
numRefsMax: wallInfo.hakuLimits.ref_capacity,
fuel: wallInfo.hakuLimits.fuel - this.haku.remainingFuel, fuel: wallInfo.hakuLimits.fuel - this.haku.remainingFuel,
fuelMax: wallInfo.hakuLimits.fuel, fuelMax: wallInfo.hakuLimits.fuel,
memory: wallInfo.hakuLimits.memory - this.haku.remainingMemory, memory: this.haku.usedMemory,
memoryMax: wallInfo.hakuLimits.memory, memoryMax: wallInfo.hakuLimits.memory,
}; };
} }