Skip to content

Releases: tetratelabs/wazero

wazero v1.0.0-pre.9

19 Feb 01:13
e2ebce5
Compare
Choose a tag to compare
wazero v1.0.0-pre.9 Pre-release
Pre-release

wazero v1.0.0-pre.9 integrates Go context to limit execution time running third party code. This is our last API affecting version before 1.0 in March. We'll cut at least one release candidate between now and then.

For those only interested in breaking changes, here's what you need to do:

  • Build with minimally Go 1.18
  • Rename Runtime.InstantiateModuleFromBinary to Runtime.Instantiate
mod, err := r.InstantiateModuleFromBinary(ctx, guestWasm)
mod, err := r.Instantiate(ctx, guestWasm)

Those of you attending wasmio will be able to meet many contributors and end users in person. This conference is timed almost exactly with our 1.0 release, so quite convenient for the community. If interested about in-person and virtual activities around our release, join gophers slack #wazero channel. Note: You may need an invite to join gophers. Regardless, if you like what we are doing, please star our repo as folks appreciate it. Meanwhile, let's dig into this month's changes!

Stop runaways with Go context!

@mathetake led an exciting development, which allows more control of the third-party wasm you run with wazero. Specifically, a cancel or deadline context can now halt potentially endless loops.

Here's an example:

ctx := context.Background()

r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig().
	// Integrate go context with the WebAssembly runtime
	.WithCloseOnContextDone(true))
defer r.Close(ctx)

mod, _ := r.Instantiate(ctx, infiniteLoopWasm)

infiniteLoop := mod.ExportedFunction("infinite_loop")

// Add function-scoped timeouts however you like.
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()

// Pass that context when calling a function to prevent it from looping.
_, err = infiniteLoop.Call(ctx)

When the context is done before wasm returns, you'll get an ExitError with one of these codes: ExitCodeContextCanceled or ExitCodeDeadlineExceeded

It is understood that some would like more features, such as metering or work-based limits. However, we are excited to have the most commonly requested form of function limiting implemented prior to 1.0. Without this last minute spike from @mathetake, it might not have happened, or ended up as an experiment.

Passing all the tests

Besides features, there has been a large amount of effort to pass all available system tests, on darwin, linux and windows. This means that not only do we pass our own integration tests, but also third party ones like so:

wasi-testsuite

wasi-testsuite is an emerging test suite by the custodians of the WASI (system calls for wasm, basically). These include tests compiled from multiple languages including AssemblyScript, C, rust. Runtimes adapt into the suite, so there's one for wazero similar to wasmtime.

With the exception of a minor detail about dot vs dot-dot directory entries (ignored by most wasm compilers), wazero's CLI passes all tests on darwin, linux and windows.

Thanks to @evacchi and @loganek for infrastructure changes on wasi-testsuite which allowed us to be able to test windows. We'd also like to thank @sunfishcode for elaboration on various compatibility points we hit along the way, especially for working these feedback into the next version of WASI.

To pass all the tests meant we had to change from only implementing functions users request, to basically any function accessed by tests. Thanks to @codefromthecrypt and @mathetake for backfilling over ten WASI functions, as well @evacchi @ncruces as well newcomer @egonelbre for helping with various platform support issues.

TinyGo

TinyGo is the de facto Go compiler for WASI. We've had TinyGo tests for a long time, but this is the first time we can execute TinyGo's tests with our CLI using only configuration. These pass on darwin, linux and windows.

@mathetake lead this work, but this also involved support on the TinyGo side, especially to allow another runtime besides wasmtime to execute tests. It is now possible execute tinygo test in a way that it runs WebAssembly tests with our CLI. This is thanks to recent work by @anuraaga and @codefromthecrypt, supported by our TinyGo champions @deadprogram and @dgryski.

Zig

Zig is a very popular language that compiles to wasm, specifically targeting WASI. Part of supporting Zig are issues like ensuring wazero supports all host functions that it might call (such as fd_readdir). Other parts are making sure our CLI can execute their system tests, and those tests pass.

