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