From e667c6336ad23cfec2822cc5017ce01c0647b008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Sun, 15 Jun 2025 21:29:50 +0200 Subject: [PATCH] haku2: on the client resource indicators are currently unimplemented --- crates/haku-wasm/src/lib.rs | 438 +++++++++++++++--------------------- crates/haku2/build.rs | 4 +- crates/haku2/src/haku2.zig | 12 +- crates/haku2/src/lib.rs | 2 + crates/haku2/src/vm.zig | 2 +- crates/rkgk/src/build.rs | 1 + crates/rkgk/src/haku.rs | 3 +- rkgk.toml | 14 +- static/haku.js | 324 ++++++++++++++++++-------- template/index.hbs.html | 1 + 10 files changed, 437 insertions(+), 364 deletions(-) diff --git a/crates/haku-wasm/src/lib.rs b/crates/haku-wasm/src/lib.rs index 416092f..c75e568 100644 --- a/crates/haku-wasm/src/lib.rs +++ b/crates/haku-wasm/src/lib.rs @@ -2,9 +2,9 @@ extern crate alloc; -use core::{alloc::Layout, slice}; +use core::{alloc::Layout, mem, slice}; -use alloc::{boxed::Box, vec::Vec}; +use alloc::{boxed::Box, string::String, vec::Vec}; use haku::{ ast::Ast, bytecode::{Chunk, Defs, DefsImage, DefsLimits}, @@ -12,16 +12,13 @@ use haku::{ diagnostic::Diagnostic, lexer::{lex, Lexer}, parser::{self, IntoAstError, Parser}, - render::{ - tiny_skia::{Pixmap, PremultipliedColorU8}, - RendererLimits, + render::tiny_skia::{ + BlendMode, Color, FillRule, LineCap, Paint, PathBuilder, Pixmap, PremultipliedColorU8, + Rect, Shader, Stroke, Transform, }, source::SourceCode, - system::{ChunkId, System, SystemImage}, + system::{System, SystemImage}, token::Lexis, - trampoline::{Cont, Trampoline}, - value::{Closure, Ref, Vec2}, - vm::{Exception, Vm, VmImage, VmLimits}, }; use log::{debug, info}; @@ -57,8 +54,7 @@ struct Limits { ref_capacity: usize, fuel: usize, memory: usize, - pixmap_stack_capacity: usize, - transform_stack_capacity: usize, + render_max_depth: usize, } impl Default for Limits { @@ -77,8 +73,7 @@ impl Default for Limits { ref_capacity: 2048, fuel: 65536, memory: 1024 * 1024, - pixmap_stack_capacity: 4, - transform_stack_capacity: 16, + render_max_depth: 256, } } } @@ -123,8 +118,7 @@ limit_setter!(call_stack_capacity); limit_setter!(ref_capacity); limit_setter!(fuel); limit_setter!(memory); -limit_setter!(pixmap_stack_capacity); -limit_setter!(transform_stack_capacity); +limit_setter!(render_max_depth); #[derive(Debug, Clone)] struct Instance { @@ -134,23 +128,23 @@ struct Instance { system_image: SystemImage, defs: Defs, defs_image: DefsImage, - vm: Vm, - vm_image: VmImage, - trampoline: Option, - exception: Option, + compile_result2: Option, + diagnostics2: Vec, } -impl Instance { - fn set_exception(&mut self, exn: Exception) { - debug!("setting exception = {exn:?}"); - self.exception = Some(exn); - } +#[derive(Debug, Clone)] +struct CompileResult { + defs_string: String, + tags_string: String, + chunk: Chunk, + closure_spec: ClosureSpec, + stats: CompileStats, +} - fn reset_exception(&mut self) { - debug!("resetting exception"); - self.exception = None; - } +#[derive(Debug, Clone)] +struct CompileStats { + ast_size: usize, } #[unsafe(no_mangle)] @@ -164,20 +158,9 @@ unsafe extern "C" fn haku_instance_new(limits: *const Limits) -> *mut Instance { max_defs: limits.max_defs, max_tags: limits.max_tags, }); - let vm = Vm::new( - &defs, - &VmLimits { - stack_capacity: limits.stack_capacity, - call_stack_capacity: limits.call_stack_capacity, - ref_capacity: limits.ref_capacity, - fuel: limits.fuel, - memory: limits.memory, - }, - ); let system_image = system.image(); let defs_image = defs.image(); - let vm_image = vm.image(); let instance = Box::new(Instance { limits, @@ -185,10 +168,8 @@ unsafe extern "C" fn haku_instance_new(limits: *const Limits) -> *mut Instance { system_image, defs, defs_image, - vm, - vm_image, - trampoline: None, - exception: None, + compile_result2: None, + diagnostics2: Vec::new(), }); let ptr = Box::leak(instance) as *mut _; @@ -210,21 +191,6 @@ unsafe extern "C" fn haku_reset(instance: *mut Instance) { instance.defs.restore_image(&instance.defs_image); } -#[unsafe(no_mangle)] -unsafe extern "C" fn haku_has_exception(instance: *mut Instance) -> bool { - (*instance).exception.is_some() -} - -#[unsafe(no_mangle)] -unsafe extern "C" fn haku_exception_message(instance: *const Instance) -> *const u8 { - (*instance).exception.as_ref().unwrap().message.as_ptr() -} - -#[unsafe(no_mangle)] -unsafe extern "C" fn haku_exception_message_len(instance: *const Instance) -> u32 { - (*instance).exception.as_ref().unwrap().message.len() as u32 -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(C)] enum StatusCode { @@ -236,10 +202,6 @@ enum StatusCode { ParserUnbalancedEvents, ChunkTooBig, DiagnosticsEmitted, - TooManyChunks, - OutOfRefSlots, - EvalException, - RenderException, } #[unsafe(no_mangle)] @@ -252,14 +214,6 @@ extern "C" fn haku_is_diagnostics_emitted(code: StatusCode) -> bool { code == StatusCode::DiagnosticsEmitted } -#[unsafe(no_mangle)] -extern "C" fn haku_is_exception(code: StatusCode) -> bool { - matches!( - code, - StatusCode::EvalException | StatusCode::RenderException - ) -} - #[unsafe(no_mangle)] extern "C" fn haku_status_string(code: StatusCode) -> *const i8 { match code { @@ -271,91 +225,162 @@ extern "C" fn haku_status_string(code: StatusCode) -> *const i8 { StatusCode::ParserUnbalancedEvents => c"parser produced unbalanced events", StatusCode::ChunkTooBig => c"compiled bytecode is too large", StatusCode::DiagnosticsEmitted => c"diagnostics were emitted", - StatusCode::TooManyChunks => c"too many registered bytecode chunks", - StatusCode::OutOfRefSlots => c"out of ref slots (did you forget to restore the VM image?)", - StatusCode::EvalException => c"an exception occurred while evaluating your code", - StatusCode::RenderException => c"an exception occurred while rendering your brush", } .as_ptr() } -#[derive(Debug)] -struct CompileStats { - ast_size: usize, -} - -#[derive(Debug)] -struct RunnableBrush { - chunk_id: ChunkId, - closure_spec: ClosureSpec, - - compile_stats: CompileStats, -} - -#[derive(Debug, Default)] -enum BrushState { - #[default] - Default, - Ready(RunnableBrush), -} - -#[derive(Debug, Default)] -struct Brush { - diagnostics: Vec, - state: BrushState, +struct PixmapCanvas { + pixmap: Pixmap, + pb: PathBuilder, + transform: Transform, } #[unsafe(no_mangle)] -extern "C" fn haku_brush_new() -> *mut Brush { - let ptr = Box::leak(Box::new(Brush::default())) as *mut _; - debug!("created brush: {ptr:?}"); +extern "C" fn haku_pixmap_new(width: u32, height: u32) -> *mut PixmapCanvas { + let ptr = Box::leak(Box::new(PixmapCanvas { + pixmap: Pixmap::new(width, height).expect("invalid pixmap size"), + pb: PathBuilder::new(), + transform: Transform::identity(), + })) as *mut _; + debug!("created pixmap with size {width}x{height}: {ptr:?}"); ptr } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_brush_destroy(brush: *mut Brush) { - debug!("destroying brush: {brush:?}"); - drop(Box::from_raw(brush)) +unsafe extern "C" fn haku_pixmap_destroy(c: *mut PixmapCanvas) { + debug!("destroying pixmap: {c:?}"); + drop(Box::from_raw(c)) } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_num_diagnostics(brush: *const Brush) -> u32 { - (*brush).diagnostics.len() as u32 +unsafe extern "C" fn haku_pixmap_data(c: *mut PixmapCanvas) -> *mut u8 { + let c = &mut *c; + c.pixmap.pixels_mut().as_mut_ptr() as *mut u8 } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_diagnostic_start(brush: *const Brush, index: u32) -> u32 { - (*brush).diagnostics[index as usize].span().start +unsafe extern "C" fn haku_pixmap_clear(c: *mut PixmapCanvas) { + let c = &mut *c; + c.pixmap + .pixels_mut() + .fill(PremultipliedColorU8::TRANSPARENT); } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_diagnostic_end(brush: *const Brush, index: u32) -> u32 { - (*brush).diagnostics[index as usize].span().end +unsafe extern "C" fn haku_pixmap_set_translation(c: *mut PixmapCanvas, x: f32, y: f32) { + let c = &mut *c; + c.transform = Transform::from_translate(x, y); } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_diagnostic_message(brush: *const Brush, index: u32) -> *const u8 { - (*brush).diagnostics[index as usize].message().as_ptr() +unsafe extern "C" fn haku_pixmap_begin(c: *mut PixmapCanvas) -> bool { + let c = &mut *c; + c.pb.clear(); + true } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_diagnostic_message_len(brush: *const Brush, index: u32) -> u32 { - (*brush).diagnostics[index as usize].message().len() as u32 +unsafe extern "C" fn haku_pixmap_line( + c: *mut PixmapCanvas, + x1: f32, + y1: f32, + x2: f32, + y2: f32, +) -> bool { + let c = &mut *c; + c.pb.move_to(x1, y1); + c.pb.line_to(x2, y2); + true } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_compile_brush( +unsafe extern "C" fn haku_pixmap_rectangle( + c: *mut PixmapCanvas, + x: f32, + y: f32, + width: f32, + height: f32, +) -> bool { + let c = &mut *c; + if let Some(rect) = Rect::from_xywh(x, y, width, height) { + c.pb.push_rect(rect); + } + true +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn haku_pixmap_circle(c: *mut PixmapCanvas, x: f32, y: f32, r: f32) -> bool { + let c = &mut *c; + c.pb.push_circle(x, y, r); + true +} + +fn default_paint() -> Paint<'static> { + Paint { + shader: Shader::SolidColor(Color::BLACK), + blend_mode: BlendMode::SourceOver, + anti_alias: false, + force_hq_pipeline: false, + } +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn haku_pixmap_fill(c: *mut PixmapCanvas, r: u8, g: u8, b: u8, a: u8) -> bool { + let c = &mut *c; + let pb = mem::take(&mut c.pb); + if let Some(path) = pb.finish() { + let paint = Paint { + shader: Shader::SolidColor(Color::from_rgba8(r, g, b, a)), + ..default_paint() + }; + c.pixmap + .fill_path(&path, &paint, FillRule::EvenOdd, c.transform, None); + } + true +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn haku_pixmap_stroke( + c: *mut PixmapCanvas, + r: u8, + g: u8, + b: u8, + a: u8, + thickness: f32, +) -> bool { + let c = &mut *c; + let pb = mem::take(&mut c.pb); + if let Some(path) = pb.finish() { + let paint = Paint { + shader: Shader::SolidColor(Color::from_rgba8(r, g, b, a)), + ..default_paint() + }; + c.pixmap.stroke_path( + &path, + &paint, + &Stroke { + width: thickness, + line_cap: LineCap::Round, + ..Default::default() + }, + c.transform, + None, + ); + } + true +} + +// v2 compile-only support + +#[unsafe(no_mangle)] +unsafe extern "C" fn haku_compile_brush2( instance: *mut Instance, - out_brush: *mut Brush, code_len: u32, code: *const u8, ) -> StatusCode { - info!("compiling brush"); + info!("compiling brush (2)"); let instance = &mut *instance; - let brush = &mut *out_brush; - - *brush = Brush::default(); let code = core::str::from_utf8(slice::from_raw_parts(code, code_len as usize)) .expect("invalid UTF-8"); @@ -428,8 +453,8 @@ unsafe extern "C" fn haku_compile_brush( let mut diagnostics = lexer.diagnostics; diagnostics.append(&mut parser_diagnostics); diagnostics.append(&mut compiler.diagnostics); - if !diagnostics.is_empty() { - brush.diagnostics = diagnostics; + instance.diagnostics2 = diagnostics; + if !instance.diagnostics2.is_empty() { debug!("compiling failed: diagnostics were emitted"); return StatusCode::DiagnosticsEmitted; } @@ -440,179 +465,82 @@ unsafe extern "C" fn haku_compile_brush( ); debug!("compiling: {closure_spec:?}"); - let chunk_id = match instance.system.add_chunk(chunk) { - Ok(chunk_id) => chunk_id, - Err(_) => return StatusCode::TooManyChunks, - }; - brush.state = BrushState::Ready(RunnableBrush { - chunk_id, + instance.compile_result2 = Some(CompileResult { + defs_string: instance.defs.serialize_defs(), + tags_string: instance.defs.serialize_tags(), + chunk, closure_spec, - compile_stats: CompileStats { + stats: CompileStats { ast_size: ast.len(), }, }); - info!("brush compiled into {chunk_id:?}"); + info!("brush code compiled into instance"); StatusCode::Ok } -#[unsafe(no_mangle)] -extern "C" fn haku_pixmap_new(width: u32, height: u32) -> *mut Pixmap { - let ptr = Box::leak(Box::new( - Pixmap::new(width, height).expect("invalid pixmap size"), - )) as *mut _; - debug!("created pixmap with size {width}x{height}: {ptr:?}"); - ptr +unsafe fn compile_result<'a>(instance: *const Instance) -> &'a CompileResult { + let cr = (*instance).compile_result2.as_ref().unwrap(); + cr } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_pixmap_destroy(pixmap: *mut Pixmap) { - debug!("destroying pixmap: {pixmap:?}"); - drop(Box::from_raw(pixmap)) +unsafe extern "C" fn haku_num_diagnostics2(instance: *const Instance) -> u32 { + (*instance).diagnostics2.len() as u32 } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_pixmap_data(pixmap: *mut Pixmap) -> *mut u8 { - let pixmap = &mut *pixmap; - pixmap.pixels_mut().as_mut_ptr() as *mut u8 +unsafe extern "C" fn haku_diagnostic_start2(instance: *const Instance, index: u32) -> u32 { + (*instance).diagnostics2[index as usize].span().start } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_pixmap_clear(pixmap: *mut Pixmap) { - let pixmap = &mut *pixmap; - pixmap.pixels_mut().fill(PremultipliedColorU8::TRANSPARENT); +unsafe extern "C" fn haku_diagnostic_end2(instance: *const Instance, index: u32) -> u32 { + (*instance).diagnostics2[index as usize].span().end } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_begin_brush(instance: *mut Instance, brush: *const Brush) -> StatusCode { - let instance = &mut *instance; - let brush = &*brush; - - let BrushState::Ready(runnable) = &brush.state else { - panic!("brush is not compiled and ready to be used"); - }; - - instance.vm.restore_image(&instance.vm_image); - instance.vm.apply_defs(&instance.defs); - instance.reset_exception(); - instance.trampoline = None; - - let Ok(closure_id) = instance.vm.create_ref(Ref::Closure(Closure::chunk( - runnable.chunk_id, - runnable.closure_spec, - ))) else { - return StatusCode::OutOfRefSlots; - }; - - instance.reset_exception(); - let value = match instance.vm.run(&instance.system, closure_id, &[]) { - Ok(value) => value, - Err(exn) => { - instance.set_exception(exn); - return StatusCode::EvalException; - } - }; - instance.trampoline = Some(Trampoline::new(value)); - - StatusCode::Ok +unsafe extern "C" fn haku_diagnostic_message2(instance: *const Instance, index: u32) -> *const u8 { + (*instance).diagnostics2[index as usize].message().as_ptr() } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_cont_kind(instance: *mut Instance) -> Cont { - let instance = &mut *instance; - instance.trampoline.as_ref().unwrap().cont(&instance.vm) -} - -fn wrap_exception( - instance: &mut Instance, - error_code: StatusCode, - f: impl FnOnce(&mut Instance) -> Result<(), Exception>, -) -> StatusCode { - match f(instance) { - Ok(_) => StatusCode::Ok, - Err(exn) => { - instance.set_exception(exn); - error_code - } - } +unsafe extern "C" fn haku_diagnostic_message_len2(instance: *const Instance, index: u32) -> u32 { + (*instance).diagnostics2[index as usize].message().len() as u32 } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_cont_scribble( - instance: *mut Instance, - pixmap: *mut Pixmap, - translation_x: f32, - translation_y: f32, -) -> StatusCode { - let instance = &mut *instance; - instance.reset_exception(); - - debug!("cont_scribble: pixmap={pixmap:?} translation_x={translation_x:?} translation_y={translation_y:?} trampoline={:?}", instance.trampoline); - - wrap_exception(instance, StatusCode::RenderException, |instance| { - instance.trampoline.as_mut().unwrap().scribble( - &instance.vm, - &mut *pixmap, - Vec2 { - x: translation_x, - y: translation_y, - }, - &RendererLimits { - pixmap_stack_capacity: instance.limits.pixmap_stack_capacity, - transform_stack_capacity: instance.limits.transform_stack_capacity, - }, - ) - }) +unsafe extern "C" fn haku_defs_len2(instance: *const Instance) -> usize { + compile_result(instance).defs_string.len() } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_cont_dotter( - instance: *mut Instance, - from_x: f32, - from_y: f32, - to_x: f32, - to_y: f32, - num: f32, -) -> StatusCode { - let instance = &mut *instance; - instance.reset_exception(); - - debug!( - "cont_dotter: from_x={from_x} from_y={from_y} to_x={to_x} to_y={to_y} trampoline={:?}", - instance.trampoline - ); - - wrap_exception(instance, StatusCode::RenderException, |instance| { - instance.trampoline.as_mut().unwrap().dotter( - &mut instance.vm, - &instance.system, - Vec2::new(from_x, from_y), - Vec2::new(to_x, to_y), - num, - ) - }) +unsafe extern "C" fn haku_defs2(instance: *const Instance) -> *const u8 { + compile_result(instance).defs_string.as_ptr() } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_stat_ast_size(brush: *const Brush) -> usize { - match &(*brush).state { - BrushState::Default => 0, - BrushState::Ready(runnable) => runnable.compile_stats.ast_size, - } +unsafe extern "C" fn haku_tags_len2(instance: *const Instance) -> usize { + compile_result(instance).tags_string.len() } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_stat_num_refs(instance: *const Instance) -> usize { - (*instance).vm.num_refs() +unsafe extern "C" fn haku_tags2(instance: *const Instance) -> *const u8 { + compile_result(instance).tags_string.as_ptr() } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_stat_remaining_fuel(instance: *const Instance) -> usize { - (*instance).vm.remaining_fuel() +unsafe extern "C" fn haku_bytecode_len2(instance: *const Instance) -> usize { + compile_result(instance).chunk.bytecode.len() } #[unsafe(no_mangle)] -unsafe extern "C" fn haku_stat_remaining_memory(instance: *const Instance) -> usize { - (*instance).vm.remaining_memory() +unsafe extern "C" fn haku_bytecode2(instance: *const Instance) -> *const u8 { + compile_result(instance).chunk.bytecode.as_ptr() +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn haku_local_count2(instance: *const Instance) -> u8 { + compile_result(instance).closure_spec.local_count } diff --git a/crates/haku2/build.rs b/crates/haku2/build.rs index 13f7158..b5495f3 100644 --- a/crates/haku2/build.rs +++ b/crates/haku2/build.rs @@ -48,7 +48,7 @@ fn main() -> Result<(), Box> { .arg("--cache-dir") .arg(out_path.join("zig-cache")) .arg("--prefix") - .arg(out_path.join("zig-out").join(target)) + .arg(out_path.join("zig-out")) // Build settings .arg(format!("-Doptimize={optimize}")) .arg(format!("-Dtarget={target}")) @@ -58,7 +58,7 @@ fn main() -> Result<(), Box> { panic!("zig failed to build"); } - println!("cargo::rustc-link-search={out_dir}/zig-out/{target}/lib"); + println!("cargo::rustc-link-search={out_dir}/zig-out/lib"); println!("cargo::rustc-link-lib=haku2"); Ok(()) diff --git a/crates/haku2/src/haku2.zig b/crates/haku2/src/haku2.zig index c4bd960..2b91006 100644 --- a/crates/haku2/src/haku2.zig +++ b/crates/haku2/src/haku2.zig @@ -10,7 +10,7 @@ const value = @import("value.zig"); const Vm = @import("vm.zig"); const log = @import("log.zig"); -const debug_logs = false; +const debug_logs = true; pub const allocator = if (builtin.cpu.arch == .wasm32) @@ -23,6 +23,16 @@ pub const std_options: std.Options = .{ .logFn = log.logFn, }; +// Allocator + +export fn haku2_alloc(size: usize, alignment: usize) ?[*]u8 { + return allocator.rawAlloc(size, mem.Alignment.fromByteUnits(alignment), @returnAddress()); +} + +export fn haku2_free(alloc: [*]u8, size: usize, alignment: usize) void { + allocator.rawFree(alloc[0..size], mem.Alignment.fromByteUnits(alignment), @returnAddress()); +} + // Scratch export fn haku2_scratch_new(max: usize) ?*Scratch { diff --git a/crates/haku2/src/lib.rs b/crates/haku2/src/lib.rs index 7f59511..b37bd72 100644 --- a/crates/haku2/src/lib.rs +++ b/crates/haku2/src/lib.rs @@ -9,6 +9,8 @@ use std::{ use log::trace; +pub static WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/zig-out/bin/haku2.wasm")); + #[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) { diff --git a/crates/haku2/src/vm.zig b/crates/haku2/src/vm.zig index ad0939e..7b294ee 100644 --- a/crates/haku2/src/vm.zig +++ b/crates/haku2/src/vm.zig @@ -60,7 +60,7 @@ pub fn reset(vm: *Vm, fuel: u32) void { } pub fn throw(vm: *Vm, comptime fmt: []const u8, args: anytype) Error { - log.debug("throw: fmt={s}", .{fmt}); + log.info("throw: fmt={s}", .{fmt}); const Args = @TypeOf(args); const max_args_size = @sizeOf(@TypeOf(vm.exception.?.args)); diff --git a/crates/rkgk/src/build.rs b/crates/rkgk/src/build.rs index 7720945..82e0c26 100644 --- a/crates/rkgk/src/build.rs +++ b/crates/rkgk/src/build.rs @@ -37,6 +37,7 @@ pub fn build(paths: &Paths<'_>, config: &BuildConfig) -> eyre::Result<()> { paths.target_dir.join("static/wasm/haku.wasm"), ) .context("cannot copy haku.wasm file")?; + write(paths.target_dir.join("static/wasm/haku2.wasm"), haku2::WASM)?; let import_map = ImportMap::generate("".into(), &config.import_roots); write( diff --git a/crates/rkgk/src/haku.rs b/crates/rkgk/src/haku.rs index 3a3eefd..e5a32ad 100644 --- a/crates/rkgk/src/haku.rs +++ b/crates/rkgk/src/haku.rs @@ -35,8 +35,7 @@ pub struct Limits { pub ref_capacity: usize, pub fuel: usize, pub memory: usize, - pub pixmap_stack_capacity: usize, - pub transform_stack_capacity: usize, + pub render_max_depth: usize, } pub struct Haku { diff --git a/rkgk.toml b/rkgk.toml index 6b5959c..906f663 100644 --- a/rkgk.toml +++ b/rkgk.toml @@ -114,15 +114,5 @@ fuel = 65536 # In particular, large arrays use up this memory - such as list backing arrays. memory = 1048576 -# Capacity of the renderer's pixmap stack. -# The pixmap stack is used for blending layers together within a brush. -# Each (composite)-type scribble requires a single entry on this pixmap stack. -# In the end, this defines how deep compositing operations may nest. -pixmap_stack_capacity = 4 - -# Capacity of the renderer's transformation stack. -# The transformation stack is used for operations on the transform matrix, such as (translate). -# To render each transformed operation, a single entry of the transform stack is used. -# In the end, this defines how deep matrix transform operations may nest. -transform_stack_capacity = 16 - +# Maximum depth the renderer is allowed to recurse to. +render_max_depth = 256 diff --git a/static/haku.js b/static/haku.js index 2a734df..d63f9bc 100644 --- a/static/haku.js +++ b/static/haku.js @@ -1,5 +1,16 @@ let panicImpl; -let logImpl; +let logImpl, log2Impl; +let canvasBeginImpl, + canvasLineImpl, + canvasRectangleImpl, + canvasCircleImpl, + canvasFillImpl, + canvasStrokeImpl; + +function allocCheck(p) { + if (p == 0) throw new Error("out of memory"); + return p; +} function makeLogFunction(level) { return (length, pMessage) => { @@ -7,9 +18,17 @@ function makeLogFunction(level) { }; } -let { instance: hakuInstance, module: hakuModule } = await WebAssembly.instantiateStreaming( - fetch(HAKU_WASM_PATH), - { +function makeLogFunction2(level) { + return (pScope, scopeLen, pMsg, len) => { + log2Impl(level, pScope, scopeLen, pMsg, len); + }; +} + +// NOTE: It may seem tempting to share memory between the two modules, but that's *impossible*. +// This is because Wasm binaries are not position-independent; addresses of the stack and the heap +// are hardcoded. +let [hakuWasm, haku2Wasm] = await Promise.all([ + WebAssembly.instantiateStreaming(fetch(HAKU_WASM_PATH), { env: { panic(length, pMessage) { panicImpl(length, pMessage); @@ -20,17 +39,37 @@ let { instance: hakuInstance, module: hakuModule } = await WebAssembly.instantia warn: makeLogFunction("warn"), error: makeLogFunction("error"), }, - }, -); + }), + WebAssembly.instantiateStreaming(fetch(HAKU2_WASM_PATH), { + env: { + __haku2_log_err: makeLogFunction2("error"), + __haku2_log_warn: makeLogFunction2("warn"), + __haku2_log_info: makeLogFunction2("info"), + __haku2_log_debug: makeLogFunction2("debug"), -let memory = hakuInstance.exports.memory; -let w = hakuInstance.exports; + // 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) => + canvasRectangleImpl(c, x, y, width, height), + __haku2_canvas_circle: (c, x, y, r) => canvasCircleImpl(c, x, y, r), + __haku2_canvas_fill: (c, r, g, b, a) => canvasFillImpl(c, r, g, b, a), + __haku2_canvas_stroke: (c, r, g, b, a, thickness) => + canvasStrokeImpl(c, r, g, b, a, thickness), + }, + }), +]); + +let memory = hakuWasm.instance.exports.memory; +let w = hakuWasm.instance.exports; +let memory2 = haku2Wasm.instance.exports.memory; +let w2 = haku2Wasm.instance.exports; let textEncoder = new TextEncoder(); function allocString(string) { let size = string.length * 3; let align = 1; - let pString = w.haku_alloc(size, align); + let pString = allocCheck(w.haku_alloc(size, align)); let buffer = new Uint8Array(memory.buffer, pString, size); let result = textEncoder.encodeInto(string, buffer); @@ -48,7 +87,7 @@ function freeString(alloc) { } let textDecoder = new TextDecoder(); -function readString(size, pString) { +function readString(memory, size, pString) { let buffer = new Uint8Array(memory.buffer, pString, size); return textDecoder.decode(buffer); } @@ -62,7 +101,36 @@ function readCString(pCString) { } let size = pCursor - pCString; - return readString(size, pCString); + return readString(memory, size, pCString); +} + +function dup1to2(pHaku1, size, align) { + if (size <= 0) { + return { + ptr: 0, + length: 0, + size: 0, + align: 1, + }; + } + + let pHaku2 = allocCheck(w2.haku2_alloc(size, align)); + + let src = new Uint8Array(memory.buffer, pHaku1, size); + let dst = new Uint8Array(memory2.buffer, pHaku2, size); + dst.set(src); + + return { + ptr: pHaku2, + length: size, + size, + align, + }; +} + +function freeString2(alloc) { + if (alloc.ptr == 0) return; + w2.haku2_free(alloc.ptr, alloc.size, alloc.align); } class Panic extends Error { @@ -70,20 +138,34 @@ class Panic extends Error { } panicImpl = (length, pMessage) => { - throw new Panic(readString(length, pMessage)); + throw new Panic(readString(memory, length, pMessage)); }; logImpl = (level, length, pMessage) => { - console[level](readString(length, pMessage)); + console[level]("h1:", readString(memory, length, pMessage)); }; +log2Impl = (level, pScope, scopeLen, pMsg, len) => { + console[level]( + "h2:", + `${readString(memory2, scopeLen, pScope)}: ${readString(memory2, len, pMsg)}`, + ); +}; + +canvasBeginImpl = w.haku_pixmap_begin; +canvasLineImpl = w.haku_pixmap_line; +canvasRectangleImpl = w.haku_pixmap_rectangle; +canvasCircleImpl = w.haku_pixmap_circle; +canvasFillImpl = w.haku_pixmap_fill; +canvasStrokeImpl = w.haku_pixmap_stroke; + w.haku_init_logging(); export class Pixmap { #pPixmap = 0; constructor(width, height) { - this.#pPixmap = w.haku_pixmap_new(width, height); + this.#pPixmap = allocCheck(w.haku_pixmap_new(width, height)); this.width = width; this.height = height; } @@ -113,7 +195,6 @@ export class Pixmap { } } -// NOTE: This must be kept in sync with ContKind on the haku-wasm side. export const ContKind = { Scribble: 0, Dotter: 1, @@ -121,53 +202,65 @@ export const ContKind = { export class Haku { #pInstance = 0; - #pBrush = 0; - #brushCode = null; + + #pLimits2 = 0; + #pScratch2 = 0; + #pVm2 = 0; + + #bytecode2 = null; + #localCount = 0; + + #fuel = 0; + #renderMaxDepth = 0; constructor(limits) { console.groupCollapsed("construct Haku"); + { + let pLimits = allocCheck(w.haku_limits_new()); + for (let name of Object.keys(limits)) { + w[`haku_limits_set_${name}`](pLimits, limits[name]); + } - let pLimits = w.haku_limits_new(); - for (let name of Object.keys(limits)) { - w[`haku_limits_set_${name}`](pLimits, limits[name]); + this.#pInstance = allocCheck(w.haku_instance_new(pLimits)); + + w.haku_limits_destroy(pLimits); } - - this.#pInstance = w.haku_instance_new(pLimits); - this.#pBrush = w.haku_brush_new(); - - w.haku_limits_destroy(pLimits); - console.groupEnd(); + + // NOTE(v2): VM is not constructed until there is a brush ready + this.#pLimits2 = allocCheck(w2.haku2_limits_new()); + w2.haku2_limits_set_stack_capacity(this.#pLimits2, limits.stack_capacity); + w2.haku2_limits_set_call_stack_capacity(this.#pLimits2, limits.call_stack_capacity); + this.#fuel = limits.fuel; + this.#renderMaxDepth = limits.render_max_depth; + + this.#pScratch2 = allocCheck(w2.haku2_scratch_new(limits.memory)); } destroy() { - w.haku_brush_destroy(this.#pBrush); w.haku_instance_destroy(this.#pInstance); + + w2.haku2_scratch_destroy(this.#pScratch2); + w2.haku2_vm_destroy(this.#pVm2); + w2.haku2_limits_destroy(this.#pLimits2); + w2.haku_dealloc(this.#bytecode2.ptr); } setBrush(code) { - w.haku_reset(this.#pInstance); - // NOTE: Brush is invalid at this point, because we reset removes all defs and registered chunks. - - if (this.#brushCode != null) freeString(this.#brushCode); - this.#brushCode = allocString(code); - - let statusCode = w.haku_compile_brush( - this.#pInstance, - this.#pBrush, - this.#brushCode.length, - this.#brushCode.ptr, - ); + let brushCode = allocString(code); + let statusCode = w.haku_compile_brush2(this.#pInstance, brushCode.length, brushCode.ptr); + freeString(brushCode); if (!w.haku_is_ok(statusCode)) { if (w.haku_is_diagnostics_emitted(statusCode)) { let diagnostics = []; - for (let i = 0; i < w.haku_num_diagnostics(this.#pBrush); ++i) { + for (let i = 0; i < w.haku_num_diagnostics2(this.#pInstance); ++i) { diagnostics.push({ - start: w.haku_diagnostic_start(this.#pBrush, i), - end: w.haku_diagnostic_end(this.#pBrush, i), + start: w.haku_diagnostic_start2(this.#pInstance, i), + end: w.haku_diagnostic_end2(this.#pInstance, i), message: readString( - w.haku_diagnostic_message_len(this.#pBrush, i), - w.haku_diagnostic_message(this.#pBrush, i), + memory, + w.haku_diagnostic_message_len2(this.#pInstance, i), + w.haku_diagnostic_message2(this.#pInstance, i), ), }); } @@ -185,51 +278,106 @@ export class Haku { } } + if (this.#pVm2 != 0) w2.haku2_vm_destroy(this.#pVm2); + if (this.#bytecode2 != null) freeString2(this.#bytecode2); + + let pDefsString = dup1to2( + w.haku_defs2(this.#pInstance), + w.haku_defs_len2(this.#pInstance), + 1, + ); + let pTagsString = dup1to2( + w.haku_tags2(this.#pInstance), + w.haku_tags_len2(this.#pInstance), + 1, + ); + let defs = allocCheck( + w2.haku2_defs_parse( + pDefsString, + w.haku_defs_len2(this.#pInstance), + pTagsString, + 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.#bytecode2 = dup1to2( + w.haku_bytecode2(this.#pInstance), + w.haku_bytecode_len2(this.#pInstance), + 1, + ); + this.#localCount = w.haku_local_count2(this.#pInstance); + + w2.haku2_defs_destroy(defs); + freeString2(pDefsString); + freeString2(pTagsString); + return { status: "ok" }; } - #statusCodeToResultObject(statusCode) { - if (!w.haku_is_ok(statusCode)) { - if (w.haku_is_exception(statusCode)) { - return { - status: "error", - errorKind: "exception", - description: readCString(w.haku_status_string(statusCode)), - message: readString( - w.haku_exception_message_len(this.#pInstance), - w.haku_exception_message(this.#pInstance), - ), - }; - } else { - return { - status: "error", - errorKind: "plain", - message: readCString(w.haku_status_string(statusCode)), - }; - } + #exceptionMessage() { + let len = w2.haku2_vm_exception_len(this.#pVm2); + let pExn = allocCheck(w2.haku2_alloc(len, 1)); + w2.haku2_vm_exception_render(this.#pVm2, pExn); + let exn = readString(memory2, len, pExn); + w2.haku2_free(pExn, len, 1); + return exn; + } + + #exceptionResult() { + return { + status: "error", + errorKind: "exception", + description: "Runtime error", + message: this.#exceptionMessage(), + }; + } + + beginBrush() { + if (this.#pVm2 == 0) { + console.warn("VM instance is not available for drawing"); + return; + } + + w2.haku2_vm_reset(this.#pVm2, this.#fuel); + let ok = w2.haku2_vm_run_main( + this.#pVm2, + this.#pScratch2, + this.#bytecode2.ptr, + this.#bytecode2.size, + this.#localCount, + ); + if (!ok) { + return this.#exceptionResult(); + } + + return { status: "ok" }; + } + + expectedContKind() { + if (w2.haku2_vm_is_dotter(this.#pVm2)) return ContKind.Dotter; + else return ContKind.Scribble; + } + + contScribble(pixmap, translationX, translationY) { + w.haku_pixmap_set_translation(pixmap.ptr, translationX, translationY); + let ok = w2.haku2_render(this.#pVm2, pixmap.ptr, this.#renderMaxDepth); + if (!ok) { + return this.#exceptionResult(); } else { return { status: "ok" }; } } - beginBrush() { - return this.#statusCodeToResultObject(w.haku_begin_brush(this.#pInstance, this.#pBrush)); - } - - expectedContKind() { - return w.haku_cont_kind(this.#pInstance); - } - - contScribble(pixmap, translationX, translationY) { - return this.#statusCodeToResultObject( - w.haku_cont_scribble(this.#pInstance, pixmap.ptr, translationX, translationY), - ); - } - contDotter({ fromX, fromY, toX, toY, num }) { - return this.#statusCodeToResultObject( - w.haku_cont_dotter(this.#pInstance, fromX, fromY, toX, toY, num), - ); + let ok = w2.haku2_vm_run_dotter(this.#pVm2, this.#pScratch2, fromX, fromY, toX, toY, num); + if (!ok) { + return this.#exceptionResult(); + } else { + return { status: "ok" }; + } } async evalBrush(options) { @@ -239,7 +387,7 @@ export class Haku { result = this.beginBrush(); if (result.status != "ok") return result; - while (this.expectedContKind() != ContKind.Invalid) { + while (true) { switch (this.expectedContKind()) { case ContKind.Scribble: result = await runScribble((pixmap, translationX, translationY) => { @@ -254,27 +402,21 @@ export class Haku { break; } } - - return { status: "ok" }; } get astSize() { - if (this.#pBrush != 0) { - return w.haku_stat_ast_size(this.#pBrush); - } else { - return 0; - } + return 0; // TODO } get numRefs() { - return w.haku_stat_num_refs(this.#pInstance); + return 0; // TODO } get remainingFuel() { - return w.haku_stat_remaining_fuel(this.#pInstance); + return 0; // TODO } get remainingMemory() { - return w.haku_stat_remaining_memory(this.#pInstance); + return 0; // TODO } } diff --git a/template/index.hbs.html b/template/index.hbs.html index dbac156..3d455d8 100644 --- a/template/index.hbs.html +++ b/template/index.hbs.html @@ -16,6 +16,7 @@