A big milestone happened this this month where @mathetake merged a build check that requires zig system tests to pass on darwin, linux and windows. These passing rides on work similar to wasi-testsuite, but also required changes to Zig both requested by us and those done on their own. Special thanks to @brendandburns @evacchi @jedisct1 @Luukdegram for collaborating to ensure things work both in Zig and in wazero before users notice!

Minor Changes

  • Adds Runtime.InstantiateWithConfig to allow configuration without a compile step.
  • Adds ModuleConfig.WithOsyield to allow users to control behavior of WASI sched_yield
  • Adds a CLI flag -interpreter to force use of the interpreter engine
  • Adds a CLI flag -env-inherit to propagate ENV to wasm, useful in Docker.
  • Adds concurrent-instantiation example

wazero v1.0.0-pre.8

31 Jan 16:17
f040753
Compare
Choose a tag to compare
wazero v1.0.0-pre.8 Pre-release
Pre-release

wazero v1.0.0-pre.8 adds a filesystem configuration API that supports writes, tested by multiple third-party suites. It also brings CompilationCache out of experimental state, obviating a hard to explain Namespace API. Finally, this adds more logging scopes.

We don't expect any API changes next month, as we prepare for wazero 1.0 in March. Most of the scheduled work will be completing WASI and improving tests, so that our first formal release is trustworthy. Please upgrade to this version and give us feedback on how it's going.

The best way to contact us is to join gophers slack #wazero channel. Note: You may need an invite to join gophers. If you like what we are doing, please star our repo as folks appreciate it. Meanwhile, let's dig into this month's changes!

Writable filesystem support

An exciting change for many is the ability to configure writeable filesystems. To do that, we've added ModuleConfig.WithFSConfig which has options to mount directories or a fs.FS such that wasm can access it. Before, we had ModuleConfig.WithFS, and we'll leave that forever. This will help reduce complexity for simple-case configuration.

Here's an example of how to allow read access to the current directory as the root filesystem, while write access to a different directory as "/tmp"

moduleConfig = wazero.NewModuleConfig().
	// Make the current directory read-only accessible to the guest.
	WithReadOnlyDirMount(".", "/").
	// Make "/tmp/wasm" accessible to the guest as "/tmp".
	WithDirMount("/tmp/wasm", "/tmp")

Under the scenes, this maps to appropriate WebAssembly primitives, namely "preopens" for those compiling WASI or a virtual root if GOOS=js.

Those using the wazero CLI can take advantage of this with mount-based syntax, which looks very similar to Docker.

For example, here's the same configuration via the command line:

$ wazero run -mount=.:/:ro -mount=/tmp/wasm:/tmp ...

Under the scenes is more comprehensive than last time. Those compiling source via WASI or GOOS=js can take advantage of newly supported system calls, tested on Linux, MacOS and Windows operating systems. Thanks very much to @codefromthecrypt @evacchi @mathetake and @ncruces for collaborating on these!

  • fd_filestat_set_size
  • fd_filestat_set_times
  • fd_sync
  • fd_tell
  • fd_pwrite

Some of you may wonder about our progress on a custom filesystem plugin. We have plans to do that, but after version 1.0. This configuration API was designed to be forwards compatible with a raw filesystem plugin once it is ready.

Standard Library Integration Tests

For the first time, wazero change depends on 3rd party integration tests for aspects beyond the WebAssembly Core specification. Specifically, we use multiple tests to ensure WASI not only works based on what the spec leads define, but also work in practice in TinyGo and Zig programming languages. By running multiple tests we are able to get an implicit quorum of what certain functions are expected to do, and reduce the amount of surprise by end users who simply want things to work.

For example, we use wazero instead of wasmtime to run TinyGo wasi target tests. If any fails, our build breaks. We are nearly there with Zig, too, and will be by next month. Both of these are also thanks to the language communities themselves, who have helped champion patches needed to make things portable.

We also run the emerging wasi-testsuite, defined by the spec team. We pass tests they define for the AssemblyScript and C programming languages. We don't yet pass all rust tests recently added from wasmtime: 5 fail mostly due to some edge case functions we've not yet implemented. However, we expect to pass all of them by 1.0 or sooner, or have a very good reason if we don't.

