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:
3  *
4  * Copyright 2021 Mozilla Foundation
5  *
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
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  */
18 
19 #ifndef wasm_frame_h
20 #define wasm_frame_h
21 
22 #include <stdint.h>
23 #include <type_traits>
24 
25 #include "NamespaceImports.h"
26 
27 #include "wasm/WasmConstants.h"
28 #include "wasm/WasmValue.h"
29 
30 namespace js {
31 namespace wasm {
32 
33 struct TlsData;
34 
35 // Bit set as the lowest bit of a frame pointer, used in two different mutually
36 // exclusive situations:
37 // - either it's a low bit tag in a FramePointer value read from the
38 // Frame::callerFP of an inner wasm frame. This indicates the previous call
39 // frame has been set up by a JIT caller that directly called into a wasm
40 // function's body. This is only stored in Frame::callerFP for a wasm frame
41 // called from JIT code, and thus it can not appear in a JitActivation's
42 // exitFP.
43 // - or it's the low big tag set when exiting wasm code in JitActivation's
44 // exitFP.
45 
46 constexpr uintptr_t ExitOrJitEntryFPTag = 0x1;
47 
48 // wasm::Frame represents the bytes pushed by the call instruction and the
49 // fixed prologue generated by wasm::GenerateCallablePrologue.
50 //
51 // Across all architectures it is assumed that, before the call instruction, the
52 // stack pointer is WasmStackAlignment-aligned. Thus after the prologue, and
53 // before the function has made its stack reservation, the stack alignment is
54 // sizeof(Frame) % WasmStackAlignment.
55 //
56 // During MacroAssembler code generation, the bytes pushed after the wasm::Frame
57 // are counted by masm.framePushed. Thus, the stack alignment at any point in
58 // time is (sizeof(wasm::Frame) + masm.framePushed) % WasmStackAlignment.
59 
60 class Frame {
61   // See GenerateCallableEpilogue for why this must be
62   // the first field of wasm::Frame (in a downward-growing stack).
63   // It's either the caller's Frame*, for wasm callers, or the JIT caller frame
64   // plus a tag otherwise.
65   uint8_t* callerFP_;
66 
67   // The return address pushed by the call (in the case of ARM/MIPS the return
68   // address is pushed by the first instruction of the prologue).
69   void* returnAddress_;
70 
71  public:
callerFPOffset()72   static constexpr uint32_t callerFPOffset() {
73     return offsetof(Frame, callerFP_);
74   }
returnAddressOffset()75   static constexpr uint32_t returnAddressOffset() {
76     return offsetof(Frame, returnAddress_);
77   }
78 
returnAddress()79   uint8_t* returnAddress() const {
80     return reinterpret_cast<uint8_t*>(returnAddress_);
81   }
82 
addressOfReturnAddress()83   void** addressOfReturnAddress() {
84     return reinterpret_cast<void**>(&returnAddress_);
85   }
86 
rawCaller()87   uint8_t* rawCaller() const { return callerFP_; }
88 
wasmCaller()89   Frame* wasmCaller() const {
90     MOZ_ASSERT(!callerIsExitOrJitEntryFP());
91     return reinterpret_cast<Frame*>(callerFP_);
92   }
93 
callerIsExitOrJitEntryFP()94   bool callerIsExitOrJitEntryFP() const {
95     return isExitOrJitEntryFP(callerFP_);
96   }
97 
jitEntryCaller()98   uint8_t* jitEntryCaller() const { return toJitEntryCaller(callerFP_); }
99 
fromUntaggedWasmExitFP(const void * savedFP)100   static const Frame* fromUntaggedWasmExitFP(const void* savedFP) {
101     MOZ_ASSERT(!isExitOrJitEntryFP(savedFP));
102     return reinterpret_cast<const Frame*>(savedFP);
103   }
104 
isExitOrJitEntryFP(const void * fp)105   static bool isExitOrJitEntryFP(const void* fp) {
106     return reinterpret_cast<uintptr_t>(fp) & ExitOrJitEntryFPTag;
107   }
108 
toJitEntryCaller(const void * fp)109   static uint8_t* toJitEntryCaller(const void* fp) {
110     MOZ_ASSERT(isExitOrJitEntryFP(fp));
111     return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) &
112                                       ~ExitOrJitEntryFPTag);
113   }
114 
addExitOrJitEntryFPTag(const Frame * fp)115   static uint8_t* addExitOrJitEntryFPTag(const Frame* fp) {
116     MOZ_ASSERT(!isExitOrJitEntryFP(fp));
117     return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) |
118                                       ExitOrJitEntryFPTag);
119   }
120 };
121 
122 static_assert(!std::is_polymorphic_v<Frame>, "Frame doesn't need a vtable.");
123 static_assert(sizeof(Frame) == 2 * sizeof(void*),
124               "Frame is a two pointer structure");
125 
126 class FrameWithTls : public Frame {
127   TlsData* calleeTls_;
128   TlsData* callerTls_;
129 
130  public:
calleeTls()131   TlsData* calleeTls() { return calleeTls_; }
callerTls()132   TlsData* callerTls() { return callerTls_; }
133 
sizeWithoutFrame()134   constexpr static uint32_t sizeWithoutFrame() {
135     return sizeof(wasm::FrameWithTls) - sizeof(wasm::Frame);
136   }
137 
calleeTLSOffset()138   constexpr static uint32_t calleeTLSOffset() {
139     return offsetof(FrameWithTls, calleeTls_) - sizeof(wasm::Frame);
140   }
141 
callerTLSOffset()142   constexpr static uint32_t callerTLSOffset() {
143     return offsetof(FrameWithTls, callerTls_) - sizeof(wasm::Frame);
144   }
145 };
146 
147 static_assert(FrameWithTls::calleeTLSOffset() == 0u,
148               "Callee tls stored right above the return address.");
149 static_assert(FrameWithTls::callerTLSOffset() == sizeof(void*),
150               "Caller tls stored right above the callee tls.");
151 
152 static_assert(FrameWithTls::sizeWithoutFrame() == 2 * sizeof(void*),
153               "There are only two additional slots");
154 
155 #if defined(JS_CODEGEN_ARM64)
156 static_assert(sizeof(Frame) % 16 == 0, "frame is aligned");
157 #endif
158 
159 // A DebugFrame is a Frame with additional fields that are added after the
160 // normal function prologue by the baseline compiler. If a Module is compiled
161 // with debugging enabled, then all its code creates DebugFrames on the stack
162 // instead of just Frames. These extra fields are used by the Debugger API.
163 
164 class DebugFrame {
165   // The register results field.  Initialized only during the baseline
166   // compiler's return sequence to allow the debugger to inspect and
167   // modify the return values of a frame being debugged.
168   union SpilledRegisterResult {
169    private:
170     int32_t i32_;
171     int64_t i64_;
172     intptr_t ref_;
173     AnyRef anyref_;
174     float f32_;
175     double f64_;
176 #ifdef ENABLE_WASM_SIMD
177     V128 v128_;
178 #endif
179 #ifdef DEBUG
180     // Should we add a new value representation, this will remind us to update
181     // SpilledRegisterResult.
assertAllValueTypesHandled(ValType type)182     static inline void assertAllValueTypesHandled(ValType type) {
183       switch (type.kind()) {
184         case ValType::I32:
185         case ValType::I64:
186         case ValType::F32:
187         case ValType::F64:
188         case ValType::V128:
189         case ValType::Rtt:
190           return;
191         case ValType::Ref:
192           switch (type.refTypeKind()) {
193             case RefType::Func:
194             case RefType::Extern:
195             case RefType::Eq:
196             case RefType::TypeIndex:
197               return;
198           }
199       }
200     }
201 #endif
202   };
203   SpilledRegisterResult registerResults_[MaxRegisterResults];
204 
205   // The returnValue() method returns a HandleValue pointing to this field.
206   js::Value cachedReturnJSValue_;
207 
208   // If the function returns multiple results, this field is initialized
209   // to a pointer to the stack results.
210   void* stackResultsPointer_;
211 
212   // The function index of this frame. Technically, this could be derived
213   // given a PC into this frame (which could lookup the CodeRange which has
214   // the function index), but this isn't always readily available.
215   uint32_t funcIndex_;
216 
217   // Flags whose meaning are described below.
218   union Flags {
219     struct {
220       uint32_t observing : 1;
221       uint32_t isDebuggee : 1;
222       uint32_t prevUpToDate : 1;
223       uint32_t hasCachedSavedFrame : 1;
224       uint32_t hasCachedReturnJSValue : 1;
225       uint32_t hasSpilledRefRegisterResult : MaxRegisterResults;
226     };
227     uint32_t allFlags;
228   } flags_;
229 
230   // Avoid -Wunused-private-field warnings.
231  protected:
232 #if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_ARM) || \
233     defined(JS_CODEGEN_X86) || defined(__wasi__)
234   // See alignmentStaticAsserts().  For MIPS32, ARM32 and X86 DebugFrame is only
235   // 4-byte aligned, so we add another word to get up to 8-byte
236   // alignment.
237   uint32_t padding_;
238 #endif
239 #if defined(ENABLE_WASM_SIMD) && defined(JS_CODEGEN_ARM64)
240   uint64_t padding_;
241 #endif
242 
243  private:
244   // The Frame goes at the end since the stack grows down.
245   Frame frame_;
246 
247  public:
248   static DebugFrame* from(Frame* fp);
frame()249   Frame& frame() { return frame_; }
funcIndex()250   uint32_t funcIndex() const { return funcIndex_; }
251   Instance* instance() const;
252   GlobalObject* global() const;
253   bool hasGlobal(const GlobalObject* global) const;
254   JSObject* environmentChain() const;
255   bool getLocal(uint32_t localIndex, MutableHandleValue vp);
256 
257   // The return value must be written from the unboxed representation in the
258   // results union into cachedReturnJSValue_ by updateReturnJSValue() before
259   // returnValue() can return a Handle to it.
260 
hasCachedReturnJSValue()261   bool hasCachedReturnJSValue() const { return flags_.hasCachedReturnJSValue; }
262   [[nodiscard]] bool updateReturnJSValue(JSContext* cx);
263   HandleValue returnValue() const;
264   void clearReturnJSValue();
265 
266   // Once the debugger observes a frame, it must be notified via
267   // onLeaveFrame() before the frame is popped. Calling observe() ensures the
268   // leave frame traps are enabled. Both methods are idempotent so the caller
269   // doesn't have to worry about calling them more than once.
270 
271   void observe(JSContext* cx);
272   void leave(JSContext* cx);
273 
274   // The 'isDebugge' bit is initialized to false and set by the WebAssembly
275   // runtime right before a frame is exposed to the debugger, as required by
276   // the Debugger API. The bit is then used for Debugger-internal purposes
277   // afterwards.
278 
isDebuggee()279   bool isDebuggee() const { return flags_.isDebuggee; }
setIsDebuggee()280   void setIsDebuggee() { flags_.isDebuggee = true; }
unsetIsDebuggee()281   void unsetIsDebuggee() { flags_.isDebuggee = false; }
282 
283   // These are opaque boolean flags used by the debugger to implement
284   // AbstractFramePtr. They are initialized to false and not otherwise read or
285   // written by wasm code or runtime.
286 
prevUpToDate()287   bool prevUpToDate() const { return flags_.prevUpToDate; }
setPrevUpToDate()288   void setPrevUpToDate() { flags_.prevUpToDate = true; }
unsetPrevUpToDate()289   void unsetPrevUpToDate() { flags_.prevUpToDate = false; }
290 
hasCachedSavedFrame()291   bool hasCachedSavedFrame() const { return flags_.hasCachedSavedFrame; }
setHasCachedSavedFrame()292   void setHasCachedSavedFrame() { flags_.hasCachedSavedFrame = true; }
clearHasCachedSavedFrame()293   void clearHasCachedSavedFrame() { flags_.hasCachedSavedFrame = false; }
294 
hasSpilledRegisterRefResult(size_t n)295   bool hasSpilledRegisterRefResult(size_t n) const {
296     uint32_t mask = hasSpilledRegisterRefResultBitMask(n);
297     return (flags_.allFlags & mask) != 0;
298   }
299 
300   // DebugFrame is accessed directly by JIT code.
301 
offsetOfRegisterResults()302   static constexpr size_t offsetOfRegisterResults() {
303     return offsetof(DebugFrame, registerResults_);
304   }
offsetOfRegisterResult(size_t n)305   static constexpr size_t offsetOfRegisterResult(size_t n) {
306     MOZ_ASSERT(n < MaxRegisterResults);
307     return offsetOfRegisterResults() + n * sizeof(SpilledRegisterResult);
308   }
offsetOfCachedReturnJSValue()309   static constexpr size_t offsetOfCachedReturnJSValue() {
310     return offsetof(DebugFrame, cachedReturnJSValue_);
311   }
offsetOfStackResultsPointer()312   static constexpr size_t offsetOfStackResultsPointer() {
313     return offsetof(DebugFrame, stackResultsPointer_);
314   }
offsetOfFlags()315   static constexpr size_t offsetOfFlags() {
316     return offsetof(DebugFrame, flags_);
317   }
hasSpilledRegisterRefResultBitMask(size_t n)318   static constexpr uint32_t hasSpilledRegisterRefResultBitMask(size_t n) {
319     MOZ_ASSERT(n < MaxRegisterResults);
320     union Flags flags = {.allFlags = 0};
321     flags.hasSpilledRefRegisterResult = 1 << n;
322     MOZ_ASSERT(flags.allFlags != 0);
323     return flags.allFlags;
324   }
offsetOfFuncIndex()325   static constexpr size_t offsetOfFuncIndex() {
326     return offsetof(DebugFrame, funcIndex_);
327   }
offsetOfFrame()328   static constexpr size_t offsetOfFrame() {
329     return offsetof(DebugFrame, frame_);
330   }
331 
332   // DebugFrames are aligned to 8-byte aligned, allowing them to be placed in
333   // an AbstractFramePtr.
334 
335   static const unsigned Alignment = 8;
336   static void alignmentStaticAsserts();
337 };
338 
339 }  // namespace wasm
340 }  // namespace js
341 
342 #endif  // wasm_frame_h
343