Go WebAssembly Internals - Part 1
Tags: go, webassembly, programming, webdev
Published: 2022-10-29 17:56:11

In Go 1.11 was introduced WebAssembly support. WebAssembly is a binary executable format that was primary designed to run in web browsers but later started to become popular on other targets such as [serverless](https://www.cncf.io/blog/2021/08/25/webassembly-serverless-functions-in-aws-lambda/) and even [Docker](https://www.docker.com/blog/docker-wasm-technical-preview/).

This series or articles will cover some Go internals that used to communicate with host JavaScript environment, its limitations and hacks that can be achieved by exploiting some undocumented features used for Go runtime internal purpose.

This first article will cover basics of `syscall/js` package and how Go code invocation from JS works.

**Update:** The [next part](https://dev.to/x1unix/go-webassembly-internals-part-2-51am) of series is already available.

## WebAssembly Communication Model

By default, WebAssembly doesn't have direct access to host system (JS world in our case), it can't directly access [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model), [BOM](https://developer.mozilla.org/ru/docs/Web/API/Window) (`window` object) and other JS APIs.

Each WebAssembly module has to declare a list of imported and exported symbols.

Imported symbols usually used to communicate with a browser (or other host environment) and and are linked by a browser during WebAssembly module instantiation. WebAssembly module caller on JS side should pass those symbols as import object.

```js
WebAssembly.instantiate(<wasm module binary>, <import object>);
```

Exports are used to export module symbols (functions usually) to be executed from JavaScript.

Unlike Go, [Emscripten](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html) or [Rust](https://rustwasm.github.io/wasm-bindgen/contributing/design/exporting-rust.html) provide convenient way to import or export symbols from our program. 

Go uses imports and exports for it's internal purposes and out of box doesn't provide a convenient way to link Go program functions to exports. I will cover this topic more in depth in next series.

## Code Sample

The only way to communicate with JavaScript world is to interact with global namespace (`window` or `globalThis`), attaching or obtaining values from it using `syscall/js` package.

Go provides way to wrap Go functions to JavaScript callable functions using `js.FuncOf` and assigning them to JS objects (usually global object like `window`) that can be used to call Go code from JS.

Let's create a simple Go program that will export a simple greeter function that will print a simple "hello world" message.

```go
package main

import (
	"fmt"
	"syscall/js"
)

func main() {
	wait := make(chan struct{})

	// Wrap our Go function as JS function to make it callable.
	jsFunc := js.FuncOf(greeter)

	// Assign our function to window.greeter
	js.Global().Set("greeter", jsFunc)

	// Prevent the program from exit
	<-wait
}

// First argument is JS execution context (this)
// and list of arguments passed to the function.
func greeter(this js.Value, args []js.Value) any {
	if len(args) == 0 {
		// The only way to return an error is to panic.
		panic("Missing name")
	}
	
	name := args[0].String()
	fmt.Println("Hello", name)
	return name
}
``` 

Program should be compiled for WebAssembly architecture with this command:

```shell
GOOS=js GOARCH=wasm go build main.go
```

### Run a program

Let's try to run this program. For demonstration purposes, I already created a snippet on Better Go Playground which allows executing Go programs in WASM environment - https://goplay.tools/snippet/sNWAb8yhHtu.

Open the [snippet](https://goplay.tools/snippet/sNWAb8yhHtu), and select WebAssembly environment as shown on a screenshot below:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a15llp41tpzfymzx7tiy.png)

Then, click **▶️ Run** button. Our program will notify that it's running and expecting `greeter` function to be called.

Open browser DevTools by pressing `F12` key and go to **Console** tab and call our `greeter` function.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ur021eu9d17ivhqgg641.png)

As you see, our function is working, but how this magic actually works?

## JavaScript Invocation Internals

To execute Go WebAssembly program, Go SDK provides a small glue-code file `wasm_exec.js` which is located in `$GOROOT/misc/wasm/wasm_exec.js`.

It provides a `window.Go` object helper which will manage all JS-to-Go communication and will provide access to all JS objects. The `syscall/js.FuncOf` function using this helper for wrapping Go functions and making them callable from JavaScript.

### Exporting Go Function

The `js.FuncOf` method wraps function to make it callable from JS. Wrapped function is put in specific internal map and have ID assigned to them. When function will be called from JS, wrapper will create an event with call information which will be routed to destination function by ID.

All wrapped Go functions are registered in [`funcs`](https://github.com/golang/go/blob/master/src/syscall/js/func.go#L13) map inside of `syscall/js` package. Map key is function ID which will be used to find target function.

Here is a very abstract explanation of how [`js.FuncOf`](https://github.com/golang/go/blob/master/src/syscall/js/func.go#L41) is working:

```go
var (
  nextFuncID int

  // Key-value pair of function ID and function
  funcs map[int]func

  // Pointer to `wasm_exec.js` Go object in Javascript
  jsGo = js.Global().Get('Go')
)

func js.FuncOf(fn) {
  funcId := nextFuncID
  nextFuncID++

  js.funcs[funcId] = fn
  // Ask Go wasm_exec.js bridge to create a function wrapper using last inserted index
  jsFuncWrapper := jsGo.Get('_makeFuncWrapper').Call(funcId)

  // js.Func is a wrapper between target Go function and JS mapping
  return js.Func{
    id: funcId,
    value: jsFuncWrapper,
  }
}
```

### Go Function Wrapper Anatomy

Let's take a look, how our exported `window.greeter` function looks like. This function was created by `go._makeFuncWrapper` method which was called by Go at previous step.

JavaScript allows to get function's source code by calling `window.greeter.toString()`.
Here is how our greeter function is actually look like:

```js
function(){
  var event = {
    id:id,          // Target Go function ID
    this:this,      // JS function execution context
    args:arguments  // Passed arguments
  };
  Go._pendingEvent = event
  Go._resume();
  return event.result;
}
```

As you see, under the hood it will call Go `wasm_exec.js` helper object, register an event with Go function and it's arguments and will resume a program to handle the request.

### Invoking Target Go Function

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v87fsc0rry7i0qkcmp6n.png)

When Go function wrapper is called from JavaScript, under the hood wrapper takes passed index of a function, creates an event with passed arguments and stores it in `go._pendingEvent`.

After that, it will “awake” Go by calling internal Go function `wasm_export_resume` which is exported by every Go WebAssembly binary as `resume` function.

`wasm_export_resume` calls a global event handler function `handleEvent()` which is located at `syscall/js` package.

`handleEvent` reads target function ID and call arguments from `_pendingEvent` property of JS Go object where we put all function call information inside Go function wrapper.

Then, it gets target Go function from `funcs` map, calls it and writes call result back to `go._pendingEvent.result` property. Result of event is retuned by JS wrapper function.

## Conclusion

As this article shows, it's easy to export and call Go functions from JavaScript but Go function invocation process quite complex and requires context switching which is an expensive procedure. 

The [next article](https://dev.to/x1unix/go-webassembly-internals-part-2-51am) will cover process of how Go communicates with JavaScript world under the hood and obtains values from JavaScript world.