Beyond WASI, we also test the GOARCH=wasm GOOS=js platform baked into the standard Go sdk. This is typically tested via the node.js runtime (which uses V8), but our CLI works in lieu of that also. As Go considers this experimental, we don't require passing all tests, yet. That said we test each function that also exists in WASI. This helps existing users of GOARCH=wasm GOOS=js as well paves an easier transition for those working on the upcoming Go WASI proposal.

Getting these tests understood and integrated into our CI took a lot of effort, with special thanks to @achille-roussel @evacchi and @mathetake for their hard work.

Compilation Cache

In previous versions, we had experimental support for compilation cache. This reduces the first-request penalty on cold starts from the same wasm file by re-using work serialized to disk. We've now exported CompilationCache as a stable API which can be configured by RuntimeConfig. Along the way, we removed the complicated Namespace type as the same isolation+performance benefit can happen by sharing a compilation cache between runtimes. Thanks @mathetake for the hard work simplifying the user experience!

Here's an example of this in action:

// Initializes the new compilation cache with the cache directory.
// This allows the compilation caches to be shared even across multiple OS processes.
cache, err := wazero.NewCompilationCacheWithDir(cacheDir)
if err != nil {
	log.Panicln(err)
}
defer cache.Close(ctx)

// Creates a shared runtime config to share the cache across multiple wazero.Runtime.
runtimeConfig := wazero.NewRuntimeConfig().WithCompilationCache(cache)

// Creates two wazero.Runtimes with the same compilation cache.
runtimeA := wazero.NewRuntimeWithConfig(ctx, runtimeConfig)
runtimeB := wazero.NewRuntimeWithConfig(ctx, runtimeConfig)

HostLogging Scopes

In this version, we've added several log scopes that can compose together, and give you more insight into what's happening in the third-party code you are loading. Thanks @codefromthecrypt for the continued work on making execution easier to understand.

Here's example CLI output that uses the "exit" and "filesystem" logging scopes.

$ wazero run --hostlogging=exit,filesystem --mount=.:/:ro cat.wasm /not_found.txt
==> wasi_snapshot_preview1.fd_prestat_get(fd=3)
<== (prestat={pr_name_len=1},errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_prestat_dir_name(fd=3)
<== (path=/,errno=ESUCCESS)
==> wasi_snapshot_preview1.fd_prestat_get(fd=4)
<== (prestat=,errno=EBADF)
==> wasi_snapshot_preview1.fd_fdstat_get(fd=3)
<== (stat={filetype=DIRECTORY,fdflags=,fs_rights_base=,fs_rights_inheriting=},errno=ESUCCESS)
==> wasi_snapshot_preview1.path_open(fd=3,dirflags=SYMLINK_FOLLOW,path=not_found.txt,oflags=,fs_rights_base=,fs_rights_inheriting=,fdflags=)
<== (opened_fd=,errno=ENOENT)
==> wasi_snapshot_preview1.proc_exit(rval=1)

Those programming in Go can also use this, but note that this API is still experimental, so can cause version compatibility problems. Only compile this API if you are able to change that code when upgrading wazero.

loggingCtx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
	logging.NewHostLoggingListenerFactory(&log, logging.LogScopeRandom|logging.LogScopeExit))

Other interesting changes

wazero v1.0.0-pre.7

02 Jan 23:39
4197caa
Compare
Choose a tag to compare
wazero v1.0.0-pre.7 Pre-release
Pre-release

wazero v1.0.0-pre.7 fixes a compilation problem affecting those building GOARCH=386. Other users can skip this version.

wazero v1.0.0-pre.6

31 Dec 13:16
94491fe
Compare
Choose a tag to compare
wazero v1.0.0-pre.6 Pre-release
Pre-release

wazero v1.0.0-pre.6 improves performance, CLI and adds a writeable filesystem.

Performance, reliability and usability have been recurring trends in wazero this year, and they will continue into next. We're thankful to have so many end users and enthusiasts active, and wow.. it was an active month!

Performance

This December, @mathetake has been working hard on the compiler backend, while @ckaznocha put in time to optimize various parts of module initialization. We've also put many more benchmarks in place including I/O functions like WASI. Results were dramatic.

