Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/js/src/vm/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 116 kB image not shown  

Quelle  JSScript.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


/*
 * JS script operations.
 */


#include "vm/JSScript-inl.h"

#include "mozilla/ArrayUtils.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Span.h"  // mozilla::{Span,Span}
#include "mozilla/Sprintf.h"
#include "mozilla/Utf8.h"
#include "mozilla/Vector.h"

#include <algorithm>
#include <new>
#include <string.h>
#include <type_traits>
#include <utility>

#include "jstypes.h"

#include "frontend/BytecodeSection.h"
#include "frontend/CompilationStencil.h"  // frontend::CompilationStencil, frontend::InitialStencilAndDelazifications
#include "frontend/FrontendContext.h"  // AutoReportFrontendContext
#include "frontend/ParseContext.h"
#include "frontend/SourceNotes.h"  // SrcNote, SrcNoteType, SrcNoteIterator
#include "frontend/Stencil.h"  // DumpFunctionFlagsItems, DumpImmutableScriptFlags
#include "frontend/StencilXdr.h"  // XDRStencilEncoder
#include "gc/GCContext.h"
#include "jit/BaselineJIT.h"
#include "jit/CacheIRHealth.h"
#include "jit/Ion.h"
#include "jit/IonScript.h"
#include "jit/JitCode.h"
#include "jit/JitOptions.h"
#include "jit/JitRuntime.h"
#include "js/CharacterEncoding.h"  // JS_EncodeStringToUTF8
#include "js/ColumnNumber.h"  // JS::LimitedColumnNumberOneOrigin, JS::ColumnNumberOneOrigin, JS::ColumnNumberOffset
#include "js/CompileOptions.h"
#include "js/experimental/SourceHook.h"
#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "js/HeapAPI.h"               // JS::GCCellPtr
#include "js/MemoryMetrics.h"
#include "js/Printer.h"  // js::GenericPrinter, js::Fprinter, js::Sprinter, js::QuoteString
#include "js/Transcoding.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"  // JS::UniqueChars
#include "js/Value.h"    // JS::Value
#include "util/Poison.h"
#include "util/StringBuilder.h"
#include "util/Text.h"
#include "vm/BigIntType.h"  // JS::BigInt
#include "vm/BytecodeIterator.h"
#include "vm/BytecodeLocation.h"
#include "vm/BytecodeUtil.h"  // Disassemble
#include "vm/Compression.h"
#include "vm/HelperThreadState.h"  // js::RunPendingSourceCompressions
#include "vm/JSFunction.h"
#include "vm/JSObject.h"
#include "vm/JSONPrinter.h"  // JSONPrinter
#include "vm/Opcodes.h"
#include "vm/PortableBaselineInterpret.h"
#include "vm/Scope.h"  // Scope
#include "vm/SharedImmutableStringsCache.h"
#include "vm/StencilEnums.h"  // TryNote, TryNoteKind, ScopeNote
#include "vm/StringType.h"    // JSString, JSAtom
#include "vm/Time.h"          // AutoIncrementalTimer
#include "vm/ToSource.h"      // JS::ValueToSource
#ifdef MOZ_VTUNE
#  include "vtune/VTuneWrapper.h"
#endif

#include "gc/Marking-inl.h"
#include "vm/BytecodeIterator-inl.h"
#include "vm/BytecodeLocation-inl.h"
#include "vm/Compartment-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/SharedImmutableStringsCache-inl.h"
#include "vm/Stack-inl.h"

using namespace js;

using mozilla::CheckedInt;
using mozilla::Maybe;
using mozilla::PointerRangeSize;
using mozilla::Utf8Unit;

using JS::ReadOnlyCompileOptions;
using JS::SourceText;

bool js::BaseScript::isUsingInterpreterTrampoline(JSRuntime* rt) const {
  return jitCodeRaw() == rt->jitRuntime()->interpreterStub().value;
}

js::ScriptSource* js::BaseScript::maybeForwardedScriptSource() const {
  return MaybeForwarded(sourceObject())->source();
}

void js::BaseScript::setEnclosingScript(BaseScript* enclosingScript) {
  MOZ_ASSERT(enclosingScript);
  warmUpData_.initEnclosingScript(enclosingScript);
}

void js::BaseScript::setEnclosingScope(Scope* enclosingScope) {
  if (warmUpData_.isEnclosingScript()) {
    warmUpData_.clearEnclosingScript();
  }

  MOZ_ASSERT(enclosingScope);
  warmUpData_.initEnclosingScope(enclosingScope);
}

void js::BaseScript::finalize(JS::GCContext* gcx) {
  // Scripts with bytecode may have optional data stored in per-runtime or
  // per-zone maps. Note that a failed compilation must not have entries since
  // the script itself will not be marked as having bytecode.
  if (hasBytecode()) {
    JSScript* script = this->asJSScript();

    if (coverage::IsLCovEnabled()) {
      coverage::CollectScriptCoverage(script, true);
    }

    script->destroyScriptCounts();
  }

  {
    JSRuntime* rt = gcx->runtime();
    if (rt->hasJitRuntime() && rt->jitRuntime()->hasInterpreterEntryMap()) {
      rt->jitRuntime()->getInterpreterEntryMap()->remove(this);
    }

    rt->geckoProfiler().onScriptFinalized(this);
  }

#ifdef MOZ_VTUNE
  if (zone()->scriptVTuneIdMap) {
    // Note: we should only get here if the VTune JIT profiler is running.
    zone()->scriptVTuneIdMap->remove(this);
  }
#endif

  if (warmUpData_.isJitScript()) {
    JSScript* script = this->asJSScript();
#ifdef JS_CACHEIR_SPEW
    maybeUpdateWarmUpCount(script);
#endif
    script->releaseJitScriptOnFinalize(gcx);
  }

#ifdef JS_CACHEIR_SPEW
  if (hasBytecode()) {
    maybeSpewScriptFinalWarmUpCount(this->asJSScript());
  }
#endif

  if (data_) {
    // We don't need to triger any barriers here, just free the memory.
    size_t size = data_->allocationSize();
    AlwaysPoison(data_, JS_POISONED_JSSCRIPT_DATA_PATTERN, size,
                 MemCheckKind::MakeNoAccess);
    gcx->free_(this, data_, size, MemoryUse::ScriptPrivateData);
  }

  freeSharedData();
}

js::Scope* js::BaseScript::releaseEnclosingScope() {
  Scope* enclosing = warmUpData_.toEnclosingScope();
  warmUpData_.clearEnclosingScope();
  return enclosing;
}

void js::BaseScript::swapData(UniquePtr<PrivateScriptData>& other) {
  if (data_) {
    RemoveCellMemory(this, data_->allocationSize(),
                     MemoryUse::ScriptPrivateData);
  }

  PrivateScriptData* old = data_;
  data_.set(zone(), other.release());
  other.reset(old);

  if (data_) {
    AddCellMemory(this, data_->allocationSize(), MemoryUse::ScriptPrivateData);
  }
}

js::Scope* js::BaseScript::enclosingScope() const {
  MOZ_ASSERT(!warmUpData_.isEnclosingScript(),
             "Enclosing scope is not computed yet");

  if (warmUpData_.isEnclosingScope()) {
    return warmUpData_.toEnclosingScope();
  }

  MOZ_ASSERT(data_, "Script doesn't seem to be compiled");

  return gcthings()[js::GCThingIndex::outermostScopeIndex()]
      .as<Scope>()
      .enclosing();
}

size_t JSScript::numAlwaysLiveFixedSlots() const {
  if (bodyScope()->is<js::FunctionScope>()) {
    return bodyScope()->as<js::FunctionScope>().nextFrameSlot();
  }
  if (bodyScope()->is<js::ModuleScope>()) {
    return bodyScope()->as<js::ModuleScope>().nextFrameSlot();
  }
  if (bodyScope()->is<js::EvalScope>() &&
      bodyScope()->kind() == ScopeKind::StrictEval) {
    return bodyScope()->as<js::EvalScope>().nextFrameSlot();
  }
  return 0;
}

unsigned JSScript::numArgs() const {
  if (bodyScope()->is<js::FunctionScope>()) {
    return bodyScope()->as<js::FunctionScope>().numPositionalFormalParameters();
  }
  return 0;
}

bool JSScript::functionHasParameterExprs() const {
  // Only functions have parameters.
  js::Scope* scope = bodyScope();
  if (!scope->is<js::FunctionScope>()) {
    return false;
  }
  return scope->as<js::FunctionScope>().hasParameterExprs();
}

bool JSScript::isModule() const { return bodyScope()->is<js::ModuleScope>(); }

js::ModuleObject* JSScript::module() const {
  MOZ_ASSERT(isModule());
  return bodyScope()->as<js::ModuleScope>().module();
}

