diff options
author | Keith Randall <khr@golang.org> | 2022-12-06 13:40:33 -0800 |
---|---|---|
committer | Gopher Robot <gobot@golang.org> | 2023-01-06 16:58:43 +0000 |
commit | 660d4815ea4229f96cede86f396017ebf5ca4bb0 (patch) | |
tree | 312b5dac2074e89894d76b75558ea3bbc8546db3 | |
parent | 119f679a3bd2e60cfc990920f82fd1a5cb006f4c (diff) | |
download | go-git-660d4815ea4229f96cede86f396017ebf5ca4bb0.tar.gz |
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>
-rw-r--r-- | src/cmd/compile/internal/wasm/ssa.go | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/wasm/ssa.go b/src/cmd/compile/internal/wasm/ssa.go index 27ba98c9cd..0578c20d16 100644 --- a/src/cmd/compile/internal/wasm/ssa.go +++ b/src/cmd/compile/internal/wasm/ssa.go @@ -17,6 +17,119 @@ import ( "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) { arch.LinkArch = &wasm.Linkwasm arch.REGSP = wasm.REG_SP |