For example, our benchmark of go compiled to wasm (GOOS=js GOARCH=wasm) improved almost 25%, while our benchmark for WASI+TinyGo improved compilation time by almost 25% and instantiation time nearly 10%.

While these improvements are great, wazero still won't beat optimizing compilers. The good news is that @mathetake proved it is possible to do JIT-ish compilation and execution in pure Go. Moreover, Tetrate has hired an extremely bright engineer to start working with Takeshi on this problem next month. For those interested in how wazero works today, take a look at this year's GopherCon presentation.

CLI

wazero works with diverse wasm compilers. Many re-use the same libraries to perform tasks such as I/O, like wasi-libc. However, even those who use that routinely drop down to pure wasm functions to optimize or decouple. Moreover, Go's compiler uses its own syscall ABI which is unlike WASI. In order for us to be more efficient, we've significantly enhanced our CLI.

Most notably, we added --hostlogging=filesystem which can show you what's happening during a command.

$ tinygo build -target wasi -o cat.wasm cat.go
$ echo hello world > /tmp/hello.txt
$ wazero run -mount=/tmp:/ --hostlogging=filesystem cat.wasm /hello.txt
==> wasi_snapshot_preview1.fd_prestat_get(fd=3)
<== (prestat={pr_name_len=1},errno=ESUCCESS)
--snip--

We also detect if you are using Go generated wasm automatically now (GOOS=js GOARCH=wasm). We support logging on that, even though the functions underneath act wildly differently.

$ GOARCH=wasm GOOS=js go build -o cat.wasm cat.go
$ wazero run -mount=/tmp:/ --hostlogging=filesystem cat.wasm /hello.txt
==> go.syscall/js.valueCall(fs.open(name=/hello.txt,flags=,perm=----------))
<== (err=<nil>,fd=4)
--snip--

We've also added a -cachedir flag which allows running the same wasm faster the second time. Those building images can use the compile command first, as that allows you to build cache without actually executing the module.

Ex. This command took almost 140ms as it implied compilation. By precompiling, it executes 100ms quicker.

$ time wazero run -mount=/tmp:/ cat.wasm /hello.txt
hello world

real	0m0.139s
user	0m0.178s
sys	0m0.036s

$ wazero compile -cachedir=$HOME/.wazero cat.wasm
$ time wazero run  -cachedir=$HOME/.wazero -mount=/tmp:/ cat.wasm /hello.txt
hello world

real	0m0.038s
user	0m0.018s
sys	0m0.019s

Finally, we added wazero version command, which does what you think it would. Thanks to @codefromthecrypt and @mathetake for the work here.

Writeable filesystem

Getting filesystem I/O working properly in sandboxed wasm across different compilers is an arduous task. We've had a lot of help this release from @vyskocilm @ncruces and @robbertvanginkel getting various system calls working as they should. Recently, @achille-roussel offered to help with polishing up things to allow users to plug-in their own filesystems in a way that goes beyond the limitations of Go's fs.FS. Notably, while fs.FS allows writing existing files, it has no support for routine tasks such as creating, renaming or deleting files.

While we are at least a month, possibly two from a plug-in abstraction, but meanwhile we added a new hook that allows you to opt into write support.

fs, err := writefs.NewDirFS("/work/appA")
if err != nil {
	log.Panicln(err)
}
config = wazero.NewModuleConfig().WithFS(fs)

writefs.NewDirFS currently adds creating, renaming and deleting files, and thanks to @achille-roussel we now have better virtualized file descriptors.

January we will continue to complete syscall support in our internal abstraction. This takes a long time, as we like to do things right, by vetting against multiple compilers such as zig, clang, TinyGo and normal Go. We'll setup as many tests as we can, including running TinyGo and Go's stdlib tests with wazero, and wasi-testsuite. Once all of that is working and documented, we'll expose an pluggable API, even if it means slipping our 1.0 release from end of February to March. Meanwhile, we're thrilled to have @achille-roussel contributing code and guidance.

Getting in touch

The wazero team hangs out in two channels in gophers slack: #wazero and #wazero-dev. Note: You may need an invite to join gophers. If you like what we are doing, please star our repo as folks appreciate it.

