YieldFrame: pause wasm environment till end of frame

This commit is contained in:
Pyrex 2024-04-22 12:21:25 -07:00
parent 13b61a6def
commit 03a8ae9a24
8 changed files with 160 additions and 22 deletions

View File

@ -7,8 +7,17 @@ use crate::go_builder::GoBuilder;
mod go_builder; mod go_builder;
fn main() -> VResult<()> { fn main() -> VResult<()> {
let builder = GoBuilder::new(&PathBuf::from("example_project"))?; let builder = GoBuilder::new(
&PathBuf::from("example_project"))?;
let wasm = builder.build()?; let wasm = builder.build()?;
player::run_entry_point(&wasm)?; let mut executor = player::Executor::new(&wasm)?;
while executor.is_running() {
println!("update started");
executor.update();
println!("update completed");
executor.get_error()?;
}
Ok(()) Ok(())
} }

View File

@ -0,0 +1,31 @@
use std::fmt::Display;
use viperid::VResult;
use wasmi::{core::{HostError, Trap}, Caller, Linker};
use crate::executor::ExecutorState;
pub(crate) fn integrate(linker: &mut Linker<ExecutorState>) -> VResult<()> {
linker.func_wrap("viperid", "YieldFrame", yield_frame)?;
Ok(())
}
fn yield_frame(caller: Caller<ExecutorState>) -> Result<(), Trap> {
Err(Trap::from(YieldFrame {}))
}
#[derive(Debug)]
pub(crate) struct YieldFrame {
}
impl Display for YieldFrame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("<device wants to display a frame>")
}
}
impl HostError for YieldFrame {
}

View File

@ -1,9 +1,16 @@
use std::{fmt::Display, mem};
use viperid::VResult; use viperid::VResult;
use wasmi::{Engine, Linker, Module, Store}; use wasmi::{core::{HostError, Trap}, Engine, Linker, Module, Store, TypedFunc, TypedResumableCall, TypedResumableInvocation};
use crate::wasi::{self, StockWasi}; use crate::{engine_api::{self, YieldFrame}, wasi::{self, StockWasi}};
struct ExecutorState { pub struct Executor {
store: Store<ExecutorState>,
resumable: Resumable
}
pub(crate) struct ExecutorState {
wasi: wasi::StockWasi wasi: wasi::StockWasi
} }
@ -16,22 +23,105 @@ impl ExecutorState {
} }
} }
pub fn run_entry_point(wasm: &[u8]) -> VResult<()> {
let engine = Engine::default();
let module = Module::new(&engine, wasm)?; impl Executor {
pub fn new(wasm: &[u8]) -> VResult<Self> {
let engine = Engine::default();
let mut store = Store::new(&engine, ExecutorState::new()); let module = Module::new(&engine, wasm)?;
let mut linker = <Linker<ExecutorState>>::new(&engine); let mut store = Store::new(&engine, ExecutorState::new());
wasi::integrate(&mut linker, |hs| &mut hs.wasi)?;
let instance = linker let mut linker = <Linker<ExecutorState>>::new(&engine);
.instantiate(&mut store, &module)? wasi::integrate(&mut linker, |hs| &mut hs.wasi)?;
.start(&mut store)?; engine_api::integrate(&mut linker);
let main = instance.get_typed_func::<(), ()>(&store, "_start")?; let instance = linker
main.call(&mut store, ())?; .instantiate(&mut store, &module)?
.start(&mut store)?;
Ok(()) let main = instance.get_typed_func::<(), ()>(&store, "_start")?;
Ok(Executor {
store,
resumable: Resumable::Unstarted(main)
})
}
pub fn is_running(&self) -> bool {
self.resumable.is_running()
}
pub fn get_error(&self) -> VResult<()> {
if let Err(e) = self.resumable.get_error() {
anyhow::bail!("executor error: {}", e);
}
Ok(())
}
pub fn update(&mut self) {
let mut tmp = Resumable::Failed(wasmi::Error::Trap(Trap::new("unexpected crash during update")));
mem::swap(&mut tmp, &mut self.resumable);
let mut tmp2 = tmp.resume(&mut self.store);
mem::swap(&mut tmp2, &mut self.resumable);
}
}
enum Resumable {
Unstarted(TypedFunc<(), ()>),
Paused(TypedResumableInvocation<()>),
Failed(wasmi::Error),
Completed(())
}
impl Resumable {
pub fn resume(self, store: &mut Store<ExecutorState>) -> Resumable {
let result: Result<TypedResumableCall<()>, wasmi::Error> = match self {
Resumable::Failed(f) => return Resumable::Failed(f),
Resumable::Completed(c) => return Resumable::Completed(c),
Resumable::Unstarted(u) => {
match u.call_resumable(store, ()) {
Ok(o) => Ok(o),
Err(e) => Err(wasmi::Error::Trap(e))
}
}
Resumable::Paused(p) => {
let trap = p.host_error();
if let Some(_) = trap.downcast_ref::<YieldFrame>() {
p.resume(store, &[])
} else {
Err(wasmi::Error::Trap(Trap::new(format!("unexpected host error: {}", trap))))
}
}
};
match result {
Ok(o) => match o {
TypedResumableCall::Finished(f) => Resumable::Completed(f),
TypedResumableCall::Resumable(p) => Resumable::Paused(p)
}
Err(e) => Resumable::Failed(e),
}
}
fn is_running(&self) -> bool {
match self {
Resumable::Unstarted(_) => true,
Resumable::Paused(_) => true,
Resumable::Failed(_) => false,
Resumable::Completed(_) => false
}
}
fn get_error(&self) -> Result<(), &wasmi::Error> {
match self {
Resumable::Unstarted(_) => (),
Resumable::Paused(_) => (),
Resumable::Failed(e) => return Err(e),
Resumable::Completed(_) => ()
};
Ok(())
}
} }

View File

@ -1,4 +1,5 @@
mod engine_api;
mod executor; mod executor;
mod wasi; mod wasi;
pub use executor::run_entry_point; pub use executor::Executor;

View File

@ -0,0 +1 @@
build/

Binary file not shown.

1
example_project/go.work Normal file
View File

@ -0,0 +1 @@
go 1.21.5

View File

@ -2,7 +2,6 @@ package main
import ( import (
"fmt" "fmt"
"runtime/debug"
) )
func main() { func main() {
@ -10,7 +9,13 @@ func main() {
for { for {
fmt.Printf("Hello! %d", count) fmt.Printf("Hello! %d", count)
count++ count++
debug.PrintStack() /*
panic("pizza!") debug.PrintStack()
*/
YieldFrame()
} }
} }
//go:wasmimport viperid YieldFrame
//go:noescape
func YieldFrame()