From 00a48527ca9ebb25deb70106d2b25496b8537e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Mon, 16 Jun 2025 16:39:54 +0200 Subject: [PATCH] haku2: add stats + fix crash w uninit vm --- crates/haku-wasm/src/lib.rs | 5 +++++ crates/haku2/src/bytecode.zig | 3 +++ crates/haku2/src/haku2.zig | 27 ++++++++++++++++++--------- crates/haku2/src/lib.rs | 35 +++++++++++++++++++++++------------ crates/haku2/src/log.zig | 2 ++ crates/haku2/src/render.zig | 2 +- crates/haku2/src/system.zig | 1 - crates/haku2/src/vm.zig | 6 +++--- crates/rkgk/src/haku.rs | 31 ++++++++++++++----------------- static/brush-cost.js | 11 +---------- static/haku.js | 32 +++++++++++++++++--------------- static/online-users.js | 4 +--- 12 files changed, 88 insertions(+), 71 deletions(-) diff --git a/crates/haku-wasm/src/lib.rs b/crates/haku-wasm/src/lib.rs index c75e568..771117f 100644 --- a/crates/haku-wasm/src/lib.rs +++ b/crates/haku-wasm/src/lib.rs @@ -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 { 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 +} diff --git a/crates/haku2/src/bytecode.zig b/crates/haku2/src/bytecode.zig index d04603a..953c568 100644 --- a/crates/haku2/src/bytecode.zig +++ b/crates/haku2/src/bytecode.zig @@ -1,5 +1,6 @@ const std = @import("std"); const mem = std.mem; +const log = std.log.scoped(.bytecode); /// NOTE: This must be mirrored in bytecode.rs. pub const Opcode = enum(u8) { @@ -70,6 +71,8 @@ pub const Defs = struct { .num_tags = mem.count(u8, tags_string, "\n"), }; + log.debug("parsed defs {*} tags {s}: {}", .{ defs_string, tags_string, d }); + return d; } diff --git a/crates/haku2/src/haku2.zig b/crates/haku2/src/haku2.zig index 2b91006..111b77e 100644 --- a/crates/haku2/src/haku2.zig +++ b/crates/haku2/src/haku2.zig @@ -10,8 +10,6 @@ const value = @import("value.zig"); const Vm = @import("vm.zig"); const log = @import("log.zig"); -const debug_logs = true; - pub const allocator = if (builtin.cpu.arch == .wasm32) std.heap.wasm_allocator @@ -19,10 +17,14 @@ pub const allocator = @import("allocator.zig").hostAllocator; pub const std_options: std.Options = .{ - .log_level = if (debug_logs) .debug else .info, + .log_level = .debug, .logFn = log.logFn, }; +pub fn enableLogScope(scope: @TypeOf(.enum_literal)) bool { + if (scope == .vm) return false else return true; +} + // Allocator 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); } +export fn haku2_scratch_used(scratch: *const Scratch) usize { + return scratch.fixedBuffer.end_index; +} + // Limits export fn haku2_limits_new() ?*Vm.Limits { @@ -88,12 +94,9 @@ export fn haku2_defs_destroy(defs: *bytecode.Defs) void { // 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; - errdefer allocator.destroy(vm); - - vm.* = Vm.init(s.allocator(), defs, limits) catch return null; - + vm.* = .{}; return vm; } @@ -101,8 +104,10 @@ export fn haku2_vm_destroy(vm: *Vm) void { 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); + return true; } export fn haku2_vm_run_main( @@ -160,6 +165,10 @@ export fn haku2_vm_run_dotter( return true; } +export fn haku2_vm_fuel(vm: *const Vm) usize { + return vm.fuel; +} + export fn haku2_vm_exception_len(vm: *const Vm) usize { if (vm.exception) |exn| { return exn.len; diff --git a/crates/haku2/src/lib.rs b/crates/haku2/src/lib.rs index b37bd72..708a5fc 100644 --- a/crates/haku2/src/lib.rs +++ b/crates/haku2/src/lib.rs @@ -134,9 +134,15 @@ extern "C" { ) -> *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_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( vm: *mut VmC, scratch: *mut ScratchC, @@ -296,6 +302,7 @@ impl Code { pub struct Vm { scratch: Scratch, code: Code, + limits: Limits, inner: VmInner, } @@ -318,15 +325,10 @@ 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"); + pub fn new(scratch: Scratch, code: Code, limits: Limits) -> Self { + // SAFETY: haku2_vm_new cannot fail. + // Do note that this returns an uninitialized VM, which must be reset before use. + let raw = NonNull::new(unsafe { haku2_vm_new() }).expect("out of memory"); trace!("Vm::new({scratch:?}, {code:?}, {limits:?}) -> {raw:?}"); Self { // SAFETY: @@ -336,6 +338,7 @@ impl Vm { inner: VmInner { raw }, scratch, code, + limits, } } @@ -346,8 +349,15 @@ impl Vm { /// 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> { trace!("Vm::begin({self:?}, {fuel})"); + self.scratch.reset(); 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( self.inner.raw.as_ptr(), self.scratch.raw.as_ptr(), @@ -422,6 +432,7 @@ impl Vm { mut scratch, code: _, inner: _, + limits: _, } = self; scratch.reset(); scratch diff --git a/crates/haku2/src/log.zig b/crates/haku2/src/log.zig index 94b466d..7336c58 100644 --- a/crates/haku2/src/log.zig +++ b/crates/haku2/src/log.zig @@ -9,6 +9,8 @@ pub fn logFn( comptime fmt: []const u8, args: anytype, ) void { + if (!root.enableLogScope(scope)) return; + const stack_buf_size = 1024; var stack_buf: [stack_buf_size]u8 = undefined; var heap_buf: ?[]u8 = null; diff --git a/crates/haku2/src/render.zig b/crates/haku2/src/render.zig index 2528fd2..7f6c94e 100644 --- a/crates/haku2/src/render.zig +++ b/crates/haku2/src/render.zig @@ -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 { - const val = vm.stack[vm.stack_top - 1]; + const val = vm.top(); try renderRec(vm, canvas, val, 0, max_depth); } diff --git a/crates/haku2/src/system.zig b/crates/haku2/src/system.zig index 009e8c4..464d1ba 100644 --- a/crates/haku2/src/system.zig +++ b/crates/haku2/src/system.zig @@ -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 { - 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}); } diff --git a/crates/haku2/src/vm.zig b/crates/haku2/src/vm.zig index 7b294ee..970d672 100644 --- a/crates/haku2/src/vm.zig +++ b/crates/haku2/src/vm.zig @@ -12,11 +12,11 @@ const Value = value.Value; const Vm = @This(); -stack: []Value, +stack: []Value = &.{}, stack_top: u32 = 0, -call_stack: []CallFrame, +call_stack: []CallFrame = &.{}, call_stack_top: u32 = 0, -defs: []Value, +defs: []Value = &.{}, fuel: u32 = 0, // NOTE: VM must be refueled via reset() before running code exception: ?Exception = null, diff --git a/crates/rkgk/src/haku.rs b/crates/rkgk/src/haku.rs index e5a32ad..e160afa 100644 --- a/crates/rkgk/src/haku.rs +++ b/crates/rkgk/src/haku.rs @@ -127,23 +127,20 @@ impl Haku { .context("too many chunks")?; self.brush = Some((chunk_id, closure_spec)); - // haku2 setup - { - let scratch = self - .vm2 - .take() - .map(|vm| vm.into_scratch()) - .unwrap_or_else(|| haku2::Scratch::new(self.limits.memory)); - let defs = haku2::Defs::parse(&self.defs.serialize_defs(), &self.defs.serialize_tags()); - // SAFETY: The code is fresh out of the compiler oven, so it is guaranteed to be valid. - // Well, more or less. There may lurk bugs. - let code = unsafe { haku2::Code::new(defs, chunk.bytecode, closure_spec.local_count) }; - let limits = haku2::Limits::new(haku2::LimitsSpec { - stack_capacity: self.limits.stack_capacity, - call_stack_capacity: self.limits.call_stack_capacity, - }); - self.vm2 = Some(haku2::Vm::new(scratch, code, &limits)) - } + let scratch = self + .vm2 + .take() + .map(|vm| vm.into_scratch()) + .unwrap_or_else(|| haku2::Scratch::new(self.limits.memory)); + let defs = haku2::Defs::parse(&self.defs.serialize_defs(), &self.defs.serialize_tags()); + // SAFETY: The code is fresh out of the compiler oven, so it is guaranteed to be valid. + // Well, more or less. There may lurk bugs. + let code = unsafe { haku2::Code::new(defs, chunk.bytecode, closure_spec.local_count) }; + let limits = haku2::Limits::new(haku2::LimitsSpec { + stack_capacity: self.limits.stack_capacity, + call_stack_capacity: self.limits.call_stack_capacity, + }); + self.vm2 = Some(haku2::Vm::new(scratch, code, limits)); info!("brush set successfully"); diff --git a/static/brush-cost.js b/static/brush-cost.js index 1b6a690..e1b9bc5 100644 --- a/static/brush-cost.js +++ b/static/brush-cost.js @@ -43,31 +43,22 @@ export class BrushCostGauges extends HTMLElement { label: "Fuel", }), ); - this.objectsGauge = this.appendChild( - createGauge({ - className: "objects", - iconName: "object", - label: "Objects", - }), - ); this.memoryGauge = this.appendChild( createGauge({ className: "memory", iconName: "memory", - label: "Bulk memory", + label: "Memory", }), ); this.codeSizeGauge.setValue(0); this.fuelGauge.setValue(0); - this.objectsGauge.setValue(0); this.memoryGauge.setValue(0); } update(stats) { this.codeSizeGauge.setValue(stats.astSize, stats.astSizeMax); this.fuelGauge.setValue(stats.fuel, stats.fuelMax); - this.objectsGauge.setValue(stats.numRefs, stats.numRefsMax); this.memoryGauge.setValue(stats.memory, stats.memoryMax); } } diff --git a/static/haku.js b/static/haku.js index d63f9bc..9daf6c7 100644 --- a/static/haku.js +++ b/static/haku.js @@ -47,7 +47,6 @@ let [hakuWasm, haku2Wasm] = await Promise.all([ __haku2_log_info: makeLogFunction2("info"), __haku2_log_debug: makeLogFunction2("debug"), - // TODO: Renderer __haku2_canvas_begin: (c) => canvasBeginImpl(c), __haku2_canvas_line: (c, x1, y1, x2, y2) => canvasLineImpl(c, x1, y1, x2, y2), __haku2_canvas_rectangle: (c, x, y, width, height) => @@ -205,6 +204,7 @@ export class Haku { #pLimits2 = 0; #pScratch2 = 0; + #pDefs2 = 0; #pVm2 = 0; #bytecode2 = null; @@ -242,11 +242,14 @@ export class Haku { w2.haku2_scratch_destroy(this.#pScratch2); w2.haku2_vm_destroy(this.#pVm2); + w2.haku2_defs_destroy(this.#pDefs2); w2.haku2_limits_destroy(this.#pLimits2); w2.haku_dealloc(this.#bytecode2.ptr); } setBrush(code) { + w.haku_reset(this.#pInstance); + let brushCode = allocString(code); let statusCode = w.haku_compile_brush2(this.#pInstance, brushCode.length, brushCode.ptr); freeString(brushCode); @@ -279,8 +282,11 @@ export class Haku { } 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); + console.log(w.haku_defs2(this.#pInstance), w.haku_defs_len2(this.#pInstance)); + let pDefsString = dup1to2( w.haku_defs2(this.#pInstance), w.haku_defs_len2(this.#pInstance), @@ -291,17 +297,17 @@ export class Haku { w.haku_tags_len2(this.#pInstance), 1, ); - let defs = allocCheck( + this.#pDefs2 = allocCheck( w2.haku2_defs_parse( - pDefsString, + pDefsString.ptr, w.haku_defs_len2(this.#pInstance), - pTagsString, + pTagsString.ptr, w.haku_tags_len2(this.#pInstance), ), ); 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( w.haku_bytecode2(this.#pInstance), @@ -310,7 +316,6 @@ export class Haku { ); this.#localCount = w.haku_local_count2(this.#pInstance); - w2.haku2_defs_destroy(defs); freeString2(pDefsString); freeString2(pTagsString); @@ -341,7 +346,8 @@ export class Haku { 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( this.#pVm2, this.#pScratch2, @@ -405,18 +411,14 @@ export class Haku { } get astSize() { - return 0; // TODO - } - - get numRefs() { - return 0; // TODO + return w.haku_stat_ast_size(this.#pInstance); } get remainingFuel() { - return 0; // TODO + return w2.haku2_vm_fuel(this.#pVm2); } - get remainingMemory() { - return 0; // TODO + get usedMemory() { + return w2.haku2_scratch_used(this.#pScratch2); } } diff --git a/static/online-users.js b/static/online-users.js index 9424412..fbee3ee 100644 --- a/static/online-users.js +++ b/static/online-users.js @@ -94,11 +94,9 @@ export class User { return { astSize: this.haku.astSize, astSizeMax: wallInfo.hakuLimits.ast_capacity, - numRefs: this.haku.numRefs, - numRefsMax: wallInfo.hakuLimits.ref_capacity, fuel: wallInfo.hakuLimits.fuel - this.haku.remainingFuel, fuelMax: wallInfo.hakuLimits.fuel, - memory: wallInfo.hakuLimits.memory - this.haku.remainingMemory, + memory: this.haku.usedMemory, memoryMax: wallInfo.hakuLimits.memory, }; }