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