We also attend conferences who accept our talks: The next up is that.us covering backend WebAssembly use cases in wazero. Please encourage conferences you like to consider wazero, as it is a unique and useful part of both Go programming and WebAssembly landscape.

Regardless, Happy New Year! Thanks for all the engagement that made 2022 so exciting and productive: we're thrilled to continue that forward.

wazero v1.0.0-pre.5

15 Dec 07:08
5f7467b
Compare
Choose a tag to compare
wazero v1.0.0-pre.5 Pre-release
Pre-release

wazero v1.0.0-pre.5 improves debugability. It also makes some API changes that reduce its surface area.

We did an early release to allow the few folks impacted by API changes to test them prior to our normal month-end release. Most users should wait for v1.0.0-pre.6, which will include better performance, as well the ability to create new files.

On that note, we'll remind we have a gophers slack #wazero channel for support, updates and conversation! Note: You may need an invite to join gophers. If you like what we are doing, please star our repo as folks appreciate it.

Better stack traces

Those who compile "debug" wasm will appreciate better looking stack traces on error.

Before, a stack trace message looks like:

wasm stack trace:
        .runtime._panic(i32)
        .c()
        .b()
        .a()
        .main.main()
        .runtime.run()
        ._start()

Now, if the %.wasm includes DWARF debug info (usually a debug build), stack traces look a lot more precise:

wasm stack trace:
        .runtime._panic(i32)
                0x16e2: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_tinygowasm.go:73:6 (inlined)
                        /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/panic.go:52:7
        .c()
                0x1911: /Users/mathetake/wazero/internal/testing/dwarftestdata/testdata/tinygo/main.go:19:7
        .b()
                0x1901: /Users/mathetake/wazero/internal/testing/dwarftestdata/testdata/tinygo/main.go:14:3
        .a()
                0x18f7: /Users/mathetake/wazero/internal/testing/dwarftestdata/testdata/tinygo/main.go:9:3
        .main.main()
                0x18ed: /Users/mathetake/wazero/internal/testing/dwarftestdata/testdata/tinygo/main.go:4:3
        .runtime.run()
                0x18cc: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/scheduler_none.go:26:10
        ._start()
                0x18b6: /opt/homebrew/Cellar/tinygo/0.26.0/src/runtime/runtime_wasm_wasi.go:22:5

Many thanks to @mathetake for the work on this, which was easier due to standard libraries in Go.

Moreover, we added logging.NewHostLoggingListenerFactory for those only interested in host function calls. This reduces the volume of logging considerably for those trying to troubleshoot their custom functions.

Thanks to @codefromthecrypt for the work making things easier to understand.

API Changes

As we near 1.0, we made some decisions to cancel some features that either weren't used or aren't worth their performance impact.

Notably, we canceled the ability to change the filesystem without re-instantiating the module (experimental.WithFS). This was a complicated feature and made function calls slower also. Instead, folks should re-instantiate or otherwise lock the file system if it needs to change dynamically between calls. Feel free to ask us on gophers slack #wazero channel if you need advice.

We also removed the ctx parameter from fine grained calls such as memory reads. This sounded like a nice design idea to have it, but it introduced overhead and also wasn't really actionable except in host functions. This is because most memory accesses are from native code, which don't use go's context. Instead, we left the ctx parameter on function calls and lifecycle methods.

wazero v1.0.0-pre.4

02 Dec 03:11
b4f3371
Compare
Choose a tag to compare
wazero v1.0.0-pre.4 Pre-release
Pre-release

wazero v1.0.0-pre.4 focuses on portability, and ultimately led to a couple notable new projects using wazero: mosn and pdfium-cli. We also improved performance and infrastructure needed to develop faster. While we have a lot more to go, this version is significantly better and worth looking at and upgrading. Please star repositories of those who help keep this project working!

We want to recognize a couple end users whose direct efforts made wazero better for everyone. They are without a doubt the MVPs of v1.0.0-pre.4

@ckaznocha began the month optimizing how we instantiate modules, so that they are fast enough for request-scope. Clifton used a scientific approach, showing other people how to do flame graphs to see for themselves the performance before and after. The results were verifiable by benchmarks and also ad-hoc feedback from others in the community. Just fantastic work and a good role model.

