wasm3 / wasm3-rs

Rust wrapper for Wasm3, the fastest WebAssembly interpreter
MIT License
155 stars 43 forks source link

Closure with arguments #8

Closed ericflo closed 4 years ago

ericflo commented 4 years ago

The ability to link a closure is super useful, but I'm running into trouble specifying the closure type signature when linking a closure with arguments. For example, I want to register a function which takes two i32s and returns nothing. The problem is that the link_closure closure signature requires Sized, but boxing the closure doesn't seem to work either. Attached below is an example of what I've been trying (this doesn't compile because it needs Sized). Any clarification on how these closure args should be specified would be much appreciated - thanks!

use std::sync::{Arc, Mutex};

use anyhow::{Result};
use wasm3::{Module, Runtime};

use super::interop;

type LogDebugClosureType = dyn FnMut(i32, i32) -> () + 'static;

pub fn link_closures(rt_mtx: Arc<Mutex<Runtime>>, module: &mut Module) -> Result<()> {
    module.link_closure::<(i32, i32), (), LogDebugClosureType>("env", "Engine_Log_Debug", make_log_debug(rt_mtx));
    Ok(())
}

fn make_log_debug(rt_mtx: Arc<Mutex<Runtime>>) -> impl FnMut(i32, i32) -> () + 'static {
    move |ptr: i32, len: i32| -> () {
        let line = match interop::read(rt_mtx.clone(), ptr, len) {
            Ok(s) => s,
            Err(err) => {
                error!("Got error reading string: {:?}", err);
                return ()
            },
        };
        info!("DEBUG: {}", line);
    }
}
Veykril commented 4 years ago

The closure is a bit more annoying to use, as it takes its arguments as a tuple atm due to limitations im facing( I might be able to solve this by rewriting the type trait things, but im not sure yet). So atm you have to take the args as a tuple in your closure which would turn your type def into(if im not mistaking the problem at hand right now):

type LogDebugClosureType = dyn FnMut((i32, i32)) -> () + 'static;
// on the most recent commit it would look like the following:
type LogDebugClosureType = dyn for<'cc> FnMut(&'cc wasm3::CallContext, (i32, i32)) -> () + 'static;
// as im currently experimenting with some things

So I believe your desired code should be something like this:


use std::sync::{Arc, Mutex};

use anyhow::{Result};
use wasm3::{Module, Runtime};

use super::interop;

type LogDebugClosureType = dyn FnMut((i32, i32)) -> () + 'static;

pub fn link_closures(rt_mtx: Arc<Mutex<Runtime>>, module: &mut Module) -> Result<()> {
    module.link_closure::<(i32, i32), (), LogDebugClosureType>("env", "Engine_Log_Debug", make_log_debug(rt_mtx));
    Ok(())
}

fn make_log_debug(rt_mtx: Arc<Mutex<Runtime>>) -> impl FnMut((i32, i32)) -> () + 'static {
    move |(ptr: i32, len: i32)| -> () {
        let line = match interop::read(rt_mtx.clone(), ptr, len) {
            Ok(s) => s,
            Err(err) => {
                error!("Got error reading string: {:?}", err);
                return ()
            },
        };
        info!("DEBUG: {}", line);
    }
}```
ericflo commented 4 years ago

This makes a ton of sense, I had completely missed the tuple arguments stuff! For whatever reason that tuple destructuring syntax didn't work for me, and I did need to box it in the end:

use std::sync::{Arc, Mutex};

use anyhow::{Result};
use wasm3::{Module, Runtime};

use super::interop;

type LogDebugClosureType = dyn FnMut((i32, i32)) -> () + 'static;

pub fn link_closures(rt_mtx: Arc<Mutex<Runtime>>, module: &mut Module) -> Result<()> {
    module.link_closure::<(i32, i32), (), Box<LogDebugClosureType>>("env", "Engine_Log_Debug", Box::new(make_log_debug(rt_mtx)));
    Ok(())
}

fn make_log_debug(rt_mtx: Arc<Mutex<Runtime>>) -> impl FnMut((i32, i32)) -> () + 'static {
    move |args: (i32, i32)| -> () {
        let ptr = args.0;
        let len = args.1;
        let line = match interop::read(rt_mtx.clone(), ptr, len) {
            Ok(s) => s,
            Err(err) => {
                error!("Got error reading string: {:?}", err);
                return ()
            },
        };
        info!("DEBUG: {}", line);
    }
}

Closing this now since what I want to do is completely achievable - thanks again! That CallContext stuff looks useful too, rather than having to pass around the runtime.

Veykril commented 4 years ago

Ah yes I forgot to add the boxing back, as you are using a trait object which is unsized you have to box it to make it sized again. Ye the idea of CallContext is to give you access to some runtime things but i havent thought too much about what it should do in the end.