From 5de4f9d7c6204ac8814568b5987caeebada2d8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Wed, 4 Jun 2025 00:28:21 +0200 Subject: [PATCH] hotwire haku2 into rkgk really a bodge job right now and it crashes but it's a start --- crates/haku/src/bytecode.rs | 18 +++++++ crates/haku/src/compiler.rs | 2 +- crates/haku2/build.zig | 1 + crates/haku2/src/haku2.zig | 18 +++++-- crates/haku2/src/lib.rs | 102 +++++++++++++++++++++++++++--------- crates/haku2/src/render.zig | 2 +- crates/haku2/src/vm.zig | 8 +++ crates/rkgk/src/api/wall.rs | 53 +++++-------------- crates/rkgk/src/haku.rs | 45 +++++++++++++++- 9 files changed, 178 insertions(+), 71 deletions(-) diff --git a/crates/haku/src/bytecode.rs b/crates/haku/src/bytecode.rs index d6598c1..60a6a35 100644 --- a/crates/haku/src/bytecode.rs +++ b/crates/haku/src/bytecode.rs @@ -306,6 +306,24 @@ impl Defs { panic!("image must be a subset of the current defs") }); } + + pub fn serialize_defs(&self) -> String { + let mut result = String::new(); + for def in &self.defs { + result.push_str(def); + result.push('\n'); + } + result + } + + pub fn serialize_tags(&self) -> String { + let mut result = String::new(); + for tag in &self.tags { + result.push_str(tag); + result.push('\n'); + } + result + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/haku/src/compiler.rs b/crates/haku/src/compiler.rs index 50eb6ac..ec56f61 100644 --- a/crates/haku/src/compiler.rs +++ b/crates/haku/src/compiler.rs @@ -47,7 +47,7 @@ pub struct Compiler<'a> { #[derive(Debug, Clone, Copy)] pub struct ClosureSpec { - pub(crate) local_count: u8, + pub local_count: u8, } impl<'a> Compiler<'a> { diff --git a/crates/haku2/build.zig b/crates/haku2/build.zig index 471b287..f540685 100644 --- a/crates/haku2/build.zig +++ b/crates/haku2/build.zig @@ -17,6 +17,7 @@ pub fn build(b: *std.Build) void { .root_module = mod, }); lib.pie = true; + lib.bundle_compiler_rt = true; b.installArtifact(lib); const mod_wasm = b.createModule(.{ diff --git a/crates/haku2/src/haku2.zig b/crates/haku2/src/haku2.zig index 9c5363a..046d990 100644 --- a/crates/haku2/src/haku2.zig +++ b/crates/haku2/src/haku2.zig @@ -9,7 +9,11 @@ const Scratch = @import("scratch.zig"); const value = @import("value.zig"); const Vm = @import("vm.zig"); -const allocator = if (builtin.cpu.arch == .wasm32) std.heap.wasm_allocator else @import("allocator.zig").hostAllocator; +const allocator = + if (builtin.cpu.arch == .wasm32) + std.heap.wasm_allocator + else + @import("allocator.zig").hostAllocator; // Scratch @@ -28,7 +32,9 @@ export fn haku2_scratch_reset(scratch: *Scratch) void { // Limits export fn haku2_limits_new() ?*Vm.Limits { - return allocator.create(Vm.Limits) catch null; + const limits = allocator.create(Vm.Limits) catch return null; + limits.* = .{}; + return limits; } export fn haku2_limits_destroy(limits: *Vm.Limits) void { @@ -102,9 +108,15 @@ export fn haku2_vm_run_main( return true; } +export fn haku2_vm_has_cont(vm: *const Vm) bool { + if (vm.stack.len == 0) return false; + const top = vm.top(); + return top == .ref and top.ref.* == .reticle; +} + export fn haku2_vm_is_dotter(vm: *const Vm) bool { if (vm.stack.len == 0) return false; - const top = vm.stack[vm.stack_top]; + const top = vm.top(); return top == .ref and top.ref.* == .reticle and top.ref.reticle == .dotter; } diff --git a/crates/haku2/src/lib.rs b/crates/haku2/src/lib.rs index f246983..d9306ed 100644 --- a/crates/haku2/src/lib.rs +++ b/crates/haku2/src/lib.rs @@ -3,6 +3,7 @@ use std::{ error::Error, fmt::{self, Display}, marker::{PhantomData, PhantomPinned}, + mem::forget, ptr::{self, NonNull}, }; @@ -91,6 +92,7 @@ extern "C" { code_len: usize, local_count: u8, ) -> bool; + fn haku2_vm_has_cont(vm: *const VmC) -> bool; fn haku2_vm_is_dotter(vm: *const VmC) -> bool; fn haku2_vm_run_dotter( vm: *mut VmC, @@ -211,10 +213,38 @@ impl Drop for Defs { } } +#[derive(Debug)] +pub struct Code { + defs: Defs, + main_chunk: Vec, + main_local_count: u8, +} + +impl Code { + /// Creates a new instance of `Code` from a valid vector of bytes. + /// + /// # Safety + /// + /// This does not perform any validation, and there is no way to perform such + /// validation before constructing this. The bytecode must simply be valid, which is the case + /// for bytecode emitted directly by the compiler. + /// + /// Untrusted bytecode should never ever be loaded under any circumstances. + pub unsafe fn new(defs: Defs, main_chunk: Vec, main_local_count: u8) -> Self { + Self { + defs, + main_chunk, + main_local_count, + } + } +} + +/// A VM that is ready to run and loaded with valid bytecode. #[derive(Debug)] pub struct Vm { scratch: Scratch, - raw: NonNull, + code: Code, + inner: VmInner, } #[derive(Debug)] @@ -236,17 +266,24 @@ pub struct Dotter { } impl Vm { - pub fn new(scratch: Scratch, defs: &Defs, limits: &Limits) -> Self { + pub fn new(scratch: Scratch, code: Code, limits: &Limits) -> Self { Self { // SAFETY: - // - Ownership of s is passed to the VM, so the VM cannot outlive the scratch space. + // - 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. - raw: NonNull::new(unsafe { - haku2_vm_new(scratch.raw.as_ptr(), defs.raw.as_ptr(), limits.raw.as_ptr()) - }) - .expect("out of memory"), + 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"), + }, scratch, + code, } } @@ -255,19 +292,14 @@ 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. - /// - /// # Safety - /// - /// The bytecode passed in must be valid, because bytecode validation is done on a best-effort - /// basis. Bytecode retrieved out of the compiler is guaranteed to be safe. - pub unsafe fn begin(&mut self, code: &[u8], local_count: u8) -> Result<(), Exception> { + pub fn begin(&mut self) -> Result<(), Exception> { let ok = unsafe { haku2_vm_run_main( - self.raw.as_ptr(), + self.inner.raw.as_ptr(), self.scratch.raw.as_ptr(), - code.as_ptr(), - code.len(), - local_count, + self.code.main_chunk.as_ptr(), + self.code.main_chunk.len(), + self.code.main_local_count, ) }; if ok { @@ -277,9 +309,14 @@ impl Vm { } } + /// Returns whether `cont()` can be called to run the next continuation. + pub fn has_cont(&self) -> bool { + unsafe { haku2_vm_has_cont(self.inner.raw.as_ptr()) } + } + fn is_dotter(&self) -> bool { // SAFETY: The pointer is valid. - unsafe { haku2_vm_is_dotter(self.raw.as_ptr()) } + unsafe { haku2_vm_is_dotter(self.inner.raw.as_ptr()) } } /// Returns how the VM should continue executing after the previous execution. @@ -291,12 +328,12 @@ impl Vm { } /// Renders the current scribble on top of the stack. - /// If the value on top is not a scribble, throws an exception (indicated by the return type.) + /// If the value on top is not a scribble, throws an exception. /// /// The rendering is performed by calling into the [`Canvas`] trait. pub fn render(&mut self, canvas: &mut dyn Canvas, max_depth: usize) -> Result<(), Exception> { let mut wrapped = CanvasC { inner: canvas }; - let ok = unsafe { haku2_render(self.raw.as_ptr(), &mut wrapped, max_depth) }; + let ok = unsafe { haku2_render(self.inner.raw.as_ptr(), &mut wrapped, max_depth) }; if ok { Ok(()) } else { @@ -308,7 +345,7 @@ impl Vm { /// Returns `None` if there's no exception. pub fn exception(&self) -> Option { // SAFETY: The pointer passed to this function is valid. - let len = unsafe { haku2_vm_exception_len(self.raw.as_ptr()) }; + let len = unsafe { haku2_vm_exception_len(self.inner.raw.as_ptr()) }; if len == 0 { return None; } @@ -316,12 +353,24 @@ impl Vm { let mut buffer = vec![0; len]; // SAFETY: The length of the buffer is as indicated by haku2_vm_exception_len. unsafe { - haku2_vm_exception_render(self.raw.as_ptr(), buffer.as_mut_ptr()); + haku2_vm_exception_render(self.inner.raw.as_ptr(), buffer.as_mut_ptr()); } Some(Exception { message: String::from_utf8_lossy(&buffer).into_owned(), }) } + + /// 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 { + let Vm { + mut scratch, + code: _, + inner: _, + } = self; + scratch.reset(); + scratch + } } impl ContDotter<'_> { @@ -334,7 +383,7 @@ impl ContDotter<'_> { let ok = unsafe { haku2_vm_run_dotter( - self.vm.raw.as_ptr(), + self.vm.inner.raw.as_ptr(), self.vm.scratch.raw.as_ptr(), from_x, from_y, @@ -351,7 +400,12 @@ impl ContDotter<'_> { } } -impl Drop for Vm { +#[derive(Debug)] +struct VmInner { + raw: NonNull, +} + +impl Drop for VmInner { fn drop(&mut self) { // SAFETY: The pointer passed is non-null. unsafe { diff --git a/crates/haku2/src/render.zig b/crates/haku2/src/render.zig index 72ce279..2528fd2 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 = try vm.pop(); + const val = vm.stack[vm.stack_top - 1]; try renderRec(vm, canvas, val, 0, max_depth); } diff --git a/crates/haku2/src/vm.zig b/crates/haku2/src/vm.zig index 42556fa..9cab95e 100644 --- a/crates/haku2/src/vm.zig +++ b/crates/haku2/src/vm.zig @@ -113,6 +113,14 @@ pub fn pop(vm: *Vm) Error!Value { return vm.stack[vm.stack_top]; } +pub fn top(vm: *const Vm) Value { + if (vm.stack_top > 0) { + return vm.stack[vm.stack_top - 1]; + } else { + return .nil; + } +} + pub fn pushCall(vm: *Vm, frame: CallFrame) Error!void { if (vm.call_stack_top >= vm.call_stack.len) { return vm.throw("too much recursion", .{}); diff --git a/crates/rkgk/src/api/wall.rs b/crates/rkgk/src/api/wall.rs index d36682d..42587c6 100644 --- a/crates/rkgk/src/api/wall.rs +++ b/crates/rkgk/src/api/wall.rs @@ -371,47 +371,7 @@ impl SessionLoop { // TODO: Auto save. This'll need us to compute which chunks will be affected // by the interactions. - } // wall::EventKind::SetBrush { brush } => { - // // SetBrush is not dropped because it is a very important event. - // _ = self - // .render_commands_tx - // .send(RenderCommand::SetBrush { - // brush: brush.clone(), - // }) - // .await; - // } - // wall::EventKind::Plot { points } => { - // let chunks_to_modify: Vec<_> = - // chunks_to_modify(&self.wall, points).into_iter().collect(); - // match self.chunk_images.load(chunks_to_modify.clone()).await { - // Ok(_) => { - // // We drop commands if we take too long to render instead of lagging - // // the WebSocket thread. - // // Theoretically this will yield much better responsiveness, but it _will_ - // // result in some visual glitches if we're getting bottlenecked. - // let (done_tx, done_rx) = oneshot::channel(); - // let send_result = - // self.render_commands_tx.try_send(RenderCommand::Plot { - // points: points.clone(), - // done: done_tx, - // }); - - // if send_result.is_err() { - // info!( - // ?points, - // "render thread is overloaded, dropping request to draw points" - // ); - // } - - // let auto_save = Arc::clone(&self.auto_save); - // tokio::spawn(async move { - // _ = done_rx.await; - // auto_save.request(chunks_to_modify).await; - // }); - // } - // Err(err) => error!(?err, "while loading chunks for render command"), - // } - // } + } } self.wall.event(wall::Event { @@ -516,6 +476,8 @@ impl SessionLoop { Interaction::Dotter { from, to, num } => { if brush_ok { + jumpstart_trampoline2(&mut haku); + if let Some(tramp) = jumpstart_trampoline(&mut haku, &mut trampoline) { let cont = haku.cont(tramp); if cont == Cont::Dotter { @@ -598,6 +560,7 @@ fn chunks_to_modify( chunks } + fn jumpstart_trampoline<'a>( haku: &mut Haku, trampoline: &'a mut Option, @@ -608,6 +571,14 @@ fn jumpstart_trampoline<'a>( trampoline.as_mut() } +fn jumpstart_trampoline2(haku: &mut Haku) { + if !haku.has_cont2() { + if let Err(e) = haku.eval_brush2() { + error!("eval_brush2: {e}"); + } + } +} + #[instrument(skip(wall, haku, value))] fn draw_to_chunks( wall: &Wall, diff --git a/crates/rkgk/src/haku.rs b/crates/rkgk/src/haku.rs index cd0f562..02599e5 100644 --- a/crates/rkgk/src/haku.rs +++ b/crates/rkgk/src/haku.rs @@ -55,6 +55,8 @@ pub struct Haku { vm: Vm, vm_image: VmImage, + vm2: Option, + brush: Option<(ChunkId, ClosureSpec)>, } @@ -88,6 +90,7 @@ impl Haku { defs_image, vm, vm_image, + vm2: None, brush: None, } } @@ -140,9 +143,31 @@ impl Haku { bail!("diagnostics were emitted"); } - let chunk_id = self.system.add_chunk(chunk).context("too many chunks")?; + let chunk_id = self + .system + .add_chunk(chunk.clone()) + .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, + fuel: self.limits.fuel as u32, + }); + self.vm2 = Some(haku2::Vm::new(scratch, code, &limits)) + } + info!("brush set successfully"); Ok(()) @@ -170,6 +195,16 @@ impl Haku { Ok(scribble) } + #[instrument(skip(self), err(level = Level::INFO))] + pub fn eval_brush2(&mut self) -> eyre::Result<()> { + let vm = self + .vm2 + .as_mut() + .ok_or_eyre("brush is not compiled and ready to be used")?; + vm.begin().context("an exception occurred during begin()")?; + Ok(()) + } + #[instrument(skip(self, pixmap, value, translation), err(level = Level::INFO))] pub fn render_value( &self, @@ -194,6 +229,14 @@ impl Haku { trampoline.cont(&self.vm) } + pub fn has_cont2(&mut self) -> bool { + self.vm2.as_mut().expect("VM is not started").has_cont() + } + + pub fn cont2(&mut self) -> haku2::Cont<'_> { + self.vm2.as_mut().expect("VM is not started").cont() + } + pub fn dotter( &mut self, trampoline: &mut Trampoline,