diff --git a/.circleci/config.yml b/.circleci/config.yml index 33f3bc82..babdb9fc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,7 +70,8 @@ commands: - run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1 - run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1 - run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2 - - run: tinygo build -o wasm.wasm -target=wasm examples/wasm + - run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export + - run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main test-linux: parameters: llvm: diff --git a/src/examples/wasm/.gitignore b/src/examples/wasm/.gitignore new file mode 100644 index 00000000..cf7cb60e --- /dev/null +++ b/src/examples/wasm/.gitignore @@ -0,0 +1 @@ +html/* diff --git a/src/examples/wasm/Makefile b/src/examples/wasm/Makefile new file mode 100644 index 00000000..6927db72 --- /dev/null +++ b/src/examples/wasm/Makefile @@ -0,0 +1,15 @@ +export: clean wasm_exec + tinygo build -o ./html/wasm.wasm -target wasm ./export/wasm.go + cp ./export/wasm.js ./html/ + cp ./export/index.html ./html/ + +main: clean wasm_exec + tinygo build -o ./html/wasm.wasm -target wasm ./main/main.go + cp ./main/index.html ./html/ + +wasm_exec: + cp ../../../targets/wasm_exec.js ./html/ + +clean: + rm -rf ./html + mkdir ./html diff --git a/src/examples/wasm/README.md b/src/examples/wasm/README.md new file mode 100644 index 00000000..b77999ca --- /dev/null +++ b/src/examples/wasm/README.md @@ -0,0 +1,111 @@ +# TinyGo WebAssembly examples + +The examples here show two different ways of using WebAssembly with TinyGo; + +1. Defining and exporting functions via the `//go:export ` directive. See +[the export folder](./export) for an example of this. +1. Defining and executing a `func main()`. This is similar to how the Go +standard library implementation works. See [the main folder](./main) for an +example of this. + +## Building + +Build using the `tinygo` compiler: + +```bash +$ tinygo build -o ./wasm.wasm -target wasm ./main/main.go +``` + +This creates a `wasm.wasm` file, which we can load in JavaScript and execute in +a browser. + +This examples folder contains two examples that can be built using `make`: + +```bash +$ make export +``` + +```bash +$ make main +``` + +## Running + +Start the local webserver: + +```bash +$ go run main.go +Serving ./html on http://localhost:8080 +``` + +`fmt.Println` prints to the browser console. + +## How it works + +Execution of the contents require a few JS helper functions which are called +from WebAssembly. We have defined these in +[wasm_exec.js](../../../targets/wasm_exec.js). It is based on +`$GOROOT/misc/wasm/wasm_exec.js` from the standard library, but is slightly +different. Ensure you are using the same version of `wasm_exec.js` as the +version of `tinygo` you are using to compile. + +The general steps required to run the WebAssembly file in the browser includes +loading it into JavaScript with `WebAssembly.instantiateStreaming`, or +`WebAssembly.instantiate` in some browsers: + +```js +const go = new Go(); // Defined in wasm_exec.js +const WASM_URL = 'wasm.wasm'; + +var wasm; + +if ('instantiateStreaming' in WebAssembly) { + WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) { + wasm = obj.instance; + go.run(wasm); + }) +} else { + fetch(WASM_URL).then(resp => + resp.arrayBuffer() + ).then(bytes => + WebAssembly.instantiate(bytes, go.importObject).then(function (obj) { + wasm = obj.instance; + go.run(wasm); + }) + ) +} +``` + +If you have used explicit exports, you can call them by invoking them under the +`wasm.exports` namespace. See the [`export`](./export/wasm.js) directory for an +example of this. + +In addition to this piece of JavaScript, it is important that the file is served +with the correct `Content-Type` header set. + +```go +package main + +import ( + "log" + "net/http" + "strings" +) + +const dir = "./html" + +func main() { + fs := http.FileServer(http.Dir(dir)) + log.Print("Serving " + dir + " on http://localhost:8080") + http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + if strings.HasSuffix(req.URL.Path, ".wasm") { + resp.Header().Set("content-type", "application/wasm") + } + + fs.ServeHTTP(resp, req) + })) +} +``` + +This simple server serves anything inside the `./html` directory on port `8080`, +setting any `*.wasm` files `Content-Type` header appropriately. diff --git a/src/examples/wasm/export/index.html b/src/examples/wasm/export/index.html new file mode 100644 index 00000000..e6634a60 --- /dev/null +++ b/src/examples/wasm/export/index.html @@ -0,0 +1,20 @@ + + + + + + + Go WebAssembly + + + + + + +

WebAssembly

+

Add two numbers, using WebAssembly:

+ + = + + + diff --git a/src/examples/wasm/wasm.go b/src/examples/wasm/export/wasm.go similarity index 55% rename from src/examples/wasm/wasm.go rename to src/examples/wasm/export/wasm.go index c697e89e..0925efed 100644 --- a/src/examples/wasm/wasm.go +++ b/src/examples/wasm/export/wasm.go @@ -16,10 +16,10 @@ func add(a, b int) int { //go:export update func update() { document := js.Global().Get("document") - a_str := document.Call("getElementById", "a").Get("value").String() - b_str := document.Call("getElementById", "b").Get("value").String() - a, _ := strconv.Atoi(a_str) - b, _ := strconv.Atoi(b_str) - result := a + b + aStr := document.Call("getElementById", "a").Get("value").String() + bStr := document.Call("getElementById", "b").Get("value").String() + a, _ := strconv.Atoi(aStr) + b, _ := strconv.Atoi(bStr) + result := add(a, b) document.Call("getElementById", "result").Set("value", result) } diff --git a/src/examples/wasm/wasm.js b/src/examples/wasm/export/wasm.js similarity index 89% rename from src/examples/wasm/wasm.js rename to src/examples/wasm/export/wasm.js index 030d0bbe..98faa35c 100644 --- a/src/examples/wasm/wasm.js +++ b/src/examples/wasm/export/wasm.js @@ -1,6 +1,6 @@ 'use strict'; -const WASM_URL = '../../../wasm.wasm'; +const WASM_URL = 'wasm.wasm'; var wasm; @@ -14,7 +14,7 @@ function init() { const go = new Go(); if ('instantiateStreaming' in WebAssembly) { - WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function(obj) { + WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); updateResult(); @@ -23,7 +23,7 @@ function init() { fetch(WASM_URL).then(resp => resp.arrayBuffer() ).then(bytes => - WebAssembly.instantiate(bytes, go.importObject).then(function(obj) { + WebAssembly.instantiate(bytes, go.importObject).then(function (obj) { wasm = obj.instance; go.run(wasm); updateResult(); diff --git a/src/examples/wasm/main/README.md b/src/examples/wasm/main/README.md new file mode 100644 index 00000000..89367eaf --- /dev/null +++ b/src/examples/wasm/main/README.md @@ -0,0 +1,8 @@ +# WebAssembly main execution example + +A simple hello world that prints to the browser console. + +## License + +Note that `index.html` is copied almost verbatim from the Go 1.12 source at +`$GOROOT/misc/wasm/wasm_exec.html`. Its license applies to this file. diff --git a/src/examples/wasm/main/index.html b/src/examples/wasm/main/index.html new file mode 100644 index 00000000..0be754f7 --- /dev/null +++ b/src/examples/wasm/main/index.html @@ -0,0 +1,49 @@ + + + + + + + Go wasm + + + + + + + + + + + diff --git a/src/examples/wasm/main/main.go b/src/examples/wasm/main/main.go new file mode 100644 index 00000000..033d8b07 --- /dev/null +++ b/src/examples/wasm/main/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello world!") +} diff --git a/src/examples/wasm/server.go b/src/examples/wasm/server.go new file mode 100644 index 00000000..bff7a62a --- /dev/null +++ b/src/examples/wasm/server.go @@ -0,0 +1,21 @@ +package main + +import ( + "log" + "net/http" + "strings" +) + +const dir = "./html" + +func main() { + fs := http.FileServer(http.Dir(dir)) + log.Print("Serving " + dir + " on http://localhost:8080") + http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + if strings.HasSuffix(req.URL.Path, ".wasm") { + resp.Header().Set("content-type", "application/wasm") + } + + fs.ServeHTTP(resp, req) + })) +} diff --git a/src/examples/wasm/wasm.html b/src/examples/wasm/wasm.html deleted file mode 100644 index f8c45b40..00000000 --- a/src/examples/wasm/wasm.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - Go WebAssembly - - - - - -

WebAssembly

-

Add two numbers, using WebAssembly:

- + = - -