tor-browser

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

Bailouts.cpp (13398B)


      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 "jit/Bailouts.h"
      8 
      9 #include "mozilla/ArrayUtils.h"
     10 #include "mozilla/ScopeExit.h"
     11 
     12 #include "gc/GC.h"
     13 #include "jit/Assembler.h"  // jit::FramePointer
     14 #include "jit/BaselineJIT.h"
     15 #include "jit/JitFrames.h"
     16 #include "jit/JitRuntime.h"
     17 #include "jit/JitSpewer.h"
     18 #include "jit/JSJitFrameIter.h"
     19 #include "jit/SafepointIndex.h"
     20 #include "jit/ScriptFromCalleeToken.h"
     21 #include "vm/Interpreter.h"
     22 #include "vm/JSContext.h"
     23 #include "vm/Stack.h"
     24 
     25 #include "vm/JSScript-inl.h"
     26 #include "vm/Probes-inl.h"
     27 #include "vm/Stack-inl.h"
     28 
     29 using namespace js;
     30 using namespace js::jit;
     31 
     32 using mozilla::IsInRange;
     33 
     34 #if defined(_WIN32)
     35 #  pragma pack(push, 1)
     36 #endif
     37 class js::jit::BailoutStack {
     38  RegisterDump::FPUArray fpregs_;
     39  RegisterDump::GPRArray regs_;
     40  uintptr_t frameSize_;
     41  uintptr_t snapshotOffset_;
     42 
     43 public:
     44  MachineState machineState() {
     45    return MachineState::FromBailout(regs_, fpregs_);
     46  }
     47  uint32_t snapshotOffset() const { return snapshotOffset_; }
     48  uint32_t frameSize() const { return frameSize_; }
     49  uint8_t* parentStackPointer() {
     50    return (uint8_t*)this + sizeof(BailoutStack);
     51  }
     52 };
     53 #if defined(_WIN32)
     54 #  pragma pack(pop)
     55 #endif
     56 
     57 #if !defined(JS_CODEGEN_NONE)
     58 // Make sure the compiler doesn't add extra padding on 32-bit platforms.
     59 static_assert((sizeof(BailoutStack) % 8) == 0,
     60              "BailoutStack should be 8-byte aligned.");
     61 #endif
     62 
     63 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations,
     64                                   BailoutStack* bailout)
     65    : machine_(bailout->machineState()), activation_(nullptr) {
     66  uint8_t* sp = bailout->parentStackPointer();
     67  framePointer_ = sp + bailout->frameSize();
     68  MOZ_RELEASE_ASSERT(uintptr_t(framePointer_) == machine_.read(FramePointer));
     69 
     70  JSScript* script =
     71      ScriptFromCalleeToken(((JitFrameLayout*)framePointer_)->calleeToken());
     72  topIonScript_ = script->ionScript();
     73 
     74  attachOnJitActivation(activations);
     75  snapshotOffset_ = bailout->snapshotOffset();
     76 }
     77 
     78 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations,
     79                                   InvalidationBailoutStack* bailout)
     80    : machine_(bailout->machine()), activation_(nullptr) {
     81  framePointer_ = (uint8_t*)bailout->fp();
     82  MOZ_RELEASE_ASSERT(uintptr_t(framePointer_) == machine_.read(FramePointer));
     83 
     84  topIonScript_ = bailout->ionScript();
     85  attachOnJitActivation(activations);
     86 
     87  uint8_t* returnAddressToFp_ = bailout->osiPointReturnAddress();
     88  const OsiIndex* osiIndex = topIonScript_->getOsiIndex(returnAddressToFp_);
     89  snapshotOffset_ = osiIndex->snapshotOffset();
     90 }
     91 
     92 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations,
     93                                   const JSJitFrameIter& frame)
     94    : machine_(frame.machineState()) {
     95  framePointer_ = (uint8_t*)frame.fp();
     96  topIonScript_ = frame.ionScript();
     97  attachOnJitActivation(activations);
     98 
     99  const OsiIndex* osiIndex = frame.osiIndex();
    100  snapshotOffset_ = osiIndex->snapshotOffset();
    101 }
    102 
    103 // This address is a magic number made to cause crashes while indicating that we
    104 // are making an attempt to mark the stack during a bailout.
    105 static constexpr uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2;
    106 static uint8_t* const FAKE_EXITFP_FOR_BAILOUT =
    107    reinterpret_cast<uint8_t*>(FAKE_EXITFP_FOR_BAILOUT_ADDR);
    108 
    109 static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & wasm::ExitFPTag),
    110              "FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged "
    111              "wasm exit fp");
    112 
    113 bool jit::Bailout(BailoutStack* sp, BaselineBailoutInfo** bailoutInfo) {
    114  JSContext* cx = TlsContext.get();
    115  MOZ_ASSERT(bailoutInfo);
    116 
    117  // We don't have an exit frame.
    118  MOZ_ASSERT(IsInRange(FAKE_EXITFP_FOR_BAILOUT, 0, 0x1000) &&
    119                 IsInRange(FAKE_EXITFP_FOR_BAILOUT + sizeof(CommonFrameLayout),
    120                           0, 0x1000),
    121             "Fake exitfp pointer should be within the first page.");
    122 
    123 #ifdef DEBUG
    124  // Reset the counter when we bailed after MDebugEnterGCUnsafeRegion, but
    125  // before the matching MDebugLeaveGCUnsafeRegion.
    126  //
    127  // NOTE: EnterJit ensures the counter is zero when we enter JIT code.
    128  cx->resetInUnsafeRegion();
    129 #endif
    130 
    131  cx->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT);
    132 
    133  JitActivationIterator jitActivations(cx);
    134  BailoutFrameInfo bailoutData(jitActivations, sp);
    135  JSJitFrameIter frame(jitActivations->asJit());
    136  MOZ_ASSERT(!frame.ionScript()->invalidated());
    137  JitFrameLayout* currentFramePtr = frame.jsFrame();
    138 
    139  JitSpew(JitSpew_IonBailouts, "Took bailout! Snapshot offset: %u",
    140          frame.snapshotOffset());
    141 
    142  MOZ_ASSERT(IsBaselineJitEnabled(cx));
    143 
    144  *bailoutInfo = nullptr;
    145  bool success =
    146      BailoutIonToBaseline(cx, bailoutData.activation(), frame, bailoutInfo,
    147                           /*exceptionInfo=*/nullptr, BailoutReason::Normal);
    148  MOZ_ASSERT_IF(success, *bailoutInfo != nullptr);
    149 
    150  if (!success) {
    151    MOZ_ASSERT(cx->isExceptionPending());
    152    JSScript* script = frame.script();
    153    probes::ExitScript(cx, script, script->function(),
    154                       /* popProfilerFrame = */ false);
    155  }
    156 
    157  // This condition was wrong when we entered this bailout function, but it
    158  // might be true now. A GC might have reclaimed all the Jit code and
    159  // invalidated all frames which are currently on the stack. As we are
    160  // already in a bailout, we could not switch to an invalidation
    161  // bailout. When the code of an IonScript which is on the stack is
    162  // invalidated (see InvalidateActivation), we remove references to it and
    163  // increment the reference counter for each activation that appear on the
    164  // stack. As the bailed frame is one of them, we have to decrement it now.
    165  if (frame.ionScript()->invalidated()) {
    166    frame.ionScript()->decrementInvalidationCount(cx->gcContext());
    167  }
    168 
    169  // NB: Commentary on how |lastProfilingFrame| is set from bailouts.
    170  //
    171  // Once we return to jitcode, any following frames might get clobbered,
    172  // but the current frame will not (as it will be clobbered "in-place"
    173  // with a baseline frame that will share the same frame prefix).
    174  // However, there may be multiple baseline frames unpacked from this
    175  // single Ion frame, which means we will need to once again reset
    176  // |lastProfilingFrame| to point to the correct unpacked last frame
    177  // in |FinishBailoutToBaseline|.
    178  //
    179  // In the case of error, the jitcode will jump immediately to an
    180  // exception handler, which will unwind the frames and properly set
    181  // the |lastProfilingFrame| to point to the frame being resumed into
    182  // (see |AutoResetLastProfilerFrameOnReturnFromException|).
    183  //
    184  // In both cases, we want to temporarily set the |lastProfilingFrame|
    185  // to the current frame being bailed out, and then fix it up later.
    186  if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(
    187          cx->runtime())) {
    188    cx->jitActivation->setLastProfilingFrame(currentFramePtr);
    189  }
    190 
    191  return success;
    192 }
    193 
    194 bool jit::InvalidationBailout(InvalidationBailoutStack* sp,
    195                              BaselineBailoutInfo** bailoutInfo) {
    196  sp->checkInvariants();
    197 
    198  JSContext* cx = TlsContext.get();
    199 
    200 #ifdef DEBUG
    201  // Reset the counter when we bailed after MDebugEnterGCUnsafeRegion, but
    202  // before the matching MDebugLeaveGCUnsafeRegion.
    203  //
    204  // NOTE: EnterJit ensures the counter is zero when we enter JIT code.
    205  cx->resetInUnsafeRegion();
    206 #endif
    207 
    208  // We don't have an exit frame.
    209  cx->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT);
    210 
    211  JitActivationIterator jitActivations(cx);
    212  BailoutFrameInfo bailoutData(jitActivations, sp);
    213  JSJitFrameIter frame(jitActivations->asJit());
    214  JitFrameLayout* currentFramePtr = frame.jsFrame();
    215 
    216  JitSpew(JitSpew_IonBailouts, "Took invalidation bailout! Snapshot offset: %u",
    217          frame.snapshotOffset());
    218 
    219  MOZ_ASSERT(IsBaselineJitEnabled(cx));
    220 
    221  *bailoutInfo = nullptr;
    222  bool success = BailoutIonToBaseline(cx, bailoutData.activation(), frame,
    223                                      bailoutInfo, /*exceptionInfo=*/nullptr,
    224                                      BailoutReason::Invalidate);
    225  MOZ_ASSERT_IF(success, *bailoutInfo != nullptr);
    226 
    227  if (!success) {
    228    MOZ_ASSERT(cx->isExceptionPending());
    229 
    230    // If the bailout failed, then bailout trampoline will pop the
    231    // current frame and jump straight to exception handling code when
    232    // this function returns.  Any Gecko Profiler entry pushed for this
    233    // frame will be silently forgotten.
    234    //
    235    // We call ExitScript here to ensure that if the ionScript had Gecko
    236    // Profiler instrumentation, then the entry for it is popped.
    237    //
    238    // However, if the bailout was during argument check, then a
    239    // pseudostack frame would not have been pushed in the first
    240    // place, so don't pop anything in that case.
    241    JSScript* script = frame.script();
    242    probes::ExitScript(cx, script, script->function(),
    243                       /* popProfilerFrame = */ false);
    244 
    245 #ifdef JS_JITSPEW
    246    JitFrameLayout* layout = frame.jsFrame();
    247    JitSpew(JitSpew_IonInvalidate, "Bailout failed (Fatal Error)");
    248    JitSpew(JitSpew_IonInvalidate, "   calleeToken %p",
    249            (void*)layout->calleeToken());
    250    JitSpew(JitSpew_IonInvalidate, "   callerFramePtr %p",
    251            layout->callerFramePtr());
    252    JitSpew(JitSpew_IonInvalidate, "   ra %p", (void*)layout->returnAddress());
    253 #endif
    254  }
    255 
    256  frame.ionScript()->decrementInvalidationCount(cx->gcContext());
    257 
    258  // Make the frame being bailed out the top profiled frame.
    259  if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(
    260          cx->runtime())) {
    261    cx->jitActivation->setLastProfilingFrame(currentFramePtr);
    262  }
    263 
    264  return success;
    265 }
    266 
    267 bool jit::ExceptionHandlerBailout(JSContext* cx,
    268                                  const InlineFrameIterator& frame,
    269                                  ResumeFromException* rfe,
    270                                  const ExceptionBailoutInfo& excInfo) {
    271  // If we are resuming in a finally block, the exception has already
    272  // been captured.
    273  // We can also be propagating debug mode exceptions without there being an
    274  // actual exception pending. For instance, when we return false from an
    275  // operation callback like a timeout handler.
    276  MOZ_ASSERT_IF(
    277      !cx->isExceptionPending(),
    278      excInfo.isFinally() || excInfo.propagatingIonExceptionForDebugMode());
    279 
    280  JS::AutoSaveExceptionState savedExc(cx);
    281 
    282  JitActivation* act = cx->activation()->asJit();
    283  uint8_t* prevExitFP = act->jsExitFP();
    284  auto restoreExitFP =
    285      mozilla::MakeScopeExit([&]() { act->setJSExitFP(prevExitFP); });
    286  act->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT);
    287 
    288  gc::AutoSuppressGC suppress(cx);
    289 
    290  JitActivationIterator jitActivations(cx);
    291  BailoutFrameInfo bailoutData(jitActivations, frame.frame());
    292  JSJitFrameIter frameView(jitActivations->asJit());
    293  JitFrameLayout* currentFramePtr = frameView.jsFrame();
    294 
    295  BaselineBailoutInfo* bailoutInfo = nullptr;
    296  bool success = BailoutIonToBaseline(cx, bailoutData.activation(), frameView,
    297                                      &bailoutInfo, &excInfo,
    298                                      BailoutReason::ExceptionHandler);
    299  if (success) {
    300    MOZ_ASSERT(bailoutInfo);
    301 
    302    // Overwrite the kind so HandleException after the bailout returns
    303    // false, jumping directly to the exception tail.
    304    if (excInfo.propagatingIonExceptionForDebugMode()) {
    305      bailoutInfo->bailoutKind =
    306          mozilla::Some(BailoutKind::IonExceptionDebugMode);
    307    } else if (excInfo.isFinally()) {
    308      bailoutInfo->bailoutKind = mozilla::Some(BailoutKind::Finally);
    309    }
    310 
    311    rfe->kind = ExceptionResumeKind::Bailout;
    312    rfe->stackPointer = bailoutInfo->incomingStack;
    313    rfe->bailoutInfo = bailoutInfo;
    314  } else {
    315    // Drop the exception that triggered the bailout and instead propagate the
    316    // failure caused by processing the bailout (eg. OOM). Note that recover
    317    // instructions can do an interrupt check and throw an uncatchable
    318    // exception.
    319    savedExc.drop();
    320    MOZ_ASSERT(!bailoutInfo);
    321    MOZ_ASSERT(cx->isExceptionPending() || cx->hadUncatchableException());
    322  }
    323 
    324  // Make the frame being bailed out the top profiled frame.
    325  if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(
    326          cx->runtime())) {
    327    cx->jitActivation->setLastProfilingFrame(currentFramePtr);
    328  }
    329 
    330  return success;
    331 }
    332 
    333 // Initialize the NamedLambdaObject and CallObject of the current frame if
    334 // needed.
    335 bool jit::EnsureHasEnvironmentObjects(JSContext* cx, AbstractFramePtr fp) {
    336  // Ion does not compile eval scripts.
    337  MOZ_ASSERT(!fp.isEvalFrame());
    338 
    339  if (fp.isFunctionFrame() && !fp.hasInitialEnvironment() &&
    340      fp.callee()->needsFunctionEnvironmentObjects()) {
    341    if (!fp.initFunctionEnvironmentObjects(cx)) {
    342      return false;
    343    }
    344  }
    345 
    346  return true;
    347 }
    348 
    349 void BailoutFrameInfo::attachOnJitActivation(
    350    const JitActivationIterator& jitActivations) {
    351  MOZ_ASSERT(jitActivations->asJit()->jsExitFP() == FAKE_EXITFP_FOR_BAILOUT);
    352  activation_ = jitActivations->asJit();
    353  activation_->setBailoutData(this);
    354 }
    355 
    356 BailoutFrameInfo::~BailoutFrameInfo() { activation_->cleanBailoutData(); }