1
0
mirror of https://github.com/golang/go synced 2024-11-23 23:10:09 -07:00

cmd/compile: describe how Go maps to wasm implementation

Change-Id: Ie4d8e1ae9c4c6046d27a27a61ef1147bc0ff373c
Reviewed-on: https://go-review.googlesource.com/c/go/+/455715
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Keith Randall <khr@google.com>
Auto-Submit: Keith Randall <khr@golang.org>
Reviewed-by: Richard Musiol <neelance@gmail.com>
Reviewed-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Keith Randall 2022-12-06 13:40:33 -08:00 committed by Gopher Robot
parent 119f679a3b
commit 660d4815ea

View File

@ -17,6 +17,119 @@ import (
"internal/buildcfg" "internal/buildcfg"
) )
/*
Wasm implementation
-------------------
Wasm is a strange Go port because the machine isn't
a register-based machine, threads are different, code paths
are different, etc. We outline those differences here.
See the design doc for some additional info on this topic.
https://docs.google.com/document/d/131vjr4DH6JFnb-blm_uRdaC0_Nv3OUwjEY5qVCxCup4/edit#heading=h.mjo1bish3xni
PCs:
Wasm doesn't have PCs in the normal sense that you can jump
to or call to. Instead, we simulate these PCs using our own construct.
A PC in the Wasm implementation is the combination of a function
ID and a block ID within that function. The function ID is an index
into a function table which transfers control to the start of the
function in question, and the block ID is a sequential integer
indicating where in the function we are.
Every function starts with a branch table which transfers control
to the place in the function indicated by the block ID. The block
ID is provided to the function as the sole Wasm argument.
Block IDs do not encode every possible PC. They only encode places
in the function where it might be suspended. Typically these places
are call sites.
Sometimes we encode the function ID and block ID separately. When
recorded together as a single integer, we use the value F<<16+B.
Threads:
Wasm doesn't (yet) have threads. We have to simulate threads by
keeping goroutine stacks in linear memory and unwinding
the Wasm stack each time we want to switch goroutines.
To support unwinding a stack, each function call returns on the Wasm
stack a boolean that tells the function whether it should return
immediately or not. When returning immediately, a return address
is left on the top of the Go stack indicating where the goroutine
should be resumed.
Stack pointer:
There is a single global stack pointer which records the stack pointer
used by the currently active goroutine. This is just an address in
linear memory where the Go runtime is maintaining the stack for that
goroutine.
Functions cache the global stack pointer in a local variable for
faster access, but any changes must be spilled to the global variable
before any call and restored from the global variable after any call.
Calling convention:
All Go arguments and return values are passed on the Go stack, not
the wasm stack. In addition, return addresses are pushed on the
Go stack at every call point. Return addresses are not used during
normal execution, they are used only when resuming goroutines.
(So they are not really a "return address", they are a "resume address".)
All Go functions have the Wasm type (i32)->i32. The argument
is the block ID and the return value is the exit immediately flag.
Callsite:
- write arguments to the Go stack (starting at SP+0)
- push return address to Go stack (8 bytes)
- write local SP to global SP
- push 0 (type i32) to Wasm stack
- issue Call
- restore local SP from global SP
- pop int32 from top of Wasm stack. If nonzero, exit function immediately.
- use results from Go stack (starting at SP+sizeof(args))
- note that the callee will have popped the return address
Prologue:
- initialize local SP from global SP
- jump to the location indicated by the block ID argument
(which appears in local variable 0)
- at block 0
- check for Go stack overflow, call morestack if needed
- subtract frame size from SP
- note that arguments now start at SP+framesize+8
Normal epilogue:
- pop frame from Go stack
- pop return address from Go stack
- push 0 (type i32) on the Wasm stack
- return
Exit immediately epilogue:
- push 1 (type i32) on the Wasm stack
- return
- note that the return address and stack frame are left on the Go stack
The main loop that executes goroutines is wasm_pc_f_loop, in
runtime/rt0_js_wasm.s. It grabs the saved return address from
the top of the Go stack (actually SP-8?), splits it up into F
and B parts, then calls F with its Wasm argument set to B.
Note that when resuming a goroutine, only the most recent function
invocation of that goroutine appears on the Wasm stack. When that
Wasm function returns normally, the next most recent frame will
then be started up by wasm_pc_f_loop.
Global 0 is SP (stack pointer)
Global 1 is CTXT (closure pointer)
Global 2 is GP (goroutine pointer)
*/
func Init(arch *ssagen.ArchInfo) { func Init(arch *ssagen.ArchInfo) {
arch.LinkArch = &wasm.Linkwasm arch.LinkArch = &wasm.Linkwasm
arch.REGSP = wasm.REG_SP arch.REGSP = wasm.REG_SP