no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / js / src / wasm / WasmFrame.h
blob23e1c4f49cca9c88220350e1be9c572d4dbe1038
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
4 * Copyright 2021 Mozilla Foundation
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 /* [SMDOC] The WASM ABIs
21 * Wasm-internal ABI.
23 * The *Wasm-internal ABI* is the ABI a wasm function assumes when it is
24 * entered, and the one it assumes when it is making a call to what it believes
25 * is another wasm function.
27 * We pass the first function arguments in registers (GPR and FPU both) and the
28 * rest on the stack, generally according to platform ABI conventions (which can
29 * be hairy). On x86-32 there are no register arguments.
31 * We have no callee-saves registers in the wasm-internal ABI, regardless of the
32 * platform ABI conventions, though see below about InstanceReg or HeapReg.
34 * We return the last return value in the first return register, according to
35 * platform ABI conventions. If there is more than one return value, an area is
36 * allocated in the caller's frame to receive the other return values, and the
37 * address of this area is passed to the callee as the last argument. Return
38 * values except the last are stored in ascending order within this area. Also
39 * see below about alignment of this area and the values in it.
41 * When a function is entered, there are two incoming register values in
42 * addition to the function's declared parameters: InstanceReg must have the
43 * correct instance pointer, and HeapReg the correct memoryBase, for the
44 * function. (On x86-32 there is no HeapReg.) From the instance we can get to
45 * the JSContext, the instance, the MemoryBase, and many other things. The
46 * instance maps one-to-one with an instance.
48 * HeapReg and InstanceReg are not parameters in the usual sense, nor are they
49 * callee-saves registers. Instead they constitute global register state, the
50 * purpose of which is to bias the call ABI in favor of intra-instance calls,
51 * the predominant case where the caller and the callee have the same
52 * InstanceReg and HeapReg values.
54 * With this global register state, literally no work needs to take place to
55 * save and restore the instance and MemoryBase values across intra-instance
56 * call boundaries.
58 * For inter-instance calls, in contrast, there must be an instance switch at
59 * the call boundary: Before the call, the callee's instance must be loaded
60 * (from a closure or from the import table), and from the instance we load the
61 * callee's MemoryBase, the realm, and the JSContext. The caller's and callee's
62 * instance values must be stored into the frame (to aid unwinding), the
63 * callee's realm must be stored into the JSContext, and the callee's instance
64 * and MemoryBase values must be moved to appropriate registers. After the
65 * call, the caller's instance must be loaded, and from it the caller's
66 * MemoryBase and realm, and the JSContext. The realm must be stored into the
67 * JSContext and the caller's instance and MemoryBase values must be moved to
68 * appropriate registers.
70 * Direct calls to functions within the same module are always intra-instance,
71 * while direct calls to imported functions are always inter-instance. Indirect
72 * calls -- call_indirect in the MVP, future call_ref and call_funcref -- may or
73 * may not be intra-instance.
75 * call_indirect, and future call_funcref, also pass a signature value in a
76 * register (even on x86-32), this is a small integer or a pointer value
77 * denoting the caller's expected function signature. The callee must compare
78 * it to the value or pointer that denotes its actual signature, and trap on
79 * mismatch.
81 * This is what the stack looks like during a call, after the callee has
82 * completed the prologue:
84 * | |
85 * +-----------------------------------+ <-+
86 * | ... | |
87 * | Caller's private frame | |
88 * +-----------------------------------+ |
89 * | Multi-value return (optional) | |
90 * | ... | |
91 * +-----------------------------------+ |
92 * | Stack args (optional) | |
93 * | ... | |
94 * +-----------------------------------+ -+|
95 * | Caller instance slot | \
96 * | Callee instance slot | | \
97 * +-----------------------------------+ | \
98 * | Shadowstack area (Win64) | | wasm::FrameWithInstances
99 * | (32 bytes) | | /
100 * +-----------------------------------+ | / <= SP "Before call"
101 * | Return address | // <= SP "After call"
102 * | Saved FP ----|--+/
103 * +-----------------------------------+ -+ <= FP (a wasm::Frame*)
104 * | DebugFrame, Locals, spills, etc |
105 * | (i.e., callee's private frame) |
106 * | .... |
107 * +-----------------------------------+ <= SP
109 * The FrameWithInstances is a struct with four fields: the saved FP, the return
110 * address, and the two instance slots; the shadow stack area is there only on
111 * Win64 and is unused by wasm but is part of the native ABI, with which the
112 * wasm ABI is mostly compatible. The slots for caller and callee instance are
113 * only populated by the instance switching code in inter-instance calls so that
114 * stack unwinding can keep track of the correct instance value for each frame,
115 * the instance not being obtainable from anywhere else. Nothing in the frame
116 * itself indicates directly whether the instance slots are valid - for that,
117 * the return address must be used to look up a CallSite structure that carries
118 * that information.
120 * The stack area above the return address is owned by the caller, which may
121 * deallocate the area on return or choose to reuse it for subsequent calls.
122 * (The baseline compiler allocates and frees the stack args area and the
123 * multi-value result area per call. Ion reuses the areas and allocates them as
124 * part of the overall activation frame when the procedure is entered; indeed,
125 * the multi-value return area can be anywhere within the caller's private
126 * frame, not necessarily directly above the stack args.)
128 * If the stack args area contain references, it is up to the callee's stack map
129 * to name the locations where those references exist, and the caller's stack
130 * map must not (redundantly) name those locations. (The callee's ownership of
131 * this area will be crucial for making tail calls work, as the types of the
132 * locations can change if the callee makes a tail call.) If pointer values are
133 * spilled by anyone into the Shadowstack area they will not be traced.
135 * References in the multi-return area are covered by the caller's map, as these
136 * slots outlive the call.
138 * The address "Before call", ie the part of the FrameWithInstances above the
139 * Frame, must be aligned to WasmStackAlignment, and everything follows from
140 * that, with padding inserted for alignment as required for stack arguments. In
141 * turn WasmStackAlignment is at least as large as the largest parameter type.
143 * The address of the multiple-results area is currently 8-byte aligned by Ion
144 * and its alignment in baseline is uncertain, see bug 1747787. Result values
145 * are stored packed within the area in fields whose size is given by
146 * ResultStackSize(ValType), this breaks alignment too. This all seems
147 * underdeveloped.
149 * In the wasm-internal ABI, the ARM64 PseudoStackPointer (PSP) is garbage on
150 * entry but must be synced with the real SP at the point the function returns.
153 * The Wasm Builtin ABIs.
155 * Also see `[SMDOC] Process-wide builtin thunk set` in WasmBuiltins.cpp.
157 * The *Wasm-builtin ABIs* comprise the ABIs used when wasm makes calls directly
158 * to the C++ runtime (but not to the JS interpreter), including instance
159 * methods, helpers for operations such as 64-bit division on 32-bit systems,
160 * allocation and writer barriers, conversions to/from JS values, special
161 * fast-path JS imports, and trap handling.
163 * The callee of a builtin call will always assume the C/C++ ABI. Therefore
164 * every volatile (caller-saves) register that wasm uses must be saved across
165 * the call, the stack must be aligned as for a C/C++-ABI call before the call,
166 * and any ABI registers the callee expect to have specific values must be set
167 * up (eg the frame pointer, if the C/C++ ABI assumes it is set).
169 * Most builtin calls are straightforward: the wasm caller knows that it is
170 * performing a call, and so it saves live registers, moves arguments into ABI
171 * locations, etc, before calling. Abstractions in the masm make sure to pass
172 * the instance pointer to an instance "method" call and to restore the
173 * InstanceReg and HeapReg after the call. In these straightforward cases,
174 * calling the builtin additionally amounts to:
176 * - exiting the wasm activation
177 * - adjusting parameter values to account for platform weirdness (FP arguments
178 * are handled differently in the C/C++ ABIs on ARM and x86-32 than in the
179 * Wasm ABI)
180 * - copying stack arguments into place for the C/C++ ABIs
181 * - making the call
182 * - adjusting the return values on return
183 * - re-entering the wasm activation and returning to the wasm caller
185 * The steps above are performed by the *builtin thunk* for the builtin and the
186 * builtin itself is said to be *thunked*. Going via the thunk is simple and,
187 * except for always having to copy stack arguments on x86-32 and the extra call
188 * in the thunk, close to as fast as we can make it without heroics. Except for
189 * the arithmetic helpers on 32-bit systems, most builtins are rarely used, are
190 * asm.js-specific, or are expensive anyway, and the overhead of the extra call
191 * doesn't matter.
193 * A few builtins for special purposes are *unthunked* and fall into two
194 * classes: they would normally be thunked but are used in circumstances where
195 * the VM is in an unusual state; or they do their work within the activation.
197 * In the former class, we find the debug trap handler, which must preserve all
198 * live registers because it is called in contexts where live registers have not
199 * been saved; argument coercion functions, which are called while a call frame
200 * is being built for a JS->Wasm or Wasm->JS call; and other routines that have
201 * special needs for constructing the call. These all exit the activation, but
202 * handle the exit specially.
204 * In the latter class, we find two functions that abandon the VM state and
205 * unwind the activation, HandleThrow and HandleTrap; and some debug print
206 * functions that do not affect the VM state at all.
208 * To summarize, when wasm calls a builtin thunk the stack will end up looking
209 * like this from within the C++ code:
211 * | |
212 * +-------------------------+
213 * | Wasm frame |
214 * +-------------------------+
215 * | Thunk frame (exit) |
216 * +-------------------------+
217 * | Builtin frame (C++) |
218 * +-------------------------+ <= SP
220 * There is an assumption in the profiler (in initFromExitFP) that an exit has
221 * left precisely one frame on the stack for the thunk itself. There may be
222 * additional assumptions elsewhere, not yet found.
224 * Very occasionally, Wasm will call C++ without going through the builtin
225 * thunks, and this can be a source of problems. The one case I know about
226 * right now is that the JS pre-barrier filtering code is called directly from
227 * Wasm, see bug 1464157.
230 * Wasm stub ABIs.
232 * Also see `[SMDOC] Exported wasm functions and the jit-entry stubs` in
233 * WasmJS.cpp.
235 * The "stub ABIs" are not properly speaking ABIs themselves, but ABI
236 * converters. An "entry" stub calls in to wasm and an "exit" stub calls out
237 * from wasm. The entry stubs must convert from whatever data formats the
238 * caller has to wasm formats (and in the future must provide some kind of type
239 * checking for pointer types); the exit stubs convert from wasm formats to the
240 * callee's expected format.
242 * There are different entry paths from the JS interpreter (using the C++ ABI
243 * and data formats) and from jitted JS code (using the JIT ABI and data
244 * formats); indeed there is a "normal" JitEntry path ("JitEntry") that will
245 * perform argument and return value conversion, and the "fast" JitEntry path
246 * ("DirectCallFromJit") that is only used when it is known that the JIT will
247 * only pass and receive wasm-compatible data and no conversion is needed.
249 * Similarly, there are different exit paths to the interpreter (using the C++
250 * ABI and data formats) and to JS JIT code (using the JIT ABI and data
251 * formats). Also, builtin calls described above are themselves a type of exit,
252 * and builtin thunks are properly a type of exit stub.
254 * Data conversions are difficult because the VM is in an intermediate state
255 * when they happen, we want them to be fast when possible, and some conversions
256 * can re-enter both JS code and wasm code.
259 #ifndef wasm_frame_h
260 #define wasm_frame_h
262 #include "mozilla/Assertions.h"
264 #include <stddef.h>
265 #include <stdint.h>
266 #include <type_traits>
268 #include "jit/Registers.h" // For js::jit::ShadowStackSpace
270 namespace js {
271 namespace wasm {
273 class Instance;
275 // Bit tag set when exiting wasm code in JitActivation's exitFP.
276 constexpr uintptr_t ExitFPTag = 0x1;
278 // wasm::Frame represents the bytes pushed by the call instruction and the
279 // fixed prologue generated by wasm::GenerateCallablePrologue.
281 // Across all architectures it is assumed that, before the call instruction, the
282 // stack pointer is WasmStackAlignment-aligned. Thus after the prologue, and
283 // before the function has made its stack reservation, the stack alignment is
284 // sizeof(Frame) % WasmStackAlignment.
286 // During MacroAssembler code generation, the bytes pushed after the wasm::Frame
287 // are counted by masm.framePushed. Thus, the stack alignment at any point in
288 // time is (sizeof(wasm::Frame) + masm.framePushed) % WasmStackAlignment.
290 class Frame {
291 // See GenerateCallableEpilogue for why this must be
292 // the first field of wasm::Frame (in a downward-growing stack).
293 // It's either the caller's Frame*, for wasm callers, or the JIT caller frame
294 // plus a tag otherwise.
295 uint8_t* callerFP_;
297 // The return address pushed by the call (in the case of ARM/MIPS the return
298 // address is pushed by the first instruction of the prologue).
299 void* returnAddress_;
301 public:
302 static constexpr uint32_t callerFPOffset() {
303 return offsetof(Frame, callerFP_);
305 static constexpr uint32_t returnAddressOffset() {
306 return offsetof(Frame, returnAddress_);
309 uint8_t* returnAddress() const {
310 return reinterpret_cast<uint8_t*>(returnAddress_);
313 void** addressOfReturnAddress() {
314 return reinterpret_cast<void**>(&returnAddress_);
317 uint8_t* rawCaller() const { return callerFP_; }
319 Frame* wasmCaller() const { return reinterpret_cast<Frame*>(callerFP_); }
321 uint8_t* jitEntryCaller() const { return callerFP_; }
323 static const Frame* fromUntaggedWasmExitFP(const void* savedFP) {
324 MOZ_ASSERT(!isExitFP(savedFP));
325 return reinterpret_cast<const Frame*>(savedFP);
328 static bool isExitFP(const void* fp) {
329 return reinterpret_cast<uintptr_t>(fp) & ExitFPTag;
332 static uint8_t* untagExitFP(const void* fp) {
333 MOZ_ASSERT(isExitFP(fp));
334 return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) &
335 ~ExitFPTag);
338 static uint8_t* addExitFPTag(const Frame* fp) {
339 MOZ_ASSERT(!isExitFP(fp));
340 return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) |
341 ExitFPTag);
345 static_assert(!std::is_polymorphic_v<Frame>, "Frame doesn't need a vtable.");
346 static_assert(sizeof(Frame) == 2 * sizeof(void*),
347 "Frame is a two pointer structure");
349 // Note that sizeof(FrameWithInstances) does not account for ShadowStackSpace.
350 // Use FrameWithInstances::sizeOf() if you are not incorporating
351 // ShadowStackSpace through other means (eg the ABIArgIter).
353 class FrameWithInstances : public Frame {
354 // `ShadowStackSpace` bytes will be allocated here on Win64, at higher
355 // addresses than Frame and at lower addresses than the instance fields.
357 // The instance area MUST be two pointers exactly.
358 Instance* calleeInstance_;
359 Instance* callerInstance_;
361 public:
362 Instance* calleeInstance() { return calleeInstance_; }
363 Instance* callerInstance() { return callerInstance_; }
365 constexpr static uint32_t sizeOf() {
366 return sizeof(wasm::FrameWithInstances) + js::jit::ShadowStackSpace;
369 constexpr static uint32_t sizeOfInstanceFields() {
370 return sizeof(wasm::FrameWithInstances) - sizeof(wasm::Frame);
373 constexpr static uint32_t sizeOfInstanceFieldsAndShadowStack() {
374 return sizeOfInstanceFields() + js::jit::ShadowStackSpace;
377 constexpr static uint32_t calleeInstanceOffset() {
378 return offsetof(FrameWithInstances, calleeInstance_) +
379 js::jit::ShadowStackSpace;
382 constexpr static uint32_t calleeInstanceOffsetWithoutFrame() {
383 return calleeInstanceOffset() - sizeof(wasm::Frame);
386 constexpr static uint32_t callerInstanceOffset() {
387 return offsetof(FrameWithInstances, callerInstance_) +
388 js::jit::ShadowStackSpace;
391 constexpr static uint32_t callerInstanceOffsetWithoutFrame() {
392 return callerInstanceOffset() - sizeof(wasm::Frame);
396 static_assert(FrameWithInstances::calleeInstanceOffsetWithoutFrame() ==
397 js::jit::ShadowStackSpace,
398 "Callee instance stored right above the return address.");
399 static_assert(FrameWithInstances::callerInstanceOffsetWithoutFrame() ==
400 js::jit::ShadowStackSpace + sizeof(void*),
401 "Caller instance stored right above the callee instance.");
403 static_assert(FrameWithInstances::sizeOfInstanceFields() == 2 * sizeof(void*),
404 "There are only two additional slots");
406 #if defined(JS_CODEGEN_ARM64)
407 static_assert(sizeof(Frame) % 16 == 0, "frame is aligned");
408 #endif
410 } // namespace wasm
411 } // namespace js
413 #endif // wasm_frame_h