Wasmer: Yielding from host calls

Created on 9 Jan 2020  ·  10Comments  ·  Source: wasmerio/wasmer

I'm currently embedding wasmer into a Rust project. So far I'm really happy with it and I made great progress.

One feature I need though is being able to call from WASM back to Rust and then suspend the executing of WASM until some IO finishes. I'm basically trying to embed wasmer into an async/await environment. From the perspective of wasm it would be a blocking call (runtime suspended). Lucet exposed an API to do this, but I couldn't find anything similar in wasmer.

What would be the best approach to implement something like this? I would also be happy to contribute some code if someone pointed me in the right direction. Thanks!

ℹ️ help wanted ❓ question

Most helpful comment

That would be a great addition. We would love to support yielding from host calls.

I think first, we need to figure out a good API to use it (we can use this issue to make proposals) and then just create a PR to implement it.

There are a few ways we can get it working with the following green-threads/fibers approaches:

After reviewing Lucet API and all the different yielding libraries implementations, it seems the simplest way to achieve it is via async generators (genawaiter), as it will support any platform and since it relies on native async/await and it's implementation is close to zero-cost.

Here's an example API that I have in mind. Thoughts? @bkolobara @MarkMcCaskey

pub enum Factorial {
    Multiply(u64, u64),
    Result(u64),
}

#![wasmer_generator]
pub unsafe extern "C" fn factorial(
    &mut vmctx,
    n: u64,
) -> u64 {
    let result = if n <= 1 {
        1
    } else {
        let n_rec = factorial(vmctx, n - 1);
        vmctx.yield(Factorial::Multiply(n, n_rec))
    };
    vmctx.yield(Factorial::Result(result))
    result
}

let import_object = imports! {
  factorial => factorial
};

// Run the Wasm
let instance = instantiate(WASM, &import_object)?;

let result = instance
        .dyn_func("run")?
        .call(&[Value::I32(42)])?;

let mut factorials = vec![];

while let Yield(val) = result.resume() {
    match k {
        Factorial::Multiply(n, n_rec) => {
            // guest wants us to multiply for it
            res = inst.resume_with(n * n_rec);
        }
        Factorial::Result(n) => {
            // guest is returning an answer
            factorials.push(*n);
            res = inst.resume();
        }
    }
}

All 10 comments

That would be a great addition. We would love to support yielding from host calls.

I think first, we need to figure out a good API to use it (we can use this issue to make proposals) and then just create a PR to implement it.

There are a few ways we can get it working with the following green-threads/fibers approaches:

After reviewing Lucet API and all the different yielding libraries implementations, it seems the simplest way to achieve it is via async generators (genawaiter), as it will support any platform and since it relies on native async/await and it's implementation is close to zero-cost.

Here's an example API that I have in mind. Thoughts? @bkolobara @MarkMcCaskey

pub enum Factorial {
    Multiply(u64, u64),
    Result(u64),
}

#![wasmer_generator]
pub unsafe extern "C" fn factorial(
    &mut vmctx,
    n: u64,
) -> u64 {
    let result = if n <= 1 {
        1
    } else {
        let n_rec = factorial(vmctx, n - 1);
        vmctx.yield(Factorial::Multiply(n, n_rec))
    };
    vmctx.yield(Factorial::Result(result))
    result
}

let import_object = imports! {
  factorial => factorial
};

// Run the Wasm
let instance = instantiate(WASM, &import_object)?;

let result = instance
        .dyn_func("run")?
        .call(&[Value::I32(42)])?;

let mut factorials = vec![];

while let Yield(val) = result.resume() {
    match k {
        Factorial::Multiply(n, n_rec) => {
            // guest wants us to multiply for it
            res = inst.resume_with(n * n_rec);
        }
        Factorial::Result(n) => {
            // guest is returning an answer
            factorials.push(*n);
            res = inst.resume();
        }
    }
}

So I haven't had time to really dig into async/await in Rust yet but here are my initial thoughts,

  • I think we don't need the unsafe extern "C" part in Wasmer due to the way our imports work
  • if we're using procedural macros then these don't have to be methods on Ctx, we can probably just use the yield keyword directly. Alternatively, we can do this without macros.
  • we'd be updating the return result of every function with an API like this, which seems hard to avoid in general (though probably possible with a different API, i.e. have a separate case for when no imports have this) just because we can't know the full CFG of the program in general before executing it.
  • We should consider integrating with the standard async/await and Future trait now that it's stable (I haven't looked at Lucet's implementation in detail, perhaps they're doing this as well); though admittedly I probably don't understand the proposed feature enough to have a good sense of this
  • The way I understand how green-thread like things are implemented would make something like exceptions potentially very complicated when talking about unwinding through host and guest functions. Though perhaps this a larger architectural thing, like maybe we need to virtualize all our stacks and have Wasmer manage everything itself (I think we'll end up here eventually if we want to offer fine-grained control over our system)

Any update on this?
It looks like a great addition indeed.

@satrobit After a few attempts, I did not manage to make it work with the current wasmer architecture. I ended up writing my own WASM runtime with virtual stacks (as @MarkMcCaskey suggested in his comment), that's also how Lucet does it.

I couldn't come up with a proof of concept without virtual stacks. This would probably be a necessary addition to wasmer, before yielding becomes possible

On a side note, implementing my own (pretty limited) async wasm runtime, using Cranelift + Tokio.rs + Lucet inspired virtual stacks, was not as difficult as I anticipated it to be. It could be a viable route to go.

This is something that's still very much on the table for us, we just haven't had the spare resources to focus on it recently! Sorry if this is a blocking issue for you

Any updates on this? I heard there is a huge refactoring incoming, will it include this feature?

I am interested in working on this.

@bkolobara was there a specific thing that blocked you from implementing it or was the codebase just too complicated to get it to work quickly?

@bkolobara was there a specific thing that blocked you from implementing it or was the codebase just too complicated to get it to work quickly?

I ended up implementing this as part of the Lunatic project. I wrote something that pretends to be a rust Future and is compatible with Rust's async runtimes, but uses separate stacks to execute Wasmer instances. So I can suspend the instance at any point.

This way I can use async code in Wasmer/Wasmtime host functions with almost zero-cost abstractions, solving my initial problem.

I spent one year thinking about this problem, implementing different solutions and looking what others are doing. My conclusion would be that just running the current Wasmer implementation on top of async-wormhole solves the problem quite elegantly.

I spent one year thinking about this problem, implementing different solutions and looking what others are doing. My conclusion would be that just running the current Wasmer implementation on top of async-wormhole solves the problem quite elegantly.

I actually ended up doing this yesterday and it seems to work fine indeed. Not sure if we should keep this issue open?

Kind of off-topic:
The WasmerEnv trait (in the 1.0 API) is Sync and Send for some reason, which makes it a little hard to pass the AsyncYielder around. I ended up using unsafe code to get it to work; not sure if you have this issue with wasmtime too.

I didn't decide yet what a safe API around the Async Yielder would look like. Would definitely like some feedback and suggestions on this.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

vitiral picture vitiral  ·  4Comments

leonardas103 picture leonardas103  ·  5Comments

repi picture repi  ·  3Comments

aergonaut picture aergonaut  ·  3Comments

danchitnis picture danchitnis  ·  4Comments