github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/site/content/languages/tinygo.md (about) 1 +++ 2 title = "TinyGo" 3 +++ 4 5 ## Introduction 6 7 [TinyGo][1] is an alternative compiler for Go source code. It can generate 8 `%.wasm` files instead of architecture-specific binaries through two targets: 9 10 * `wasm`: for browser (JavaScript) use. 11 * `wasi`: for use outside the browser. 12 13 This document is maintained by wazero, which is a WebAssembly runtime that 14 embeds in Go applications. Hence, all notes below will be about TinyGo's 15 `wasi` target. 16 17 ## Overview 18 19 When TinyGo compiles a `%.go` file with its `wasi` target, the output `%.wasm` 20 depends on a subset of features in the [WebAssembly 2.0 Core specification] 21 ({{< ref "/specs#core" >}}) and [WASI]({{< ref "/specs#wasi" >}}) host 22 functions. 23 24 Unlike some compilers, TinyGo also supports importing custom host functions and 25 exporting functions back to the host. 26 27 Here's a basic example of source in TinyGo: 28 29 ```go 30 package main 31 32 //export add 33 func add(x, y uint32) uint32 { 34 return x + y 35 } 36 37 // main is required for the `wasi` target, even if it isn't used. 38 func main() {} 39 ``` 40 41 The following is the minimal command to build a `%.wasm` binary. 42 ```bash 43 tinygo build -o main.wasm -target=wasi main.go 44 ``` 45 46 The resulting wasm exports the `add` function so that the embedding host can 47 call it, regardless of if the host is written in Go or not. 48 49 ## Disclaimer 50 51 This document includes notes contributed by the wazero community. While wazero 52 includes TinyGo examples, and maintainers often contribute to TinyGo, this 53 isn't a TinyGo official document. For more help, consider the [TinyGo Using 54 WebAssembly Guide][4] or joining the [#TinyGo channel on the Gophers Slack][5]. 55 56 Meanwhile, please help us [maintain][6] this document and [star our GitHub 57 repository][7], if it is helpful. Together, we can make WebAssembly easier on 58 the next person. 59 60 ## Constraints 61 62 Please read our overview of WebAssembly and 63 [constraints]({{< ref "_index.md#constraints" >}}). In short, expect 64 limitations in both language features and library choices when developing your 65 software. 66 67 ### Unsupported standard libraries 68 69 TinyGo does not completely implement the Go standard library when targeting 70 `wasi`. What is missing is documented [here][26]. 71 72 The first constraint people notice is that `encoding/json` usage compiles, but 73 panics at runtime. 74 ```go 75 package main 76 77 import "encoding/json" 78 79 type response struct { 80 Ok bool `json:"ok"` 81 } 82 83 func main() { 84 var res response 85 if err := json.Unmarshal([]byte(`{"ok": true}`), &res); err != nil { 86 println(err) 87 } 88 } 89 ``` 90 This is due to limited support for reflection, and effects other [serialization 91 tools][18] also. See [Frequently Asked Questions](#frequently-asked-questions) 92 for some workarounds. 93 94 ### Unsupported System Calls 95 96 You may also notice some other features not yet work. For example, the below 97 will compile, but print "readdir unimplemented : errno 54" at runtime. 98 99 ```go 100 package main 101 102 import "os" 103 104 func main() { 105 if _, err := os.ReadDir("."); err != nil { 106 println(err) 107 } 108 } 109 ``` 110 111 The underlying error is often, but not always `syscall.ENOSYS` which is the 112 standard way to stub a syscall until it is implemented. If you are interested 113 in more, see [System Calls](#system-calls). 114 115 ## Memory 116 117 When TinyGo compiles go into wasm, it configures the WebAssembly linear memory 118 to an initial size of 2 pages (128KB), and marks a position in that memory as 119 the heap base. All memory beyond that is used for the Go heap. 120 121 Allocations within Go (compiled to `%.wasm`) are managed as one would expect. 122 The allocator can [grow][20] until `memory.grow` on the host returns -1. 123 124 ### Host Allocations 125 126 Sometimes a host function needs to allocate memory directly. For example, to 127 write JSON of a given length before invoking an exported function to parse it. 128 129 The below snippet is a realistic example of a function exported to the host, 130 who needs to allocate memory first. 131 ```go 132 //export configure 133 func configure(ptr uintptr, size uint32) { 134 json := ptrToString(ptr, size) 135 } 136 ``` 137 Note: WebAssembly uses 32-bit memory addressing, so a `uintptr` is 32-bits. 138 139 The general flow is that the host allocates memory by calling an allocation 140 function with the size needed. Then, it writes data, in this case JSON, to the 141 memory offset (`ptr`). At that point, it can call a host function, ex 142 `configure`, passing the `ptr` and `size` allocated. The guest wasm (compiled 143 from Go) will be able to read the data. To ensure no memory leaks, the host 144 calls a free function, with the same `ptr`, afterwards and unconditionally. 145 146 Note: wazero includes an [example project][8] that shows this. 147 148 The general call patterns are the following. Host is the process embedding the 149 WebAssembly runtime, such as wazero. Guest is the TinyGo source compiled to 150 target wasi. 151 152 * Host allocates a string to call an exported Guest function 153 * Host calls the built-in export `malloc` to get the memory offset to write 154 the string, which is passed as a parameter to the exported Guest function. 155 The host owns that allocation, so must call the built-in export `free` when 156 done. The Guest uses `ptrToString` to retrieve the string from the Wasm 157 parameters. 158 * Guest passes a string to an imported Host function 159 * Guest uses `stringToPtr` to get the memory offset needed by the Host 160 function. The host reads that string directly from Wasm memory. The 161 original string is subject to garbage collection on the Guest, so the Host 162 shouldn't call the built-in export `free` on it. 163 * Guest returns a string from an exported function 164 * Guest uses `ptrToLeakedString` to get the memory offset needed by the Host, 165 and returns it and the length. This is a transfer of ownership, so the 166 string won't be garbage collected on the Guest. The host reads that string 167 directly from Wasm memory and must call the built-in export `free` when 168 complete. 169 170 The built-in `malloc` and `free` functions the Host calls like this in the 171 WebAssembly text format. 172 ```webassembly 173 (func (export "malloc") (param $size i32) (result (;$ptr;) i32)) 174 (func (export "free") (param $ptr i32)) 175 ``` 176 177 The other Guest function, such as `ptrToString` are too much code to inline 178 into this document, If you need these, you can copy them from the 179 [example project][8] or add a dependency on [tinymem][9]. 180 181 ## System Calls 182 183 Please read our overview of WebAssembly and 184 [System Calls]({{< ref "_index.md#system-calls" >}}). In short, WebAssembly is 185 a stack-based virtual machine specification, so operates at a lower level than 186 an operating system. 187 188 For functionality the operating system would otherwise provide, TinyGo imports 189 host functions defined in [WASI]({{< ref "/specs#wasi" >}}). 190 191 For example, `tinygo build -o main.wasm -target=wasi main.go` compiles the 192 below `main` function into a WASI function exported as `_start`. 193 194 When the WebAssembly runtime calls `_start`, you'll see the effective 195 `GOARCH=wasm` and `GOOS=linux`. 196 197 ```go 198 package main 199 200 import ( 201 "fmt" 202 "runtime" 203 ) 204 205 func main() { 206 fmt.Println(runtime.GOARCH, runtime.GOOS) 207 } 208 ``` 209 210 Note: wazero includes an [example WASI project][21] including [source code][22] 211 that implements `cat` without any WebAssembly-specific code. 212 213 ### WASI Internals 214 215 While developing WASI in TinyGo is outside the scope of this document, the 216 below pointers will help you understand the underlying architecture of the 217 `wasi` target. Ideally, these notes can help you frame support or feature 218 requests with the TinyGo team. 219 220 A close look at the [wasi target][11] reveals how things work. Underneath, 221 TinyGo leverages the `wasm32-unknown-wasi` LLVM target for the system call 222 layer (libc), which is eventually implemented by the [wasi-libc][12] library. 223 224 Similar to normal code, TinyGo decides which abstraction to use with GOOS and 225 GOARCH specific suffixes and build flags. 226 227 For example, `os.Args` is implemented directly using WebAssembly host functions 228 in [runtime_wasm_wasi.go][13]. `syscall.Chdir` is implemented with the same 229 [syscall_libc.go][14] used for other architectures, while `syscall.ReadDirent` 230 is stubbed (returns `syscall.ENOSYS`), in [syscall_libc_wasi.go][15]. 231 232 ## Concurrency 233 234 Please read our overview of WebAssembly and 235 [concurrency]({{< ref "_index.md#concurrency" >}}). In short, the current 236 WebAssembly specification does not support parallel processing. 237 238 Tinygo uses only one core/thread regardless of target. This happens to be a 239 good match for Wasm's current lack of support for (multiple) threads. Tinygo's 240 goroutine scheduler on Wasm currently uses Binaryen's [Asyncify][23], a Wasm 241 postprocessor also used by other languages targeting Wasm to provide similar 242 concurrency. 243 244 In summary, TinyGo supports goroutines by default and acts like `GOMAXPROCS=1`. 245 Since [goroutines are not threads][24], the following code will run with the 246 expected output, despite goroutines defined in opposite dependency order. 247 ```go 248 package main 249 250 import "fmt" 251 252 func main() { 253 msg := make(chan int) 254 finished := make(chan int) 255 go func() { 256 <-msg 257 fmt.Println("consumer") 258 finished <- 1 259 }() 260 go func() { 261 fmt.Println("producer") 262 msg <- 1 263 }() 264 <-finished 265 } 266 ``` 267 268 There are some glitches to this. For example, if that same function was 269 exported (`//export notMain`), and called while main wasn't running, the line 270 that creates a goroutine currently [panics at runtime][25]. 271 272 Given problems like this, some choose a compile-time failure instead, via 273 `-scheduler=none`. Since code often needs to be custom in order to work with 274 wasm anyway, there may be limited impact to removing goroutine support. 275 276 ## Optimizations 277 278 Below are some commonly used configurations that allow optimizing for size or 279 performance vs defaults. Note that sometimes one sacrifices the other. 280 281 ### Binary size 282 283 Those with `%.wasm` binary size constraints can set `tinygo` flags to reduce 284 it. For example, a simple `cat` program can reduce from default of 260KB to 285 60KB using both flags below. 286 287 * `-scheduler=none`: Reduces size, but fails at compile time on goroutines. 288 * `--no-debug`: Strips DWARF, but retains the WebAssembly name section. 289 290 ### Performance 291 292 Those with runtime performance constraints can set `tinygo` flags to improve 293 it. 294 295 * `-gc=leaking`: Avoids GC which improves performance for short-lived programs. 296 * `-opt=2`: Enable additional optimizations, frequently at the expense of binary 297 size. 298 299 ## Frequently Asked Questions 300 301 ### Why do I have to define main? 302 303 If you are using TinyGo's `wasi` target, you should define at least a no-op 304 `func main() {}` in your source. 305 306 If you don't, instantiation of the WebAssembly will fail unless you've exported 307 the following from the host: 308 ```webassembly 309 (func (import "env" "main.main") (param i32) (result i32)) 310 ``` 311 312 ### How do I use json? 313 TinyGo doesn't yet implement [reflection APIs][16] needed by `encoding/json`. 314 Meanwhile, most users resort to non-reflective parsers, such as [gjson][17]. 315 316 ### Why does my wasm import WASI functions even when I don't use it? 317 TinyGo has a `wasm` target (for browsers) and a `wasi` target for runtimes that 318 support [WASI]({{< ref "/specs#wasi" >}}). This document is written only about 319 the `wasi` target. 320 321 Some users are surprised to see imports from WASI (`wasi_snapshot_preview1`), 322 when their neither has a main function nor uses memory. At least implementing 323 `panic` requires writing to the console, and `fd_write` is used for this. 324 325 A bare or standalone WebAssembly target doesn't yet exist, but if interested, 326 you can follow [this issue][19]. 327 328 ### Why is my `%.wasm` binary so big? 329 TinyGo defaults can be overridden for those who can sacrifice features or 330 performance for a [smaller binary](#binary-size). After that, tuning your 331 source code may reduce binary size further. 332 333 TinyGo minimally needs to implement garbage collection and `panic`, and the 334 wasm to implement that is often not considered big (~4KB). What's often 335 surprising to users are APIs that seem simple, but require a lot of supporting 336 functions, such as `fmt.Println`, which can require 100KB of wasm. 337 338 [1]: https://tinygo.org/ 339 [4]: https://tinygo.org/docs/guides/webassembly/ 340 [5]: https://github.com/tinygo-org/tinygo#getting-help 341 [6]: https://github.com/bananabytelabs/wazero/tree/main/site/content/languages/tinygo.md 342 [7]: https://github.com/bananabytelabs/wazero/stargazers 343 [8]: https://github.com/bananabytelabs/wazero/tree/main/examples/allocation/tinygo 344 [9]: https://github.com/tetratelabs/tinymem 345 [11]: https://github.com/tinygo-org/tinygo/blob/v0.25.0/targets/wasi.json 346 [12]: https://github.com/WebAssembly/wasi-libc 347 [13]: https://github.com/tinygo-org/tinygo/blob/v0.25.0/src/runtime/runtime_wasm_wasi.go#L34-L62 348 [14]: https://github.com/tinygo-org/tinygo/blob/v0.25.0/src/syscall/syscall_libc.go#L85-L92 349 [15]: https://github.com/tinygo-org/tinygo/blob/v0.25.0/src/syscall/syscall_libc_wasi.go#L263-L265 350 [16]: https://github.com/tinygo-org/tinygo/issues/2660 351 [17]: https://github.com/tidwall/gjson 352 [18]: https://github.com/tinygo-org/tinygo/issues/447 353 [19]: https://github.com/tinygo-org/tinygo/issues/3068 354 [20]: https://github.com/tinygo-org/tinygo/blob/v0.25.0/src/runtime/arch_tinygowasm.go#L47-L62 355 [21]: https://github.com/bananabytelabs/wazero/tree/main/imports/wasi_snapshot_preview1/example 356 [22]: https://github.com/bananabytelabs/wazero/tree/main/imports/wasi_snapshot_preview1/example/testdata/tinygo 357 [23]: https://github.com/WebAssembly/binaryen/blob/main/src/passes/Asyncify.cpp 358 [24]: http://tleyden.github.io/blog/2014/10/30/goroutines-vs-threads/ 359 [25]: https://github.com/tinygo-org/tinygo/issues/3095 360 [26]: https://tinygo.org/docs/reference/lang-support/stdlib/