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