wa-lang.org/wazero@v1.0.2/internal/engine/compiler/RATIONALE.md (about) 1 # Compiler engine 2 3 This package implements the Compiler engine for WebAssembly *purely written in Go*. 4 In this README, we describe the background, technical difficulties and some design choices. 5 6 ## General limitations on pure Go Compiler engines 7 8 In Go program, each Goroutine manages its own stack, and each item on Goroutine 9 stack is managed by Go runtime for garbage collection, etc. 10 11 These impose some difficulties on compiler engine purely written in Go because 12 we *cannot* use native push/pop instructions to save/restore temporary 13 variables spilling from registers. This results in making it impossible for us 14 to invoke Go functions from compiled native codes with the native `call` 15 instruction since it involves stack manipulations. 16 17 *TODO: maybe it is possible to hack the runtime to make it possible to achieve 18 function calls with `call`.* 19 20 ## How to generate native codes 21 22 wazero uses its own assembler, implemented from scratch in the 23 [`internal/asm`](../../asm/) package. The primary rationale are wazero's zero 24 dependency policy, and to enable concurrent compilation (a feature the 25 WebAssembly binary format optimizes for). 26 27 Before this, wazero used [`twitchyliquid64/golang-asm`](https://github.com/twitchyliquid64/golang-asm). 28 However, this was not only a dependency (one of our goals is to have zero 29 dependencies), but also a large one (several megabytes added to the binary). 30 Moreover, any copy of golang-asm is not thread-safe, so can't be used for 31 concurrent compilation (See [#233](https://github.com/tetratelabs/wazero/issues/233)). 32 33 The assembled native codes are represented as `[]byte` and the slice region is 34 marked as executable via mmap system call. 35 36 ## How to enter native codes 37 38 Assuming that we have a native code as `[]byte`, it is straightforward to enter 39 the native code region via Go assembly code. In this package, we have the 40 function without body called `nativecall` 41 42 ```go 43 func nativecall(codeSegment, engine, memory uintptr) 44 ``` 45 46 where we pass `codeSegment uintptr` as a first argument. This pointer is to the 47 first instruction to be executed. The pointer can be easily derived from 48 `[]byte` via `unsafe.Pointer`: 49 50 ```go 51 code := []byte{} 52 /* ...Compilation ...*/ 53 codeSegment := uintptr(unsafe.Pointer(&code[0])) 54 nativecall(codeSegment, ...) 55 ``` 56 57 And `nativecall` is actually implemented in [arch_amd64.s](./arch_amd64.s) 58 as a convenience layer to comply with the Go's official calling convention. 59 We delegate the task to jump into the code segment to the Go assembler code. 60 61 ## How to achieve function calls 62 63 Given that we cannot use `call` instruction at all in native code, here's how 64 we achieve the function calls back and forth among Go and (compiled) Wasm 65 native functions. 66 67 TODO: