WasmFrameIter.h (18160B)
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 2014 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_iter_h 20 #define wasm_frame_iter_h 21 22 #include "js/ColumnNumber.h" // JS::TaggedColumnNumberOneOrigin 23 #include "js/ProfilingFrameIterator.h" 24 #include "js/TypeDecls.h" 25 26 #include "wasm/WasmCode.h" // For CodeBlockKind 27 #include "wasm/WasmCodegenTypes.h" // for BytecodeOffsetSpan 28 29 namespace js { 30 31 namespace jit { 32 class JitActivation; 33 class MacroAssembler; 34 struct Register; 35 enum class FrameType; 36 } // namespace jit 37 38 namespace wasm { 39 40 class CallIndirectId; 41 class Code; 42 class CodeRange; 43 class DebugFrame; 44 class Instance; 45 class Instance; 46 47 struct CallableOffsets; 48 struct ImportOffsets; 49 struct FuncOffsets; 50 struct Offsets; 51 class Frame; 52 class FrameWithInstances; 53 54 using RegisterState = JS::ProfilingFrameIterator::RegisterState; 55 56 // Iterates over a linear group of wasm frames of a single wasm JitActivation, 57 // called synchronously from C++ in the wasm thread. It will stop at the first 58 // frame that is not of the same kind, or at the end of an activation. 59 // 60 // If you want to handle every kind of frames (including JS jit frames), use 61 // JitFrameIter. 62 63 class WasmFrameIter { 64 // 65 // State that is constant for the entire wasm activation 66 // 67 68 jit::JitActivation* activation_ = nullptr; 69 bool isLeavingFrames_ = false; 70 bool enableInlinedFrames_ = false; 71 72 // 73 // State that is updated for every frame 74 // 75 76 const Code* code_ = nullptr; 77 uint32_t funcIndex_ = UINT32_MAX; 78 uint32_t lineOrBytecode_ = UINT32_MAX; 79 BytecodeOffsetSpan inlinedCallerOffsets_; 80 Frame* fp_ = nullptr; 81 Instance* instance_ = nullptr; 82 // The address of the next instruction that will execute in this frame, once 83 // control returns to this frame. 84 uint8_t* resumePCinCurrentFrame_ = nullptr; 85 // See wasm::TrapData for more information. 86 bool failedUnwindSignatureMismatch_ = false; 87 // Whether the current frame is on a different stack from the previous stack. 88 bool currentFrameStackSwitched_ = false; 89 90 // 91 // State that is found after we've unwound the entire wasm activation 92 // 93 94 // The address of the final wasm::Frame::returnAddress_. Only found once 95 // we've iterated over all wasm frames. 96 void** unwoundAddressOfReturnAddress_ = nullptr; 97 // The value of the final wasm::Frame::callerFP_. Only found once we've 98 // iterated over all wasm frames. 99 uint8_t* unwoundCallerFP_ = nullptr; 100 // Whether unwoundCallerFP_ is a JS JIT exit frame. 101 bool unwoundCallerFPIsJSJit_ = false; 102 103 // Pop the frame. `isLeavingFrame` indicates if we should update the 104 // JitActivation so that any other frame iteration doesn't see the frame we 105 // just popped. This is normally equal to `isLeavingFrames_`, but is 106 // different for the very first `popFrame` of a wasm exit frame. 107 void popFrame(bool isLeavingFrame); 108 109 public: 110 // See comment above this class definition. 111 explicit WasmFrameIter(jit::JitActivation* activation, Frame* fp = nullptr); 112 113 // Iterate over frames from a known starting (fp, ra). 114 WasmFrameIter(FrameWithInstances* fp, void* returnAddress); 115 116 // Cause this WasmFrameIter to remove every popped from its JitActivation so 117 // that any other frame iteration will not see it. 118 // 119 // This is a method instead of a parameter to the constructor because it 120 // needs to be enabled after FrameIter has already created the WasmFrameIter. 121 void setIsLeavingFrames() { 122 MOZ_ASSERT(activation_); 123 MOZ_ASSERT(!isLeavingFrames_); 124 isLeavingFrames_ = true; 125 } 126 127 // Visit inlined frames instead of only 'physical' frames. This is required 128 // to access source information. 129 void enableInlinedFrames() { enableInlinedFrames_ = true; } 130 131 // 132 // Iteration methods 133 // 134 135 void operator++(); 136 bool done() const; 137 138 // 139 // Source information about the current frame 140 // 141 142 bool hasSourceInfo() const; 143 const char* filename() const; 144 const char16_t* displayURL() const; 145 bool mutedErrors() const; 146 JSAtom* functionDisplayAtom() const; 147 unsigned lineOrBytecode() const; 148 uint32_t funcIndex() const; 149 unsigned computeLine(JS::TaggedColumnNumberOneOrigin* column) const; 150 151 // 152 // Physical information about the current (not inlined) frame 153 // 154 155 // The instance that the function for this wasm frame is from. 156 Instance* instance() const { 157 MOZ_ASSERT(!done()); 158 // Getting the instance always works even with inlining because we never 159 // inline across instances. 160 return instance_; 161 } 162 163 // The wasm function frame pointer. 164 Frame* frame() const { 165 MOZ_ASSERT(!done()); 166 MOZ_ASSERT(!enableInlinedFrames_); 167 return fp_; 168 } 169 170 // Returns the address of the next instruction that will execute in this 171 // frame, once control returns to this frame. 172 uint8_t* resumePCinCurrentFrame() const { 173 MOZ_ASSERT(!done()); 174 MOZ_ASSERT(!enableInlinedFrames_); 175 return resumePCinCurrentFrame_; 176 } 177 178 // Whether the current frame is on a different stack from the previous frame. 179 bool currentFrameStackSwitched() const { 180 MOZ_ASSERT(!done()); 181 return currentFrameStackSwitched_; 182 } 183 184 // 185 // Debug information about the current frame 186 // 187 188 // Whether this frame has a debuggable wasm frame. 189 bool debugEnabled() const; 190 191 // The debuggable wasm frame, if any. 192 DebugFrame* debugFrame() const; 193 194 // 195 // Information for after we've unwound the entire wasm activation 196 // 197 198 // The address of the final wasm::Frame::returnAddress_. 199 void** unwoundAddressOfReturnAddress() const { 200 MOZ_ASSERT(done()); 201 MOZ_ASSERT(unwoundAddressOfReturnAddress_); 202 return unwoundAddressOfReturnAddress_; 203 } 204 205 // The value of the final wasm::Frame::callerFP_. 206 uint8_t* unwoundCallerFP() const { 207 MOZ_ASSERT(done()); 208 MOZ_ASSERT(unwoundCallerFP_); 209 return unwoundCallerFP_; 210 } 211 212 // Whether 'unwoundCallerFP' is for a JS JIT frame or not. 213 bool unwoundCallerFPIsJSJit() const { 214 MOZ_ASSERT(done()); 215 MOZ_ASSERT_IF(unwoundCallerFPIsJSJit_, unwoundCallerFP_); 216 return unwoundCallerFPIsJSJit_; 217 } 218 }; 219 220 enum class SymbolicAddress; 221 222 // An ExitReason describes the possible reasons for leaving compiled wasm 223 // code or the state of not having left compiled wasm code 224 // (ExitReason::None). It is either a known reason, or a enumeration to a native 225 // function that is used for better display in the profiler. 226 class ExitReason { 227 public: 228 enum class Fixed : uint32_t { 229 None, // default state, the pc is in wasm code 230 ImportJit, // fast-path call directly into JIT code 231 ImportInterp, // slow-path call into C++ Invoke() 232 BuiltinNative, // fast-path call directly into native C++ code 233 Trap, // call to trap handler 234 DebugStub, // call to debug stub 235 RequestTierUp // call to request tier-2 compilation 236 }; 237 238 private: 239 uint32_t payload_; 240 241 ExitReason() : ExitReason(Fixed::None) {} 242 243 public: 244 MOZ_IMPLICIT ExitReason(Fixed exitReason) 245 : payload_(0x0 | (uint32_t(exitReason) << 1)) { 246 MOZ_ASSERT(isFixed()); 247 MOZ_ASSERT_IF(isNone(), payload_ == 0); 248 } 249 250 explicit ExitReason(SymbolicAddress sym) 251 : payload_(0x1 | (uint32_t(sym) << 1)) { 252 MOZ_ASSERT(uint32_t(sym) <= (UINT32_MAX << 1), "packing constraints"); 253 MOZ_ASSERT(!isFixed()); 254 } 255 256 static ExitReason Decode(uint32_t payload) { 257 ExitReason reason; 258 reason.payload_ = payload; 259 return reason; 260 } 261 262 static ExitReason None() { return ExitReason(ExitReason::Fixed::None); } 263 264 bool isFixed() const { return (payload_ & 0x1) == 0; } 265 bool isNone() const { return isFixed() && fixed() == Fixed::None; } 266 bool isNative() const { 267 return !isFixed() || fixed() == Fixed::BuiltinNative; 268 } 269 270 uint32_t encode() const { return payload_; } 271 Fixed fixed() const { 272 MOZ_ASSERT(isFixed()); 273 return Fixed(payload_ >> 1); 274 } 275 SymbolicAddress symbolic() const { 276 MOZ_ASSERT(!isFixed()); 277 return SymbolicAddress(payload_ >> 1); 278 } 279 }; 280 281 // Iterates over the frames of a single wasm JitActivation, given an 282 // asynchronously-profiled thread's state. 283 class ProfilingFrameIterator { 284 public: 285 enum class Category { 286 Baseline, 287 Ion, 288 Other, 289 }; 290 291 private: 292 const Code* code_; 293 const CodeRange* codeRange_; 294 Category category_; 295 uint8_t* callerFP_; 296 void* callerPC_; 297 void* stackAddress_; 298 // See JS::ProfilingFrameIterator::endStackAddress_ comment. 299 void* endStackAddress_ = nullptr; 300 uint8_t* unwoundJitCallerFP_; 301 ExitReason exitReason_; 302 303 void initFromExitFP(const Frame* fp); 304 305 public: 306 ProfilingFrameIterator(); 307 308 // Start unwinding at a non-innermost activation that has necessarily been 309 // exited from wasm code (and thus activation.hasWasmExitFP). 310 explicit ProfilingFrameIterator(const jit::JitActivation& activation); 311 312 // Start unwinding at a group of wasm frames after unwinding an inner group 313 // of JSJit frames. 314 explicit ProfilingFrameIterator(const Frame* fp); 315 316 // Start unwinding at the innermost activation given the register state when 317 // the thread was suspended. 318 ProfilingFrameIterator(const jit::JitActivation& activation, 319 const RegisterState& state); 320 321 void operator++(); 322 323 bool done() const { 324 MOZ_ASSERT_IF(!exitReason_.isNone(), codeRange_); 325 return !codeRange_; 326 } 327 328 void* stackAddress() const { 329 MOZ_ASSERT(!done()); 330 return stackAddress_; 331 } 332 uint8_t* unwoundJitCallerFP() const { 333 MOZ_ASSERT(done()); 334 return unwoundJitCallerFP_; 335 } 336 const char* label() const; 337 338 Category category() const; 339 340 void* endStackAddress() const { return endStackAddress_; } 341 342 // Convert a CodeBlockKind to a Category. 343 static ProfilingFrameIterator::Category categoryFromCodeBlock( 344 CodeBlockKind kind) { 345 if (kind == CodeBlockKind::BaselineTier) { 346 return ProfilingFrameIterator::Category::Baseline; 347 } 348 if (kind == CodeBlockKind::OptimizedTier) { 349 return ProfilingFrameIterator::Category::Ion; 350 } 351 return ProfilingFrameIterator::Category::Other; 352 } 353 }; 354 355 // Prologue/epilogue code generation 356 357 void LoadActivation(jit::MacroAssembler& masm, jit::Register instance, 358 jit::Register dest); 359 void SetExitFP(jit::MacroAssembler& masm, ExitReason reason, 360 jit::Register activation, jit::Register scratch); 361 void ClearExitFP(jit::MacroAssembler& masm, jit::Register activation); 362 363 #ifdef ENABLE_WASM_JSPI 364 // [SMDOC] Wasm dynamic stack switches on 'exit' 365 // 366 // The SpiderMonkey codebase and embedders, shouldn't run on wasm suspendable 367 // stacks. Some code theoretically could work okay on an alternative stack, but 368 // we want to be conservative and not assume that. This gives us flexibility to 369 // use smaller stacks than the main stack and not worry about stack overflow. 370 // 371 // To ensure this, all wasm 'exits' from JIT to the VM are instrumented to 372 // perform a dynamic check and switch to the main stack if they are currently 373 // running on a wasm stack. 374 // 375 // This is done in the prologue of the exit, and reversed in the epilogue. 376 // 377 // If we're running on a suspendable stack, we switch SP to the main stack's SP, 378 // but keep the FP pointing at the original FP on the incoming stack: 379 // 380 // Suspendable Stack 381 // ┌────────────────┐ 382 // │ Caller Args │ 383 // ├────────────────┤ 384 // │ wasm::Frame │ 385 // └────────────────┘◄───── FP 386 // 387 // SP 388 // Main Stack │ 389 // ┌────────────────┐ │ 390 // │ Previous │ │ 391 // │ Frames │ │ 392 // ├────────────────┤ │ 393 // │ │ │ 394 // │ framePushed() │ │ 395 // │ for Exit Stub │ │ 396 // │ │ │ 397 // └────────────────┘◄───────┘ 398 // 399 // If we're not running on a suspendable stack, nothing is done at all and 400 // SP/FP are unchanged: 401 // 402 // Main Stack 403 // ┌────────────────┐ 404 // │ Caller Args │ 405 // ├────────────────┤ 406 // │ wasm::Frame │ 407 // ├────────────────┤◄───── FP 408 // │ │ SP 409 // │ framePushed() │ │ 410 // │ for exit stub │ │ 411 // │ │ │ 412 // └────────────────┘◄───────┘ 413 // 414 // This 'split' function body lets the function still address all the incoming 415 // arguments through FP, and it's own 'framePushed' through SP. 416 // 417 // However this means the SP/FP are no longer guaranteed to be contiguous (they 418 // are in the main stack case, but we don't know that statically). So the 419 // function body must not access the original frame or incoming arguments 420 // through SP, or the 'framePushed' area through FP. 421 void GenerateExitPrologueMainStackSwitch(jit::MacroAssembler& masm, 422 jit::Register instance, 423 jit::Register scratch1, 424 jit::Register scratch2, 425 jit::Register scratch3); 426 427 // Generate the dynamic switch back to the wasm suspendable stack we originally 428 // were on. See "Wasm dynamic stack switches on 'exit'" for more information. 429 // 430 // NOTE: this doesn't actually switch SP back to the original SP. The caller 431 // must do that through some method, such as setting SP := FP. 432 void GenerateExitEpilogueMainStackReturn(jit::MacroAssembler& masm, 433 jit::Register instance, 434 jit::Register activationAndScratch1, 435 jit::Register scratch2); 436 #endif 437 438 // Generate an 'exit' prologue. 439 // 440 // This will exit the JitActivation, allowing arbitrary code to run. The 441 // `reason` will be noted on the JitActivation for any future stack iteration. 442 // 443 // If `switchToMainStack` is true, the prologue will check if a suspendable 444 // stack is active, and if so switch the stack to the main stack. 445 // 446 // In this case, the body of the exit function will have a 'split' sp/fp where 447 // the fp points at the wasm::Frame on the suspendable stack and the sp points 448 // to the main stack. See "Wasm dynamic stack switches on 'exit'" above for more 449 // information and a diagram. 450 // 451 // `framePushedPreSwitch` will be reserved on the original stack, and 452 // `framePushedPostSwitch` will be reserved on the final stack (either the 453 // original stack, or the main stack if there is a switch). 454 void GenerateExitPrologue(jit::MacroAssembler& masm, ExitReason reason, 455 bool switchToMainStack, unsigned framePushedPreSwitch, 456 unsigned framePushedPostSwitch, 457 CallableOffsets* offsets); 458 // Generate an 'exit' epilogue that is the inverse of 459 // wasm::GenerateExitPrologue. 460 void GenerateExitEpilogue(jit::MacroAssembler& masm, ExitReason reason, 461 bool switchToMainStack, CallableOffsets* offsets); 462 463 // Generate the most minimal possible prologue/epilogue: `push FP; FP := SP` 464 // and `pop FP; return` respectively. 465 void GenerateMinimalPrologue(jit::MacroAssembler& masm, uint32_t* entry); 466 void GenerateMinimalEpilogue(jit::MacroAssembler& masm, uint32_t* ret); 467 468 void GenerateJitExitPrologue(jit::MacroAssembler& masm, uint32_t fallbackOffset, 469 ImportOffsets* offsets); 470 void GenerateJitExitEpilogue(jit::MacroAssembler& masm, 471 CallableOffsets* offsets); 472 473 void GenerateJitEntryPrologue(jit::MacroAssembler& masm, 474 CallableOffsets* offsets); 475 void GenerateJitEntryEpilogue(jit::MacroAssembler& masm, 476 CallableOffsets* offsets); 477 478 void GenerateFunctionPrologue(jit::MacroAssembler& masm, 479 const CallIndirectId& callIndirectId, 480 const mozilla::Maybe<uint32_t>& tier1FuncIndex, 481 FuncOffsets* offsets); 482 void GenerateFunctionEpilogue(jit::MacroAssembler& masm, unsigned framePushed, 483 FuncOffsets* offsets); 484 485 // Iterates through frames for either possible cross-instance call or an entry 486 // stub to obtain instance that corresponds to the passed fp. 487 const Instance* GetNearestEffectiveInstance(const Frame* fp); 488 Instance* GetNearestEffectiveInstance(Frame* fp); 489 490 // Describes register state and associated code at a given call frame. 491 492 struct UnwindState { 493 uint8_t* fp; 494 void* pc; 495 const Code* code; 496 const CodeRange* codeRange; 497 UnwindState() : fp(nullptr), pc(nullptr), code(nullptr), codeRange(nullptr) {} 498 }; 499 500 // Ensures the register state at a call site is consistent: pc must be in the 501 // code range of the code described by fp. This prevents issues when using 502 // the values of pc/fp, especially at call sites boundaries, where the state 503 // hasn't fully transitioned from the caller's to the callee's. 504 // 505 // unwoundCaller is set to true if we were in a transitional state and had to 506 // rewind to the caller's frame instead of the current frame. 507 // 508 // Returns true if it was possible to get to a clear state, or false if the 509 // frame should be ignored. 510 511 bool StartUnwinding(const RegisterState& registers, UnwindState* unwindState, 512 bool* unwoundCaller); 513 514 } // namespace wasm 515 } // namespace js 516 517 #endif // wasm_frame_iter_h