github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/site/content/languages/go.md (about)

     1  +++
     2  title = "Go"
     3  +++
     4  
     5  ## Introduction
     6  
     7  When `GOOS=js GOARCH=wasm`, Go's compiler targets WebAssembly Binary format
     8  (%.wasm).
     9  
    10  Here's a typical compilation command:
    11  ```bash
    12  $ GOOS=js GOARCH=wasm go build -o my.wasm .
    13  ```
    14  
    15  The operating system is "js", but more specifically it is [wasm_exec.js][1].
    16  This package runs the `%.wasm` just like `wasm_exec.js` would.
    17  
    18  ## Experimental
    19  
    20  It is important to note that while there are some interesting features, such
    21  as HTTP client support, the ABI (host functions imported by Wasm) used is
    22  complicated and custom to Go. For this reason, there are few implementations
    23  outside the web browser.
    24  
    25  Moreover, Go defines js "EXPERIMENTAL... exempt from the Go compatibility
    26  promise." While WebAssembly signatures haven't broken between 1.18 and 1.19,
    27  then have in the past and can in the future.
    28  
    29  For this reason implementations such as wazero's [gojs][14], cannot guarantee
    30  portability from release to release, or that the code will work well in
    31  production.
    32  
    33  Due to lack of adoption, support and relatively high implementation overhead,
    34  most choose [TinyGo]({{< relref "/tinygo.md" >}}) to compile source code, even if it supports less
    35  features.
    36  
    37  ## WebAssembly Features
    38  
    39  `GOOS=js GOARCH=wasm` uses instructions in [WebAssembly Core Specification 1.0]
    40  [15] unless `GOWASM` includes features added afterwards.
    41  
    42  Here are the valid [GOWASM values][16]:
    43  * `satconv` - [Non-trapping Float-to-int Conversions][17]
    44  * `signext` - [Sign-extension operators][18]
    45  
    46  Note that both the above features are included [working draft][19] of
    47  WebAssembly Core Specification 2.0.
    48  
    49  ## Constraints
    50  
    51  Please read our overview of WebAssembly and
    52  [constraints]({{< ref "_index.md#constraints" >}}). In short, expect
    53  limitations in both language features and library choices when developing your
    54  software.
    55  
    56  `GOOS=js GOARCH=wasm` has a custom ABI which supports a subset of features in
    57  the Go standard library. Notably, the host can implement time, crypto, file
    58  system and HTTP client functions. Even where implemented, certain operations
    59  will have no effect for reasons like ignoring HTTP request properties or fake
    60  values returned (such as the pid). When not supported, many functions return
    61  `syscall.ENOSYS` errors, or the string form: "not implemented on js".
    62  
    63  Here are the more notable parts of Go which will not work when compiled via
    64  `GOOS=js GOARCH=wasm`, resulting in `syscall.ENOSYS` errors:
    65  * Raw network access. e.g. `net.Bind`
    66  * File descriptor control (`fnctl`). e.g. `syscall.Pipe`
    67  * Arbitrary syscalls. Ex `syscall.Syscall`
    68  * Process control. e.g. `syscall.Kill`
    69  * Kernel parameters. e.g. `syscall.Sysctl`
    70  * Timezone-specific clock readings. e.g. `syscall.Gettimeofday`
    71  
    72  ## Memory
    73  
    74  Memory layout begins with the "zero page" of size `runtime.minLegalPointer`
    75  (4KB) which matches the `ssa.minZeroPage` in the compiler. It is then followed
    76  by 8KB reserved for args and environment variables. This means the data section
    77  begins at [ld.wasmMinDataAddr][12], offset 12288.
    78  
    79  ## System Calls
    80  
    81  Please read our overview of WebAssembly and
    82  [System Calls]({{< ref "_index.md#system-calls" >}}). In short, WebAssembly is
    83  a stack-based virtual machine specification, so operates at a lower level than
    84  an operating system.
    85  
    86  "syscall/js.*" are host functions for features the operating system would
    87  otherwise provide. These also manage the JavaScript object graph, including
    88  functions to make and finalize objects, arrays and numbers (`js.Value`).
    89  
    90  Each `js.Value` has a `js.ref`, which is either a numeric literal or an object
    91  reference depending on its 64-bit bit pattern. When an object, the first 31
    92  bits are its identifier.
    93  
    94  There are several pre-defined values with constant `js.ref` patterns. These are
    95  either constants, globals or otherwise needed in initializers.
    96  
    97  For example, the "global" value includes properties like "fs" and "process"
    98  which implement [system calls][7] needed for functions like `os.Getuid`.
    99  
   100  Notably, not all system calls are implemented as some are stubbed by the
   101  compiler to return zero values or `syscall.ENOSYS`. This means not all Go code
   102  compiled to wasm will operate. For example, you cannot launch processes.
   103  
   104  Details beyond this are best looking at the source code of [js.go][5], or its
   105  unit tests.
   106  
   107  ## Concurrency
   108  
   109  Please read our overview of WebAssembly and
   110  [concurrency]({{< ref "_index.md#concurrency" >}}). In short, the current
   111  WebAssembly specification does not support parallel processing.
   112  
   113  Some internal code may seem strange knowing this. For example, Go's [function
   114  wrapper][9] used for `GOOS=js` is implemented using locks. Seeing this, you may
   115  feel the host side of this code (`_makeFuncWrapper`) should lock its ID
   116  namespace for parallel use as well.
   117  
   118  Digging deeper, you'll notice the [atomics][10] defined by `GOARCH=wasm` are
   119  not actually implemented with locks, rather it is awaiting the ["Threads"
   120  proposal][11].
   121  
   122  In summary, while goroutines are supported in `GOOS=js GOARCH=wasm`, they won't
   123  be able to run in parallel until the WebAssembly Specification includes atomics
   124  and Go's compiler is updated to use them.
   125  
   126  ## Error handling
   127  
   128  There are several `js.Value` used to implement `GOOS=js GOARCH=wasm` including
   129  the global, file system, HTTP round tripping, processes, etc. All of these have
   130  functions that may return an error on `js.Value.Call`.
   131  
   132  However, `js.Value.Call` does not return an error result. Internally, this
   133  dispatches to the wasm imported function `valueCall`, and interprets its two
   134  results: the real result and a boolean, represented by an integer.
   135  
   136  When false, `js.Value.Call` panics with a `js.Error` constructed from the first
   137  result. This result must be an object with one of the below properties:
   138  
   139  * JavaScript (GOOS=js): the string property "message" can be anything.
   140  * Syscall error (GOARCH=wasm): the string property "code" is constrained.
   141    * The code must be like "EIO" in [errnoByCode][13] to avoid a panic.
   142  
   143  Details beyond this are best looking at the source code of [js.go][5], or its
   144  unit tests.
   145  
   146  ## Identifying wasm compiled by Go
   147  
   148  If you have a `%.wasm` file compiled by Go (via [asm.go][2]), it has a custom
   149  section named "go.buildid".
   150  
   151  You can verify this with wasm-objdump, a part of [wabt][3]:
   152  ```
   153  $ wasm-objdump --section=go.buildid -x my.wasm
   154  
   155  example3.wasm:  file format wasm 0x1
   156  
   157  Section Details:
   158  
   159  Custom:
   160  - name: "go.buildid"
   161  ```
   162  
   163  ## Module Exports
   164  
   165  Until [wasmexport][4] is implemented, the [compiled][2] WebAssembly exports are
   166  always the same:
   167  
   168  * "mem" - (memory 265) 265 = data section plus 16MB
   169  * "run" - (func (param $argc i32) (param $argv i32)) the entrypoint
   170  * "resume" - (func) continues work after a timer delay
   171  * "getsp" - (func (result i32)) returns the stack pointer
   172  
   173  ## Module Imports
   174  
   175  Go's [compiles][3] all WebAssembly imports in the module "go", and only
   176  functions are imported.
   177  
   178  Except for the "debug" function, all function names are prefixed by their go
   179  package. Here are the defaults:
   180  
   181  * "debug" - is always function index zero, but it has unknown use.
   182  * "runtime.*" - supports system-call like functionality `GOARCH=wasm`
   183  * "syscall/js.*" - supports the JavaScript model `GOOS=js`
   184  
   185  ## PC_B calling conventions
   186  
   187  The assembly `CallImport` instruction doesn't compile signatures to WebAssembly
   188  function types, invoked by the `call` instruction.
   189  
   190  Instead, the compiler generates the same signature for all functions: a single
   191  parameter of the stack pointer, and invokes them via `call.indirect`.
   192  
   193  Specifically, any function compiled with `CallImport` has the same function
   194  type: `(func (param $sp i32))`. `$sp` is the base memory offset to read and
   195  write parameters to the stack (at 8 byte strides even if the value is 32-bit).
   196  
   197  So, implementors need to read the actual parameters from memory. Similarly, if
   198  there are results, the implementation must write those to memory.
   199  
   200  For example, `func walltime() (sec int64, nsec int32)` writes its results to
   201  memory at offsets `sp+8` and `sp+16` respectively.
   202  
   203  Note: WebAssembly compatible calling conventions has been discussed and
   204  [attempted](https://go-review.googlesource.com/c/go/+/350737) in Go before.
   205  
   206  ## Go-defined exported functions
   207  
   208  [Several functions][6] differ in calling convention by using WebAssembly type
   209  signatures instead of the single SP parameter summarized above. Functions used
   210  by the host have a "wasm_export_" prefix, which is stripped. For example,
   211  "wasm_export_run" is exported as "run", defined in [rt0_js_wasm.s][7]
   212  
   213  Here is an overview of the Go-defined exported functions:
   214   * "run" - Accepts "argc" and "argv" i32 params and begins the "wasm_pc_f_loop"
   215   * "resume" - Nullary function that resumes execution until it needs an event.
   216   * "getsp" - Returns the i32 stack pointer (SP)
   217  
   218  ## User-defined Host Functions
   219  
   220  Users can define their own "go" module function imports by defining a func
   221  without a body in their source and a `%_wasm.s` or `%_js.s` file that uses the
   222  `CallImport` instruction.
   223  
   224  For example, given `func logString(msg string)` and the below assembly:
   225  ```assembly
   226  #include "textflag.h"
   227  
   228  TEXT ·logString(SB), NOSPLIT, $0
   229  CallImport
   230  RET
   231  ```
   232  
   233  If the package was `main`, the WebAssembly function name would be
   234  "main.logString". If it was `util` and your `go.mod` module was
   235  "github.com/user/me", the WebAssembly function name would be
   236  "github.com/user/me/util.logString".
   237  
   238  Regardless of whether the function import was built-in to Go, or defined by an
   239  end user, all imports use `CallImport` conventions. Since these compile to a
   240  signature unrelated to the source, more care is needed implementing the host
   241  side, to ensure the proper count of parameters are read and results written to
   242  the Go stack.
   243  
   244  ## Hacking
   245  
   246  If you run into an issue where you need to change Go's sourcecode, the first
   247  thing you should do is read the [contributing guide][20], which details how to
   248  confirm an issue exists and a fix would be accepted. Assuming they say yes, the
   249  next step is to ensure you can build and test go.
   250  
   251  ### Make a branch for your changes
   252  
   253  First, clone upstream or your fork of golang/go and make a branch off `master`
   254  for your work, as GitHub pull requests are against that branch.
   255  
   256  ```bash
   257  $ git clone --depth=1 https://github.com/golang/go.git
   258  $ cd go
   259  $ git checkout -b my-fix
   260  ```
   261  
   262  ### Build a branch-specific `go` binary
   263  
   264  While your change may not affect the go binary itself, there are checks inside
   265  go that require version matching. Build a go binary from source to avoid these:
   266  
   267  ```bash
   268  $ cd src
   269  $ GOOS=js GOARCH=wasm ./make.bash
   270  Building Go cmd/dist using /usr/local/go. (go1.19 darwin/amd64)
   271  Building Go toolchain1 using /usr/local/go.
   272  --snip--
   273  $ cd ..
   274  $ bin/go version
   275  go version devel go1.19-c5da4fb7ac Fri Jul 22 20:12:19 2022 +0000 darwin/amd64
   276  ```
   277  
   278  Tips:
   279  * The above `bin/go` was built with whatever go version you had in your path!
   280  * `GOARCH` here is what the resulting `go` binary can target. It isn't the
   281    architecture of the current host (`GOHOSTARCH`).
   282  
   283  ### Setup ENV variables for your branch.
   284  
   285  To test the Go you just built, you need to have `GOROOT` set to your workspace,
   286  and your PATH configured to find both `bin/go` and `misc/wasm/go_js_wasm_exec`.
   287  
   288  ```bash
   289  $ export GOROOT=$PWD
   290  $ export PATH=${GOROOT}/misc/wasm:${GOROOT}/bin:$PATH
   291  ```
   292  
   293  Tip: `go_js_wasm_exec` is used because Go doesn't embed a WebAssembly runtime
   294  like wazero. In other words, go can't run the wasm it just built. Instead,
   295  `go test` uses Node.js which it assumes is installed on your host!
   296  
   297  ### Iterate until ready to submit
   298  
   299  Now, you should be all set and can iterate similar to normal Go development.
   300  The main thing to keep in mind is where files are, and remember to set
   301  `GOOS=js GOARCH=wasm` when running go commands.
   302  
   303  For example, if you fixed something in the `syscall/js` package
   304  (`${GOROOT}/src/syscall/js`), test it like so:
   305  ```bash
   306  $ GOOS=js GOARCH=wasm go test syscall/js
   307  ok  	syscall/js	1.093s
   308  ```
   309  
   310  ### Notes
   311  
   312  Here are some notes about testing `GOOS=js GOARCH=wasm`
   313  
   314  #### Skipped tests
   315  
   316  You may find tests are skipped (e.g. when run with `-v` arg).
   317  ```
   318  === RUN   TestSeekError
   319      os_test.go:1598: skipping test on js
   320  ```
   321  
   322  The go test tree has functions to check if a platform is supported before
   323  proceeding. This allows incremental development of platforms, or avoids things
   324  like launching subprocesses on wasm, which won't likely ever support that.
   325  
   326  #### Filesystem access
   327  
   328  `TestStat` tries to read `/etc/passwd` due to a [runtime.GOOS default][21].
   329  As `GOOS=js GOARCH=wasm` is a virtualized operating system, this may not make
   330  sense, because it has no files representing an operating system.
   331  
   332  Moreover, as of Go 1.19, tests don't pass through any configuration to hint at
   333  the real OS underneath the VM. You might suspect running wasm tests on Windows
   334  would fail, as that OS has no `/etc/passwd` file. In fact, they would except
   335  Windows tests don't pass anyway because the script that invokes Node.JS,
   336  [wasm_exec_node.js][22], doesn't actually work on Windows.
   337  
   338  ```bash
   339  $ GOOS=js GOARCH=wasm go test os
   340  fork/exec C:\Users\fernc\AppData\Local\Temp\go-build2236168911\b001\os.test: %1 is not a valid Win32 application.
   341  FAIL    os      0.034s
   342  FAIL
   343  ```
   344  
   345  Hosts like Darwin and Linux pass these tests because they include files like
   346  `/etc/passwd` and the test runner (`wasm_exec_node.js`) is configured to pass
   347  through any file system calls without filtering. Specifically,
   348  `globalThis.fs = require("fs")` allows code compiled to wasm any file access
   349  the host operating system's underlying controls permit.
   350  
   351  [1]: https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec.js
   352  [2]: https://github.com/golang/go/blob/go1.20/src/cmd/link/internal/wasm/asm.go
   353  [3]: https://github.com/WebAssembly/wabt
   354  [4]: https://github.com/golang/proposal/blob/6130999a9134112b156deb52da81a3cf219a6509/design/42372-wasmexport.md
   355  [5]: https://github.com/golang/go/blob/go1.20/src/syscall/js/js.go
   356  [6]: https://github.com/golang/go/blob/go1.20/src/cmd/internal/obj/wasm/wasmobj.go#L796-L810
   357  [7]: https://github.com/golang/go/blob/go1.20/src/runtime/rt0_js_wasm.s#L17-L21
   358  [8]: https://github.com/golang/go/blob/go1.20/src/syscall/syscall_js.go#L292-L306
   359  [9]: https://github.com/golang/go/blob/go1.20/src/syscall/js/func.go#L41-L51
   360  [10]: https://github.com/golang/go/blob/go1.20/src/runtime/internal/atomic/atomic_wasm.go#L5-L6
   361  [11]: https://github.com/WebAssembly/proposals
   362  [12]: https://github.com/golang/go/blob/go1.20/src/cmd/link/internal/ld/data.go#L2457
   363  [13]: https://github.com/golang/go/blob/go1.20/src/syscall/tables_js.go#L371-L494
   364  [14]: https://github.com/bananabytelabs/wazero/tree/main/experimental/gojs/example
   365  [15]: https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/
   366  [16]: https://github.com/golang/go/blob/go1.20/src/internal/buildcfg/cfg.go#L136-L150
   367  [17]: https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md
   368  [18]: https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md
   369  [19]: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/
   370  [20]: https://github.com/golang/go/blob/go1.20/CONTRIBUTING.md
   371  [21]: https://github.com/golang/go/blob/go1.20/src/os/os_test.go#L109-L116
   372  [22]: https://github.com/golang/go/blob/go1.20/misc/wasm/wasm_exec_node.js