@jerbob92 worked very hard to migrate a project which used CGO to wasm for access to PDFium. The end result was a statically compiled Go binary which embeds PDFium compiled to a wasm binary. This solved the install problem as there's no longer a shared library dependency. We are thankful to Jeroen for tens of hours of skilled, dedicated time: he worked night and day not just to fix problems he needed, but also do those in a way others can understand and consume them. This dramatically improved wazero's support of emscripten and WASI, as well meaningful clarifications on both API and processes to improve them. Please reach out and thank @jerbob92 if this benefits you!

Now, let's get to the concrete changes in wazero v1.0.0-pre.4

Much improved WASI

Thanks to @jerbob92, we have more WASI functions implemented than before, notably filesystem in nature: fd_filestat_get, fd_pread and fd_readdir. This means you can implement a command like ls in wasm and wazero can execute it.

We have better infrastructure to develop WASI functionality. This is helpful because often specifications only have a sentence or two description on the behavior one is to implement. We decided that the most effective way to address the gap is testing at least two real compilers before assuming our implementation is correct. Our first test was implementing ls in a couple compilers and making sure that must pass on every change.

https://github.com/tetratelabs/wazero/blob/v1.0.0-pre.4/imports/wasi_snapshot_preview1/wasi_stdlib_test.go

Build compatibility is now one version behind Go's support policy

Before, we supported exactly the same version policy as Go: latest and one before. As of this release it would mean Go 1.19 and 1.18. We had feedback from mosn that this policy is too aggressive as it isn't viable for users to switch so quickly. In surveying, we found a more common approach is one version behind, so three total versions. We decided to go with this, as it still allows us to use Go 1.18 features when we release 1.0 early next year.

Better host function debugging

Before, we had function listeners, but they only worked in the interpreter engine. Most people auto-select the fastest engine, which is the compiler. This meant more config work to use things like function listeners. Thanks to @mathetake, now anyone can use function listeners (noting this is an experimental API).

For example, the below will show you which WASI calls your code uses, to STDOUT:

// Set context to one that has an experimental listener
ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewHostLoggingListenerFactory(os.Stdout))

r := wazero.NewRuntime(ctx)
defer r.Close(ctx) // This closes everything this Runtime created.
...

api.GoModuleFunc now accepts a stack parameter

Few users will define host functions using the low-level api.GoModuleFunc API as it is more tricky. We changed the api slightly to allow it to allocate less, which helps performance of built-in functions and advanced third party functions.

 type GoModuleFunction interface {
-       Call(ctx context.Context, mod Module, params []uint64) []uint64
+       Call(ctx context.Context, mod Module, stack []uint64)
 }

Most users will need to do nothing as all code that used this API on GitHub have been updated.

Significant performance optimizations and infrastructure improvements

@ckaznocha made numerous optimizations to reduce cost to initialize a module. These help for users who compile once, but want a fresh module per-request. This is the safest way to execute webassembly functions quickly as it prevents side-effects between calls. @mathetake made numerous compiler improvements and added fuzzing to the default PR checks. He also ensured wazero passes on the latest version of WebAssembly 2.0 spec tests. @anuraaga found a corruption issue we corrected when people cache results, which is now fixed. @robbertvanginkel fixed a reference counting bug affecting users of gojs (which runs wasm built by GOOS=js GOARCH=wasm go build ...)

Other notable changes

  • @codefromthecrypt added wasi_snapshot_preview1.NewFunctionExporter to allow changing the WASI module name (e.g. to "wasi_unstable") or overriding built-in functions.
  • @jerbob92 and @codefromthecrypt added built-in invoke_xxx functions for Emscripten. Future change will generate these on demand given a wasm binary.
  • @mathetake added encode and encode functions for int32 and uint32 to help clarify that api.Function result arrays are intended to be decoded. Thanks to @jerbob92 for elaboration

wazero v1.0.0-pre.3

31 Oct 11:25
d108ce4
Compare
Choose a tag to compare
wazero v1.0.0-pre.3 Pre-release
Pre-release

wazero v1.0.0-pre.3 focuses on Application Binary Interface (ABI) developers and performance.