bool JSScript::isGlobalCode() const {
  return bodyScope()->is<js::GlobalScope>();
}

js::VarScope* JSScript::functionExtraBodyVarScope() const {
  MOZ_ASSERT(functionHasExtraBodyVarScope());
  for (JS::GCCellPtr gcThing : gcthings()) {
    if (!gcThing.is<js::Scope>()) {
      continue;
    }
    js::Scope* scope = &gcThing.as<js::Scope>();
    if (scope->kind() == js::ScopeKind::FunctionBodyVar) {
      return &scope->as<js::VarScope>();
    }
  }
  MOZ_CRASH("Function extra body var scope not found");
}

bool JSScript::needsBodyEnvironment() const {
  for (JS::GCCellPtr gcThing : gcthings()) {
    if (!gcThing.is<js::Scope>()) {
      continue;
    }
    js::Scope* scope = &gcThing.as<js::Scope>();
    if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment()) {
      return true;
    }
  }
  return false;
}

bool JSScript::isDirectEvalInFunction() const {
  if (!isForEval()) {
    return false;
  }
  return bodyScope()->hasOnChain(js::ScopeKind::Function);
}

// Initialize the optional arrays in the trailing allocation. This is a set of
// offsets that delimit each optional array followed by the arrays themselves.
// See comment before 'ImmutableScriptData' for more details.
void ImmutableScriptData::initOptionalArrays(Offset* pcursor,
                                             uint32_t numResumeOffsets,
                                             uint32_t numScopeNotes,
                                             uint32_t numTryNotes) {
  Offset cursor = (*pcursor);

  // The byte arrays must have already been padded.
  MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor),
             "Bytecode and source notes should be padded to keep alignment");

  // Each non-empty optional array needs will need an offset to its end.
  unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) +
                               unsigned(numScopeNotes > 0) +
                               unsigned(numTryNotes > 0);

  // Default-initialize the optional-offsets.
  initElements<Offset>(cursor, numOptionalArrays);
  cursor += numOptionalArrays * sizeof(Offset);

  // Offset between optional-offsets table and the optional arrays. This is
  // later used to access the optional-offsets table as well as first optional
  // array.
  optArrayOffset_ = cursor;

  // Each optional array that follows must store an end-offset in the offset
  // table. Assign table entries by using this 'offsetIndex'. The index 0 is
  // reserved for implicit value 'optArrayOffset'.
  int offsetIndex = 0;

  // Default-initialize optional 'resumeOffsets'.
  MOZ_ASSERT(resumeOffsetsOffset() == cursor);
  if (numResumeOffsets > 0) {
    initElements<uint32_t>(cursor, numResumeOffsets);
    cursor += numResumeOffsets * sizeof(uint32_t);
    setOptionalOffset(++offsetIndex, cursor);
  }
  flagsRef().resumeOffsetsEndIndex = offsetIndex;

  // Default-initialize optional 'scopeNotes'.
  MOZ_ASSERT(scopeNotesOffset() == cursor);
  if (numScopeNotes > 0) {
    initElements<ScopeNote>(cursor, numScopeNotes);
    cursor += numScopeNotes * sizeof(ScopeNote);
    setOptionalOffset(++offsetIndex, cursor);
  }
  flagsRef().scopeNotesEndIndex = offsetIndex;

  // Default-initialize optional 'tryNotes'
  MOZ_ASSERT(tryNotesOffset() == cursor);
  if (numTryNotes > 0) {
    initElements<TryNote>(cursor, numTryNotes);
    cursor += numTryNotes * sizeof(TryNote);
    setOptionalOffset(++offsetIndex, cursor);
  }
  flagsRef().tryNotesEndIndex = offsetIndex;

  MOZ_ASSERT(endOffset() == cursor);
  (*pcursor) = cursor;
}

ImmutableScriptData::ImmutableScriptData(uint32_t codeLength,
                                         uint32_t noteLength,
                                         uint32_t numResumeOffsets,
                                         uint32_t numScopeNotes,
                                         uint32_t numTryNotes)
    : codeLength_(codeLength) {
  // Variable-length data begins immediately after ImmutableScriptData itself.
  Offset cursor = sizeof(ImmutableScriptData);

  // The following arrays are byte-aligned with additional padding to ensure
  // that together they maintain uint32_t-alignment.
  {
    MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor));

    // Zero-initialize 'flags'
    MOZ_ASSERT(isAlignedOffset<Flags>(cursor));
    new (offsetToPointer<void>(cursor)) Flags{};
    cursor += sizeof(Flags);

    initElements<jsbytecode>(cursor, codeLength);
    cursor += codeLength * sizeof(jsbytecode);

    initElements<SrcNote>(cursor, noteLength);
    cursor += noteLength * sizeof(SrcNote);

    MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor));
  }

  // Initialization for remaining arrays.
  initOptionalArrays(&cursor, numResumeOffsets, numScopeNotes, numTryNotes);

  // Check that we correctly recompute the expected values.
  MOZ_ASSERT(this->codeLength() == codeLength);
  MOZ_ASSERT(this->noteLength() == noteLength);

  // Sanity check
  MOZ_ASSERT(endOffset() == cursor);
}

void js::FillImmutableFlagsFromCompileOptionsForTopLevel(
    const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) {
  using ImmutableFlags = ImmutableScriptFlagsEnum;

  js::FillImmutableFlagsFromCompileOptionsForFunction(options, flags);

  flags.setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce);
  flags.setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval);
}

void js::FillImmutableFlagsFromCompileOptionsForFunction(
    const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) {
  using ImmutableFlags = ImmutableScriptFlagsEnum;

  flags.setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode);
  flags.setFlag(ImmutableFlags::ForceStrict, options.forceStrictMode());
  flags.setFlag(ImmutableFlags::HasNonSyntacticScope,
                options.nonSyntacticScope);
}

// Check if flags matches to compile options for flags set by
// FillImmutableFlagsFromCompileOptionsForTopLevel above.
bool js::CheckCompileOptionsMatch(const ReadOnlyCompileOptions& options,
                                  ImmutableScriptFlags flags) {
  using ImmutableFlags = ImmutableScriptFlagsEnum;

  bool selfHosted = !!(flags & uint32_t(ImmutableFlags::SelfHosted));
  bool forceStrict = !!(flags & uint32_t(ImmutableFlags::ForceStrict));
  bool hasNonSyntacticScope =
      !!(flags & uint32_t(ImmutableFlags::HasNonSyntacticScope));
  bool noScriptRval = !!(flags & uint32_t(ImmutableFlags::NoScriptRval));
  bool treatAsRunOnce = !!(flags & uint32_t(ImmutableFlags::TreatAsRunOnce));

  return options.selfHostingMode == selfHosted &&
         options.noScriptRval == noScriptRval &&
         options.isRunOnce == treatAsRunOnce &&
         options.forceStrictMode() == forceStrict &&
         options.nonSyntacticScope == hasNonSyntacticScope;
}

JS_PUBLIC_API bool JS::CheckCompileOptionsMatch(
    const ReadOnlyCompileOptions& options, JSScript* script) {
  return js::CheckCompileOptionsMatch(options, script->immutableFlags());
}

bool JSScript::initScriptCounts(JSContext* cx) {
  MOZ_ASSERT(!hasScriptCounts());

  // Record all pc which are the first instruction of a basic block.
  mozilla::Vector<jsbytecode*, 16, SystemAllocPolicy> jumpTargets;

  js::BytecodeLocation main = mainLocation();
  AllBytecodesIterable iterable(this);
  for (auto& loc : iterable) {
    if (loc.isJumpTarget() || loc == main) {
      if (!jumpTargets.append(loc.toRawBytecode())) {
        ReportOutOfMemory(cx);
        return false;
      }
    }
  }

  // Initialize all PCCounts counters to 0.
  ScriptCounts::PCCountsVector base;
  if (!base.reserve(jumpTargets.length())) {
    ReportOutOfMemory(cx);
    return false;
  }

  for (size_t i = 0; i < jumpTargets.length(); i++) {
    base.infallibleEmplaceBack(pcToOffset(jumpTargets[i]));
  }

  // Create zone's scriptCountsMap if necessary.
  if (!zone()->scriptCountsMap) {
    auto map = cx->make_unique<ScriptCountsMap>();
    if (!map) {
      return false;
    }

    zone()->scriptCountsMap = std::move(map);
  }

  // Allocate the ScriptCounts.
  UniqueScriptCounts sc = cx->make_unique<ScriptCounts>(std::move(base));
  if (!sc) {
    return false;
  }

  MOZ_ASSERT(this->hasBytecode());

  // Register the current ScriptCounts in the zone's map.
  if (!zone()->scriptCountsMap->putNew(this, std::move(sc))) {
    ReportOutOfMemory(cx);
    return false;
  }

  // safe to set this;  we can't fail after this point.
  setHasScriptCounts();

  // Enable interrupts in any interpreter frames running on this script. This
  // is used to let the interpreter increment the PCCounts, if present.
  for (ActivationIterator iter(cx); !iter.done(); ++iter) {
    if (iter->isInterpreter()) {
      iter->asInterpreter()->enableInterruptsIfRunning(this);
    }
  }

  return true;
}

