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