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