static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript* script) {
  MOZ_ASSERT(script->hasScriptCounts());
  ScriptCountsMap::Ptr p = script->zone()->scriptCountsMap->lookup(script);
  MOZ_ASSERT(p);
  return p;
}

ScriptCounts& JSScript::getScriptCounts() {
  ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
  return *p->value();
}

js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) {
  PCCounts searched = PCCounts(offset);
  PCCounts* elem =
      std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
  if (elem == pcCounts_.end() || elem->pcOffset() != offset) {
    return nullptr;
  }
  return elem;
}

const js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) const {
  PCCounts searched = PCCounts(offset);
  const PCCounts* elem =
      std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
  if (elem == pcCounts_.end() || elem->pcOffset() != offset) {
    return nullptr;
  }
  return elem;
}

js::PCCounts* ScriptCounts::getImmediatePrecedingPCCounts(size_t offset) {
  PCCounts searched = PCCounts(offset);
  PCCounts* elem =
      std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
  if (elem == pcCounts_.end()) {
    return &pcCounts_.back();
  }
  if (elem->pcOffset() == offset) {
    return elem;
  }
  if (elem != pcCounts_.begin()) {
    return elem - 1;
  }
  return nullptr;
}

const js::PCCounts* ScriptCounts::maybeGetThrowCounts(size_t offset) const {
  PCCounts searched = PCCounts(offset);
  const PCCounts* elem =
      std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
  if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
    return nullptr;
  }
  return elem;
}

const js::PCCounts* ScriptCounts::getImmediatePrecedingThrowCounts(
    size_t offset) const {
  PCCounts searched = PCCounts(offset);
  const PCCounts* elem =
      std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
  if (elem == throwCounts_.end()) {
    if (throwCounts_.begin() == throwCounts_.end()) {
      return nullptr;
    }
    return &throwCounts_.back();
  }
  if (elem->pcOffset() == offset) {
    return elem;
  }
  if (elem != throwCounts_.begin()) {
    return elem - 1;
  }
  return nullptr;
}

js::PCCounts* ScriptCounts::getThrowCounts(size_t offset) {
  PCCounts searched = PCCounts(offset);
  PCCounts* elem =
      std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
  if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
    elem = throwCounts_.insert(elem, searched);
  }
  return elem;
}

size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
  size_t size = mallocSizeOf(this);
  size += pcCounts_.sizeOfExcludingThis(mallocSizeOf);
  size += throwCounts_.sizeOfExcludingThis(mallocSizeOf);
  if (ionCounts_) {
    size += ionCounts_->sizeOfIncludingThis(mallocSizeOf);
  }
  return size;
}

js::PCCounts* JSScript::maybeGetPCCounts(jsbytecode* pc) {
  MOZ_ASSERT(containsPC(pc));
  return getScriptCounts().maybeGetPCCounts(pcToOffset(pc));
}

const js::PCCounts* JSScript::maybeGetThrowCounts(jsbytecode* pc) {
  MOZ_ASSERT(containsPC(pc));
  return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc));
}

js::PCCounts* JSScript::getThrowCounts(jsbytecode* pc) {
  MOZ_ASSERT(containsPC(pc));
  return getScriptCounts().getThrowCounts(pcToOffset(pc));
}

uint64_t JSScript::getHitCount(jsbytecode* pc) {
  MOZ_ASSERT(containsPC(pc));
  if (pc < main()) {
    pc = main();
  }

  ScriptCounts& sc = getScriptCounts();
  size_t targetOffset = pcToOffset(pc);
  const js::PCCounts* baseCount =
      sc.getImmediatePrecedingPCCounts(targetOffset);
  if (!baseCount) {
    return 0;
  }
  if (baseCount->pcOffset() == targetOffset) {
    return baseCount->numExec();
  }
  MOZ_ASSERT(baseCount->pcOffset() < targetOffset);
  uint64_t count = baseCount->numExec();
  do {
    const js::PCCounts* throwCount =
        sc.getImmediatePrecedingThrowCounts(targetOffset);
    if (!throwCount) {
      return count;
    }
    if (throwCount->pcOffset() <= baseCount->pcOffset()) {
      return count;
    }
    count -= throwCount->numExec();
    targetOffset = throwCount->pcOffset() - 1;
  } while (true);
}

void JSScript::addIonCounts(jit::IonScriptCounts* ionCounts) {
  ScriptCounts& sc = getScriptCounts();
  if (sc.ionCounts_) {
    ionCounts->setPrevious(sc.ionCounts_);
  }
  sc.ionCounts_ = ionCounts;
}

jit::IonScriptCounts* JSScript::getIonCounts() {
  return getScriptCounts().ionCounts_;
}

void JSScript::releaseScriptCounts(ScriptCounts* counts) {
  ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
  *counts = std::move(*p->value().get());
  zone()->scriptCountsMap->remove(p);
  clearHasScriptCounts();
}

void JSScript::destroyScriptCounts() {
  if (hasScriptCounts()) {
    ScriptCounts scriptCounts;
    releaseScriptCounts(&scriptCounts);
  }
}

void JSScript::resetScriptCounts() {
  if (!hasScriptCounts()) {
    return;
  }

  ScriptCounts& sc = getScriptCounts();

  for (PCCounts& elem : sc.pcCounts_) {
    elem.numExec() = 0;
  }

  for (PCCounts& elem : sc.throwCounts_) {
    elem.numExec() = 0;
  }
}

void ScriptSourceObject::finalize(JS::GCContext* gcx, JSObject* obj) {
  MOZ_ASSERT(gcx->onMainThread());
  ScriptSourceObject* sso = &obj->as<ScriptSourceObject>();
  sso->source()->Release();

  // Clear the private value, calling the release hook if necessary.
  sso->setPrivate(gcx->runtime(), UndefinedValue());

  sso->clearStencils();
}

static const JSClassOps ScriptSourceObjectClassOps = {
    nullptr,                       // addProperty
    nullptr,                       // delProperty
    nullptr,                       // enumerate
    nullptr,                       // newEnumerate
    nullptr,                       // resolve
    nullptr,                       // mayResolve
    ScriptSourceObject::finalize,  // finalize
    nullptr,                       // call
    nullptr,                       // construct
    nullptr,                       // trace
};

const JSClass ScriptSourceObject::class_ = {
    "ScriptSource",
    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
    &ScriptSourceObjectClassOps,
};

ScriptSourceObject* ScriptSourceObject::create(JSContext* cx,
                                               ScriptSource* source) {
  ScriptSourceObject* obj =
      NewObjectWithGivenProto<ScriptSourceObject>(cx, nullptr);
  if (!obj) {
    return nullptr;
  }

  // The matching decref is in ScriptSourceObject::finalize.
  obj->initReservedSlot(SOURCE_SLOT, PrivateValue(do_AddRef(source).take()));

  // The slots below should be populated by a call to initFromOptions. Poison
  // them.
  obj->initReservedSlot(ELEMENT_PROPERTY_SLOT, MagicValue(JS_GENERIC_MAGIC));
  obj->initReservedSlot(INTRODUCTION_SCRIPT_SLOT, MagicValue(JS_GENERIC_MAGIC));

  obj->initReservedSlot(STENCILS_SLOT, UndefinedValue());

  return obj;
}

[[nodiscard]] static bool MaybeValidateFilename(
    JSContext* cx, Handle<ScriptSourceObject*> sso,
    const JS::InstantiateOptions& options) {
  if (!gFilenameValidationCallback) {
    return true;
  }

  const char* filename = sso->source()->filename();
  if (!filename || options.skipFilenameValidation) {
    return true;
  }

  if (gFilenameValidationCallback(cx, filename)) {
    return true;
  }

  const char* utf8Filename;
  if (mozilla::IsUtf8(mozilla::MakeStringSpan(filename))) {
    utf8Filename = filename;
  } else {
    utf8Filename = "(invalid UTF-8 filename)";
  }
  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNSAFE_FILENAME,
                           utf8Filename);
  return false;
}

/* static */
bool ScriptSourceObject::initFromOptions(
    JSContext* cx, Handle<ScriptSourceObject*> source,
    const JS::InstantiateOptions& options) {
  cx->releaseCheck(source);
  MOZ_ASSERT(
      source->getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic(JS_GENERIC_MAGIC));
  MOZ_ASSERT(source->getReservedSlot(INTRODUCTION_SCRIPT_SLOT)
                 .isMagic(JS_GENERIC_MAGIC));

  if (!MaybeValidateFilename(cx, source, options)) {
    return false;
  }

  if (options.deferDebugMetadata) {
    return true;
  }

  // Initialize the element attribute slot and introduction script slot
  // this marks the SSO as initialized for asserts.

  RootedString elementAttributeName(cx);
  if (!initElementProperties(cx, source, elementAttributeName)) {
    return false;
  }

  RootedValue introductionScript(cx);
  source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript);

  return true;
}

/* static */
bool ScriptSourceObject::initElementProperties(
    JSContext* cx, Handle<ScriptSourceObject*> source,
    HandleString elementAttrName) {
  RootedValue nameValue(cx);
  if (elementAttrName) {
    nameValue = StringValue(elementAttrName);
  }
  if (!cx->compartment()->wrap(cx, &nameValue)) {
    return false;
  }

  source->setReservedSlot(ELEMENT_PROPERTY_SLOT, nameValue);

  return true;
}

void ScriptSourceObject::setPrivate(JSRuntime* rt, const Value& value) {
  // Update the private value, calling addRef/release hooks if necessary
  // to allow the embedding to maintain a reference count for the
  // private data.
  JS::AutoSuppressGCAnalysis nogc;
  Value prevValue = getReservedSlot(PRIVATE_SLOT);
  rt->releaseScriptPrivate(prevValue);
  setReservedSlot(PRIVATE_SLOT, value);
  rt->addRefScriptPrivate(value);
}

void ScriptSourceObject::clearPrivate(JSRuntime* rt) {
  // Clear the private value, calling release hook if necessary.
  // |this| may be gray, be careful not to create edges to it.
  JS::AutoSuppressGCAnalysis nogc;
  Value prevValue = getReservedSlot(PRIVATE_SLOT);
  rt->releaseScriptPrivate(prevValue);
  getSlotRef(PRIVATE_SLOT).setUndefinedUnchecked();
}

class ScriptSource::LoadSourceMatcher {
  JSContext* const cx_;
  ScriptSource* const ss_;
  boolconst loaded_;

 public:
  explicit LoadSourceMatcher(JSContext* cx, ScriptSource* ss, bool* loaded)
      : cx_(cx), ss_(ss), loaded_(loaded) {}

  template <typename Unit, SourceRetrievable CanRetrieve>
  bool operator()(const Compressed<Unit, CanRetrieve>&) const {
    *loaded_ = true;
    return true;
  }

  template <typename Unit, SourceRetrievable CanRetrieve>
  bool operator()(const Uncompressed<Unit, CanRetrieve>&) const {
    *loaded_ = true;
    return true;
  }

  template <typename Unit>
  bool operator()(const Retrievable<Unit>&) {
    if (!cx_->runtime()->sourceHook.ref()) {
      *loaded_ = false;
      return true;
    }

    size_t length;

    // The first argument is just for overloading -- its value doesn't matter.
    if (!tryLoadAndSetSource(Unit('0'), &length)) {
      return false;
    }

    return true;
  }

  bool operator()(const Missing&) const {
    *loaded_ = false;
    return true;
  }

 private:
  bool tryLoadAndSetSource(const Utf8Unit&, size_t* length) const {
    char* utf8Source;
    if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), nullptr,
                                          &utf8Source, length)) {
      return false;
    }

    if (!utf8Source) {
      *loaded_ = false;
      return true;
    }

    if (!ss_->setRetrievedSource(
            cx_, EntryUnits<Utf8Unit>(reinterpret_cast<Utf8Unit*>(utf8Source)),
            *length)) {
      return false;
    }

    *loaded_ = true;
    return true;
  }

  bool tryLoadAndSetSource(const char16_t&, size_t* length) const {
    char16_t* utf16Source;
    if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &utf16Source,
                                          nullptr, length)) {
      return false;
    }

    if (!utf16Source) {
      *loaded_ = false;
      return true;
    }

    if (!ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(utf16Source),
                                 *length)) {
      return false;
    }

    *loaded_ = true;
    return true;
  }
};

/* static */
bool ScriptSource::loadSource(JSContext* cx, ScriptSource* ss, bool* loaded) {
  return ss->data.match(LoadSourceMatcher(cx, ss, loaded));
}

/* static */
JSLinearString* JSScript::sourceData(JSContext* cx, HandleScript script) {
  MOZ_ASSERT(script->scriptSource()->hasSourceText());
  return script->scriptSource()->substring(cx, script->sourceStart(),
                                           script->sourceEnd());
}

bool BaseScript::appendSourceDataForToString(JSContext* cx,
                                             StringBuilder& buf) {
  MOZ_ASSERT(scriptSource()->hasSourceText());
  return scriptSource()->appendSubstring(cx, buf, toStringStart(),
                                         toStringEnd());
}

void UncompressedSourceCache::holdEntry(AutoHoldEntry& holder,
                                        const ScriptSourceChunk& ssc) {
  MOZ_ASSERT(!holder_);
  holder.holdEntry(this, ssc);
  holder_ = &holder;
}

void UncompressedSourceCache::releaseEntry(AutoHoldEntry& holder) {
  MOZ_ASSERT(holder_ == &holder);
  holder_ = nullptr;
}

template <typename Unit>
const Unit* UncompressedSourceCache::lookup(const ScriptSourceChunk& ssc,
                                            AutoHoldEntry& holder) {
  MOZ_ASSERT(!holder_);
  MOZ_ASSERT(ssc.ss->isCompressed<Unit>());

  if (!map_) {
    return nullptr;
  }

  if (Map::Ptr p = map_->lookup(ssc)) {
    holdEntry(holder, ssc);
    return static_cast<const Unit*>(p->value().get());
  }

  return nullptr;
}

bool UncompressedSourceCache::put(const ScriptSourceChunk& ssc, SourceData data,
                                  AutoHoldEntry& holder) {
  MOZ_ASSERT(!holder_);

  if (!map_) {
    map_ = MakeUnique<Map>();
    if (!map_) {
      return false;
    }
  }

  if (!map_->put(ssc, std::move(data))) {
    return false;
  }

  holdEntry(holder, ssc);
  return true;
}

void UncompressedSourceCache::purge() {
  if (!map_) {
    return;
  }

  for (Map::Range r = map_->all(); !r.empty(); r.popFront()) {
    if (holder_ && r.front().key() == holder_->sourceChunk()) {
      holder_->deferDelete(std::move(r.front().value()));
      holder_ = nullptr;
    }
  }

  map_ = nullptr;
}

size_t UncompressedSourceCache::sizeOfExcludingThis(
    mozilla::MallocSizeOf mallocSizeOf) {
  size_t n = 0;
  if (map_ && !map_->empty()) {
    n += map_->shallowSizeOfIncludingThis(mallocSizeOf);
    for (Map::Range r = map_->all(); !r.empty(); r.popFront()) {
      n += mallocSizeOf(r.front().value().get());
    }
  }
  return n;
}

template <typename Unit>
const Unit* ScriptSource::chunkUnits(
    JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder,
    size_t chunk) {
  const CompressedData<Unit>& c = *compressedData<Unit>();

  ScriptSourceChunk ssc(this, chunk);
  if (const Unit* decompressed =
          cx->caches().uncompressedSourceCache.lookup<Unit>(ssc, holder)) {
    return decompressed;
  }

  size_t totalLengthInBytes = length() * sizeof(Unit);
  size_t chunkBytes = Compressor::chunkSize(totalLengthInBytes, chunk);

  MOZ_ASSERT((chunkBytes % sizeof(Unit)) == 0);
  const size_t chunkLength = chunkBytes / sizeof(Unit);
  EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(chunkLength));
  if (!decompressed) {
    JS_ReportOutOfMemory(cx);
    return nullptr;
  }

  // Compression treats input and output memory as plain ol' bytes. These
  // reinterpret_cast<>s accord exactly with that.
  if (!DecompressStringChunk(
          reinterpret_cast<const unsigned char*>(c.raw.chars()), chunk,
          reinterpret_cast<unsigned char*>(decompressed.get()), chunkBytes)) {
    JS_ReportOutOfMemory(cx);
    return nullptr;
  }

  const Unit* ret = decompressed.get();
  if (!cx->caches().uncompressedSourceCache.put(
          ssc, ToSourceData(std::move(decompressed)), holder)) {
    JS_ReportOutOfMemory(cx);
    return nullptr;
  }
  return ret;
}

template <typename Unit>
void ScriptSource::convertToCompressedSource(SharedImmutableString compressed,
                                             size_t uncompressedLength) {
  MOZ_ASSERT(isUncompressed<Unit>());
  MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength);

  if (data.is<Uncompressed<Unit, SourceRetrievable::Yes>>()) {
    data = SourceType(Compressed<Unit, SourceRetrievable::Yes>(
        std::move(compressed), uncompressedLength));
  } else {
    data = SourceType(Compressed<Unit, SourceRetrievable::No>(
        std::move(compressed), uncompressedLength));
  }
}

template <typename Unit>
void ScriptSource::performDelayedConvertToCompressedSource(
    ExclusiveData<ReaderInstances>::Guard& g) {
  // There might not be a conversion to compressed source happening at all.
  if (g->pendingCompressed.empty()) {
    return;
  }

  CompressedData<Unit>& pending =
      g->pendingCompressed.ref<CompressedData<Unit>>();

  convertToCompressedSource<Unit>(std::move(pending.raw),
                                  pending.uncompressedLength);

  g->pendingCompressed.destroy();
}

void ScriptSource::PinnedUnitsBase::addReader() {
  auto guard = source_->readers_.lock();
  guard->count++;
}

template <typename Unit>
void ScriptSource::PinnedUnitsBase::removeReader() {
  // If the off-thread compression task couldn't perform
  // convertToCompressedSource, the conversion is pending on
  // the pendingCompressed field.
  //
  // If there's no other reader at this point, perform the pending conversion
  // here.
  //
  // See also ScriptSource::triggerConvertToCompressedSource.
  auto guard = source_->readers_.lock();
  MOZ_ASSERT(guard->count > 0);
  if (--guard->count == 0) {
    source_->performDelayedConvertToCompressedSource<Unit>(guard);
  }
}

template <typename Unit>
ScriptSource::PinnedUnits<Unit>::~PinnedUnits() {
  if (units_) {
    removeReader<Unit>();
  }
}

template <typename Unit>
ScriptSource::PinnedUnitsIfUncompressed<Unit>::~PinnedUnitsIfUncompressed() {
  if (units_) {
    removeReader<Unit>();
  }
}

template <typename Unit>
const Unit* ScriptSource::units(JSContext* cx,
                                UncompressedSourceCache::AutoHoldEntry& holder,
                                size_t begin, size_t len) {
  MOZ_ASSERT(begin <= length());
  MOZ_ASSERT(begin + len <= length());

  if (isUncompressed<Unit>()) {
    const Unit* units = uncompressedData<Unit>()->units();
    if (!units) {
      return nullptr;
    }
    return units + begin;
  }

  if (data.is<Missing>()) {
    MOZ_CRASH("ScriptSource::units() on ScriptSource with missing source");
  }

  if (data.is<Retrievable<Unit>>()) {
    MOZ_CRASH("ScriptSource::units() on ScriptSource with retrievable source");
  }

  MOZ_ASSERT(isCompressed<Unit>());

  // Determine first/last chunks, the offset (in bytes) into the first chunk
  // of the requested units, and the number of bytes in the last chunk.
  //
  // Note that first and last chunk sizes are miscomputed and *must not be
  // used* when the first chunk is the last chunk.
  size_t firstChunk, firstChunkOffset, firstChunkSize;
  size_t lastChunk, lastChunkSize;
  Compressor::rangeToChunkAndOffset(
      begin * sizeof(Unit), (begin + len) * sizeof(Unit), &firstChunk,
      &firstChunkOffset, &firstChunkSize, &lastChunk, &lastChunkSize);
  MOZ_ASSERT(firstChunk <= lastChunk);
  MOZ_ASSERT(firstChunkOffset % sizeof(Unit) == 0);
  MOZ_ASSERT(firstChunkSize % sizeof(Unit) == 0);

  size_t firstUnit = firstChunkOffset / sizeof(Unit);

  // Directly return units within a single chunk.  UncompressedSourceCache
  // and |holder| will hold the units alive past function return.
  if (firstChunk == lastChunk) {
    const Unit* units = chunkUnits<Unit>(cx, holder, firstChunk);
    if (!units) {
      return nullptr;
    }

    return units + firstUnit;
  }

  // Otherwise the units span multiple chunks.  Copy successive chunks'
  // decompressed units into freshly-allocated memory to return.
  EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(len));
  if (!decompressed) {
    JS_ReportOutOfMemory(cx);
    return nullptr;
  }

  Unit* cursor;

  {
    // |AutoHoldEntry| is single-shot, and a holder successfully filled in
    // by |chunkUnits| must be destroyed before another can be used.  Thus
    // we can't use |holder| with |chunkUnits| when |chunkUnits| is used
    // with multiple chunks, and we must use and destroy distinct, fresh
    // holders for each chunk.
    UncompressedSourceCache::AutoHoldEntry firstHolder;
    const Unit* units = chunkUnits<Unit>(cx, firstHolder, firstChunk);
    if (!units) {
      return nullptr;
    }

    cursor = std::copy_n(units + firstUnit, firstChunkSize / sizeof(Unit),
                         decompressed.get());
  }

  for (size_t i = firstChunk + 1; i < lastChunk; i++) {
    UncompressedSourceCache::AutoHoldEntry chunkHolder;
    const Unit* units = chunkUnits<Unit>(cx, chunkHolder, i);
    if (!units) {
      return nullptr;
    }

    cursor = std::copy_n(units, Compressor::CHUNK_SIZE / sizeof(Unit), cursor);
  }

  {
    UncompressedSourceCache::AutoHoldEntry lastHolder;
    const Unit* units = chunkUnits<Unit>(cx, lastHolder, lastChunk);
    if (!units) {
      return nullptr;
    }

    cursor = std::copy_n(units, lastChunkSize / sizeof(Unit), cursor);
  }

  MOZ_ASSERT(PointerRangeSize(decompressed.get(), cursor) == len);

  // Transfer ownership to |holder|.
  const Unit* ret = decompressed.get();
  holder.holdUnits(std::move(decompressed));
  return ret;
}

template <typename Unit>
const Unit* ScriptSource::uncompressedUnits(size_t begin, size_t len) {
  MOZ_ASSERT(begin <= length());
  MOZ_ASSERT(begin + len <= length());

  if (!isUncompressed<Unit>()) {
    return nullptr;
  }

  const Unit* units = uncompressedData<Unit>()->units();
  if (!units) {
    return nullptr;
  }
  return units + begin;
}

template <typename Unit>
ScriptSource::PinnedUnits<Unit>::PinnedUnits(
    JSContext* cx, ScriptSource* source,
    UncompressedSourceCache::AutoHoldEntry& holder, size_t begin, size_t len)
    : PinnedUnitsBase(source) {
  MOZ_ASSERT(source->hasSourceType<Unit>(), "must pin units of source's type");

  addReader();

  units_ = source->units<Unit>(cx, holder, begin, len);
  if (!units_) {
    removeReader<Unit>();
  }
}

template class ScriptSource::PinnedUnits<Utf8Unit>;
template class ScriptSource::PinnedUnits<char16_t>;

template <typename Unit>
ScriptSource::PinnedUnitsIfUncompressed<Unit>::PinnedUnitsIfUncompressed(
    ScriptSource* source, size_t begin, size_t len)
    : PinnedUnitsBase(source) {
  MOZ_ASSERT(source->hasSourceType<Unit>(), "must pin units of source's type");

  addReader();

  units_ = source->uncompressedUnits<Unit>(begin, len);
  if (!units_) {
    removeReader<Unit>();
  }
}

template class ScriptSource::PinnedUnitsIfUncompressed<Utf8Unit>;
template class ScriptSource::PinnedUnitsIfUncompressed<char16_t>;

JSLinearString* ScriptSource::substring(JSContext* cx, size_t start,
                                        size_t stop) {
  MOZ_ASSERT(start <= stop);

  size_t len = stop - start;
  if (!len) {
    return cx->emptyString();
  }
  UncompressedSourceCache::AutoHoldEntry holder;

  // UTF-8 source text.
  if (hasSourceType<Utf8Unit>()) {
    PinnedUnits<Utf8Unit> units(cx, this, holder, start, len);
    if (!units.asChars()) {
      return nullptr;
    }

    const char* str = units.asChars();
    return NewStringCopyUTF8N(cx, JS::UTF8Chars(str, len));
  }

  // UTF-16 source text.
  PinnedUnits<char16_t> units(cx, this, holder, start, len);
  if (!units.asChars()) {
    return nullptr;
  }

  return NewStringCopyN<CanGC>(cx, units.asChars(), len);
}

JSLinearString* ScriptSource::substringDontDeflate(JSContext* cx, size_t start,
                                                   size_t stop) {
  MOZ_ASSERT(start <= stop);

  size_t len = stop - start;
  if (!len) {
    return cx->emptyString();
  }
  UncompressedSourceCache::AutoHoldEntry holder;

  // UTF-8 source text.
  if (hasSourceType<Utf8Unit>()) {
    PinnedUnits<Utf8Unit> units(cx, this, holder, start, len);
    if (!units.asChars()) {
      return nullptr;
    }

    const char* str = units.asChars();

    // There doesn't appear to be a non-deflating UTF-8 string creation
    // function -- but then again, it's not entirely clear how current
    // callers benefit from non-deflation.
    return NewStringCopyUTF8N(cx, JS::UTF8Chars(str, len));
  }

  // UTF-16 source text.
  PinnedUnits<char16_t> units(cx, this, holder, start, len);
  if (!units.asChars()) {
    return nullptr;
  }

  return NewStringCopyNDontDeflate<CanGC>(cx, units.asChars(), len);
}

bool ScriptSource::appendSubstring(JSContext* cx, StringBuilder& buf,
                                   size_t start, size_t stop) {
  MOZ_ASSERT(start <= stop);

  size_t len = stop - start;
  UncompressedSourceCache::AutoHoldEntry holder;

  if (hasSourceType<Utf8Unit>()) {
    PinnedUnits<Utf8Unit> pinned(cx, this, holder, start, len);
    if (!pinned.get()) {
      return false;
    }
    if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) {
      return false;
    }

    const Utf8Unit* units = pinned.get();
    return buf.append(units, len);
  } else {
    PinnedUnits<char16_t> pinned(cx, this, holder, start, len);
    if (!pinned.get()) {
      return false;
    }
    if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) {
      return false;
    }

    const char16_t* units = pinned.get();
    return buf.append(units, len);
  }
}

JSLinearString* ScriptSource::functionBodyString(JSContext* cx) {
  MOZ_ASSERT(isFunctionBody());

  size_t start = parameterListEnd_ + FunctionConstructorMedialSigils.length();
  size_t stop = length() - FunctionConstructorFinalBrace.length();
  return substring(cx, start, stop);
}

template <typename ContextT, typename Unit>
[[nodiscard]] bool ScriptSource::setUncompressedSourceHelper(
    ContextT* cx, EntryUnits<Unit>&& source, size_t length,
    SourceRetrievable retrievable) {
  auto& cache = SharedImmutableStringsCache::getSingleton();

  auto uniqueChars = SourceTypeTraits<Unit>::toCacheable(std::move(source));
  auto deduped = cache.getOrCreate(std::move(uniqueChars), length);
  if (!deduped) {
    ReportOutOfMemory(cx);
    return false;
  }

  if (retrievable == SourceRetrievable::Yes) {
    data = SourceType(
        Uncompressed<Unit, SourceRetrievable::Yes>(std::move(deduped)));
  } else {
    data = SourceType(
        Uncompressed<Unit, SourceRetrievable::No>(std::move(deduped)));
  }
  return true;
}

template <typename Unit>
[[nodiscard]] bool ScriptSource::setRetrievedSource(JSContext* cx,
                                                    EntryUnits<Unit>&& source,
                                                    size_t length) {
  MOZ_ASSERT(data.is<Retrievable<Unit>>(),
             "retrieved source can only overwrite the corresponding "
             "retrievable source");
  return setUncompressedSourceHelper(cx, std::move(source), length,
                                     SourceRetrievable::Yes);
}

bool js::IsOffThreadSourceCompressionEnabled() {
  // If we don't have concurrent execution compression will contend with
  // main-thread execution, in which case we disable. Similarly we don't want to
  // block the thread pool if it is too small.
  return GetHelperThreadCPUCount() > 1 && GetHelperThreadCount() > 1 &&
         CanUseExtraThreads();
}

bool ScriptSource::tryCompressOffThread(JSContext* cx) {
  // Beware: |js::SynchronouslyCompressSource| assumes that this function is
  // only called once, just after a script has been compiled, and it's never
  // called at some random time after that.  If multiple calls of this can ever
  // occur, that function may require changes.

  // The SourceCompressionTask needs to record the major GC number for
  // scheduling.
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));

  // If source compression was already attempted, do not queue a new task.
  if (hadCompressionTask_) {
    return true;
  }

  if (!hasUncompressedSource()) {
    // This excludes compressed, missing, and retrievable source.
    return true;
  }

  // There are several cases where source compression is not a good idea:
  //  - If the script is tiny, then compression will save little or no space.
  //  - If there is only one core, then compression will contend with JS
  //    execution (which hurts benchmarketing).
  //
  // Otherwise, enqueue a compression task to be processed when a major
  // GC is requested.

  if (length() < ScriptSource::MinimumCompressibleLength ||
      !IsOffThreadSourceCompressionEnabled()) {
    return true;
  }

  // Heap allocate the task. It will be freed upon compression
  // completing in AttachFinishedCompressedSources.
  auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), this);
  if (!task) {
    ReportOutOfMemory(cx);
    return false;
  }
  return EnqueueOffThreadCompression(cx, std::move(task));
}

template <typename Unit>
void ScriptSource::triggerConvertToCompressedSource(
    SharedImmutableString compressed, size_t uncompressedLength) {
  MOZ_ASSERT(isUncompressed<Unit>(),
             "should only be triggering compressed source installation to "
             "overwrite identically-encoded uncompressed source");
  MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength);

  // If units aren't pinned -- and they probably won't be, we'd have to have a
  // GC in the small window of time where a |PinnedUnits| was live -- then we
  // can immediately convert.
  {
    auto guard = readers_.lock();
    if (MOZ_LIKELY(!guard->count)) {
      convertToCompressedSource<Unit>(std::move(compressed),
                                      uncompressedLength);
      return;
    }

    // Otherwise, set aside the compressed-data info.  The conversion is
    // performed when the last |PinnedUnits| dies.
    MOZ_ASSERT(guard->pendingCompressed.empty(),
               "shouldn't be multiple conversions happening");
    guard->pendingCompressed.construct<CompressedData<Unit>>(
        std::move(compressed), uncompressedLength);
  }
}

template <typename Unit>
[[nodiscard]] bool ScriptSource::initializeWithUnretrievableCompressedSource(
    FrontendContext* fc, UniqueChars&& compressed, size_t rawLength,
    size_t sourceLength) {
  MOZ_ASSERT(data.is<Missing>(), "shouldn't be double-initializing");
  MOZ_ASSERT(compressed != nullptr);

  auto& cache = SharedImmutableStringsCache::getSingleton();
  auto deduped = cache.getOrCreate(std::move(compressed), rawLength);
  if (!deduped) {
    ReportOutOfMemory(fc);
    return false;
  }

#ifdef DEBUG
  {
    auto guard = readers_.lock();
    MOZ_ASSERT(
        guard->count == 0,
        "shouldn't be initializing a ScriptSource while its characters "
        "are pinned -- that only makes sense with a ScriptSource actively "
        "being inspected");
  }
#endif

  data = SourceType(Compressed<Unit, SourceRetrievable::No>(std::move(deduped),
                                                            sourceLength));

  return true;
}

template bool ScriptSource::initializeWithUnretrievableCompressedSource<
    Utf8Unit>(FrontendContext* fc, UniqueChars&& compressed, size_t rawLength,
              size_t sourceLength);
template bool ScriptSource::initializeWithUnretrievableCompressedSource<
    char16_t>(FrontendContext* fc, UniqueChars&& compressed, size_t rawLength,
              size_t sourceLength);

template <typename Unit>
bool ScriptSource::assignSource(FrontendContext* fc,
                                const ReadOnlyCompileOptions& options,
                                SourceText<Unit>& srcBuf) {
  MOZ_ASSERT(data.is<Missing>(),
             "source assignment should only occur on fresh ScriptSources");

  mutedErrors_ = options.mutedErrors();
  delazificationMode_ = options.eagerDelazificationStrategy();

  if (options.discardSource) {
    return true;
  }

  if (options.sourceIsLazy) {
    data = SourceType(Retrievable<Unit>());
    return true;
  }

  auto& cache = SharedImmutableStringsCache::getSingleton();
  auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&srcBuf]() {
    using CharT = typename SourceTypeTraits<Unit>::CharT;
    return srcBuf.ownsUnits()
               ? UniquePtr<CharT[], JS::FreePolicy>(srcBuf.takeChars())
               : DuplicateString(srcBuf.get(), srcBuf.length());
  });
  if (!deduped) {
    ReportOutOfMemory(fc);
    return false;
  }

  data =
      SourceType(Uncompressed<Unit, SourceRetrievable::No>(std::move(deduped)));
  return true;
}

template bool ScriptSource::assignSource(FrontendContext* fc,
                                         const ReadOnlyCompileOptions& options,
                                         SourceText<char16_t>& srcBuf);
template bool ScriptSource::assignSource(FrontendContext* fc,
                                         const ReadOnlyCompileOptions& options,
                                         SourceText<Utf8Unit>& srcBuf);

