This page looks best with JavaScript enabled

Writing WebAssembly Programs in Go

 ·  ☕ 6 min read

1. Introduction to WebAssembly

  • Cross-platform: it can run on any platform that supports WebAssembly, including web browsers, servers, and mobile devices

  • High performance: it uses a compact binary format that can be loaded and parsed quickly in the browser, improving application performance

  • Security: it uses a sandbox model that isolates the code running inside it, protecting the system from malicious code

  • Portability: WebAssembly code can be generated from different programming languages by compilers, so existing code can easily be converted to the WebAssembly format

  • Extensibility: WebAssembly supports backward-compatible versioning and allows new instructions and features to be added in the future, giving it better extensibility

2. Real-World Cases

For more cases of WebAssembly usage, see https://madewithwebassembly.com/

3. Common Non-Frontend WebAssembly Runtimes

A WebAssembly runtime is the execution environment for WebAssembly code; it is responsible for loading WebAssembly modules, creating WebAssembly instances, and executing WebAssembly code.

  • Implemented in C/C++ - WasmEdge, wasm3
  • Implemented in Rust - wasmer, wasmtime
  • Implemented in Go - WaZero

Today’s mainstream browsers and recent versions of Nodejs all support wasm.

4. Installing WasmEdge

Since new versions of Docker support WasmEdge, I also chose WasmEdge locally as the WebAssembly runtime so that things stay consistent inside the container.

  • Download the Wasmedge binary

Go to https://github.com/WasmEdge/WasmEdge/releases to download the binary for your platform.

1
wget https://github.com/WasmEdge/WasmEdge/releases/download/0.12.0/WasmEdge-0.12.0-darwin_x86_64.tar.gz
  • Extract and install Wasmedge
1
2
tar -zxvf WasmEdge-0.12.0-darwin_x86_64.tar.gz -C /Users/shaowenchen --strip-components=1
export PATH=$PATH:/Users/shaowenchen/bin

If you need to copy it to another directory, be careful to preserve the relative positions of the bin, include, and lib directories, otherwise you will get the following error:

1
2
3
dyld: Library not loaded: @rpath/libwasmedge.0.dylib
  Referenced from: /Users/shaowenchen/bin/wasmedge
  Reason: image not found
  • Check the Wasmedge version
1
2
3
wasmedge --version

wasmedge version 0.12.0

5. Compiling WebAssembly Programs with TinyGo

5.1 Pros and Cons of Using TinyGo

  • The benefits of using TinyGo

TinyGo is a subset of Go, and you can write WebAssembly programs in the Go language.

It can run directly on WasmEdge without a JavaScript environment.

The wasm file is small enough — a few tens of KB.

  • The drawbacks of using TinyGo

Some libraries cannot be used with TinyGo, such as net/http; see https://tinygo.org/docs/reference/lang-support/stdlib/ for details.

TinyGo does not support all Go language features, such as Cgo; see https://tinygo.org/docs/reference/lang-support/ for details.

5.1 Hello, World!

  • Create a main.go file
1
2
3
4
5
package main

func main() {
	println("Hello, World! by TinyGo")
}
  • Compile the code
1
tinygo build -o ./build/main.wasm -target=wasm
  • Run it with WasmEdge
1
2
3
wasmedge ./build/main.wasm

Hello, World! by TinyGo
  • Create a Dockerfile
1
2
3
FROM scratch
ADD ./build/main.wasm /build/main.wasm
ENTRYPOINT ["/build/main.wasm"]
  • Build the container image
1
docker build -t shaowenchen/wasm-hello-world:tinygo .
  • Run the container image
1
2
3
4
docker run  --rm --runtime=io.containerd.wasmedge.v1 \
				shaowenchen/wasm-hello-world:tinygo

Hello, World! by TinyGo

6. Compiling WebAssembly Programs with Go

6.1 Pros and Cons of Using Go

  • The benefits of using Go

You can use the syscall/js package to interact with JavaScript.

You can use all of Go’s language features and packages.

In the future, once WebAssembly runtimes support GC, program size and performance may improve.

  • The drawbacks of using Go

By default it requires a JavaScript environment.

The size is large — several MB, even tens of MB. Still, many legacy projects also compile to tens or hundreds of MB.

6.2 Hello, World!

  • Create a main.go file
1
2
3
4
5
package main

