Formerly, we introduced `wazero.Namespace` to help avoid module name or import conflicts while still sharing the runtime's compilation cache. Now that we've introduced `CompilationCache` `wazero.Namespace` is no longer necessary. By removing it, we reduce the conceptual load on end users as well internal complexity. Since most users don't use namespace, the change isn't very impactful.
Users who are only trying to avoid module name conflict can generate a name like below instead of using multiple runtimes:
```go
moduleName := fmt.Sprintf("%d", atomic.AddUint64(&m.instanceCounter, 1))
module, err := runtime.InstantiateModule(ctx, compiled, config.WithName(moduleName))
```
For `HostModuleBuilder` users, we no longer take `Namespace` as the last parameter of `Instantiate` method:
```diff
// log to the console.
_, err := r.NewHostModuleBuilder("env").
NewFunctionBuilder().WithFunc(logString).Export("log").
- Instantiate(ctx, r)
+ Instantiate(ctx)
if err != nil {
log.Panicln(err)
}
```
The following is an example diff a use of namespace can use to keep compilation cache while also ensuring their modules don't conflict:
```diff
func useMultipleRuntimes(ctx context.Context, cache) {
- r := wazero.NewRuntime(ctx)
+ cache := wazero.NewCompilationCache()
for i := 0; i < N; i++ {
- // Create a new namespace to instantiate modules into.
- ns := r.NewNamespace(ctx) // Note: this is closed when the Runtime is
+ r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig().WithCompilationCache(cache))
// Instantiate a new "env" module which exports a stateful function.
_, err := r.NewHostModuleBuilder("env").
```
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This removes ProxyFunc, which was an internal experiment to make
functions that use memory to store parameters or results easier to see.
The main issue with the approach was instantiation performance, as it
needs to dynamically generate functions. Another approach to visibility
can happen later, for example via internal logging hooks.
Notably, this fixed the performance regression after switching WASI to ProxyFunc:
```
name old time/op new time/op delta
Allocation/Compile-16 39.8ms ± 0% 39.5ms ± 0% -0.62% (p=0.016 n=4+5)
Allocation/Instantiate-16 1.74ms ± 4% 0.86ms ± 1% -50.19% (p=0.008 n=5+5)
Allocation/Call-16 4.25µs ± 1% 4.21µs ± 2% ~ (p=0.151 n=5+5)
name old alloc/op new alloc/op delta
Allocation/Compile-16 20.3MB ± 0% 20.3MB ± 0% ~ (p=0.841 n=5+5)
Allocation/Instantiate-16 1.04MB ± 0% 0.56MB ± 0% -45.88% (p=0.008 n=5+5)
Allocation/Call-16 48.0B ± 0% 48.0B ± 0% ~ (all equal)
name old allocs/op new allocs/op delta
Allocation/Compile-16 432k ± 0% 432k ± 0% ~ (p=0.833 n=5+5)
Allocation/Instantiate-16 16.6k ± 0% 6.7k ± 0% -59.34% (p=0.008 n=5+5)
Allocation/Call-16 5.00 ± 0% 5.00 ± 0% ~ (all equal)
```
Since we also removed it from `GOARCH=wasm GOOS=js`, we experienced a performance
benefit there as well:
```
name old time/op new time/op delta
_main/gojs.Run-16 13.7ms ± 1% 12.2ms ± 3% -10.76% (p=0.008 n=5+5)
name old alloc/op new alloc/op delta
_main/gojs.Run-16 25.4MB ± 0% 25.0MB ± 0% -1.66% (p=0.008 n=5+5)
name old allocs/op new allocs/op delta
_main/gojs.Run-16 166k ± 0% 158k ± 0% -4.79% (p=0.016 n=4+5)
```
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This adds ResultNames to HostFunctionBuilder and FunctionDefinition
which helps for multi-results or special-cased ones.
End users can access result names in `FunctionDefinition.ResultNames` or
set for their own host functions via
`HostFunctionBuilder.WithResultNames`. This change adds them for all
built-in functions where result names help.
Most notably, GOOS=js uses `ProxyFunc` to allow logging when a function
returns multiple results. Before, the results were returned without
names: e.g. `11231,1` and now they are named like `n=11231,ok=1`.
We soon plan to allow more visibility in WASI, for example, logging
results that will write to memory offsets. This infrastructure makes it
possible to do that.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This PR follows @hafeidejiangyou advice to not only enable end users to
avoid reflection when calling host functions, but also use that approach
ourselves internally. The performance results are staggering and will be
noticable in high performance applications.
Before
```
BenchmarkHostCall/Call
BenchmarkHostCall/Call-16 1000000 1050 ns/op
Benchmark_EnvironGet/environGet
Benchmark_EnvironGet/environGet-16 525492 2224 ns/op
```
Now
```
BenchmarkHostCall/Call
BenchmarkHostCall/Call-16 14807203 83.22 ns/op
Benchmark_EnvironGet/environGet
Benchmark_EnvironGet/environGet-16 951690 1054 ns/op
```
To accomplish this, this PR consolidates code around host function
definition and enables a fast path for functions where the user takes
responsibility for defining its WebAssembly mappings. Existing users
will need to change their code a bit, as signatures have changed.
For example, we are now more strict that all host functions require a
context parameter zero. Also, we've replaced
`HostModuleBuilder.ExportFunction` and `ExportFunctions` with a new type
`HostFunctionBuilder` that consolidates the responsibility and the
documentation.
```diff
ctx := context.Background()
-hello := func() {
+hello := func(context.Context) {
fmt.Fprintln(stdout, "hello!")
}
-_, err := r.NewHostModuleBuilder("env").ExportFunction("hello", hello).Instantiate(ctx, r)
+_, err := r.NewHostModuleBuilder("env").
+ NewFunctionBuilder().WithFunc(hello).Export("hello").
+ Instantiate(ctx, r)
```
Power users can now use `HostFunctionBuilder` to define functions that
won't use reflection. There are two choices of interfaces to use
depending on if that function needs access to the calling module or not:
`api.GoFunction` and `api.GoModuleFunction`. Here's an example defining
one.
```go
builder.WithGoFunction(api.GoFunc(func(ctx context.Context, params []uint64) []uint64 {
x, y := uint32(params[0]), uint32(params[1])
sum := x + y
return []uint64{sum}
}, []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32})
```
As you'll notice and as documented, this approach is more verbose and
not for everyone. If you aren't making a low-level library, you are
likely able to afford the 1us penalty for the convenience of reflection.
However, we are happy to enable this option for foundational libraries
and those with high performance requirements (like ourselves)!
Fixes#825
Signed-off-by: Adrian Cole <adrian@tetrate.io>
We at one point considered making `ModuleBuilder` create complete
WebAssembly binaries. However, we recently spun out
[wabin](https://github.com/tetratelabs/wabin), which allows this.
Meanwhile, the features in `ModuleBuilder` were confusing and misused.
For example, the only two cases memory was exported on GitHub were done
by accident. This is because host functions act on the guest's memory,
not their own.
Hence, this removes memory and globals from host side definitions, and
renames the type to HostModuleBuilder to clarify this is not ever going
to be used to construct normal Wasm binaries.
Most importantly, this simplifies the API and reduces a lot of code. It
is important to make changes like this, particularly deleting any
experimental things that didn't end up useful.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Co-authored-by: Anuraag Agrawal <anuraaga@gmail.com>
While compilers should be conservative when targeting WebAssembly Core
features, runtimes should be lenient as otherwise people need to
constantly turn on all features. Currently, most examples have to turn
on 2.0 features because compilers such as AssemblyScript and TinyGo use
them by default. This matches the policy with the reality, and should
make first time use easier.
This top-levels an internal type as `api.CoreFeatures` and defaults to
2.0 as opposed to 1.0, our previous default. This is less cluttered than
the excess of `WithXXX` methods we had prior to implementing all
planned WebAssembly Core Specification 1.0 features.
Finally, this backfills rationale as flat config types were a distinct
decision even if feature set selection muddied the topic.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This adds an experimental package gojs which implements the host side of Wasm compiled by GOARCH=wasm GOOS=js go build -o X.wasm X.go
This includes heavy disclaimers, in part inherited by Go's comments https://github.com/golang/go/blob/go1.19/src/syscall/js/js.go#L10-L11
Due to this many will still use TinyGo instead.
That said, this is frequently asked for and has interesting features including reflection and HTTP client support.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Before, we allowed stubbed host functions to be defined in wasm instead
of Go. This improves performance and reduces a chance of side-effects vs
Go. In fact, any pure function was supported in wasm, provided it only
called pure functions.
This changes internals so that a wasm-defined host function can use
memory. Notably, host functions use the caller's memory, so this is
simpler to initially support in the interpreter.
This is needed to simplify and reduce performance hit of GOARCH=wasm,
GOOS=js code, which perform a lot of memory reads and do not have
idiomatic signatures.
Note: wasm-defined host functions remain internal until we gain
experience, at least conclusion of the wasm_exec host module.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
* Makes CacheNumInUint64 lazy and stops crashing in assemblyscript
This makes CacheNumInUint64 lazy so that all tests for function types
don't need to handle it. This also changes the assemblyscript special
functions so they don't crash when attempting to log. Finally, this
refactors `wasm.Func` so that it can enclose the parameter names as it
is more sensible than defining them elsewhere.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This refactors host functions with no-op or constant returns to be
implemented with wasm instead of the host function bridge. This allows
better performance.
This also breaks up and makes WASI tests consistent, in a way that shows
parameter name drifts easier.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This removes the constraint of a module being exclusively wasm or host
functions. Later pull requests can optimize special imports to be
implemented in wasm, particularly useful for disabled logging callbacks.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This improves the experimental logging listener to show parameter name
and values like so:
```
--> ._start.command_export()
--> .__wasm_call_ctors()
--> .__wasilibc_initialize_environ()
==> wasi_snapshot_preview1.environ_sizes_get(result.environc=1048572,result.environBufSize=1048568)
<== ESUCCESS
<-- ()
==> wasi_snapshot_preview1.fd_prestat_get(fd=3,result.prestat=1048568)
<== ESUCCESS
--> .dlmalloc(2)
--> .sbrk(0)
<-- (1114112)
<-- (1060080)
--snip--
```
The convention `==>` implies it was a host function call
(def.IsHostFunction). This also improves the lifecycle by creating
listeners during compile. Finally, this backfills param names for
assemblyscript and wasi.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This top-levels `api.FunctionDefinition` which was formerly
experimental, and also adds import metadata to it. Now, it holds all
metadata known at compile time.
Here are the public API visible changes:
* api.ExportedFunction - replaced with api.FunctionDefinition as it is
usable for all types of functions.
* api.Function - `.ParamTypes/ResultTypes()` are replaced with
`.Definition().
* api.FunctionDefinition - extracted from experimental and adds
`.Import()` to get the any imported module and function name.
* experimental.FunctionDefinition - replaced with
api.FunctionDefinition.
* experimental.FunctionListenerFactory - adds first arg of the
instantiated module name, as it can be different than compiled.
* wazero.CompiledModule - Adds `.ImportedFunctions()` and changes result
type of `.ExportedFunctions()` to api.FunctionDefinition.
Internally, logic to create function definition are consolidated between
host and wasm-defined functions, notably wasm.Module now includes
`.BuildFunctionDefinitions()` which reduces duplication in
wasm.ModuleInstance `.BuildFunctions()`,
This obviates #681 by deleting the `ExportedFunction` type which
overlaps with this information.
This fixes#637 as it includes more metadata including imports.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Co-authored-by: Takeshi Yoneda <takeshi@tetrate.io>
This drops the text format (%.wat) and renames
InstantiateModuleFromCode to InstantiateModuleFromBinary as it is no
longer ambiguous.
We decided to stop supporting the text format as it isn't typically used
in production, yet costs a lot of work to develop. Given the resources
available and the increased work added with WebAssembly 2.0 and soon
WASI 2, we can't afford to spend the time on it.
The old parser is used only internally and will eventually be moved to
its own repository named watzero, possibly towards archival.
See #59
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This adds an experimental package to expose two work-in-progress
features:
* FunctionListener - for tracing etc.
* Sys - to control random number generators
Both the functionality and the names of the features above are
not stable. However, this should help those who can tolerate drift a
means to test things out.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Co-authored-by: Anuraag Agrawal <anuraaga@gmail.com>
This commit allows CompiledCode to be re-used regardless of
the existence of import replacement configs for instantiation.
In order to achieve this, we introduce ModuleID, which is sha256
checksum calculated on source bytes, as a key for module compilation
cache. Previously, we used*wasm.Module as keys for caches which
differ before/after import replacement.
Signed-off-by: Takeshi Yoneda takeshi@tetrate.io
This commit makes it possible for functions to be compiled before instantiation.
Notably, this adds CompileModule method on Engine interface where we pass
wasm.Module (which is the decoded module) to engines, and engines compile
all the module functions and caches them keyed on *wasm.Module.
In order to achieve that, this stops the compiled native code from embedding typeID
which is assigned for all the function types in a store.
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This adds functions to configure memory with ModuleBuilder. This uses
two functions, ExportMemory and ExportMemoryWithMax, as working with
uint32 pointers is awkward.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This adds tests that pass without changing deferred error handling.
There are some tests that don't pass even without deferred error
handling. I'll add those in a separate PR.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
During #425, @neilalexander gave constructive feedback that the API is
both moving fast, and not good enough yet. This attempts to reduce the
incidental complexity at the cost of a little conflation.
### odd presence of `wasm` and `wasi` packages -> `api` package
We had public API packages in wasm and wasi, which helped us avoid
leaking too many internals as public. That these had names that look
like there should be implementations in them cause unnecessary
confusion. This squashes both into one package "api" which has no
package collission with anything.
We've long struggled with the poorly specified and non-uniformly
implemented WASI specification. Trying to bring visibility to its
constraints knowing they are routinely invalid taints our API for no
good reason. This removes all `WASI` commands for a default to invoke
the function `_start` if it exists. In doing so, there's only one path
to start a module.
Moreover, this puts all wasi code in a top-level package "wasi" as it
isn't re-imported by any internal types.
### Reuse of Module for pre and post instantiation to `Binary` -> `Module`
Module is defined by WebAssembly in many phases, from decoded to
instantiated. However, using the same noun in multiple packages is very
confusing. We at one point tried a name "DecodedModule" or
"InstantiatedModule", but this is a fools errand. By deviating slightly
from the spec we can make it unambiguous what a module is.
This make a result of compilation a `Binary`, retaining `Module` for an
instantiated one. In doing so, there's no longer any name conflicts
whatsoever.
### Confusion about config -> `ModuleConfig`
Also caused by splitting wasm into wasm+wasi is configuration. This
conflates both into the same type `ModuleConfig` as it is simpler than
trying to explain a "will never be finished" api of wasi snapshot-01 in
routine use of WebAssembly. In other words, this further moves WASI out
of the foreground as it has been nothing but burden.
```diff
--- a/README.md
+++ b/README.md
@@ -49,8 +49,8 @@ For example, here's how you can allow WebAssembly modules to read
-wm, err := r.InstantiateModule(wazero.WASISnapshotPreview1())
-defer wm.Close()
+wm, err := wasi.InstantiateSnapshotPreview1(r)
+defer wm.Close()
-sysConfig := wazero.NewSysConfig().WithFS(os.DirFS("/work/home"))
-module, err := wazero.StartWASICommandWithConfig(r, compiled, sysConfig)
+config := wazero.ModuleConfig().WithFS(os.DirFS("/work/home"))
+module, err := r.InstantiateModule(binary, config)
defer module.Close()
...
```
This commit adds ModuleEngine interface which is used to
make calls and created per-module instance. Notably, this
enables us to remove the necessity for store to holds
FunctionInstances and FunctionIndex.
This is a follow-up from #342
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Co-authored-by: Adrian Cole <adrian@tetrate.io>
This converges host-defined modules with Wasm defined modules by
introducing a custom section for host-defined functions. The net result
are far less types and consistent initialization.
* HostModule is removed for Module
* HostFunction is removed for Function
* ModuleContext is removed for Module
Note: One impact of this is that the low-level API no longer accepts a
go context (context.Context), rather a `wasm.Module` which the function
is called in context of. This meant exposing `wasm.Module.WithContext`
to override the default.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This makes sure that concurrent use of Instantiate and
ReleaseModuleInstance is goroutine-safe.
Note that it is still not safe to expose Release in the public API
as we haven't taken into account the outstanding calls. That
would be addressed in the follow-up commit.
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Co-authored-by: Adrian Cole <adrian@tetrate.io>
This flattens `FunctionInstance.FunctionType` into `Type` and `TypeID`
fields, where the former is known prior to instantiation. This helps
pave a way for integration between Wasm declared and host defined
modules.
This also clarifies that `FunctionInstance.String` was used as a lookup
key, by renaming and caching its impl. While at it, I renamed "null" to
"v" in its output as I had been using v for void noticing others were
doing that also. Moreover, null is easy to misunderstand as a bug.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This removes more exports and adds a toehold test for
`ReleaseModuleInstance` so that it can later be completed for both
wasm and host modules.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This simplifies state management and the amount of terminology end-users
need to learn by using one concept `Runtime` instead of two: `Engine`
and `Store`. This bridges the concepts to the specification by still
having `wazero.Runtime` implement `wasm.Store`.
The net result is that we can know for sure which "engine" is used when
decoding. This allows us a lot of flexibility especially pre-compilation
when JIT is possible.
This also changes the default to JIT based on compiler flags so that
downstream projects like wapc-go don't have to do this individually
(including tracking of which OS+Arch have JIT).
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This centralizes our management of external types by choosing the word
"external" also used in the spec instead of defining the same constants
for imports vs exports. One advantage is more coherent reference
searching as constants are no longer split. Another is future work can
use a single type to manage namespace length checks.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This adds functions to read or write a single byte from memory. This can
eventually be used by wasi `FdPrestatGet`, once implemented, to verify
the tag without also needing read padding. It can also be used by host
functions who want to share flags with other host functions for other
reasons, such as state transitions.
This also carves in benchmarks for future optimizations:
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This moves code for `ValueType` constants public, so that we can
centralize discussion on how values are encoded. We'll need this when
re-introducing a Globals API.
To keep complexity down in consideration that there are only 4 types, I
copied the constants into the internalwasm package. This reduces the
import complexity otherwise would have caused.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Co-authored-by: Takaya Saeki <takaya@tetrate.io>
This adds a function to get an exported memory by name, allowing end
users to check ahead of time if memory writes might fail. This also
renames memory.len to size to as that's what the spec calls it.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Before, we emulated the map api by returning `fn, ok` to tell if a
function was there or not. This made a lot of cruft vs returning `nil`
instead, especially as many times configuration of functions is near
where they are called in the source (so they aren't likely to be nil).
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This adds `StoreConfig.Context` to centralize assignment of the initial
context used implicitly by the WebAssembly 1.0 (MVP) start function and
also the WASI snapshot-01 "_start" exported function. This also
backfills tests and comments around propagation.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This adds this interface `wasm.Store` which gives access to functions in
a store without leaking an API to change the store. This is primarily to
support configuration use cases where post-initialization, there's no
need or desire to mutate the store. This also backfills codecs needed to
handle float results.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This allows users to decouple from wazero code when authoring host
functions. Notably, this allows them to opt out of using a context, or
only using a Go context instead of HostFunctionCallContext.
This backfills docs on how to write host functions (in simple terms).
Finally, this does not optimize engines to avoid propagating context or
looking up memory if it would never be used. That could be done later.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This moves to a new end-user API under the root package `wazero`. This
simplifies call sites while hardening function calls to their known
return value. Most importantly, this moves most logic internal, as
noted in the RATIONALE.md.
Ex.
```go
// Read WebAssembly binary containing an exported "fac" function.
source, _ := os.ReadFile("./tests/engine/testdata/fac.wasm")
// Decode the binary as WebAssembly module.
mod, _ := wazero.DecodeModuleBinary(source)
// Initialize the execution environment called "store" with Interpreter-based engine.
store := wazero.NewStore()
// Instantiate the module, which returns its exported functions
functions, _ := store.Instantiate(mod)
// Get the factorial function
fac, _ := functions.GetFunctionI64Return("fac")
// Discover 7! is 5040
fmt.Println(fac(context.Background(), 7))
```
PS I changed the README to factorial because the wat version of
fibonacci is not consistent with the TinyGo one!
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Co-authored-by: Takaya Saeki <takaya@tetrate.io>
Co-authored-by: Takeshi Yoneda <takeshi@tetrate.io>