Running a Go program in Deno via WASM
Tags: webassembly, go, deno, javascript
Published: 2020-05-16 22:29:47

[Deno v1.0 landed this week](https://deno.land/v1) and I just wanted to take a moment to talk about how you can run a Go program in Deno via WASM bytecode. If you don't know what Deno is be sure to click that link and read up on the release as it's incredibly interesting. Long story short it's a Rust runtime that comes bundled with V8 and can interpret JavaScript/TypeScript (and WASM) natively within a secure environment. 

To start, we'll need to write a Go program. Let's do something trivial just to prove it works. We'll write this in a file called `main.go`.
```go
package main

import "fmt"

func main() {
        fmt.Println("hello deno")
}
```
Great, we can run `go build -o hello-deno` and we'll get a binary that we can run called `hello-deno`. Executing that binary is as easy as `./hello-deno`.
```bash
taterbase:~$ ls
main.go
taterbase:~$ go build -o hello-deno
taterbase:~$ ls
hello-deno main.go 
taterbase:~$ ./hello-deno
hello deno
```
Here we've confirmed the program will build and run natively. Now, let's generate the WASM bytecode. Go has great [docs on how to generate WASM binaries](https://github.com/golang/go/wiki/WebAssembly). I'll cut to the chase and tell you that in order to cross-compile our code to WASM we'll need to set two environment variables. `GOOS=js` and `GOARCH=wasm`. Typically when cross-compiling Go code you need to specify the target operating system/runtime environment (`GOOS`) in this case `js` for JavaScript, and the target architecture (`GOARCH`) which is `wasm`. Let's do that now.
```bash
taterbase:~$ GOOS=js GOARCH=wasm go build -o deno.wasm
taterbase:~$ ls
deno.wasm  hello-deno  main.go
```
Now that we have our WASM bytecode we can start setting up the scaffolding needed to execute it within Deno. One important note about running WASM generated from Go code is you must import a support js file that Go provides in its installation directory. You can copy it like so `cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .` (this is detailed in the Go WebAssembly docs linked above).
```bash
taterbase:~$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
taterbase:~$ ls
deno.wasm  hello-deno  main.go  wasm_exec.js
```
Let's write the bootstrap js code now. I'm going to call it `deno.js`
```js
import * as _ from "./wasm_exec.js";
const go = new window.Go();
const f = await Deno.open("./deno.wasm")
const buf = await Deno.readAll(f);
const inst = await WebAssembly.instantiate(buf, go.importObject);
go.run(inst.instance);
```
Here's what's happening line by line.

1. The import at the top is to just bring the go support js code into the runtime. It attaches a constructor, `Go`, to the window object for us to use later. 
2. We then create `go` as an instance of the `Go` "class".
3. Using a core Deno api, we open the wasm bytecode file. Opening a file is an asynchronous action and we use the `await` keyword to the tell the program to let the operation finish before proceeding.
4. We then use another built in async operation, `readAll` to read the whole buffer from the wasm file. This will give us a `Uint8Array` that represents the bytes of the of wasm file.
5. We then create a WebAssembly instance, passing in our byte array and the `importObject` provided by our Go support code. I'm not completely clear on the value of the `importObject` but from what I gather it maps important values/functions that the modules inside the WASM bytecode expect to be available to execute. All I know at this moment is it's required for execution, so pass it in.
6. We then use the support `go` instance to run instance itself. This executes the wasm code!

Let's run it and see what happens.
```bash
taterbase:~$ deno run deno.js
error: Uncaught PermissionDenied: read access to "/home/taterbase/wasm-go/deno.wasm", run again with the --allow-read flag
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10)
    at async Object.open ($deno$/files.ts:37:15)
    at async file:///home/taterbase/wasm-go/deno.js:3:11
```
We've run up against one of Deno's highly touted features, out of the box security. By default Deno won't let us read/write from the filesystem (or even make network calls for that matter). We need to explicitly allow it access to the filesystem.
```bash
taterbase:~$ deno run --allow-read deno.js
hello deno
```
There you have it. We took Go code, compiled it to wasm bytecode, and ran it within Deno! I hope you find this helpful. Most logic can be cross-compiled and run successfully however things get tricky when you start doing i/o. I've been doing some reading and while I can't get tcp socket listening in a Go program working out of the box I hope to do another writeup in the future showing a solution for that. 

Until then, happy hacking.