Note: wazero will require minimally Go 1.18 when 1.0 is released early next year.

To recap, an Application Binary Interface (ABI) is a contract between the WebAssembly host and the guest, primarily defining functions each side can import. Your application that embeds wazero is the host, and the guest is %.wasm which wazero instantiates. There are often performance concerns across this boundary. The latest release of wazero cuts some of this by 1us per call, which can add up for frequently called functions. Thanks very much to @anuraaga @codefromthecrypt and @hafeidejiangyou for the help on the main changes of v1.0.0-pre.3 described below!

A few of us have been working on a new ABI http-handler, and in the process renovated how wazero does host functions, particularly to be much more efficient. Many users will have no impact, because they don't define any custom ABI or host functions. Those who do, minimally need to make a change like this:

 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.

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})

While verbose, functions defined like this work a lot faster. If you are working on a low-level ABI, consider using it. wazero ported all of our internal functions to this approach and are happy with the results. For example, the below have no logic change except how parameters and results are passed.

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

Besides the new function definition syntax, v1.0.0-pre.3 has a few more goodies for ABI developers:

  • Memory.WriteString to avoid allocations
  • fd_write in WASI special cases io.Discard for more efficient discarding of stdout or stderr.
  • CompiledModule.ExportedMemories() is available to see if the guest exported "memory" or not, prior to instantiation.

wazero v1.0.0-pre.2

30 Sep 02:34
6cf113b
Compare
Choose a tag to compare
wazero v1.0.0-pre.2 Pre-release
Pre-release

wazero v1.0.0-pre.2 adds a CLI, simplifies syntax and increases performance. This was the result of a lot of feedback and help and we are grateful for so much attention.

If you like what we are doing, please star our repo as folks appreciate it. As usual, you can expect another release next month end.

Also, don't forget to look at the increasing amount of projects using wazero. Regardless of whether a project is an experiment or a largely re-used tool, we're together moving Go forward in WebAssembly.

wazero CLI

We've had a number of requests for a CLI to run wasm, though no one ever raised an issue about it. The most recent request was from @dgryski and @anuraaga stepped up to implement the first cut.

For example, if you have a calculator, you can run it like this:

$ go run github.com/tetratelabs/wazero/cmd/wazero run testdata/cli.wasm 3 4
result: 7

You can also install as a binary like usual in go:

$ go install github.com/tetratelabs/wazero/cmd/wazero@latest
$ wazero run testdata/cli.wasm 3 4
result: 7

Notably, this also supports basic filesystem mounts in a way familiar to docker users.

$ wazero run -mount=/host/path:/ cat.wasm /test.txt
Hello world!

One limitation is that while you can read or modify existing files, adding new files is not yet supported. Watch this issue for updates, but we'll have this working by next release.

We also understand CLI users may want to discuss features on chat. Please join us on gophers slack #wazero to talk about this or anything else. Note: You may need an invite to join gophers.

Simplified syntax

@anuraaga noticed some improvements we could make in terms of simplicity, and a few of us including @inkeliz dug deeper to identify rarely used features which could be removed or hidden to improve developer experience. Here are a few diffs of interest:

Defaults to features in WebAssembly Core Specification 2.0 (DRAFT)

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 folks had to to turn on 2.0 features because compilers such as AssemblyScript and TinyGo use some of them by default. Call sites are simpler for most users as a result:

-	rc := wazero.NewRuntimeConfig().WithWasmCore2()
-	r := wazero.NewRuntimeWithConfig(ctx, rc)
+	r := wazero.NewRuntime(ctx)

Note: You can still customize whatever features you want like so

features := api.CoreFeaturesV2.SetEnabled(api.CoreFeatureMutableGlobal, false)
rConfig = wazero.NewRuntimeConfig().WithCoreFeatures(features)

MustInstantiate if all you do is panic on error

Folks setting up infrastructure layer code often know there is no source of conflict. For example, a module name conflict due to someone else adding WASI. Such call sites can now be simplified like so:

-	_, err := wasi_snapshot_preview1.Instantiate(ctx, wasm)
-	if err != nil {
-		panic(err)
-	}
+	wasi_snapshot_preview1.MustInstantiate(ctx, wasm)