func main() {
	println("Hello, World! by Go 1.19")
}
  • Compile the code
1
GOOS=js GOARCH=wasm go build -o ./dist/main.wasm
  • Run it with node

Note that nodejs must be 12 or above.

1
2
3
4
cp $(shell go env GOROOT)/misc/wasm/wasm_exec_node.js ./dist/
node ./dist/wasm_exec_node.js ./dist/main.wasm

Hello, World! by Go 1.19
  • Create a Dockerfile
1
2
3
FROM node:12-alpine
ADD ./dist /dist
ENTRYPOINT ["node","/dist/wasm_exec_node.js", "/dist/main.wasm"]
  • Build the container image
1
docker build -t shaowenchen/wasm-hello-world:go .
  • Run the container image
1
2
3
docker run  --rm shaowenchen/wasm-hello-world:go

Hello, World! by Go 1.19

6.3 Using syscall/js to Exchange Data Between Go and JavaScript

Go provides the syscall/js package, which can be used to interact with JavaScript.

  • Calling a JavaScript function from Go

Use the Global() function provided by the syscall/js package to get the object of the host JavaScript environment.

1
js.Global().Get("console").Get("log").Invoke("Hello, World!")

is equivalent to

1
console.log("Hello, World!");
  • Calling a Go function from JavaScript
1
2
3
4
5
js.Global().Set("myfunc", js.FuncOf(myfunc))

func myfunc(this js.Value, args []js.Value) interface{} {
	return nil
}

js.Global().Set registers the object into the JavaScript environment, so the myfunc function can be called directly from the browser frontend.

  • Rewriting hello world to execute wasm on a browser page
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
	"syscall/js"
)

func main() {
	// Call a frontend function to print in the console
	js.Global().Get("console").Get("log").Invoke("Hello, World by Go")
	// Find the element with ID hello and insert text
	js.Global().Get("document").Call("getElementById", "hello").Set("innerHTML", "Hello, World! by Go")
  // Register the Go function with the frontend
	js.Global().Set("myfunc", js.FuncOf(myfunc))
  // Do not exit immediately, otherwise it will error out
	<-make(chan bool)
}

func myfunc(this js.Value, args []js.Value) interface{} {
	println("myfunc called")
	// Get the function arguments
	myfunc_arg0 := args[0].String()
	// Set a variable on the frontend window object
	js.Global().Set("myfunc_arg0", myfunc_arg0)
	js.Global().Get("console").Get("log").Invoke(myfunc_arg0)
	return nil
}
  • Copy the wasm_exec.js and wasm_exec.html files

The Go compiler ships with a sample.

1
2
cp $(shell go env GOROOT)/misc/wasm/wasm_exec.js ./dist/
cp $(shell go env GOROOT)/misc/wasm/wasm_exec.html ./dist/

wasm_exec.js is what loads and executes wasm in the browser, and wasm_exec.html is an example.

  • Modify wasm_exec.html

To demonstrate the data interchange between Go and JavaScript, let’s modify the sample a little.

1
WebAssembly.instantiateStreaming(fetch("main.wasm")...

The fetch here should be the compiled wasm file; the default is test.wasm, so change it to main.wasm as needed.

1
2
3
4
5
6
7
</script>
function callGo() {
			myfunc("abc");
		}
</script>
<button onClick="callGo();" id="callGoButton" >callGoButton</button>
<div id="hello"></div>

Here a button is added to call the Go function myfunc.

  • See the result

Start a local http service

1
2
3
python3 -m http.server --directory ./dist

Serving HTTP on :: port 8000 (http://[::]:8000/) ...

Visit the http://localhost:8000/wasm_exec.html page

Click the Run button; Go calls the frontend object, prints text, and inserts text into the page.

Click callGoButton; the frontend calls the Go function.

In the console, access windows.myfunc_arg0 to get the value that Go set on the frontend object.

7. Summary

This post is mainly an attempt at some ways to write WebAssembly in Go. Without extra configuration, the two quicker approaches at present are:

  • Write it with TingyGo and run it directly on WasmEdge
  • Write it with Go; it needs JS to load it and runs on Nodejs

In addition, it provides an example of writing WebAssembly in Go 1.19 that interacts with JavaScript functions and data.

The related code in this post is all at https://github.com/shaowenchen/demo/tree/master/wasm-hello-world .

8. References


微信公众号
WRITTEN BY
微信公众号