tor-browser

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

JitActivation.cpp (9922B)


      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 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "vm/JitActivation.h"
      8 
      9 #include "mozilla/Assertions.h"  // MOZ_ASSERT, MOZ_RELEASE_ASSERT
     10 
     11 #include <stddef.h>  // size_t
     12 #include <stdint.h>  // uint8_t, uint32_t
     13 #include <utility>   // std::move
     14 
     15 #include "debugger/DebugAPI.h"        // js::DebugAPI
     16 #include "jit/Invalidation.h"         // js::jit::Invalidate
     17 #include "jit/JSJitFrameIter.h"       // js::jit::InlineFrameIterator
     18 #include "jit/RematerializedFrame.h"  // js::jit::RematerializedFrame
     19 #include "js/AllocPolicy.h"           // js::ReportOutOfMemory
     20 #include "vm/EnvironmentObject.h"     // js::DebugEnvironments
     21 #include "vm/JSContext.h"             // JSContext
     22 #include "vm/Realm.h"                 // js::AutoRealmUnchecked
     23 #include "wasm/WasmCode.h"            // js::wasm::Code
     24 #include "wasm/WasmConstants.h"       // js::wasm::Trap
     25 #include "wasm/WasmFrameIter.h"  // js::wasm::{RegisterState,StartUnwinding,UnwindState}
     26 #include "wasm/WasmInstance.h"  // js::wasm::Instance
     27 #include "wasm/WasmProcess.h"   // js::wasm::LookupCode
     28 
     29 #include "vm/Realm-inl.h"  // js::~AutoRealm
     30 
     31 class JS_PUBLIC_API JSTracer;
     32 
     33 js::jit::JitActivation::JitActivation(JSContext* cx)
     34    : Activation(cx, Jit),
     35      packedExitFP_(nullptr),
     36      encodedWasmExitReason_(0),
     37 #ifdef ENABLE_WASM_JSPI
     38      wasmExitSuspender_(cx, nullptr),
     39 #endif
     40      prevJitActivation_(cx->jitActivation),
     41      ionRecovery_(cx),
     42      bailoutData_(nullptr),
     43      lastProfilingFrame_(nullptr),
     44      lastProfilingCallSite_(nullptr) {
     45  cx->jitActivation = this;
     46  registerProfiling();
     47 }
     48 
     49 js::jit::JitActivation::~JitActivation() {
     50  if (isProfiling()) {
     51    unregisterProfiling();
     52  }
     53  cx_->jitActivation = prevJitActivation_;
     54 
     55  // All reocvered value are taken from activation during the bailout.
     56  MOZ_ASSERT(ionRecovery_.empty());
     57 
     58  // The BailoutFrameInfo should have unregistered itself from the
     59  // JitActivations.
     60  MOZ_ASSERT(!bailoutData_);
     61 
     62  // Traps get handled immediately.
     63  MOZ_ASSERT(!isWasmTrapping());
     64 
     65  // Rematerialized frames must have been removed by either the bailout code or
     66  // the exception handler.
     67  MOZ_ASSERT_IF(rematerializedFrames_, rematerializedFrames_->empty());
     68 }
     69 
     70 void js::jit::JitActivation::trace(JSTracer* trc) {
     71  if (rematerializedFrames_) {
     72    for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty();
     73         e.popFront()) {
     74      e.front().value().trace(trc);
     75    }
     76  }
     77 
     78  for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end();
     79       it++) {
     80    it->trace(trc);
     81  }
     82 }
     83 
     84 void js::jit::JitActivation::setBailoutData(
     85    jit::BailoutFrameInfo* bailoutData) {
     86  MOZ_ASSERT(!bailoutData_);
     87  bailoutData_ = bailoutData;
     88 }
     89 
     90 void js::jit::JitActivation::cleanBailoutData() {
     91  MOZ_ASSERT(bailoutData_);
     92  bailoutData_ = nullptr;
     93 }
     94 
     95 void js::jit::JitActivation::removeRematerializedFrame(uint8_t* top) {
     96  if (!rematerializedFrames_) {
     97    return;
     98  }
     99 
    100  if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) {
    101    rematerializedFrames_->remove(p);
    102  }
    103 }
    104 
    105 js::jit::RematerializedFrame* js::jit::JitActivation::getRematerializedFrame(
    106    JSContext* cx, const JSJitFrameIter& iter, size_t inlineDepth,
    107    IsLeavingFrame leaving) {
    108  MOZ_ASSERT(iter.activation() == this);
    109  MOZ_ASSERT(iter.isIonScripted());
    110 
    111  if (!rematerializedFrames_) {
    112    rematerializedFrames_ = cx->make_unique<RematerializedFrameTable>(cx);
    113    if (!rematerializedFrames_) {
    114      return nullptr;
    115    }
    116  }
    117 
    118  uint8_t* top = iter.fp();
    119  RematerializedFrameTable::AddPtr p = rematerializedFrames_->lookupForAdd(top);
    120  if (!p) {
    121    RematerializedFrameVector frames(cx);
    122 
    123    // The unit of rematerialization is an uninlined frame and its inlined
    124    // frames. Since inlined frames do not exist outside of snapshots, it
    125    // is impossible to synchronize their rematerialized copies to
    126    // preserve identity. Therefore, we always rematerialize an uninlined
    127    // frame and all its inlined frames at once.
    128    InlineFrameIterator inlineIter(cx, &iter);
    129 
    130    // We can run recover instructions without invalidating if we're always
    131    // leaving the frame.
    132    MaybeReadFallback::FallbackConsequence consequence =
    133        MaybeReadFallback::Fallback_Invalidate;
    134    if (leaving == IsLeavingFrame::Yes) {
    135      consequence = MaybeReadFallback::Fallback_DoNothing;
    136    }
    137    MaybeReadFallback recover(cx, this, &iter, consequence);
    138 
    139    // Frames are often rematerialized with the cx inside a Debugger's
    140    // realm. To recover slots and to create CallObjects, we need to
    141    // be in the script's realm.
    142    AutoRealmUnchecked ar(cx, iter.script()->realm());
    143 
    144    // The Ion frame must be invalidated to ensure the rematerialized frame will
    145    // be removed by the bailout code or the exception handler. If we're always
    146    // leaving the frame, the caller is responsible for cleaning up the
    147    // rematerialized frame.
    148    if (leaving == IsLeavingFrame::No && !iter.checkInvalidation()) {
    149      jit::Invalidate(cx, iter.script());
    150    }
    151 
    152    if (!RematerializedFrame::RematerializeInlineFrames(cx, top, inlineIter,
    153                                                        recover, frames)) {
    154      return nullptr;
    155    }
    156 
    157    if (!rematerializedFrames_->add(p, top, std::move(frames))) {
    158      ReportOutOfMemory(cx);
    159      return nullptr;
    160    }
    161 
    162    // See comment in unsetPrevUpToDateUntil.
    163    DebugEnvironments::unsetPrevUpToDateUntil(cx,
    164                                              p->value()[inlineDepth].get());
    165  }
    166 
    167  return p->value()[inlineDepth].get();
    168 }
    169 
    170 js::jit::RematerializedFrame* js::jit::JitActivation::lookupRematerializedFrame(
    171    uint8_t* top, size_t inlineDepth) {
    172  if (!rematerializedFrames_) {
    173    return nullptr;
    174  }
    175  if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) {
    176    return inlineDepth < p->value().length() ? p->value()[inlineDepth].get()
    177                                             : nullptr;
    178  }
    179  return nullptr;
    180 }
    181 
    182 void js::jit::JitActivation::removeRematerializedFramesFromDebugger(
    183    JSContext* cx, uint8_t* top) {
    184  // Ion bailout can fail due to overrecursion and OOM. In such cases we
    185  // cannot honor any further Debugger hooks on the frame, and need to
    186  // ensure that its Debugger.Frame entry is cleaned up.
    187  if (!cx->realm()->isDebuggee() || !rematerializedFrames_) {
    188    return;
    189  }
    190  if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) {
    191    for (uint32_t i = 0; i < p->value().length(); i++) {
    192      DebugAPI::handleUnrecoverableIonBailoutError(cx, p->value()[i].get());
    193    }
    194    rematerializedFrames_->remove(p);
    195  }
    196 }
    197 
    198 bool js::jit::JitActivation::registerIonFrameRecovery(
    199    RInstructionResults&& results) {
    200  // Check that there is no entry in the vector yet.
    201  MOZ_ASSERT(!maybeIonFrameRecovery(results.frame()));
    202  if (!ionRecovery_.append(std::move(results))) {
    203    return false;
    204  }
    205 
    206  return true;
    207 }
    208 
    209 js::jit::RInstructionResults* js::jit::JitActivation::maybeIonFrameRecovery(
    210    JitFrameLayout* fp) {
    211  for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end();
    212       it++) {
    213    if (it->frame() == fp) {
    214      return it;
    215    }
    216  }
    217 
    218  return nullptr;
    219 }
    220 
    221 void js::jit::JitActivation::removeIonFrameRecovery(JitFrameLayout* fp) {
    222  RInstructionResults* elem = maybeIonFrameRecovery(fp);
    223  if (!elem) {
    224    return;
    225  }
    226 
    227  ionRecovery_.erase(elem);
    228 }
    229 
    230 void js::jit::JitActivation::startWasmTrap(wasm::Trap trap,
    231                                           const wasm::TrapSite& trapSite,
    232                                           const wasm::RegisterState& state) {
    233  MOZ_ASSERT(!isWasmTrapping());
    234 
    235  bool unwound;
    236  wasm::UnwindState unwindState;
    237  MOZ_RELEASE_ASSERT(wasm::StartUnwinding(state, &unwindState, &unwound));
    238  // With return calls, it is possible to not unwind when there is only an
    239  // entry left on the stack, e.g. the return call trampoline that is created
    240  // to restore realm before returning to the interpreter entry stub.
    241  MOZ_ASSERT_IF(unwound, trap == wasm::Trap::IndirectCallBadSig);
    242 
    243  void* pc = unwindState.pc;
    244  const wasm::Frame* fp = wasm::Frame::fromUntaggedWasmExitFP(unwindState.fp);
    245 
    246  const wasm::Code& code = wasm::GetNearestEffectiveInstance(fp)->code();
    247  MOZ_RELEASE_ASSERT(&code == wasm::LookupCode(pc));
    248 
    249  wasm::SuspenderObject* suspender = nullptr;
    250 #ifdef ENABLE_WASM_JSPI
    251  suspender = cx()->wasm().findSuspenderForStackAddress(fp);
    252 #endif
    253  setWasmExitFP(fp, suspender);
    254  wasmTrapData_.emplace();
    255  wasmTrapData_->resumePC =
    256      ((uint8_t*)state.pc) + jit::WasmTrapInstructionLength;
    257  wasmTrapData_->unwoundPC = pc;
    258  wasmTrapData_->trap = trap;
    259  // If the frame was unwound, the source location must be recovered from the
    260  // callsite so that it is accurate.
    261  if (unwound) {
    262    wasm::CallSite site;
    263    MOZ_ALWAYS_TRUE(code.lookupCallSite(pc, &site));
    264    wasmTrapData_->trapSite.bytecodeOffset =
    265        wasm::BytecodeOffset(site.lineOrBytecode());
    266    wasmTrapData_->trapSite.inlinedCallerOffsets = site.inlinedCallerOffsets();
    267  } else {
    268    wasmTrapData_->trapSite = trapSite;
    269  }
    270  wasmTrapData_->failedUnwindSignatureMismatch =
    271      !unwound && trap == wasm::Trap::IndirectCallBadSig;
    272 
    273  MOZ_ASSERT(isWasmTrapping());
    274 }
    275 
    276 void js::jit::JitActivation::finishWasmTrap(bool isResuming) {
    277  MOZ_ASSERT(hasWasmExitFP());
    278  MOZ_ASSERT(isWasmTrapping());
    279  wasmTrapData_.reset();
    280  packedExitFP_ = nullptr;
    281  // Don't clear the exit suspender if we're resuming, or else the trap stub
    282  // won't know that it needs to switch back to the main stack.
    283 #ifdef ENABLE_WASM_JSPI
    284  if (!isResuming) {
    285    wasmExitSuspender_ = nullptr;
    286  }
    287 #endif
    288  MOZ_ASSERT(!isWasmTrapping());
    289 }