ModuleBuilder is now HostModuleBuilder

The most common mistake we saw in practice was people exporting memory from host modules. This memory would never be used as host functions use the caller's memory. We removed the confusing APIs and renamed the type accordingly: host modules only define functions your guest (%.wasm) imports!

-	envBuilder := r.NewModuleBuilder("env")
+	envBuilder := r.NewHostModuleBuilder("env")

No more CompileConfig

We originally introduced CompileConfig to do transformations, and the first was memory limit in nature. Later, we introduced our experimental compilation cache, and noticed that this config was not only never used in open source, but also would make it hard to reason with cache invalidation. We moved relevant settings to RuntimeConfig and removed CompileConfig, which stopped this sort of boilerplate:

-	if m.compiled, err = r.CompileModule(ctx, guest, wazero.NewCompileConfig()); err != nil {
+	if m.compiled, err = r.CompileModule(ctx, guest); err != nil {

Note: You can still limit memory like so

// Ex. To reduce the largest possible memory size from 4GB to 128KB:
rConfig = wazero.NewRuntimeConfig().WithMemoryLimitPages(2)

Moreover, you can change the %.wasm binary if your defaults aren't correct, via compiler flags or in worst case manipulating it. If the compiler you use for WebAssembly cannot control max memory, consider raising an issue with them to make that possible.

Better performance

When compiling wasm into native code, we formerly had to check function call boundaries twice (value and call frame stacks). This resulted in inefficient performance for code that calls functions a lot. Thanks to a very focused week of effort from @mathetake, the wazero's calling convention is simplified and consolidated into one stack. While your performance may vary, the best case gains for Fibonacci were between 10 and 25% faster depending on architecture.

Welcome to wazero

01 Sep 04:49
3a90e04
Compare
Choose a tag to compare
Welcome to wazero Pre-release
Pre-release

WebAssembly is a way to safely run code compiled in other languages. Runtimes execute WebAssembly Modules (Wasm), which are most often binaries with a .wasm extension.

wazero is the only zero dependency WebAssembly runtime written in Go and v1.0.0-pre.1 is our first milestone on the way to 1.0 in Feb 2023.

It has been ten months since Tetrate chose to evolve a hobby project of Takeshi Yoneda, into a maturing professional codebase named wazero.

This time was full of community contributions, feedback and evolution. wazero is particularly grateful to these folks because without them, WebAssembly is often too difficult. Most developers will prefer to choose an extensibility or runtime framework without having to implement each part on their own.

Please star all the projects below or otherwise tell them thanks for doing their best to bring Go forward in WebAssembly.

First, let's thank the two main compilers that can turn Go into Wasm

Here are some end-user tools that integrate wazero for to expose WebAssembly as a feature:

  • capsule - Allows you to invoke potentially remote CLI and web functions.
  • Trivy - a vulnerability/misconfiguration/secret scanner, extended with modules defined in WebAssembly

Here are some end-user tools who leverage functionality not specifically written for Go, using wazero. This means reuse without platform dependencies or porting!

Here are some frameworks who make WebAssembly integration easier on the next person:

  • waPC - simplifies the SDK layer between Go applications and guests functions compiled to WebAssembly.
  • go-plugin - extends go applications with gRPC services, implemented with WebAssembly communicating via memory.

Finally, some in our ecosystem use wazero only because it is a more natural way to test WebAssembly in Go projects:

  • karmem - a fast serialization format, which uses wazero to benchmark performance across the myriad of languages it supports.
  • proxy-wasm-go-sdk - allows users to write Proxy-Wasm plugins in Go. This uses wazero to integration test proxy plugins.

We have a lot of work to do between now and February 2023 when we release 1.0. This is a combination of code, bolstering documentation on our website and supporting our ecosystem, so that they can be successful serving you.

We've also opened a gophers slack #wazero channel for support, updates and conversation! Note: You may need an invite to join gophers. If you like what we are doing, please star our repo as folks appreciate it.

Last, but not least keep an eye out for our at least monthly pre-releases. Each moving forward will give updates that may impact or at least interest you. Thanks for reading this and best luck in your WebAssembly endeavors!