Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow called host functions to execute Wasm functions via the wasmi::Engine #572

Closed
Robbepop opened this issue Nov 21, 2022 · 9 comments · Fixed by #590
Closed

Allow called host functions to execute Wasm functions via the wasmi::Engine #572

Robbepop opened this issue Nov 21, 2022 · 9 comments · Fixed by #590
Labels
enhancement New feature or request priority-high

Comments

@Robbepop
Copy link
Member

As stated in #567 it is currently not possible in wasmi to call Wasm functions from within called host functions due to a deadlock in the Engine.
We designed it this way because it was a simple implementation and we thought it was done similarly in Wasmtime, however, we were wrong about that since in Wasmtime it is possible to call Wasm functions from within called host functions as stated by the issue author.

Since we want to ideally mirror the Wasmtime functionality we want to implement this feature as well. The only downside is that it is slightly harder to implement since wasmi is an interpreter that has to carry arround all the execution state with each call stack. Therefore we need to implement a way to allow for multiple executors for a single wasmi::Engine.

This issue tracks the progress of the feature.

@mariaschett
Copy link

We also rely on calling Wasm functions from within host function. Any chance this issue is on the roadmap? Thank you!

@tiziano88
Copy link

Another +1 to this issue, we also rely on a pattern similar to what described in #567 (comment) , in which the Wasm module invokes a host function, and during its execution the host calls back into the Wasm module to perform an allocation. This is not a super common use case, but I have already seen a similar pattern in other projects as well, so I hope wasmi can prioritize and implement this. Thanks in advance!

@Robbepop
Copy link
Member Author

Robbepop commented Nov 29, 2022

I will tag this issue with higher priority due to the following reasons:

  • Many projects seem to be relying on this feature.
  • Wasmtime supports this behavior already out of the box.
  • Without this feature wasmi is at disparity with the Wasm standardized behavior.

@Robbepop
Copy link
Member Author

Robbepop commented Dec 1, 2022

I thought a bit about this issue and how we could implement it in wasmi. I came up with 3 competing designs:

  1. We could do something very similar to Python's GIL where we'd not allow for actually concurrent Wasm executions but instead users could "queue" them up. This way we could reuse most engine resources since we would only need one Wasm executor. The neat side effect with this solution is that it is very easy to implement on top of the current architecture and things like stack overflow checking work out of the box.
  2. We could do something very similar to how the old wasmi behaved. The old wasmi simply spawned an entirely new Wasm engine for every function invocation. This is extremely costly and didn't allow to reuse resources in the way the old wasmi was implemented. However, it is also the design that allows for most freedom for users and in theory users could even run multiple Wasm executions in parallel since they are not actually sharing resources. If we were to implement something like this in the new wasmi we would try to reuse as many resources as possible instead of simply spinning up an entirely new engine every time. A problem with this solution is that we need to take extra care to not violate stack overflow checks etc. I bet that this was not done properly in the old wasmi engine.
  3. Do a mix of the two to get the best of both worlds. This means that we generally spin up a new Wasm executor for new function executions. However, in the case where a called host function executes Wasm functions one by one in sequence (which should be a common case) we can actually reuse the Wasm executor of the Wasm execution that called the host function. If the host calls Wasm functions concurrently then only 1 of the executions will actually reuse the caller's Wasm executor and the other will spin up a new one for its own execution. The upside is that we already have a type (Caller) that we can utilize to provide the information needed to dispatch between both types of resource reuse in Wasm execution. The downside is that this further complicates the task.

I am currently leaning towards 3) since it will be the superior solution and efficient for the common case. However, at this point I am hoping for a simpler alternative idea.

@tiziano88
Copy link

To clarify, what we need is a way, while we are fulfilling an exported function call from Wasm to the host (let's call this "outer"), to be able to call back into the Wasm module (e.g. in order to perform an allocation) (let's call this "inner").

IIUC, neither of the two suggested options are ideal, since the inner call really needs to happen while the outer call is "waiting" (it cannot be done afterwards), and also it's not feasible to spawn a new Wasm engine, it needs to operate on the same Wasm instance that invoked the outer function. But I am not 100% sure.

@Robbepop
Copy link
Member Author

Robbepop commented Dec 2, 2022

@tiziano88 All three design proposals are going to support your use case. Sorry if my write up was confusing to you.

Maybe what confused you was the term "Wasm executor" which is different from the wasmi::Engine. Currently a wasmi::Engine has one Wasm executor that is locked when executing a Wasm function. Proposal 2) and 3) will make a single wasmi::Engine have multiple Wasm executors so that concurrent Wasm executions can be run at the same time.

Proposal 1) does not go the full way in that it still provides only a single Wasm executor but allows to reuse that for sequentially consecutive Wasm function calls which would also support your use case but is more limited albeit simpler to implement.

@Berrysoft
Copy link
Contributor

I wonder why the executor is locked when calling a function? If the calling and locking actions could be decoupled, we may achieve the goal.

If they cannot be decoupled, we can set a global variable like tokio. The global variable stores the current executor in use, and it will be checked when calling a function. If it is set, we don't need to lock the executor again.

@Robbepop
Copy link
Member Author

Robbepop commented Dec 2, 2022

Currently the Wasm executor is locked during the lifetime of a host call because of performance considerations. Unlocking before a host call and re-locking after a host call is inefficient and I was under the impression initially that host functions do not need to call back into the Wasm engine. If I knew better I had designed the system differently. My fault!

So yeah: Basically proposal 1) is about shifting the lock so that the host call will happen outside of the lock. But as stated prior this is not an ideal solution and will still not support all use cases that Wasmtime currently supports.

@Robbepop
Copy link
Member Author

After researching the best possible kind of implementation I ended up choosing a rather simple approach to the problem. The PR for this issue is no longer WIP and there are only a few minor rough edges left to carve out before merging: #590

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request priority-high
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants