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/