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: