tor-browser

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

ConcurrentDelazification.cpp (9840B)


      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/ConcurrentDelazification.h"
      8 
      9 #include "mozilla/Assertions.h"       // MOZ_ASSERT, MOZ_CRASH
     10 #include "mozilla/RefPtr.h"           // RefPtr
     11 #include "mozilla/ReverseIterator.h"  // mozilla::Reversed
     12 #include "mozilla/ScopeExit.h"        // mozilla::MakeScopeExit
     13 
     14 #include <stddef.h>  // size_t
     15 #include <utility>   // std::move, std::pair
     16 
     17 #include "ds/LifoAlloc.h"  // LifoAlloc
     18 #include "frontend/BytecodeCompiler.h"  // DelazifyCanonicalScriptedFunction, DelazifyFailureReason
     19 #include "frontend/CompilationStencil.h"  // CompilationStencil, ExtensibleCompilationStencil, BorrowingCompilationStencil, ScriptStencilRef
     20 #include "frontend/FrontendContext.h"     // JS::FrontendContext
     21 #include "frontend/ScopeBindingCache.h"   // StencilScopeBindingCache
     22 #include "frontend/Stencil.h"  // TaggedScriptThingIndex, ScriptStencilExtra
     23 #include "js/AllocPolicy.h"    // ReportOutOfMemory
     24 #include "js/experimental/JSStencil.h"  // RefPtrTraits<JS::Stencil>
     25 #include "vm/JSContext.h"               // JSContext
     26 
     27 using namespace js;
     28 
     29 // This is an equivalent of std::swap but this one should work with
     30 // ScriptStencilRef constant fields by relying on placement-new trickery to
     31 // "create" a new value instead of updating the value of each field.
     32 template <typename T>
     33 void const_swap(T& a, T& b) {
     34  alignas(T) unsigned char buffer[sizeof(T)];
     35  T* temp = new (buffer) T(std::move(a));
     36  a.~T();
     37  new (&a) T(std::move(b));
     38  b.~T();
     39  new (&b) T(std::move(*temp));
     40  temp->~T();
     41 }
     42 
     43 bool DelazifyStrategy::add(FrontendContext* fc, ScriptStencilRef& ref) {
     44  using namespace js::frontend;
     45 
     46  // Only functions with bytecode are allowed to be added.
     47  MOZ_ASSERT(!ref.scriptDataFromEnclosing().isGhost());
     48  MOZ_ASSERT(ref.context()->scriptData[0].hasSharedData());
     49 
     50  // Lookup the gc-things range which are referenced by this script.
     51  auto gcThingData = ref.gcThingsFromInitial();
     52 
     53  // Iterate over gc-things of the script and queue inner functions.
     54  for (TaggedScriptThingIndex index : mozilla::Reversed(gcThingData)) {
     55    if (!index.isFunction()) {
     56      continue;
     57    }
     58 
     59    ScriptIndex innerIndex = index.toFunction();
     60    ScriptStencilRef innerRef{ref.stencils_, innerIndex};
     61    MOZ_ASSERT(innerRef.enclosingScript().scriptIndex_ == ref.scriptIndex_);
     62 
     63    const ScriptStencil& innerScriptData = innerRef.scriptDataFromEnclosing();
     64    if (innerScriptData.isGhost() ||
     65        !innerScriptData.functionFlags.isInterpreted() ||
     66        !innerScriptData.wasEmittedByEnclosingScript()) {
     67      continue;
     68    }
     69    if (innerScriptData.hasSharedData()) {
     70      // The function has been parsed as part of its enclosing script, thus we
     71      // should visit its inner function the same way.
     72      if (!add(fc, innerRef)) {
     73        return false;
     74      }
     75      continue;
     76    }
     77 
     78    // Maybe insert the new script index in the queue of functions to delazify.
     79    if (!insert(innerRef)) {
     80      ReportOutOfMemory(fc);
     81      return false;
     82    }
     83  }
     84 
     85  return true;
     86 }
     87 
     88 frontend::ScriptStencilRef LargeFirstDelazification::next() {
     89  const_swap(heap.back(), heap[0]);
     90  ScriptStencilRef result = heap.popCopy().second;
     91 
     92  // NOTE: These are a heap indexes offseted by 1, such that we can manipulate
     93  // the tree of heap-sorted values which bubble up the largest values towards
     94  // the root of the tree.
     95  size_t len = heap.length();
     96  size_t i = 1;
     97  while (true) {
     98    // NOTE: We write (n + 1) - 1, instead of n, to explicit that the
     99    // manipualted indexes are all offseted by 1.
    100    size_t n = 2 * i;
    101    size_t largest;
    102    if (n + 1 <= len && heap[(n + 1) - 1].first > heap[n - 1].first) {
    103      largest = n + 1;
    104    } else if (n <= len) {
    105      // The condition is n <= len in case n + 1 is out of the heap vector, but
    106      // not n, in which case we still want to check if the last element of the
    107      // heap vector should be swapped. Otherwise heap[n - 1] represents a
    108      // larger function than heap[(n + 1) - 1].
    109      largest = n;
    110    } else {
    111      // n is out-side the heap vector, thus our element is already in a leaf
    112      // position and would not be moved any more.
    113      break;
    114    }
    115 
    116    if (heap[i - 1].first < heap[largest - 1].first) {
    117      // We found a function which has a larger body as a child of the current
    118      // element. we swap it with the current element, such that the largest
    119      // element is closer to the root of the tree.
    120      const_swap(heap[i - 1], heap[largest - 1]);
    121      i = largest;
    122    } else {
    123      // The largest function found as a child of the current node is smaller
    124      // than the current node's function size. The heap tree is now organized
    125      // as expected.
    126      break;
    127    }
    128  }
    129 
    130  return result;
    131 }
    132 
    133 bool LargeFirstDelazification::insert(frontend::ScriptStencilRef& ref) {
    134  const frontend::ScriptStencilExtra& extra = ref.scriptExtra();
    135  SourceSize size = extra.extent.sourceEnd - extra.extent.sourceStart;
    136  if (!heap.append(std::pair(size, ref))) {
    137    return false;
    138  }
    139 
    140  // NOTE: These are a heap indexes offseted by 1, such that we can manipulate
    141  // the tree of heap-sorted values which bubble up the largest values towards
    142  // the root of the tree.
    143  size_t i = heap.length();
    144  while (i > 1) {
    145    if (heap[i - 1].first <= heap[(i / 2) - 1].first) {
    146      return true;
    147    }
    148 
    149    const_swap(heap[i - 1], heap[(i / 2) - 1]);
    150    i /= 2;
    151  }
    152 
    153  return true;
    154 }
    155 
    156 bool DelazificationContext::init(
    157    const JS::ReadOnlyCompileOptions& options,
    158    frontend::InitialStencilAndDelazifications* stencils) {
    159  using namespace js::frontend;
    160 
    161  stencils_ = stencils;
    162 
    163  if (!fc_.allocateOwnedPool()) {
    164    return false;
    165  }
    166 
    167  // Initialize the relative indexes which are necessary for walking
    168  // delazification stencils from the CompilationInput.
    169  auto indexesGuard = stencils->ensureRelativeIndexes(&fc_);
    170  if (!indexesGuard) {
    171    return false;
    172  }
    173  indexesGuard_.emplace(std::move(indexesGuard));
    174 
    175  switch (options.eagerDelazificationStrategy()) {
    176    case JS::DelazificationOption::OnDemandOnly:
    177      // OnDemandOnly will parse function as they are require to continue the
    178      // execution on the main thread.
    179      MOZ_CRASH("OnDemandOnly should not create a DelazificationContext.");
    180      break;
    181    case JS::DelazificationOption::CheckConcurrentWithOnDemand:
    182    case JS::DelazificationOption::ConcurrentDepthFirst:
    183      // ConcurrentDepthFirst visit all functions to be delazified, visiting the
    184      // inner functions before the siblings functions.
    185      strategy_ = fc_.getAllocator()->make_unique<DepthFirstDelazification>();
    186      break;
    187    case JS::DelazificationOption::ConcurrentLargeFirst:
    188      // ConcurrentLargeFirst visit all functions to be delazified, visiting the
    189      // largest function first.
    190      strategy_ = fc_.getAllocator()->make_unique<LargeFirstDelazification>();
    191      break;
    192    case JS::DelazificationOption::ParseEverythingEagerly:
    193      // ParseEverythingEagerly parse all functions eagerly, thus leaving no
    194      // functions to be parsed on demand.
    195      MOZ_CRASH(
    196          "ParseEverythingEagerly should not create a DelazificationContext");
    197      break;
    198  }
    199 
    200  if (!strategy_) {
    201    return false;
    202  }
    203 
    204  // Queue functions from the top-level to be delazify.
    205  ScriptStencilRef topLevel{*stencils_, ScriptIndex{0}};
    206  return strategy_->add(&fc_, topLevel);
    207 }
    208 
    209 bool DelazificationContext::delazify() {
    210  fc_.setStackQuota(stackQuota_);
    211  auto purgePool =
    212      mozilla::MakeScopeExit([&] { fc_.nameCollectionPool().purge(); });
    213 
    214  using namespace js::frontend;
    215 
    216  // Create a scope-binding cache dedicated to this delazification. The memory
    217  // would be reclaimed when interrupted or if all delazification are completed.
    218  //
    219  // We do not use the one from the JSContext/Runtime, as it is not thread safe
    220  // to use it, as it could be purged by a GC in the mean time.
    221  StencilScopeBindingCache scopeCache(*stencils_);
    222 
    223  LifoAlloc tempLifoAlloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE,
    224                          js::BackgroundMallocArena);
    225 
    226  while (!strategy_->done()) {
    227    if (isInterrupted_) {
    228      isInterrupted_ = false;
    229      break;
    230    }
    231    const CompilationStencil* innerStencil = nullptr;
    232    ScriptStencilRef scriptRef = strategy_->next();
    233    {
    234      // Parse and generate bytecode for the inner function and save it on the
    235      // InitialStencilAndDelazifications object. If the function had already
    236      // been parsed, then just get the result back from the stencil.
    237      DelazifyFailureReason failureReason;
    238      innerStencil = DelazifyCanonicalScriptedFunction(
    239          &fc_, tempLifoAlloc, initialPrefableOptions_, &scopeCache,
    240          scriptRef.scriptIndex_, stencils_.get(), &failureReason);
    241      if (!innerStencil) {
    242        if (failureReason == DelazifyFailureReason::Compressed) {
    243          // The script source is already compressed, and delazification cannot
    244          // be performed without decompressing.
    245          // There is no reason to keep our eager delazification going.
    246          strategy_->clear();
    247          return true;
    248        }
    249 
    250        strategy_->clear();
    251        return false;
    252      }
    253    }
    254 
    255    if (!strategy_->add(&fc_, scriptRef)) {
    256      strategy_->clear();
    257      return false;
    258    }
    259  }
    260 
    261  return true;
    262 }
    263 
    264 bool DelazificationContext::done() const {
    265  if (!strategy_) {
    266    return true;
    267  }
    268 
    269  return strategy_->done();
    270 }
    271 
    272 size_t DelazificationContext::sizeOfExcludingThis(
    273    mozilla::MallocSizeOf mallocSizeOf) const {
    274  size_t size = stencils_->sizeOfIncludingThis(mallocSizeOf);
    275  return size;
    276 }