tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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