[[nodiscard]] static bool reallocUniquePtr(UniqueChars& unique, size_t size) {
  auto newPtr = static_cast<char*>(js_realloc(unique.get(), size));
  if (!newPtr) {
    return false;
  }

  // Since the realloc succeeded, unique is now holding a freed pointer.
  (void)unique.release();
  unique.reset(newPtr);
  return true;
}

template <typename Unit>
void SourceCompressionTask::workEncodingSpecific() {
  MOZ_ASSERT(source_->isUncompressed<Unit>());

  // Try to keep the maximum memory usage down by only allocating half the
  // size of the string, first.
  size_t inputBytes = source_->length() * sizeof(Unit);
  size_t firstSize = inputBytes / 2;
  UniqueChars compressed(js_pod_malloc<char>(firstSize));
  if (!compressed) {
    return;
  }

  const Unit* chars = source_->uncompressedData<Unit>()->units();
  Compressor comp(reinterpret_cast<const unsigned char*>(chars), inputBytes);
  if (!comp.init()) {
    return;
  }

  comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), firstSize);
  bool cont = true;
  bool reallocated = false;
  while (cont) {
    if (shouldCancel()) {
      return;
    }

    switch (comp.compressMore()) {
      case Compressor::CONTINUE:
        break;
      case Compressor::MOREOUTPUT: {
        if (reallocated) {
          // The compressed string is longer than the original string.
          return;
        }

        // The compressed output is greater than half the size of the
        // original string. Reallocate to the full size.
        if (!reallocUniquePtr(compressed, inputBytes)) {
          return;
        }

        comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()),
                       inputBytes);
        reallocated = true;
        break;
      }
      case Compressor::DONE:
        cont = false;
        break;
      case Compressor::OOM:
        return;
    }
  }

  size_t totalBytes = comp.totalBytesNeeded();

  // Shrink the buffer to the size of the compressed data.
  if (!reallocUniquePtr(compressed, totalBytes)) {
    return;
  }

  comp.finish(compressed.get(), totalBytes);

  if (shouldCancel()) {
    return;
  }

  auto& strings = SharedImmutableStringsCache::getSingleton();
  resultString_ = strings.getOrCreate(std::move(compressed), totalBytes);
}

struct SourceCompressionTask::PerformTaskWork {
  SourceCompressionTask* const task_;

  explicit PerformTaskWork(SourceCompressionTask* task) : task_(task) {}

  template <typename Unit, SourceRetrievable CanRetrieve>
  void operator()(const ScriptSource::Uncompressed<Unit, CanRetrieve>&) {
    task_->workEncodingSpecific<Unit>();
  }

  template <typename T>
  void operator()(const T&) {
    MOZ_CRASH(
        "why are we compressing missing, missing-but-retrievable, "
        "or already-compressed source?");
  }
};

void ScriptSource::performTaskWork(SourceCompressionTask* task) {
  MOZ_ASSERT(hasUncompressedSource());
  data.match(SourceCompressionTask::PerformTaskWork(task));
}

void SourceCompressionTask::runTask() {
  if (shouldCancel()) {
    return;
  }

  MOZ_ASSERT(source_->hasUncompressedSource());

  source_->performTaskWork(this);
}

void SourceCompressionTask::runHelperThreadTask(
    AutoLockHelperThreadState& locked) {
  {
    AutoUnlockHelperThreadState unlock(locked);
    this->runTask();
  }

  {
    AutoEnterOOMUnsafeRegion oomUnsafe;
    if (!HelperThreadState().compressionFinishedList(locked).append(this)) {
      oomUnsafe.crash("SourceCompressionTask::runHelperThreadTask");
    }
  }
}

void ScriptSource::triggerConvertToCompressedSourceFromTask(
    SharedImmutableString compressed) {
  data.match(TriggerConvertToCompressedSourceFromTask(this, compressed));
}

void SourceCompressionTask::complete() {
  if (!shouldCancel() && resultString_) {
    source_->triggerConvertToCompressedSourceFromTask(std::move(resultString_));
  }
}

bool js::SynchronouslyCompressSource(JSContext* cx,
                                     JS::Handle<BaseScript*> script) {
  // Finish all pending source compressions, including the single compression
  // task that may have been created (by |ScriptSource::tryCompressOffThread|)
  // just after the script was compiled.  Because we have flushed this queue,
  // no code below needs to synchronize with an off-thread parse task that
  // assumes the immutability of a |ScriptSource|'s data.
  //
  // This *may* end up compressing |script|'s source.  If it does -- we test
  // this below -- that takes care of things.  But if it doesn't, we will
  // synchronously compress ourselves (and as noted above, this won't race
  // anything).
  RunPendingSourceCompressions(cx->runtime());

  ScriptSource* ss = script->scriptSource();
#ifdef DEBUG
  {
    auto guard = ss->readers_.lock();
    MOZ_ASSERT(guard->count == 0,
               "can't synchronously compress while source units are in use");
  }
#endif

  // In principle a previously-triggered compression on a helper thread could
  // have already completed.  If that happens, there's nothing more to do.
  if (ss->hasCompressedSource()) {
    return true;
  }

  MOZ_ASSERT(ss->hasUncompressedSource(),
             "shouldn't be compressing uncompressible source");

  // Use an explicit scope to delineate the lifetime of |task|, for simplicity.
  {
#ifdef DEBUG
    uint32_t sourceRefs = ss->refs;
#endif
    MOZ_ASSERT(sourceRefs > 0, "at least |script| here should have a ref");

    // |SourceCompressionTask::shouldCancel| can periodically result in source
    // compression being canceled if we're not careful.  Guarantee that two refs
    // to |ss| are always live in this function (at least one preexisting and
    // one held by the task) so that compression is never canceled.
    auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), ss);
    if (!task) {
      ReportOutOfMemory(cx);
      return false;
    }

    MOZ_ASSERT(ss->refs > sourceRefs, "must have at least two refs now");

    // Attempt to compress.  This may not succeed if OOM happens, but (because
    // it ordinarily happens on a helper thread) no error will ever be set here.
    MOZ_ASSERT(!cx->isExceptionPending());
    ss->performTaskWork(task.get());
    MOZ_ASSERT(!cx->isExceptionPending());

    // Convert |ss| from uncompressed to compressed data.
    task->complete();

    MOZ_ASSERT(!cx->isExceptionPending());
  }

  // The only way source won't be compressed here is if OOM happened.
  return ss->hasCompressedSource();
}

void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                          JS::ScriptSourceInfo* info) const {
  info->misc += mallocSizeOf(this);
  info->numScripts++;
}

frontend::InitialStencilAndDelazifications*
ScriptSourceObject::maybeGetStencils() {
  Value stencilsVal = getReservedSlot(STENCILS_SLOT);
  if (stencilsVal.isUndefined()) {
    return nullptr;
  }

  return reinterpret_cast<frontend::InitialStencilAndDelazifications*>(
      uintptr_t(stencilsVal.toPrivate()) & ~STENCILS_MASK);
}

void ScriptSourceObject::clearStencils() {
  auto* stencils = maybeGetStencils();
  if (!stencils) {
    return;
  }

  stencils->Release();
  setReservedSlot(STENCILS_SLOT, UndefinedValue());
}

template <uintptr_t flag>
void ScriptSourceObject::setStencilsFlag() {
  JS::Value stencilsVal = getReservedSlot(STENCILS_SLOT);
  MOZ_ASSERT(!stencilsVal.isUndefined(),
             "This should be called after setStencils");
  uintptr_t raw = uintptr_t(stencilsVal.toPrivate());
  MOZ_ASSERT((raw & flag) == 0);
  raw |= flag;
  setReservedSlot(STENCILS_SLOT, PrivateValue(raw));
}

template <uintptr_t flag>
void ScriptSourceObject::unsetStencilsFlag() {
  JS::Value stencilsVal = getReservedSlot(STENCILS_SLOT);
  MOZ_ASSERT(!stencilsVal.isUndefined(),
             "This should be called after setStencils");
  uintptr_t raw = uintptr_t(stencilsVal.toPrivate());
  raw &= ~flag;
  if (raw & STENCILS_MASK) {
    setReservedSlot(STENCILS_SLOT, PrivateValue(raw));
  } else {
    clearStencils();
  }
}

template <uintptr_t flag>
bool ScriptSourceObject::isStencilsFlagSet() const {
  JS::Value stencilsVal = getReservedSlot(STENCILS_SLOT);
  if (stencilsVal.isUndefined()) {
    return false;
  }
  uintptr_t raw = uintptr_t(stencilsVal.toPrivate());
  return bool(raw & flag);
}

