hotwire haku2 into rkgk

really a bodge job right now and it crashes but it's a start
This commit is contained in:
りき萌 2025-06-04 00:28:21 +02:00
parent 550227da34
commit 5de4f9d7c6
9 changed files with 178 additions and 71 deletions

View file

@ -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(.{

View file

@ -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;
}

View file

@ -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<u8>,
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<u8>, 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<VmC>,
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<Exception> {
// 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<VmC>,
}
impl Drop for VmInner {
fn drop(&mut self) {
// SAFETY: The pointer passed is non-null.
unsafe {

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 {
const val = try vm.pop();
const val = vm.stack[vm.stack_top - 1];
try renderRec(vm, canvas, val, 0, max_depth);
}

View file

@ -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", .{});