Bug 1744513 - Document Wasm's internal ABI. r=rhunt DONTBUILD

Document the wasm-internal ABI.  The prose was copied from the Wasm
ABI 2022 document.

Differential Revision: https://phabricator.services.mozilla.com/D136868
This commit is contained in:
Lars T Hansen 2022-01-31 07:28:00 +00:00
parent 3fc9c13854
commit c577f72dae
2 changed files with 136 additions and 1 deletions

View File

@ -28,7 +28,10 @@
#include "wasm/WasmBCStk.h"
#include "wasm/WasmConstants.h" // For MaxFrameSize
// The stack frame.
// [SMDOC] Wasm baseline compiler's stack frame.
//
// For background, see "Wasm's ABIs" in WasmFrame.h, the following should never
// be in conflict with that.
//
// The stack frame has four parts ("below" means at lower addresses):
//

View File

@ -16,6 +16,138 @@
* limitations under the License.
*/
/* [SMDOC] The WASM ABIs
*
* Wasm-internal ABI.
*
* This section pertains to calls from one wasm function to another, and to
* wasm's view of the situation when calling into wasm from JS and C++. Calls
* to JS and to C++ have other conventions.
*
* We pass the first function arguments in registers (GPR and FPU both) and the
* rest on the stack, generally according to platform ABI conventions (which can
* be hairy). On x86-32 there are no register arguments.
*
* We have no callee-saves registers in the wasm-internal ABI, regardless of the
* platform ABI conventions, though see below about TlsReg or HeapReg.
*
* We return the last return value in the first return register, according to
* platform ABI conventions. If there is more than one return value, an area is
* allocated in the caller's frame to receive the other return values, and the
* address of this area is passed to the callee as the last argument. Return
* values except the last are stored in ascending order within this area. Also
* see below about alignment of this area and the values in it.
*
* When a function is entered, there are two incoming register values in
* addition to the function's declared parameters: TlsReg must have the correct
* TLS pointer, and HeapReg the correct memoryBase, for the function. (On
* x86-32 there is no HeapReg.) From the TLS we can get to the JSContext, the
* instance, the MemoryBase, and many other things. The TLS maps one-to-one
* with an instance.
*
* HeapReg and TlsReg are not parameters in the usual sense, nor are they
* callee-saves registers. Instead they constitute global register state, the
* purpose of which is to bias the call ABI in favor of intra-instance calls,
* the predominant case where the caller and the callee have the same TlsReg and
* HeapReg values.
*
* With this global register state, literally no work needs to take place to
* save and restore the TLS and MemoryBase values across intra-instance call
* boundaries.
*
* For inter-instance calls, in contrast, there must be an instance switch at
* the call boundary: Before the call, the callee's TLS must be loaded (from a
* closure or from the import table), and from the TLS we load the callee's
* MemoryBase, the realm, and the JSContext. The caller's and callee's TLS
* values must be stored into the frame (to aid unwinding), the callee's realm
* must be stored into the JSContext, and the callee's TLS and MemoryBase values
* must be moved to appropriate registers. After the call, the caller's TLS
* must be loaded, and from it the caller's MemoryBase and realm, and the
* JSContext. The realm must be stored into the JSContext and the caller's TLS
* and MemoryBase values must be moved to appropriate registers.
*
* Direct calls to functions within the same module are always intra-instance,
* while direct calls to imported functions are always inter-instance. Indirect
* calls -- call_indirect in the MVP, future call_ref and call_funcref -- may or
* may not be intra-instance.
*
* call_indirect, and future call_funcref, also pass a signature value in a
* register (even on x86-32), this is a small integer or a pointer value
* denoting the caller's expected function signature. The callee must compare
* it to the value or pointer that denotes its actual signature, and trap on
* mismatch.
*
* This is what the stack looks like during a call, after the callee has
* completed the prologue:
*
* | |
* +-----------------------------------+ <-+
* | ... | |
* | Caller's private frame | |
* +-----------------------------------+ |
* | Multi-value return (optional) | |
* | ... | |
* +-----------------------------------+ |
* | Stack args (optional) | |
* | ... | |
* +-----------------------------------+ -+|
* | Caller TLS slot | \
* | Callee TLS slot | | \
* +-----------------------------------+ | \
* | Shadowstack area (Win64) | | wasm::FrameWithTls
* | (32 bytes) | | /
* +-----------------------------------+ | / <= SP "Before call"
* | Return address | // <= SP "After call"
* | Saved FP ----|--+/
* +-----------------------------------+ -+ <= FP (a wasm::Frame*)
* | DebugFrame, Locals, spills, etc |
* | (i.e., callee's private frame) |
* | .... |
* +-----------------------------------+ <= SP
*
* The FrameWithTls is a struct with four fields: the saved FP, the return
* address, and the two TLS slots; the shadow stack area is there only on Win64
* and is unused by wasm but is part of the native ABI, with which the wasm ABI
* is mostly compatible. The slots for caller and callee TLS are only populated
* by the instance switching code in inter-instance calls so that stack
* unwinding can keep track of the correct TLS value for each frame, the TLS not
* being obtainable from anywhere else. Nothing in the frame itself indicates
* directly whether the TLS slots are valid - for that, the return address must
* be used to look up a CallSite structure that carries that information.
*
* The stack area above the return address is owned by the caller, which may
* deallocate the area on return or choose to reuse it for subsequent calls.
* (The baseline compiler allocates and frees the stack args area and the
* multi-value result area per call. Ion reuses the areas and allocates them as
* part of the overall activation frame when the procedure is entered; indeed,
* the multi-value return area can be anywhere within the caller's private
* frame, not necessarily directly above the stack args.)
*
* If the stack args area contain references, it is up to the callee's stack map
* to name the locations where those references exist, and the caller's stack
* map must not (redundantly) name those locations. (The callee's ownership of
* this area will be crucial for making tail calls work, as the types of the
* locations can change if the callee makes a tail call.) If pointer values are
* spilled by anyone into the Shadowstack area they will not be traced.
*
* References in the multi-return area are covered by the caller's map, as these
* slots outlive the call.
*
* The address "Before call", ie the part of the FrameWithTls above the Frame,
* must be aligned to WasmStackAlignment, and everything follows from that, with
* padding inserted for alignment as required for stack arguments. In turn
* WasmStackAlignment is at least as large as the largest parameter type.
*
* The address of the multiple-results area is currently 8-byte aligned by Ion
* and its alignment in baseline is uncertain, see bug 1747787. Result values
* are stored packed within the area in fields whose size is given by
* ResultStackSize(ValType), this breaks alignment too. This all seems
* underdeveloped.
*
* In the wasm-internal ABI, the ARM64 PseudoStackPointer (PSP) is garbage on
* entry but must be synced with the real SP at the point the function returns.
*/
#ifndef wasm_frame_h
#define wasm_frame_h