rkgk/crates/haku-wasm/src/lib.rs

577 lines
16 KiB
Rust
Raw Normal View History

#![cfg_attr(not(feature = "std"), no_std)]
2024-08-10 23:10:03 +02:00
extern crate alloc;
use core::{alloc::Layout, slice};
2024-08-10 23:10:03 +02:00
use alloc::{boxed::Box, vec::Vec};
use haku::{
ast::Ast,
bytecode::{Chunk, Defs, DefsImage, DefsLimits},
compiler::{compile_expr, ClosureSpec, CompileError, Compiler, Source},
diagnostic::Diagnostic,
lexer::{lex, Lexer},
parser::{self, IntoAstError, Parser},
2024-08-10 23:13:20 +02:00
render::{
tiny_skia::{Pixmap, PremultipliedColorU8},
RendererLimits,
2024-08-10 23:13:20 +02:00
},
source::SourceCode,
2024-08-10 23:10:03 +02:00
system::{ChunkId, System, SystemImage},
token::Lexis,
trampoline::{Cont, Trampoline},
value::{Closure, Ref, Vec2},
2024-08-10 23:10:03 +02:00
vm::{Exception, Vm, VmImage, VmLimits},
};
2024-08-15 20:01:23 +02:00
use log::{debug, info};
2024-08-10 23:10:03 +02:00
pub mod logging;
#[cfg(not(feature = "std"))]
2024-08-10 23:10:03 +02:00
mod panicking;
#[global_allocator]
static ALLOCATOR: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc;
#[no_mangle]
unsafe extern "C" fn haku_alloc(size: usize, align: usize) -> *mut u8 {
alloc::alloc::alloc(Layout::from_size_align(size, align).unwrap())
}
#[no_mangle]
unsafe extern "C" fn haku_free(ptr: *mut u8, size: usize, align: usize) {
alloc::alloc::dealloc(ptr, Layout::from_size_align(size, align).unwrap())
}
#[derive(Debug, Clone, Copy)]
struct Limits {
max_source_code_len: usize,
2024-08-10 23:10:03 +02:00
max_chunks: usize,
max_defs: usize,
max_tags: usize,
max_tokens: usize,
max_parser_events: usize,
2024-08-10 23:10:03 +02:00
ast_capacity: usize,
chunk_capacity: usize,
stack_capacity: usize,
call_stack_capacity: usize,
ref_capacity: usize,
fuel: usize,
2024-08-20 23:00:39 +02:00
memory: usize,
2024-08-10 23:13:20 +02:00
pixmap_stack_capacity: usize,
2024-08-10 23:10:03 +02:00
transform_stack_capacity: usize,
}
impl Default for Limits {
fn default() -> Self {
Self {
max_source_code_len: 65536,
2024-08-10 23:10:03 +02:00
max_chunks: 2,
max_defs: 256,
max_tags: 256,
max_tokens: 1024,
max_parser_events: 1024,
2024-08-10 23:10:03 +02:00
ast_capacity: 1024,
chunk_capacity: 65536,
stack_capacity: 1024,
call_stack_capacity: 256,
ref_capacity: 2048,
fuel: 65536,
2024-08-20 23:00:39 +02:00
memory: 1024 * 1024,
2024-08-10 23:13:20 +02:00
pixmap_stack_capacity: 4,
2024-08-10 23:10:03 +02:00
transform_stack_capacity: 16,
}
}
}
2024-08-15 20:01:23 +02:00
#[no_mangle]
extern "C" fn haku_limits_new() -> *mut Limits {
let ptr = Box::leak(Box::new(Limits::default())) as *mut _;
debug!("created limits: {ptr:?}");
ptr
2024-08-15 20:01:23 +02:00
}
#[no_mangle]
unsafe extern "C" fn haku_limits_destroy(limits: *mut Limits) {
debug!("destroying limits: {limits:?}");
2024-08-15 20:01:23 +02:00
drop(Box::from_raw(limits))
}
macro_rules! limit_setter {
($name:tt) => {
paste::paste! {
#[no_mangle]
unsafe extern "C" fn [<haku_limits_set_ $name>](limits: *mut Limits, value: usize) {
debug!("set limit {} = {value}", stringify!($name));
let limits = &mut *limits;
limits.$name = value;
}
}
};
}
limit_setter!(max_source_code_len);
2024-08-15 20:01:23 +02:00
limit_setter!(max_chunks);
limit_setter!(max_defs);
limit_setter!(max_tags);
limit_setter!(max_tokens);
limit_setter!(max_parser_events);
2024-08-15 20:01:23 +02:00
limit_setter!(ast_capacity);
limit_setter!(chunk_capacity);
limit_setter!(stack_capacity);
limit_setter!(call_stack_capacity);
limit_setter!(ref_capacity);
limit_setter!(fuel);
2024-08-20 23:00:39 +02:00
limit_setter!(memory);
2024-08-15 20:01:23 +02:00
limit_setter!(pixmap_stack_capacity);
limit_setter!(transform_stack_capacity);
2024-08-10 23:10:03 +02:00
#[derive(Debug, Clone)]
struct Instance {
limits: Limits,
system: System,
system_image: SystemImage,
defs: Defs,
defs_image: DefsImage,
vm: Vm,
vm_image: VmImage,
2024-08-15 20:01:23 +02:00
trampoline: Option<Trampoline>,
2024-08-10 23:10:03 +02:00
exception: Option<Exception>,
}
impl Instance {
fn set_exception(&mut self, exn: Exception) {
debug!("setting exception = {exn:?}");
self.exception = Some(exn);
}
fn reset_exception(&mut self) {
debug!("resetting exception");
self.exception = None;
}
}
2024-08-10 23:10:03 +02:00
#[no_mangle]
2024-08-15 20:01:23 +02:00
unsafe extern "C" fn haku_instance_new(limits: *const Limits) -> *mut Instance {
let limits = *limits;
debug!("creating new instance with limits: {limits:?}");
2024-08-10 23:10:03 +02:00
let system = System::new(limits.max_chunks);
let defs = Defs::new(&DefsLimits {
max_defs: limits.max_defs,
max_tags: limits.max_tags,
});
2024-08-10 23:10:03 +02:00
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,
2024-08-20 23:00:39 +02:00
memory: limits.memory,
2024-08-10 23:10:03 +02:00
},
);
let system_image = system.image();
let defs_image = defs.image();
let vm_image = vm.image();
let instance = Box::new(Instance {
limits,
system,
system_image,
defs,
defs_image,
vm,
vm_image,
trampoline: None,
2024-08-10 23:10:03 +02:00
exception: None,
});
let ptr = Box::leak(instance) as *mut _;
debug!("created instance: {ptr:?}");
ptr
2024-08-10 23:10:03 +02:00
}
#[no_mangle]
unsafe extern "C" fn haku_instance_destroy(instance: *mut Instance) {
debug!("destroying instance: {instance:?}");
2024-08-10 23:10:03 +02:00
drop(Box::from_raw(instance));
}
2024-08-10 23:13:20 +02:00
#[no_mangle]
unsafe extern "C" fn haku_reset(instance: *mut Instance) {
debug!("resetting instance: {instance:?}");
2024-08-10 23:13:20 +02:00
let instance = &mut *instance;
instance.system.restore_image(&instance.system_image);
instance.defs.restore_image(&instance.defs_image);
}
2024-08-10 23:10:03 +02:00
#[no_mangle]
unsafe extern "C" fn haku_has_exception(instance: *mut Instance) -> bool {
(*instance).exception.is_some()
}
#[no_mangle]
unsafe extern "C" fn haku_exception_message(instance: *const Instance) -> *const u8 {
(*instance).exception.as_ref().unwrap().message.as_ptr()
}
#[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 {
Ok,
SourceCodeTooLong,
TooManyTokens,
TooManyAstNodes,
TooManyParserEvents,
ParserUnbalancedEvents,
2024-08-10 23:10:03 +02:00
ChunkTooBig,
DiagnosticsEmitted,
TooManyChunks,
OutOfRefSlots,
EvalException,
RenderException,
}
#[no_mangle]
extern "C" fn haku_is_ok(code: StatusCode) -> bool {
code == StatusCode::Ok
}
2024-08-10 23:13:20 +02:00
#[no_mangle]
extern "C" fn haku_is_diagnostics_emitted(code: StatusCode) -> bool {
code == StatusCode::DiagnosticsEmitted
}
#[no_mangle]
extern "C" fn haku_is_exception(code: StatusCode) -> bool {
matches!(
code,
StatusCode::EvalException | StatusCode::RenderException
)
}
2024-08-10 23:10:03 +02:00
#[no_mangle]
extern "C" fn haku_status_string(code: StatusCode) -> *const i8 {
match code {
StatusCode::Ok => c"ok",
StatusCode::SourceCodeTooLong => c"source code is too long",
StatusCode::TooManyTokens => c"source code has too many tokens",
StatusCode::TooManyAstNodes => c"source code has too many AST nodes",
StatusCode::TooManyParserEvents => c"source code has too many parser events",
StatusCode::ParserUnbalancedEvents => c"parser produced unbalanced events",
2024-08-10 23:10:03 +02:00
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, Default)]
enum BrushState {
#[default]
Default,
Ready(ChunkId, ClosureSpec),
2024-08-10 23:10:03 +02:00
}
#[derive(Debug, Default)]
struct Brush {
diagnostics: Vec<Diagnostic>,
state: BrushState,
}
#[no_mangle]
extern "C" fn haku_brush_new() -> *mut Brush {
let ptr = Box::leak(Box::new(Brush::default())) as *mut _;
debug!("created brush: {ptr:?}");
ptr
2024-08-10 23:10:03 +02:00
}
#[no_mangle]
unsafe extern "C" fn haku_brush_destroy(brush: *mut Brush) {
debug!("destroying brush: {brush:?}");
2024-08-10 23:10:03 +02:00
drop(Box::from_raw(brush))
}
#[no_mangle]
unsafe extern "C" fn haku_num_diagnostics(brush: *const Brush) -> u32 {
(*brush).diagnostics.len() as u32
}
#[no_mangle]
unsafe extern "C" fn haku_diagnostic_start(brush: *const Brush, index: u32) -> u32 {
(*brush).diagnostics[index as usize].span().start
2024-08-10 23:10:03 +02:00
}
#[no_mangle]
unsafe extern "C" fn haku_diagnostic_end(brush: *const Brush, index: u32) -> u32 {
(*brush).diagnostics[index as usize].span().end
2024-08-10 23:10:03 +02:00
}
#[no_mangle]
unsafe extern "C" fn haku_diagnostic_message(brush: *const Brush, index: u32) -> *const u8 {
(*brush).diagnostics[index as usize].message().as_ptr()
2024-08-10 23:10:03 +02:00
}
#[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
2024-08-10 23:10:03 +02:00
}
#[no_mangle]
unsafe extern "C" fn haku_compile_brush(
instance: *mut Instance,
out_brush: *mut Brush,
code_len: u32,
code: *const u8,
) -> StatusCode {
info!("compiling brush");
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");
let Some(code) = SourceCode::limited_len(code, instance.limits.max_source_code_len as u32)
else {
return StatusCode::SourceCodeTooLong;
};
2024-08-10 23:10:03 +02:00
debug!("compiling: lexing");
let mut lexer = Lexer::new(Lexis::new(instance.limits.max_tokens), code);
if lex(&mut lexer).is_err() {
info!("compiling failed: too many tokens");
return StatusCode::TooManyTokens;
};
debug!(
"compiling: lexed successfully to {} tokens",
lexer.lexis.len()
);
debug!("compiling: parsing");
let mut ast = Ast::new(instance.limits.ast_capacity);
let mut parser = Parser::new(
&lexer.lexis,
&haku::parser::ParserLimits {
max_events: instance.limits.max_parser_events,
},
);
parser::toplevel(&mut parser);
let (root, mut parser_diagnostics) = match parser.into_ast(&mut ast) {
Ok((r, d)) => (r, d),
Err(IntoAstError::NodeAlloc(_)) => {
info!("compiling failed: too many AST nodes");
return StatusCode::TooManyAstNodes;
}
Err(IntoAstError::TooManyEvents) => {
info!("compiling failed: too many parser events");
return StatusCode::TooManyParserEvents;
}
Err(IntoAstError::UnbalancedEvents) => {
info!("compiling failed: parser produced unbalanced events");
return StatusCode::ParserUnbalancedEvents;
}
};
2024-08-10 23:10:03 +02:00
debug!(
"compiling: parsed successfully into {} AST nodes",
ast.len()
);
2024-08-10 23:10:03 +02:00
let src = Source {
code,
ast: &ast,
system: &instance.system,
};
let mut chunk = Chunk::new(instance.limits.chunk_capacity).unwrap();
let mut compiler = Compiler::new(&mut instance.defs, &mut chunk);
if let Err(error) = compile_expr(&mut compiler, &src, root) {
match error {
CompileError::Emit => {
info!("compiling failed: chunk overflowed while emitting code");
return StatusCode::ChunkTooBig;
}
2024-08-10 23:10:03 +02:00
}
}
let closure_spec = compiler.closure_spec();
2024-08-10 23:10:03 +02:00
let mut diagnostics = lexer.diagnostics;
diagnostics.append(&mut parser_diagnostics);
diagnostics.append(&mut compiler.diagnostics);
if !diagnostics.is_empty() {
brush.diagnostics = diagnostics;
debug!("compiling failed: diagnostics were emitted");
2024-08-10 23:10:03 +02:00
return StatusCode::DiagnosticsEmitted;
}
debug!(
"compiling: chunk has {} bytes of bytecode",
chunk.bytecode.len()
);
debug!("compiling: {closure_spec:?}");
2024-08-10 23:10:03 +02:00
let chunk_id = match instance.system.add_chunk(chunk) {
Ok(chunk_id) => chunk_id,
Err(_) => return StatusCode::TooManyChunks,
};
brush.state = BrushState::Ready(chunk_id, closure_spec);
2024-08-10 23:10:03 +02:00
info!("brush compiled into {chunk_id:?}");
StatusCode::Ok
}
#[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
2024-08-10 23:10:03 +02:00
}
#[no_mangle]
unsafe extern "C" fn haku_pixmap_destroy(pixmap: *mut Pixmap) {
debug!("destroying pixmap: {pixmap:?}");
2024-08-10 23:13:20 +02:00
drop(Box::from_raw(pixmap))
2024-08-10 23:10:03 +02:00
}
#[no_mangle]
unsafe extern "C" fn haku_pixmap_data(pixmap: *mut Pixmap) -> *mut u8 {
let pixmap = &mut *pixmap;
2024-08-10 23:13:20 +02:00
pixmap.pixels_mut().as_mut_ptr() as *mut u8
}
2024-08-10 23:10:03 +02:00
2024-08-10 23:13:20 +02:00
#[no_mangle]
unsafe extern "C" fn haku_pixmap_clear(pixmap: *mut Pixmap) {
let pixmap = &mut *pixmap;
2024-08-10 23:13:20 +02:00
pixmap.pixels_mut().fill(PremultipliedColorU8::TRANSPARENT);
2024-08-10 23:10:03 +02:00
}
#[no_mangle]
unsafe extern "C" fn haku_begin_brush(instance: *mut Instance, brush: *const Brush) -> StatusCode {
2024-08-10 23:10:03 +02:00
let instance = &mut *instance;
let brush = &*brush;
let BrushState::Ready(chunk_id, closure_spec) = brush.state else {
2024-08-10 23:10:03 +02:00
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(chunk_id, closure_spec)))
else {
2024-08-10 23:10:03 +02:00
return StatusCode::OutOfRefSlots;
};
instance.reset_exception();
let value = match instance.vm.run(&instance.system, closure_id, &[]) {
2024-08-10 23:10:03 +02:00
Ok(value) => value,
Err(exn) => {
instance.set_exception(exn);
2024-08-10 23:10:03 +02:00
return StatusCode::EvalException;
}
};
instance.trampoline = Some(Trampoline::new(value));
2024-08-10 23:10:03 +02:00
2024-08-15 20:01:23 +02:00
StatusCode::Ok
}
2024-08-10 23:13:20 +02:00
2024-08-15 20:01:23 +02:00
#[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
}
}
}
#[no_mangle]
unsafe extern "C" fn haku_cont_scribble(
2024-08-15 20:01:23 +02:00
instance: *mut Instance,
pixmap: *mut Pixmap,
2024-08-15 20:01:23 +02:00
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,
},
)
})
}
2024-09-01 18:54:38 +02:00
#[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();
2024-08-10 23:13:20 +02:00
debug!(
"cont_dotter: from_x={from_x} from_y={from_y} to_x={to_x} to_y={to_y} trampoline={:?}",
instance.trampoline
2024-08-15 20:01:23 +02:00
);
2024-08-10 23:10:03 +02:00
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,
)
})
2024-08-10 23:10:03 +02:00
}