void ScriptSourceObject::setStencils(
    already_AddRefed<frontend::InitialStencilAndDelazifications> stencils) {
  MOZ_ASSERT(!maybeGetStencils());
  setReservedSlot(STENCILS_SLOT, PrivateValue(stencils.take()));
}

void ScriptSourceObject::setCollectingDelazifications() {
  setStencilsFlag<STENCILS_COLLECTING_DELAZIFICATIONS_FLAG>();
}

void ScriptSourceObject::unsetCollectingDelazifications() {
  unsetStencilsFlag<STENCILS_COLLECTING_DELAZIFICATIONS_FLAG>();
}

bool ScriptSourceObject::isCollectingDelazifications() const {
  return isStencilsFlagSet<STENCILS_COLLECTING_DELAZIFICATIONS_FLAG>();
}

void ScriptSourceObject::setSharingDelazifications() {
  setStencilsFlag<STENCILS_SHARING_DELAZIFICATIONS_FLAG>();
}

bool ScriptSourceObject::isSharingDelazifications() const {
  return isStencilsFlagSet<STENCILS_SHARING_DELAZIFICATIONS_FLAG>();
}

template <typename Unit>
[[nodiscard]] bool ScriptSource::initializeUnretrievableUncompressedSource(
    FrontendContext* fc, EntryUnits<Unit>&& source, size_t length) {
  MOZ_ASSERT(data.is<Missing>(), "must be initializing a fresh ScriptSource");
  return setUncompressedSourceHelper(fc, std::move(source), length,
                                     SourceRetrievable::No);
}

template bool ScriptSource::initializeUnretrievableUncompressedSource(
    FrontendContext* fc, EntryUnits<Utf8Unit>&& source, size_t length);
template bool ScriptSource::initializeUnretrievableUncompressedSource(
    FrontendContext* fc, EntryUnits<char16_t>&& source, size_t length);

// Format and return a cx->pod_malloc'ed URL for a generated script like:
//   {filename} line {lineno} > {introducer}
// For example:
//   foo.js line 7 > eval
// indicating code compiled by the call to 'eval' on line 7 of foo.js.
UniqueChars js::FormatIntroducedFilename(const char* filename, uint32_t lineno,
                                         const char* introducer) {
  // Compute the length of the string in advance, so we can allocate a
  // buffer of the right size on the first shot.
  //
  // (JS_smprintf would be perfect, as that allocates the result
  // dynamically as it formats the string, but it won't allocate from cx,
  // and wants us to use a special free function.)
  char linenoBuf[15];
  size_t filenameLen = strlen(filename);
  size_t linenoLen = SprintfLiteral(linenoBuf, "%u", lineno);
  size_t introducerLen = strlen(introducer);
  size_t len = filenameLen + 6 /* == strlen(" line ") */ + linenoLen +
               3 /* == strlen(" > ") */ + introducerLen + 1 /* \0 */;
  UniqueChars formatted(js_pod_malloc<char>(len));
  if (!formatted) {
    return nullptr;
  }

  mozilla::DebugOnly<size_t> checkLen = snprintf(
      formatted.get(), len, "%s line %s > %s", filename, linenoBuf, introducer);
  MOZ_ASSERT(checkLen == len - 1);

  return formatted;
}

bool ScriptSource::initFromOptions(FrontendContext* fc,
                                   const ReadOnlyCompileOptions& options) {
  MOZ_ASSERT(!filename_);
  MOZ_ASSERT(!introducerFilename_);

  mutedErrors_ = options.mutedErrors();
  delazificationMode_ = options.eagerDelazificationStrategy();

  startLine_ = options.lineno;
  startColumn_ = JS::LimitedColumnNumberOneOrigin::fromUnlimited(
      JS::ColumnNumberOneOrigin(options.column));
  introductionType_ = options.introductionType;
  setIntroductionOffset(options.introductionOffset);
  // The parameterListEnd_ is initialized later by setParameterListEnd, before
  // we expose any scripts that use this ScriptSource to the debugger.

  if (options.hasIntroductionInfo) {
    MOZ_ASSERT(options.introductionType != nullptr);
    const char* filename =
        options.filename() ? options.filename().c_str() : "";
    UniqueChars formatted = FormatIntroducedFilename(
        filename, options.introductionLineno, options.introductionType);
    if (!formatted) {
      ReportOutOfMemory(fc);
      return false;
    }
    if (!setFilename(fc, std::move(formatted))) {
      return false;
    }
  } else if (options.filename()) {
    if (!setFilename(fc, options.filename().c_str())) {
      return false;
    }
  }

  if (options.introducerFilename()) {
    if (!setIntroducerFilename(fc, options.introducerFilename().c_str())) {
      return false;
    }
  }

  return true;
}

// Use the SharedImmutableString map to deduplicate input string. The input
// string must be null-terminated.
template <typename SharedT, typename CharT>
static SharedT GetOrCreateStringZ(FrontendContext* fc,
                                  UniquePtr<CharT[], JS::FreePolicy>&& str) {
  size_t lengthWithNull = std::char_traits<CharT>::length(str.get()) + 1;
  auto res = SharedImmutableStringsCache::getSingleton().getOrCreate(
      std::move(str), lengthWithNull);
  if (!res) {
    ReportOutOfMemory(fc);
  }
  return res;
}

SharedImmutableString ScriptSource::getOrCreateStringZ(FrontendContext* fc,
                                                       UniqueChars&& str) {
  return GetOrCreateStringZ<SharedImmutableString>(fc, std::move(str));
}

SharedImmutableTwoByteString ScriptSource::getOrCreateStringZ(
    FrontendContext* fc, UniqueTwoByteChars&& str) {
  return GetOrCreateStringZ<SharedImmutableTwoByteString>(fc, std::move(str));
}

bool ScriptSource::setFilename(FrontendContext* fc, const char* filename) {
  UniqueChars owned = DuplicateString(fc, filename);
  if (!owned) {
    return false;
  }
  return setFilename(fc, std::move(owned));
}

bool ScriptSource::setFilename(FrontendContext* fc, UniqueChars&& filename) {
  MOZ_ASSERT(!filename_);
  filename_ = getOrCreateStringZ(fc, std::move(filename));
  if (filename_) {
    filenameHash_ =
        mozilla::HashStringKnownLength(filename_.chars(), filename_.length());
    return true;
  }
  return false;
}

bool ScriptSource::setIntroducerFilename(FrontendContext* fc,
                                         const char* filename) {
  UniqueChars owned = DuplicateString(fc, filename);
  if (!owned) {
    return false;
  }
  return setIntroducerFilename(fc, std::move(owned));
}

bool ScriptSource::setIntroducerFilename(FrontendContext* fc,
                                         UniqueChars&& filename) {
  MOZ_ASSERT(!introducerFilename_);
  introducerFilename_ = getOrCreateStringZ(fc, std::move(filename));
  return bool(introducerFilename_);
}

bool ScriptSource::setDisplayURL(FrontendContext* fc, const char16_t* url) {
  UniqueTwoByteChars owned = DuplicateString(fc, url);
  if (!owned) {
    return false;
  }
  return setDisplayURL(fc, std::move(owned));
}

bool ScriptSource::setDisplayURL(FrontendContext* fc,
                                 UniqueTwoByteChars&& url) {
  MOZ_ASSERT(!hasDisplayURL());
  MOZ_ASSERT(url);
  if (url[0] == '\0') {
    return true;
  }

  displayURL_ = getOrCreateStringZ(fc, std::move(url));
  return bool(displayURL_);
}

bool ScriptSource::setSourceMapURL(FrontendContext* fc, const char16_t* url) {
  UniqueTwoByteChars owned = DuplicateString(fc, url);
  if (!owned) {
    return false;
  }
  return setSourceMapURL(fc, std::move(owned));
}

bool ScriptSource::setSourceMapURL(FrontendContext* fc,
                                   UniqueTwoByteChars&& url) {
  MOZ_ASSERT(url);
  if (url[0] == '\0') {
    return true;
  }

  sourceMapURL_ = getOrCreateStringZ(fc, std::move(url));
  return bool(sourceMapURL_);
}

/* static */ mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent>
    ScriptSource::idCount_;

/*
 * [SMDOC] JSScript data layout (immutable)
 *
 * Script data that shareable across processes. There are no pointers (GC or
 * otherwise) and the data is relocatable.
 *
 * Array elements   Pointed to by         Length
 * --------------   -------------         ------
 * jsbytecode       code()                codeLength()
 * jsscrnote        notes()               noteLength()
 * uint32_t         resumeOffsets()
 * ScopeNote        scopeNotes()
 * TryNote          tryNotes()
 */


/* static */ CheckedInt<uint32_t> ImmutableScriptData::sizeFor(
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=90 H=97 G=93

¤ Dauer der Verarbeitung: 0.37 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.