tor-browser

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

BaselineDebugModeOSR.cpp (19381B)


      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/BaselineDebugModeOSR.h"
      8 
      9 #include "jit/BaselineFrame.h"
     10 #include "jit/BaselineIC.h"
     11 #include "jit/BaselineJIT.h"
     12 #include "jit/Invalidation.h"
     13 #include "jit/IonScript.h"
     14 #include "jit/JitFrames.h"
     15 #include "jit/JitRuntime.h"
     16 #include "jit/JSJitFrameIter.h"
     17 
     18 #include "jit/JitScript-inl.h"
     19 #include "jit/JSJitFrameIter-inl.h"
     20 #include "vm/JSScript-inl.h"
     21 #include "vm/Realm-inl.h"
     22 
     23 using namespace js;
     24 using namespace js::jit;
     25 
     26 struct DebugModeOSREntry {
     27  JSScript* script;
     28  BaselineScript* oldBaselineScript;
     29  uint32_t pcOffset;
     30  RetAddrEntry::Kind frameKind;
     31 
     32  explicit DebugModeOSREntry(JSScript* script)
     33      : script(script),
     34        oldBaselineScript(script->baselineScript()),
     35        pcOffset(uint32_t(-1)),
     36        frameKind(RetAddrEntry::Kind::Invalid) {}
     37 
     38  DebugModeOSREntry(JSScript* script, const RetAddrEntry& retAddrEntry)
     39      : script(script),
     40        oldBaselineScript(script->baselineScript()),
     41        pcOffset(retAddrEntry.pcOffset()),
     42        frameKind(retAddrEntry.kind()) {
     43 #ifdef DEBUG
     44    MOZ_ASSERT(pcOffset == retAddrEntry.pcOffset());
     45    MOZ_ASSERT(frameKind == retAddrEntry.kind());
     46 #endif
     47  }
     48 
     49  DebugModeOSREntry(DebugModeOSREntry&& other)
     50      : script(other.script),
     51        oldBaselineScript(other.oldBaselineScript),
     52        pcOffset(other.pcOffset),
     53        frameKind(other.frameKind) {}
     54 
     55  bool recompiled() const {
     56    return oldBaselineScript != script->baselineScript();
     57  }
     58 };
     59 
     60 using DebugModeOSREntryVector = Vector<DebugModeOSREntry>;
     61 
     62 class UniqueScriptOSREntryIter {
     63  const DebugModeOSREntryVector& entries_;
     64  size_t index_;
     65 
     66 public:
     67  explicit UniqueScriptOSREntryIter(const DebugModeOSREntryVector& entries)
     68      : entries_(entries), index_(0) {}
     69 
     70  bool done() { return index_ == entries_.length(); }
     71 
     72  const DebugModeOSREntry& entry() {
     73    MOZ_ASSERT(!done());
     74    return entries_[index_];
     75  }
     76 
     77  UniqueScriptOSREntryIter& operator++() {
     78    MOZ_ASSERT(!done());
     79    while (++index_ < entries_.length()) {
     80      bool unique = true;
     81      for (size_t i = 0; i < index_; i++) {
     82        if (entries_[i].script == entries_[index_].script) {
     83          unique = false;
     84          break;
     85        }
     86      }
     87      if (unique) {
     88        break;
     89      }
     90    }
     91    return *this;
     92  }
     93 };
     94 
     95 static bool CollectJitStackScripts(JSContext* cx,
     96                                   const DebugAPI::ExecutionObservableSet& obs,
     97                                   const ActivationIterator& activation,
     98                                   DebugModeOSREntryVector& entries) {
     99  for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
    100    const JSJitFrameIter& frame = iter.frame();
    101    switch (frame.type()) {
    102      case FrameType::BaselineJS: {
    103        JSScript* script = frame.script();
    104 
    105        if (!obs.shouldRecompileOrInvalidate(script)) {
    106          break;
    107        }
    108 
    109        BaselineFrame* baselineFrame = frame.baselineFrame();
    110 
    111        if (baselineFrame->runningInInterpreter()) {
    112          // Baseline Interpreter frames for scripts that have a BaselineScript
    113          // or IonScript don't need to be patched but they do need to be
    114          // invalidated and recompiled. See also CollectInterpreterStackScripts
    115          // for C++ interpreter frames.
    116          if (!entries.append(DebugModeOSREntry(script))) {
    117            return false;
    118          }
    119        } else {
    120          // The frame must be settled on a pc with a RetAddrEntry.
    121          uint8_t* retAddr = frame.resumePCinCurrentFrame();
    122          const RetAddrEntry& retAddrEntry =
    123              script->baselineScript()->retAddrEntryFromReturnAddress(retAddr);
    124          if (!entries.append(DebugModeOSREntry(script, retAddrEntry))) {
    125            return false;
    126          }
    127        }
    128 
    129        break;
    130      }
    131 
    132      case FrameType::BaselineStub:
    133        break;
    134 
    135      case FrameType::IonJS: {
    136        InlineFrameIterator inlineIter(cx, &frame);
    137        while (true) {
    138          if (obs.shouldRecompileOrInvalidate(inlineIter.script())) {
    139            if (!entries.append(DebugModeOSREntry(inlineIter.script()))) {
    140              return false;
    141            }
    142          }
    143          if (!inlineIter.more()) {
    144            break;
    145          }
    146          ++inlineIter;
    147        }
    148        break;
    149      }
    150 
    151      default:;
    152    }
    153  }
    154 
    155  return true;
    156 }
    157 
    158 static bool CollectInterpreterStackScripts(
    159    JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
    160    const ActivationIterator& activation, DebugModeOSREntryVector& entries) {
    161  // Collect interpreter frame stacks with IonScript or BaselineScript as
    162  // well. These do not need to be patched, but do need to be invalidated
    163  // and recompiled.
    164  InterpreterActivation* act = activation.activation()->asInterpreter();
    165  for (InterpreterFrameIterator iter(act); !iter.done(); ++iter) {
    166    JSScript* script = iter.frame()->script();
    167    if (obs.shouldRecompileOrInvalidate(script)) {
    168      if (!entries.append(DebugModeOSREntry(iter.frame()->script()))) {
    169        return false;
    170      }
    171    }
    172  }
    173  return true;
    174 }
    175 
    176 #ifdef JS_JITSPEW
    177 static const char* RetAddrEntryKindToString(RetAddrEntry::Kind kind) {
    178  switch (kind) {
    179    case RetAddrEntry::Kind::IC:
    180      return "IC";
    181    case RetAddrEntry::Kind::CallVM:
    182      return "callVM";
    183    case RetAddrEntry::Kind::StackCheck:
    184      return "stack check";
    185    case RetAddrEntry::Kind::InterruptCheck:
    186      return "interrupt check";
    187    case RetAddrEntry::Kind::DebugTrap:
    188      return "debug trap";
    189    case RetAddrEntry::Kind::DebugPrologue:
    190      return "debug prologue";
    191    case RetAddrEntry::Kind::DebugAfterYield:
    192      return "debug after yield";
    193    case RetAddrEntry::Kind::DebugEpilogue:
    194      return "debug epilogue";
    195    default:
    196      MOZ_CRASH("bad RetAddrEntry kind");
    197  }
    198 }
    199 #endif  // JS_JITSPEW
    200 
    201 static void SpewPatchBaselineFrame(const uint8_t* oldReturnAddress,
    202                                   const uint8_t* newReturnAddress,
    203                                   JSScript* script,
    204                                   RetAddrEntry::Kind frameKind,
    205                                   const jsbytecode* pc) {
    206  JitSpew(JitSpew_BaselineDebugModeOSR,
    207          "Patch return %p -> %p on BaselineJS frame (%s:%u:%u) from %s at %s",
    208          oldReturnAddress, newReturnAddress, script->filename(),
    209          script->lineno(), script->column().oneOriginValue(),
    210          RetAddrEntryKindToString(frameKind), CodeName(JSOp(*pc)));
    211 }
    212 
    213 static void PatchBaselineFramesForDebugMode(
    214    JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
    215    const ActivationIterator& activation, DebugModeOSREntryVector& entries,
    216    size_t* start) {
    217  //
    218  // Recompile Patching Overview
    219  //
    220  // When toggling debug mode with live baseline scripts on the stack, we
    221  // could have entered the VM via the following ways from the baseline
    222  // script.
    223  //
    224  // Off to On:
    225  //  A. From a non-prologue IC (fallback stub or "can call" stub).
    226  //  B. From a VM call.
    227  //  C. From inside the interrupt handler via the prologue stack check.
    228  //
    229  // On to Off:
    230  //  - All the ways above.
    231  //  D. From the debug trap handler.
    232  //  E. From the debug prologue.
    233  //  F. From the debug epilogue.
    234  //  G. From a JSOp::AfterYield instruction.
    235  //
    236  // In general, we patch the return address from VM calls and ICs to the
    237  // corresponding entry in the recompiled BaselineScript. For entries that are
    238  // not present in the recompiled script (cases D to G above) we switch the
    239  // frame to interpreter mode and resume in the Baseline Interpreter.
    240  //
    241  // Specifics on what needs to be done are documented below.
    242  //
    243 
    244  const BaselineInterpreter& baselineInterp =
    245      cx->runtime()->jitRuntime()->baselineInterpreter();
    246 
    247  CommonFrameLayout* prev = nullptr;
    248  size_t entryIndex = *start;
    249 
    250  for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
    251    const JSJitFrameIter& frame = iter.frame();
    252    switch (frame.type()) {
    253      case FrameType::BaselineJS: {
    254        // If the script wasn't recompiled or is not observed, there's
    255        // nothing to patch.
    256        if (!obs.shouldRecompileOrInvalidate(frame.script())) {
    257          break;
    258        }
    259 
    260        DebugModeOSREntry& entry = entries[entryIndex];
    261 
    262        if (!entry.recompiled()) {
    263          entryIndex++;
    264          break;
    265        }
    266 
    267        BaselineFrame* baselineFrame = frame.baselineFrame();
    268        if (baselineFrame->runningInInterpreter()) {
    269          // We recompiled the script's BaselineScript but Baseline Interpreter
    270          // frames don't need to be patched.
    271          entryIndex++;
    272          break;
    273        }
    274 
    275        JSScript* script = entry.script;
    276        uint32_t pcOffset = entry.pcOffset;
    277        jsbytecode* pc = script->offsetToPC(pcOffset);
    278 
    279        MOZ_ASSERT(script == frame.script());
    280        MOZ_ASSERT(pcOffset < script->length());
    281 
    282        BaselineScript* bl = script->baselineScript();
    283        RetAddrEntry::Kind kind = entry.frameKind;
    284        uint8_t* retAddr = nullptr;
    285        switch (kind) {
    286          case RetAddrEntry::Kind::IC:
    287          case RetAddrEntry::Kind::CallVM:
    288          case RetAddrEntry::Kind::InterruptCheck:
    289          case RetAddrEntry::Kind::StackCheck: {
    290            // Cases A, B, C above.
    291            //
    292            // For the baseline frame here, we resume right after the CallVM or
    293            // IC returns.
    294            //
    295            // For CallVM (case B) the assumption is that all callVMs which can
    296            // trigger debug mode OSR are the *only* callVMs generated for their
    297            // respective pc locations in the Baseline JIT code.
    298            const RetAddrEntry* retAddrEntry = nullptr;
    299            switch (kind) {
    300              case RetAddrEntry::Kind::IC:
    301              case RetAddrEntry::Kind::CallVM:
    302              case RetAddrEntry::Kind::InterruptCheck:
    303                retAddrEntry = &bl->retAddrEntryFromPCOffset(pcOffset, kind);
    304                break;
    305              case RetAddrEntry::Kind::StackCheck:
    306                retAddrEntry = &bl->prologueRetAddrEntry(kind);
    307                break;
    308              default:
    309                MOZ_CRASH("Unexpected kind");
    310            }
    311            retAddr = bl->returnAddressForEntry(*retAddrEntry);
    312            SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind,
    313                                   pc);
    314            break;
    315          }
    316          case RetAddrEntry::Kind::DebugPrologue:
    317          case RetAddrEntry::Kind::DebugEpilogue:
    318          case RetAddrEntry::Kind::DebugTrap:
    319          case RetAddrEntry::Kind::DebugAfterYield: {
    320            // Cases D, E, F, G above.
    321            //
    322            // Resume in the Baseline Interpreter because these callVMs are not
    323            // present in the new BaselineScript if we recompiled without debug
    324            // instrumentation.
    325            if (kind == RetAddrEntry::Kind::DebugPrologue) {
    326              frame.baselineFrame()->switchFromJitToInterpreterAtPrologue(cx);
    327            } else {
    328              frame.baselineFrame()->switchFromJitToInterpreter(cx, pc);
    329            }
    330            switch (kind) {
    331              case RetAddrEntry::Kind::DebugTrap:
    332                // DebugTrap handling is different from the ones below because
    333                // it's not a callVM but a trampoline call at the start of the
    334                // bytecode op. When we return to the frame we can resume at the
    335                // interpretOp label.
    336                retAddr = baselineInterp.interpretOpAddr().value;
    337                break;
    338              case RetAddrEntry::Kind::DebugPrologue:
    339                retAddr = baselineInterp.retAddrForDebugPrologueCallVM();
    340                break;
    341              case RetAddrEntry::Kind::DebugEpilogue:
    342                retAddr = baselineInterp.retAddrForDebugEpilogueCallVM();
    343                break;
    344              case RetAddrEntry::Kind::DebugAfterYield:
    345                retAddr = baselineInterp.retAddrForDebugAfterYieldCallVM();
    346                break;
    347              default:
    348                MOZ_CRASH("Unexpected kind");
    349            }
    350            SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind,
    351                                   pc);
    352            break;
    353          }
    354          case RetAddrEntry::Kind::NonOpCallVM:
    355          case RetAddrEntry::Kind::Invalid:
    356            // These cannot trigger BaselineDebugModeOSR.
    357            MOZ_CRASH("Unexpected RetAddrEntry Kind");
    358        }
    359 
    360        prev->setReturnAddress(retAddr);
    361        entryIndex++;
    362        break;
    363      }
    364 
    365      case FrameType::IonJS: {
    366        // Nothing to patch.
    367        InlineFrameIterator inlineIter(cx, &frame);
    368        while (true) {
    369          if (obs.shouldRecompileOrInvalidate(inlineIter.script())) {
    370            entryIndex++;
    371          }
    372          if (!inlineIter.more()) {
    373            break;
    374          }
    375          ++inlineIter;
    376        }
    377        break;
    378      }
    379 
    380      default:;
    381    }
    382 
    383    prev = frame.current();
    384  }
    385 
    386  *start = entryIndex;
    387 }
    388 
    389 static void SkipInterpreterFrameEntries(
    390    const DebugAPI::ExecutionObservableSet& obs,
    391    const ActivationIterator& activation, size_t* start) {
    392  size_t entryIndex = *start;
    393 
    394  // Skip interpreter frames, which do not need patching.
    395  InterpreterActivation* act = activation.activation()->asInterpreter();
    396  for (InterpreterFrameIterator iter(act); !iter.done(); ++iter) {
    397    if (obs.shouldRecompileOrInvalidate(iter.frame()->script())) {
    398      entryIndex++;
    399    }
    400  }
    401 
    402  *start = entryIndex;
    403 }
    404 
    405 bool js::jit::RecompileBaselineScriptForDebugMode(
    406    JSContext* cx, JSScript* script, DebugAPI::IsObserving observing) {
    407  // If a script is on the stack multiple times, it may have already
    408  // been recompiled.
    409  if (script->baselineScript()->hasDebugInstrumentation() == observing) {
    410    return true;
    411  }
    412 
    413  JitSpew(JitSpew_BaselineDebugModeOSR, "Recompiling (%s:%u:%u) for %s",
    414          script->filename(), script->lineno(),
    415          script->column().oneOriginValue(),
    416          observing ? "DEBUGGING" : "NORMAL EXECUTION");
    417 
    418  AutoKeepJitScripts keepJitScripts(cx);
    419  BaselineScript* oldBaselineScript =
    420      script->jitScript()->clearBaselineScript(cx->gcContext(), script);
    421 
    422  BaselineOptions options({BaselineOption::ForceMainThreadCompilation});
    423  if (observing) {
    424    options.setFlag(BaselineOption::ForceDebugInstrumentation);
    425  }
    426  MethodStatus status = BaselineCompile(cx, script, options);
    427  if (status != Method_Compiled) {
    428    // We will only fail to recompile for debug mode due to OOM. Restore
    429    // the old baseline script in case something doesn't properly
    430    // propagate OOM.
    431    MOZ_ASSERT(status == Method_Error);
    432    script->jitScript()->setBaselineScript(script, oldBaselineScript);
    433    return false;
    434  }
    435 
    436  // Don't destroy the old baseline script yet, since if we fail any of the
    437  // recompiles we need to rollback all the old baseline scripts.
    438  MOZ_ASSERT(script->baselineScript()->hasDebugInstrumentation() == observing);
    439  return true;
    440 }
    441 
    442 static bool InvalidateScriptsInZone(JSContext* cx, Zone* zone,
    443                                    const Vector<DebugModeOSREntry>& entries) {
    444  IonScriptKeyVector invalid;
    445  for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
    446    JSScript* script = iter.entry().script;
    447    if (script->zone() != zone) {
    448      continue;
    449    }
    450 
    451    if (script->hasIonScript()) {
    452      if (!invalid.emplaceBack(script, script->ionScript()->compilationId())) {
    453        ReportOutOfMemory(cx);
    454        return false;
    455      }
    456    }
    457 
    458    // Cancel off-thread Ion compile for anything that has a
    459    // BaselineScript. If we relied on the call to Invalidate below to
    460    // cancel off-thread Ion compiles, only those with existing IonScripts
    461    // would be cancelled.
    462    if (script->hasBaselineScript()) {
    463      CancelOffThreadIonCompile(script);
    464    }
    465  }
    466 
    467  // No need to cancel off-thread Ion compiles again, we already did it
    468  // above.
    469  Invalidate(cx, invalid,
    470             /* resetUses = */ true, /* cancelOffThread = */ false);
    471  return true;
    472 }
    473 
    474 static void UndoRecompileBaselineScriptsForDebugMode(
    475    JSContext* cx, const DebugModeOSREntryVector& entries) {
    476  // In case of failure, roll back the entire set of active scripts so that
    477  // we don't have to patch return addresses on the stack.
    478  for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
    479    const DebugModeOSREntry& entry = iter.entry();
    480    JSScript* script = entry.script;
    481    if (entry.recompiled()) {
    482      BaselineScript* baselineScript =
    483          script->jitScript()->clearBaselineScript(cx->gcContext(), script);
    484      script->jitScript()->setBaselineScript(script, entry.oldBaselineScript);
    485      BaselineScript::Destroy(cx->gcContext(), baselineScript);
    486    }
    487  }
    488 }
    489 
    490 bool jit::RecompileOnStackBaselineScriptsForDebugMode(
    491    JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
    492    DebugAPI::IsObserving observing) {
    493  // First recompile the active scripts on the stack and patch the live
    494  // frames.
    495  Vector<DebugModeOSREntry> entries(cx);
    496 
    497  for (ActivationIterator iter(cx); !iter.done(); ++iter) {
    498    if (iter->isJit()) {
    499      if (!CollectJitStackScripts(cx, obs, iter, entries)) {
    500        return false;
    501      }
    502    } else if (iter->isInterpreter()) {
    503      if (!CollectInterpreterStackScripts(cx, obs, iter, entries)) {
    504        return false;
    505      }
    506    }
    507  }
    508 
    509  if (entries.empty()) {
    510    return true;
    511  }
    512 
    513  // When the profiler is enabled, we need to have suppressed sampling,
    514  // since the basline jit scripts are in a state of flux.
    515  MOZ_ASSERT(!cx->isProfilerSamplingEnabled());
    516 
    517  // Invalidate all scripts we are recompiling.
    518  if (Zone* zone = obs.singleZone()) {
    519    if (!InvalidateScriptsInZone(cx, zone, entries)) {
    520      return false;
    521    }
    522  } else {
    523    using ZoneRange = DebugAPI::ExecutionObservableSet::ZoneRange;
    524    for (ZoneRange r = obs.zones()->all(); !r.empty(); r.popFront()) {
    525      if (!InvalidateScriptsInZone(cx, r.front(), entries)) {
    526        return false;
    527      }
    528    }
    529  }
    530 
    531  // Try to recompile all the scripts. If we encounter an error, we need to
    532  // roll back as if none of the compilations happened, so that we don't
    533  // crash.
    534  for (size_t i = 0; i < entries.length(); i++) {
    535    JSScript* script = entries[i].script;
    536    AutoRealm ar(cx, script);
    537    if (!RecompileBaselineScriptForDebugMode(cx, script, observing)) {
    538      UndoRecompileBaselineScriptsForDebugMode(cx, entries);
    539      return false;
    540    }
    541  }
    542 
    543  // If all recompiles succeeded, destroy the old baseline scripts and patch
    544  // the live frames.
    545  //
    546  // After this point the function must be infallible.
    547 
    548  for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
    549    const DebugModeOSREntry& entry = iter.entry();
    550    if (entry.recompiled()) {
    551      BaselineScript::Destroy(cx->gcContext(), entry.oldBaselineScript);
    552    }
    553  }
    554 
    555  size_t processed = 0;
    556  for (ActivationIterator iter(cx); !iter.done(); ++iter) {
    557    if (iter->isJit()) {
    558      PatchBaselineFramesForDebugMode(cx, obs, iter, entries, &processed);
    559    } else if (iter->isInterpreter()) {
    560      SkipInterpreterFrameEntries(obs, iter, &processed);
    561    }
    562  }
    563  MOZ_ASSERT(processed == entries.length());
    564 
    565  return true;
    566 }