summaryrefslogtreecommitdiffstats
path: root/js/src/vm/JSScript.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/JSScript.h')
-rw-r--r--js/src/vm/JSScript.h2265
1 files changed, 2265 insertions, 0 deletions
diff --git a/js/src/vm/JSScript.h b/js/src/vm/JSScript.h
new file mode 100644
index 0000000000..8fadad9ab5
--- /dev/null
+++ b/js/src/vm/JSScript.h
@@ -0,0 +1,2265 @@
+/* -*- 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 descriptor. */
+
+#ifndef vm_JSScript_h
+#define vm_JSScript_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MaybeOneOf.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Span.h"
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/Variant.h"
+
+#include <type_traits> // std::is_same
+#include <utility> // std::move
+
+#include "jstypes.h"
+
+#include "frontend/ScriptIndex.h" // ScriptIndex
+#include "gc/Barrier.h"
+#include "js/CompileOptions.h"
+#include "js/Transcoding.h"
+#include "js/UbiNode.h"
+#include "js/UniquePtr.h"
+#include "js/Utility.h"
+#include "util/TrailingArray.h"
+#include "vm/BytecodeIterator.h"
+#include "vm/BytecodeLocation.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/MutexIDs.h" // mutexid
+#include "vm/NativeObject.h"
+#include "vm/SharedImmutableStringsCache.h"
+#include "vm/SharedStencil.h" // js::GCThingIndex, js::SourceExtent, js::SharedImmutableScriptData, MemberInitializers
+#include "vm/StencilEnums.h" // SourceRetrievable
+
+namespace JS {
+struct ScriptSourceInfo;
+template <typename UnitT>
+class SourceText;
+} // namespace JS
+
+namespace js {
+
+class FrontendContext;
+class ScriptSource;
+
+class VarScope;
+class LexicalScope;
+
+class JS_PUBLIC_API Sprinter;
+
+namespace coverage {
+class LCovSource;
+} // namespace coverage
+
+namespace gc {
+class AllocSite;
+} // namespace gc
+
+namespace jit {
+class AutoKeepJitScripts;
+class BaselineScript;
+class IonScript;
+struct IonScriptCounts;
+class JitScript;
+} // namespace jit
+
+class ModuleObject;
+class RegExpObject;
+class SourceCompressionTask;
+class Shape;
+class SrcNote;
+class DebugScript;
+
+namespace frontend {
+struct CompilationStencil;
+struct ExtensibleCompilationStencil;
+struct CompilationGCOutput;
+struct CompilationStencilMerger;
+class StencilXDR;
+} // namespace frontend
+
+class ScriptCounts {
+ public:
+ typedef mozilla::Vector<PCCounts, 0, SystemAllocPolicy> PCCountsVector;
+
+ inline ScriptCounts();
+ inline explicit ScriptCounts(PCCountsVector&& jumpTargets);
+ inline ScriptCounts(ScriptCounts&& src);
+ inline ~ScriptCounts();
+
+ inline ScriptCounts& operator=(ScriptCounts&& src);
+
+ // Return the counter used to count the number of visits. Returns null if
+ // the element is not found.
+ PCCounts* maybeGetPCCounts(size_t offset);
+ const PCCounts* maybeGetPCCounts(size_t offset) const;
+
+ // PCCounts are stored at jump-target offsets. This function looks for the
+ // previous PCCount which is in the same basic block as the current offset.
+ PCCounts* getImmediatePrecedingPCCounts(size_t offset);
+
+ // Return the counter used to count the number of throws. Returns null if
+ // the element is not found.
+ const PCCounts* maybeGetThrowCounts(size_t offset) const;
+
+ // Throw counts are stored at the location of each throwing
+ // instruction. This function looks for the previous throw count.
+ //
+ // Note: if the offset of the returned count is higher than the offset of
+ // the immediate preceding PCCount, then this throw happened in the same
+ // basic block.
+ const PCCounts* getImmediatePrecedingThrowCounts(size_t offset) const;
+
+ // Return the counter used to count the number of throws. Allocate it if
+ // none exists yet. Returns null if the allocation failed.
+ PCCounts* getThrowCounts(size_t offset);
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+ bool traceWeak(JSTracer* trc) { return true; }
+
+ private:
+ friend class ::JSScript;
+ friend struct ScriptAndCounts;
+
+ // This sorted array is used to map an offset to the number of times a
+ // branch got visited.
+ PCCountsVector pcCounts_;
+
+ // This sorted vector is used to map an offset to the number of times an
+ // instruction throw.
+ PCCountsVector throwCounts_;
+
+ // Information about any Ion compilations for the script.
+ jit::IonScriptCounts* ionCounts_;
+};
+
+// The key of these side-table hash maps are intentionally not traced GC
+// references to JSScript. Instead, we use bare pointers and manually fix up
+// when objects could have moved (see Zone::fixupScriptMapsAfterMovingGC) and
+// remove when the realm is destroyed (see Zone::clearScriptCounts and
+// Zone::clearScriptNames). They essentially behave as weak references, except
+// that the references are not cleared early by the GC. They must be non-strong
+// references because the tables are kept at the Zone level and otherwise the
+// table keys would keep scripts alive, thus keeping Realms alive, beyond their
+// expected lifetimes. However, We do not use actual weak references (e.g. as
+// used by WeakMap tables provided in gc/WeakMap.h) because they would be
+// collected before the calls to the JSScript::finalize function which are used
+// to aggregate code coverage results on the realm.
+//
+// Note carefully, however, that there is an exceptional case for which we *do*
+// want the JSScripts to be strong references (and thus traced): when the
+// --dump-bytecode command line option or the PCCount JSFriend API is used,
+// then the scripts for all counts must remain alive. See
+// Zone::traceScriptTableRoots() for more details.
+//
+// TODO: Clean this up by either aggregating coverage results in some other
+// way, or by tweaking sweep ordering.
+using UniqueScriptCounts = js::UniquePtr<ScriptCounts>;
+using ScriptCountsMap =
+ GCRekeyableHashMap<HeapPtr<BaseScript*>, UniqueScriptCounts,
+ DefaultHasher<HeapPtr<BaseScript*>>, SystemAllocPolicy>;
+
+// The 'const char*' for the function name is a pointer within the LCovSource's
+// LifoAlloc and will be discarded at the same time.
+using ScriptLCovEntry = std::tuple<coverage::LCovSource*, const char*>;
+using ScriptLCovMap =
+ GCRekeyableHashMap<HeapPtr<BaseScript*>, ScriptLCovEntry,
+ DefaultHasher<HeapPtr<BaseScript*>>, SystemAllocPolicy>;
+
+#ifdef MOZ_VTUNE
+using ScriptVTuneIdMap =
+ GCRekeyableHashMap<HeapPtr<BaseScript*>, uint32_t,
+ DefaultHasher<HeapPtr<BaseScript*>>, SystemAllocPolicy>;
+#endif
+#ifdef JS_CACHEIR_SPEW
+using ScriptFinalWarmUpCountEntry = std::tuple<uint32_t, SharedImmutableString>;
+using ScriptFinalWarmUpCountMap =
+ GCRekeyableHashMap<HeapPtr<BaseScript*>, ScriptFinalWarmUpCountEntry,
+ DefaultHasher<HeapPtr<BaseScript*>>, SystemAllocPolicy>;
+#endif
+
+// As we execute JS sources that used lazy parsing, we may generate additional
+// bytecode that we would like to include in caches if they are being used.
+// There is a dependency cycle between JSScript / ScriptSource /
+// CompilationStencil for this scenario so introduce this smart-ptr wrapper to
+// avoid needing the full details of the stencil-merger in this file.
+class StencilIncrementalEncoderPtr {
+ public:
+ frontend::CompilationStencilMerger* merger_ = nullptr;
+
+ StencilIncrementalEncoderPtr() = default;
+ ~StencilIncrementalEncoderPtr() { reset(); }
+
+ bool hasEncoder() const { return bool(merger_); }
+
+ void reset();
+
+ bool setInitial(JSContext* cx,
+ UniquePtr<frontend::ExtensibleCompilationStencil>&& initial);
+
+ bool addDelazification(JSContext* cx,
+ const frontend::CompilationStencil& delazification);
+};
+
+struct ScriptSourceChunk {
+ ScriptSource* ss = nullptr;
+ uint32_t chunk = 0;
+
+ ScriptSourceChunk() = default;
+
+ ScriptSourceChunk(ScriptSource* ss, uint32_t chunk) : ss(ss), chunk(chunk) {
+ MOZ_ASSERT(valid());
+ }
+
+ bool valid() const { return ss != nullptr; }
+
+ bool operator==(const ScriptSourceChunk& other) const {
+ return ss == other.ss && chunk == other.chunk;
+ }
+};
+
+struct ScriptSourceChunkHasher {
+ using Lookup = ScriptSourceChunk;
+
+ static HashNumber hash(const ScriptSourceChunk& ssc) {
+ return mozilla::AddToHash(DefaultHasher<ScriptSource*>::hash(ssc.ss),
+ ssc.chunk);
+ }
+ static bool match(const ScriptSourceChunk& c1, const ScriptSourceChunk& c2) {
+ return c1 == c2;
+ }
+};
+
+template <typename Unit>
+using EntryUnits = mozilla::UniquePtr<Unit[], JS::FreePolicy>;
+
+// The uncompressed source cache contains *either* UTF-8 source data *or*
+// UTF-16 source data. ScriptSourceChunk implies a ScriptSource that
+// contains either UTF-8 data or UTF-16 data, so the nature of the key to
+// Map below indicates how each SourceData ought to be interpreted.
+using SourceData = mozilla::UniquePtr<void, JS::FreePolicy>;
+
+template <typename Unit>
+inline SourceData ToSourceData(EntryUnits<Unit> chars) {
+ static_assert(std::is_same_v<SourceData::DeleterType,
+ typename EntryUnits<Unit>::DeleterType>,
+ "EntryUnits and SourceData must share the same deleter "
+ "type, that need not know the type of the data being freed, "
+ "for the upcast below to be safe");
+ return SourceData(chars.release());
+}
+
+class UncompressedSourceCache {
+ using Map = HashMap<ScriptSourceChunk, SourceData, ScriptSourceChunkHasher,
+ SystemAllocPolicy>;
+
+ public:
+ // Hold an entry in the source data cache and prevent it from being purged on
+ // GC.
+ class AutoHoldEntry {
+ UncompressedSourceCache* cache_ = nullptr;
+ ScriptSourceChunk sourceChunk_ = {};
+ SourceData data_ = nullptr;
+
+ public:
+ explicit AutoHoldEntry() = default;
+
+ ~AutoHoldEntry() {
+ if (cache_) {
+ MOZ_ASSERT(sourceChunk_.valid());
+ cache_->releaseEntry(*this);
+ }
+ }
+
+ template <typename Unit>
+ void holdUnits(EntryUnits<Unit> units) {
+ MOZ_ASSERT(!cache_);
+ MOZ_ASSERT(!sourceChunk_.valid());
+ MOZ_ASSERT(!data_);
+
+ data_ = ToSourceData(std::move(units));
+ }
+
+ private:
+ void holdEntry(UncompressedSourceCache* cache,
+ const ScriptSourceChunk& sourceChunk) {
+ // Initialise the holder for a specific cache and script source.
+ // This will hold on to the cached source chars in the event that
+ // the cache is purged.
+ MOZ_ASSERT(!cache_);
+ MOZ_ASSERT(!sourceChunk_.valid());
+ MOZ_ASSERT(!data_);
+
+ cache_ = cache;
+ sourceChunk_ = sourceChunk;
+ }
+
+ void deferDelete(SourceData data) {
+ // Take ownership of source chars now the cache is being purged. Remove
+ // our reference to the ScriptSource which might soon be destroyed.
+ MOZ_ASSERT(cache_);
+ MOZ_ASSERT(sourceChunk_.valid());
+ MOZ_ASSERT(!data_);
+
+ cache_ = nullptr;
+ sourceChunk_ = ScriptSourceChunk();
+
+ data_ = std::move(data);
+ }
+
+ const ScriptSourceChunk& sourceChunk() const { return sourceChunk_; }
+ friend class UncompressedSourceCache;
+ };
+
+ private:
+ UniquePtr<Map> map_ = nullptr;
+ AutoHoldEntry* holder_ = nullptr;
+
+ public:
+ UncompressedSourceCache() = default;
+
+ template <typename Unit>
+ const Unit* lookup(const ScriptSourceChunk& ssc, AutoHoldEntry& asp);
+
+ bool put(const ScriptSourceChunk& ssc, SourceData data, AutoHoldEntry& asp);
+
+ void purge();
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+ private:
+ void holdEntry(AutoHoldEntry& holder, const ScriptSourceChunk& ssc);
+ void releaseEntry(AutoHoldEntry& holder);
+};
+
+template <typename Unit>
+struct SourceTypeTraits;
+
+template <>
+struct SourceTypeTraits<mozilla::Utf8Unit> {
+ using CharT = char;
+ using SharedImmutableString = js::SharedImmutableString;
+
+ static const mozilla::Utf8Unit* units(const SharedImmutableString& string) {
+ // Casting |char| data to |Utf8Unit| is safe because |Utf8Unit|
+ // contains a |char|. See the long comment in |Utf8Unit|'s definition.
+ return reinterpret_cast<const mozilla::Utf8Unit*>(string.chars());
+ }
+
+ static char* toString(const mozilla::Utf8Unit* units) {
+ auto asUnsigned =
+ const_cast<unsigned char*>(mozilla::Utf8AsUnsignedChars(units));
+ return reinterpret_cast<char*>(asUnsigned);
+ }
+
+ static UniqueChars toCacheable(EntryUnits<mozilla::Utf8Unit> str) {
+ // The cache only stores strings of |char| or |char16_t|, and right now
+ // it seems best not to gunk up the cache with |Utf8Unit| too. So
+ // cache |Utf8Unit| strings by interpreting them as |char| strings.
+ char* chars = toString(str.release());
+ return UniqueChars(chars);
+ }
+};
+
+template <>
+struct SourceTypeTraits<char16_t> {
+ using CharT = char16_t;
+ using SharedImmutableString = js::SharedImmutableTwoByteString;
+
+ static const char16_t* units(const SharedImmutableString& string) {
+ return string.chars();
+ }
+
+ static char16_t* toString(const char16_t* units) {
+ return const_cast<char16_t*>(units);
+ }
+
+ static UniqueTwoByteChars toCacheable(EntryUnits<char16_t> str) {
+ return UniqueTwoByteChars(std::move(str));
+ }
+};
+
+// Synchronously compress the source of |script|, for testing purposes.
+[[nodiscard]] extern bool SynchronouslyCompressSource(
+ JSContext* cx, JS::Handle<BaseScript*> script);
+
+// [SMDOC] ScriptSource
+//
+// This class abstracts over the source we used to compile from. The current
+// representation may transition to different modes in order to save memory.
+// Abstractly the source may be one of UTF-8 or UTF-16. The data itself may be
+// unavailable, retrieveable-using-source-hook, compressed, or uncompressed. If
+// source is retrieved or decompressed for use, we may update the ScriptSource
+// to hold the result.
+class ScriptSource {
+ // NOTE: While ScriptSources may be compressed off thread, they are only
+ // modified by the main thread, and all members are always safe to access
+ // on the main thread.
+
+ friend class SourceCompressionTask;
+ friend bool SynchronouslyCompressSource(JSContext* cx,
+ JS::Handle<BaseScript*> script);
+
+ friend class frontend::StencilXDR;
+
+ private:
+ // Common base class of the templated variants of PinnedUnits<T>.
+ class PinnedUnitsBase {
+ protected:
+ ScriptSource* source_;
+
+ explicit PinnedUnitsBase(ScriptSource* source) : source_(source) {}
+ };
+
+ public:
+ // Any users that wish to manipulate the char buffer of the ScriptSource
+ // needs to do so via PinnedUnits for GC safety. A GC may compress
+ // ScriptSources. If the source were initially uncompressed, then any raw
+ // pointers to the char buffer would now point to the freed, uncompressed
+ // chars. This is analogous to Rooted.
+ template <typename Unit>
+ class PinnedUnits : public PinnedUnitsBase {
+ const Unit* units_;
+
+ public:
+ PinnedUnits(JSContext* cx, ScriptSource* source,
+ UncompressedSourceCache::AutoHoldEntry& holder, size_t begin,
+ size_t len);
+
+ ~PinnedUnits();
+
+ const Unit* get() const { return units_; }
+
+ const typename SourceTypeTraits<Unit>::CharT* asChars() const {
+ return SourceTypeTraits<Unit>::toString(get());
+ }
+ };
+
+ private:
+ // Missing source text that isn't retrievable using the source hook. (All
+ // ScriptSources initially begin in this state. Users that are compiling
+ // source text will overwrite |data| to store a different state.)
+ struct Missing {};
+
+ // Source that can be retrieved using the registered source hook. |Unit|
+ // records the source type so that source-text coordinates in functions and
+ // scripts that depend on this |ScriptSource| are correct.
+ template <typename Unit>
+ struct Retrievable {
+ // The source hook and script URL required to retrieve source are stored
+ // elsewhere, so nothing is needed here. It'd be better hygiene to store
+ // something source-hook-like in each |ScriptSource| that needs it, but that
+ // requires reimagining a source-hook API that currently depends on source
+ // hooks being uniquely-owned pointers...
+ };
+
+ // Uncompressed source text. Templates distinguish if we are interconvertable
+ // to |Retrievable| or not.
+ template <typename Unit>
+ class UncompressedData {
+ typename SourceTypeTraits<Unit>::SharedImmutableString string_;
+
+ public:
+ explicit UncompressedData(
+ typename SourceTypeTraits<Unit>::SharedImmutableString str)
+ : string_(std::move(str)) {}
+
+ const Unit* units() const { return SourceTypeTraits<Unit>::units(string_); }
+
+ size_t length() const { return string_.length(); }
+ };
+
+ template <typename Unit, SourceRetrievable CanRetrieve>
+ class Uncompressed : public UncompressedData<Unit> {
+ using Base = UncompressedData<Unit>;
+
+ public:
+ using Base::Base;
+ };
+
+ // Compressed source text. Templates distinguish if we are interconvertable
+ // to |Retrievable| or not.
+ template <typename Unit>
+ struct CompressedData {
+ // Single-byte compressed text, regardless whether the original text
+ // was single-byte or two-byte.
+ SharedImmutableString raw;
+ size_t uncompressedLength;
+
+ CompressedData(SharedImmutableString raw, size_t uncompressedLength)
+ : raw(std::move(raw)), uncompressedLength(uncompressedLength) {}
+ };
+
+ template <typename Unit, SourceRetrievable CanRetrieve>
+ struct Compressed : public CompressedData<Unit> {
+ using Base = CompressedData<Unit>;
+
+ public:
+ using Base::Base;
+ };
+
+ // The set of currently allowed encoding modes.
+ using SourceType =
+ mozilla::Variant<Compressed<mozilla::Utf8Unit, SourceRetrievable::Yes>,
+ Uncompressed<mozilla::Utf8Unit, SourceRetrievable::Yes>,
+ Compressed<mozilla::Utf8Unit, SourceRetrievable::No>,
+ Uncompressed<mozilla::Utf8Unit, SourceRetrievable::No>,
+ Compressed<char16_t, SourceRetrievable::Yes>,
+ Uncompressed<char16_t, SourceRetrievable::Yes>,
+ Compressed<char16_t, SourceRetrievable::No>,
+ Uncompressed<char16_t, SourceRetrievable::No>,
+ Retrievable<mozilla::Utf8Unit>, Retrievable<char16_t>,
+ Missing>;
+
+ //
+ // Start of fields.
+ //
+
+ mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refs = {};
+
+ // An id for this source that is unique across the process. This can be used
+ // to refer to this source from places that don't want to hold a strong
+ // reference on the source itself.
+ //
+ // This is a 32 bit ID and could overflow, in which case the ID will not be
+ // unique anymore.
+ uint32_t id_ = 0;
+
+ // Source data (as a mozilla::Variant).
+ SourceType data = SourceType(Missing());
+
+ // If the GC calls triggerConvertToCompressedSource with PinnedUnits present,
+ // the last PinnedUnits instance will install the compressed chars upon
+ // destruction.
+ //
+ // Retrievability isn't part of the type here because uncompressed->compressed
+ // transitions must preserve existing retrievability.
+ struct ReaderInstances {
+ size_t count = 0;
+ mozilla::MaybeOneOf<CompressedData<mozilla::Utf8Unit>,
+ CompressedData<char16_t>>
+ pendingCompressed;
+ };
+ ExclusiveData<ReaderInstances> readers_;
+
+ // The UTF-8 encoded filename of this script.
+ SharedImmutableString filename_;
+
+ // Hash of the script filename;
+ HashNumber filenameHash_ = 0;
+
+ // If this ScriptSource was generated by a code-introduction mechanism such
+ // as |eval| or |new Function|, the debugger needs access to the "raw"
+ // filename of the top-level script that contains the eval-ing code. To
+ // keep track of this, we must preserve the original outermost filename (of
+ // the original introducer script), so that instead of a filename of
+ // "foo.js line 30 > eval line 10 > Function", we can obtain the original
+ // raw filename of "foo.js".
+ //
+ // In the case described above, this field will be set to to the original raw
+ // UTF-8 encoded filename from above, otherwise it will be mozilla::Nothing.
+ SharedImmutableString introducerFilename_;
+
+ SharedImmutableTwoByteString displayURL_;
+ SharedImmutableTwoByteString sourceMapURL_;
+
+ // The bytecode cache encoder is used to encode only the content of function
+ // which are delazified. If this value is not nullptr, then each delazified
+ // function should be recorded before their first execution.
+ StencilIncrementalEncoderPtr xdrEncoder_;
+
+ // A string indicating how this source code was introduced into the system.
+ // This is a constant, statically allocated C string, so does not need memory
+ // management.
+ //
+ // TODO: Document the various additional introduction type constants.
+ const char* introductionType_ = nullptr;
+
+ // Bytecode offset in caller script that generated this code. This is
+ // present for eval-ed code, as well as "new Function(...)"-introduced
+ // scripts.
+ mozilla::Maybe<uint32_t> introductionOffset_;
+
+ // If this source is for Function constructor, the position of ")" after
+ // parameter list in the source. This is used to get function body.
+ // 0 for other cases.
+ uint32_t parameterListEnd_ = 0;
+
+ // Line number within the file where this source starts.
+ uint32_t startLine_ = 0;
+ // Column number within the file where this source starts.
+ uint32_t startColumn_ = 0;
+
+ // See: CompileOptions::mutedErrors.
+ bool mutedErrors_ = false;
+
+ // Carry the delazification mode per source.
+ JS::DelazificationOption delazificationMode_ =
+ JS::DelazificationOption::OnDemandOnly;
+
+ // True if an associated SourceCompressionTask was ever created.
+ bool hadCompressionTask_ = false;
+
+ //
+ // End of fields.
+ //
+
+ // How many ids have been handed out to sources.
+ static mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent> idCount_;
+
+ template <typename Unit>
+ const Unit* chunkUnits(JSContext* cx,
+ UncompressedSourceCache::AutoHoldEntry& holder,
+ size_t chunk);
+
+ // Return a string containing the chars starting at |begin| and ending at
+ // |begin + len|.
+ //
+ // Warning: this is *not* GC-safe! Any chars to be handed out must use
+ // PinnedUnits. See comment below.
+ template <typename Unit>
+ const Unit* units(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& asp,
+ size_t begin, size_t len);
+
+ public:
+ // When creating a JSString* from TwoByte source characters, we don't try to
+ // to deflate to Latin1 for longer strings, because this can be slow.
+ static const size_t SourceDeflateLimit = 100;
+
+ explicit ScriptSource()
+ : id_(++idCount_), readers_(js::mutexid::SourceCompression) {}
+ ~ScriptSource() { MOZ_ASSERT(refs == 0); }
+
+ void AddRef() { refs++; }
+ void Release() {
+ MOZ_ASSERT(refs != 0);
+ if (--refs == 0) {
+ js_delete(this);
+ }
+ }
+ [[nodiscard]] bool initFromOptions(FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options);
+
+ /**
+ * The minimum script length (in code units) necessary for a script to be
+ * eligible to be compressed.
+ */
+ static constexpr size_t MinimumCompressibleLength = 256;
+
+ SharedImmutableString getOrCreateStringZ(FrontendContext* fc,
+ UniqueChars&& str);
+ SharedImmutableTwoByteString getOrCreateStringZ(FrontendContext* fc,
+ UniqueTwoByteChars&& str);
+
+ private:
+ class LoadSourceMatcher;
+
+ public:
+ // Attempt to load usable source for |ss| -- source text on which substring
+ // operations and the like can be performed. On success return true and set
+ // |*loaded| to indicate whether usable source could be loaded; otherwise
+ // return false.
+ static bool loadSource(JSContext* cx, ScriptSource* ss, bool* loaded);
+
+ // Assign source data from |srcBuf| to this recently-created |ScriptSource|.
+ template <typename Unit>
+ [[nodiscard]] bool assignSource(FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<Unit>& srcBuf);
+
+ bool hasSourceText() const {
+ return hasUncompressedSource() || hasCompressedSource();
+ }
+
+ private:
+ template <typename Unit>
+ struct UncompressedDataMatcher {
+ template <SourceRetrievable CanRetrieve>
+ const UncompressedData<Unit>* operator()(
+ const Uncompressed<Unit, CanRetrieve>& u) {
+ return &u;
+ }
+
+ template <typename T>
+ const UncompressedData<Unit>* operator()(const T&) {
+ MOZ_CRASH(
+ "attempting to access uncompressed data in a ScriptSource not "
+ "containing it");
+ return nullptr;
+ }
+ };
+
+ public:
+ template <typename Unit>
+ const UncompressedData<Unit>* uncompressedData() {
+ return data.match(UncompressedDataMatcher<Unit>());
+ }
+
+ private:
+ template <typename Unit>
+ struct CompressedDataMatcher {
+ template <SourceRetrievable CanRetrieve>
+ const CompressedData<Unit>* operator()(
+ const Compressed<Unit, CanRetrieve>& c) {
+ return &c;
+ }
+
+ template <typename T>
+ const CompressedData<Unit>* operator()(const T&) {
+ MOZ_CRASH(
+ "attempting to access compressed data in a ScriptSource not "
+ "containing it");
+ return nullptr;
+ }
+ };
+
+ public:
+ template <typename Unit>
+ const CompressedData<Unit>* compressedData() {
+ return data.match(CompressedDataMatcher<Unit>());
+ }
+
+ private:
+ struct HasUncompressedSource {
+ template <typename Unit, SourceRetrievable CanRetrieve>
+ bool operator()(const Uncompressed<Unit, CanRetrieve>&) {
+ return true;
+ }
+
+ template <typename Unit, SourceRetrievable CanRetrieve>
+ bool operator()(const Compressed<Unit, CanRetrieve>&) {
+ return false;
+ }
+
+ template <typename Unit>
+ bool operator()(const Retrievable<Unit>&) {
+ return false;
+ }
+
+ bool operator()(const Missing&) { return false; }
+ };
+
+ public:
+ bool hasUncompressedSource() const {
+ return data.match(HasUncompressedSource());
+ }
+
+ private:
+ template <typename Unit>
+ struct IsUncompressed {
+ template <SourceRetrievable CanRetrieve>
+ bool operator()(const Uncompressed<Unit, CanRetrieve>&) {
+ return true;
+ }
+
+ template <typename T>
+ bool operator()(const T&) {
+ return false;
+ }
+ };
+
+ public:
+ template <typename Unit>
+ bool isUncompressed() const {
+ return data.match(IsUncompressed<Unit>());
+ }
+
+ private:
+ struct HasCompressedSource {
+ template <typename Unit, SourceRetrievable CanRetrieve>
+ bool operator()(const Compressed<Unit, CanRetrieve>&) {
+ return true;
+ }
+
+ template <typename T>
+ bool operator()(const T&) {
+ return false;
+ }
+ };
+
+ public:
+ bool hasCompressedSource() const { return data.match(HasCompressedSource()); }
+
+ private:
+ template <typename Unit>
+ struct IsCompressed {
+ template <SourceRetrievable CanRetrieve>
+ bool operator()(const Compressed<Unit, CanRetrieve>&) {
+ return true;
+ }
+
+ template <typename T>
+ bool operator()(const T&) {
+ return false;
+ }
+ };
+
+ public:
+ template <typename Unit>
+ bool isCompressed() const {
+ return data.match(IsCompressed<Unit>());
+ }
+
+ private:
+ template <typename Unit>
+ struct SourceTypeMatcher {
+ template <template <typename C, SourceRetrievable R> class Data,
+ SourceRetrievable CanRetrieve>
+ bool operator()(const Data<Unit, CanRetrieve>&) {
+ return true;
+ }
+
+ template <template <typename C, SourceRetrievable R> class Data,
+ typename NotUnit, SourceRetrievable CanRetrieve>
+ bool operator()(const Data<NotUnit, CanRetrieve>&) {
+ return false;
+ }
+
+ bool operator()(const Retrievable<Unit>&) {
+ MOZ_CRASH("source type only applies where actual text is available");
+ return false;
+ }
+
+ template <typename NotUnit>
+ bool operator()(const Retrievable<NotUnit>&) {
+ return false;
+ }
+
+ bool operator()(const Missing&) {
+ MOZ_CRASH("doesn't make sense to ask source type when missing");
+ return false;
+ }
+ };
+
+ public:
+ template <typename Unit>
+ bool hasSourceType() const {
+ return data.match(SourceTypeMatcher<Unit>());
+ }
+
+ private:
+ struct UncompressedLengthMatcher {
+ template <typename Unit, SourceRetrievable CanRetrieve>
+ size_t operator()(const Uncompressed<Unit, CanRetrieve>& u) {
+ return u.length();
+ }
+
+ template <typename Unit, SourceRetrievable CanRetrieve>
+ size_t operator()(const Compressed<Unit, CanRetrieve>& u) {
+ return u.uncompressedLength;
+ }
+
+ template <typename Unit>
+ size_t operator()(const Retrievable<Unit>&) {
+ MOZ_CRASH("ScriptSource::length on a missing-but-retrievable source");
+ return 0;
+ }
+
+ size_t operator()(const Missing& m) {
+ MOZ_CRASH("ScriptSource::length on a missing source");
+ return 0;
+ }
+ };
+
+ public:
+ size_t length() const {
+ MOZ_ASSERT(hasSourceText());
+ return data.match(UncompressedLengthMatcher());
+ }
+
+ JSLinearString* substring(JSContext* cx, size_t start, size_t stop);
+ JSLinearString* substringDontDeflate(JSContext* cx, size_t start,
+ size_t stop);
+
+ [[nodiscard]] bool appendSubstring(JSContext* cx, js::StringBuffer& buf,
+ size_t start, size_t stop);
+
+ void setParameterListEnd(uint32_t parameterListEnd) {
+ parameterListEnd_ = parameterListEnd;
+ }
+
+ bool isFunctionBody() { return parameterListEnd_ != 0; }
+ JSLinearString* functionBodyString(JSContext* cx);
+
+ void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
+ JS::ScriptSourceInfo* info) const;
+
+ private:
+ // Overwrites |data| with the uncompressed data from |source|.
+ //
+ // This function asserts nothing about |data|. Users should use assertions to
+ // double-check their own understandings of the |data| state transition being
+ // performed.
+ template <typename ContextT, typename Unit>
+ [[nodiscard]] bool setUncompressedSourceHelper(ContextT* cx,
+ EntryUnits<Unit>&& source,
+ size_t length,
+ SourceRetrievable retrievable);
+
+ public:
+ // Initialize a fresh |ScriptSource| with unretrievable, uncompressed source.
+ template <typename Unit>
+ [[nodiscard]] bool initializeUnretrievableUncompressedSource(
+ FrontendContext* fc, EntryUnits<Unit>&& source, size_t length);
+
+ // Set the retrieved source for a |ScriptSource| whose source was recorded as
+ // missing but retrievable.
+ template <typename Unit>
+ [[nodiscard]] bool setRetrievedSource(JSContext* cx,
+ EntryUnits<Unit>&& source,
+ size_t length);
+
+ [[nodiscard]] bool tryCompressOffThread(JSContext* cx);
+
+ // Called by the SourceCompressionTask constructor to indicate such a task was
+ // ever created.
+ void noteSourceCompressionTask() { hadCompressionTask_ = true; }
+
+ // *Trigger* the conversion of this ScriptSource from containing uncompressed
+ // |Unit|-encoded source to containing compressed source. Conversion may not
+ // be complete when this function returns: it'll be delayed if there's ongoing
+ // use of the uncompressed source via |PinnedUnits|, in which case conversion
+ // won't occur until the outermost |PinnedUnits| is destroyed.
+ //
+ // Compressed source is in bytes, no matter that |Unit| might be |char16_t|.
+ // |sourceLength| is the length in code units (not bytes) of the uncompressed
+ // source.
+ template <typename Unit>
+ void triggerConvertToCompressedSource(SharedImmutableString compressed,
+ size_t sourceLength);
+
+ // Initialize a fresh ScriptSource as containing unretrievable compressed
+ // source of the indicated original encoding.
+ template <typename Unit>
+ [[nodiscard]] bool initializeWithUnretrievableCompressedSource(
+ FrontendContext* fc, UniqueChars&& raw, size_t rawLength,
+ size_t sourceLength);
+
+ private:
+ void performTaskWork(SourceCompressionTask* task);
+
+ struct TriggerConvertToCompressedSourceFromTask {
+ ScriptSource* const source_;
+ SharedImmutableString& compressed_;
+
+ TriggerConvertToCompressedSourceFromTask(ScriptSource* source,
+ SharedImmutableString& compressed)
+ : source_(source), compressed_(compressed) {}
+
+ template <typename Unit, SourceRetrievable CanRetrieve>
+ void operator()(const Uncompressed<Unit, CanRetrieve>&) {
+ source_->triggerConvertToCompressedSource<Unit>(std::move(compressed_),
+ source_->length());
+ }
+
+ template <typename Unit, SourceRetrievable CanRetrieve>
+ void operator()(const Compressed<Unit, CanRetrieve>&) {
+ MOZ_CRASH(
+ "can't set compressed source when source is already compressed -- "
+ "ScriptSource::tryCompressOffThread shouldn't have queued up this "
+ "task?");
+ }
+
+ template <typename Unit>
+ void operator()(const Retrievable<Unit>&) {
+ MOZ_CRASH("shouldn't compressing unloaded-but-retrievable source");
+ }
+
+ void operator()(const Missing&) {
+ MOZ_CRASH(
+ "doesn't make sense to set compressed source for missing source -- "
+ "ScriptSource::tryCompressOffThread shouldn't have queued up this "
+ "task?");
+ }
+ };
+
+ template <typename Unit>
+ void convertToCompressedSource(SharedImmutableString compressed,
+ size_t uncompressedLength);
+
+ template <typename Unit>
+ void performDelayedConvertToCompressedSource(
+ ExclusiveData<ReaderInstances>::Guard& g);
+
+ void triggerConvertToCompressedSourceFromTask(
+ SharedImmutableString compressed);
+
+ public:
+ HashNumber filenameHash() const { return filenameHash_; }
+ const char* filename() const {
+ return filename_ ? filename_.chars() : nullptr;
+ }
+ [[nodiscard]] bool setFilename(FrontendContext* fc, const char* filename);
+ [[nodiscard]] bool setFilename(FrontendContext* fc, UniqueChars&& filename);
+
+ const char* introducerFilename() const {
+ return introducerFilename_ ? introducerFilename_.chars() : filename();
+ }
+ [[nodiscard]] bool setIntroducerFilename(FrontendContext* fc,
+ const char* filename);
+ [[nodiscard]] bool setIntroducerFilename(FrontendContext* fc,
+ UniqueChars&& filename);
+
+ bool hasIntroductionType() const { return introductionType_; }
+ const char* introductionType() const {
+ MOZ_ASSERT(hasIntroductionType());
+ return introductionType_;
+ }
+
+ uint32_t id() const { return id_; }
+
+ // Display URLs
+ [[nodiscard]] bool setDisplayURL(FrontendContext* fc, const char16_t* url);
+ [[nodiscard]] bool setDisplayURL(FrontendContext* fc,
+ UniqueTwoByteChars&& url);
+ bool hasDisplayURL() const { return bool(displayURL_); }
+ const char16_t* displayURL() { return displayURL_.chars(); }
+
+ // Source maps
+ [[nodiscard]] bool setSourceMapURL(FrontendContext* fc, const char16_t* url);
+ [[nodiscard]] bool setSourceMapURL(FrontendContext* fc,
+ UniqueTwoByteChars&& url);
+ bool hasSourceMapURL() const { return bool(sourceMapURL_); }
+ const char16_t* sourceMapURL() { return sourceMapURL_.chars(); }
+
+ bool mutedErrors() const { return mutedErrors_; }
+
+ uint32_t startLine() const { return startLine_; }
+ uint32_t startColumn() const { return startColumn_; }
+
+ JS::DelazificationOption delazificationMode() const {
+ return delazificationMode_;
+ }
+
+ bool hasIntroductionOffset() const { return introductionOffset_.isSome(); }
+ uint32_t introductionOffset() const { return introductionOffset_.value(); }
+ void setIntroductionOffset(uint32_t offset) {
+ MOZ_ASSERT(!hasIntroductionOffset());
+ MOZ_ASSERT(offset <= (uint32_t)INT32_MAX);
+ introductionOffset_.emplace(offset);
+ }
+
+ // Return wether an XDR encoder is present or not.
+ bool hasEncoder() const { return xdrEncoder_.hasEncoder(); }
+
+ [[nodiscard]] bool startIncrementalEncoding(
+ JSContext* cx,
+ UniquePtr<frontend::ExtensibleCompilationStencil>&& initial);
+
+ [[nodiscard]] bool addDelazificationToIncrementalEncoding(
+ JSContext* cx, const frontend::CompilationStencil& stencil);
+
+ // Linearize the encoded content in the |buffer| provided as argument to
+ // |xdrEncodeTopLevel|, and free the XDR encoder. In case of errors, the
+ // |buffer| is considered undefined.
+ bool xdrFinalizeEncoder(JSContext* cx, JS::TranscodeBuffer& buffer);
+
+ // Discard the incremental encoding data and free the XDR encoder.
+ void xdrAbortEncoder();
+};
+
+// [SMDOC] ScriptSourceObject
+//
+// ScriptSourceObject stores the ScriptSource and GC pointers related to it.
+class ScriptSourceObject : public NativeObject {
+ static const JSClassOps classOps_;
+
+ public:
+ static const JSClass class_;
+
+ static void finalize(JS::GCContext* gcx, JSObject* obj);
+
+ static ScriptSourceObject* create(JSContext* cx, ScriptSource* source);
+
+ // Initialize those properties of this ScriptSourceObject whose values
+ // are provided by |options|, re-wrapping as necessary.
+ static bool initFromOptions(JSContext* cx,
+ JS::Handle<ScriptSourceObject*> source,
+ const JS::InstantiateOptions& options);
+
+ static bool initElementProperties(JSContext* cx,
+ JS::Handle<ScriptSourceObject*> source,
+ HandleString elementAttrName);
+
+ bool hasSource() const { return !getReservedSlot(SOURCE_SLOT).isUndefined(); }
+ ScriptSource* source() const {
+ return static_cast<ScriptSource*>(getReservedSlot(SOURCE_SLOT).toPrivate());
+ }
+
+ JSObject* unwrappedElement(JSContext* cx) const;
+
+ const Value& unwrappedElementAttributeName() const {
+ MOZ_ASSERT(isInitialized());
+ const Value& v = getReservedSlot(ELEMENT_PROPERTY_SLOT);
+ MOZ_ASSERT(!v.isMagic());
+ return v;
+ }
+ BaseScript* unwrappedIntroductionScript() const {
+ MOZ_ASSERT(isInitialized());
+ Value value = getReservedSlot(INTRODUCTION_SCRIPT_SLOT);
+ if (value.isUndefined()) {
+ return nullptr;
+ }
+ return value.toGCThing()->as<BaseScript>();
+ }
+
+ void setPrivate(JSRuntime* rt, const Value& value);
+ void clearPrivate(JSRuntime* rt);
+
+ void setIntroductionScript(const Value& introductionScript) {
+ setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript);
+ }
+
+ Value getPrivate() const {
+ MOZ_ASSERT(isInitialized());
+ Value value = getReservedSlot(PRIVATE_SLOT);
+ return value;
+ }
+
+ private:
+#ifdef DEBUG
+ bool isInitialized() const {
+ Value element = getReservedSlot(ELEMENT_PROPERTY_SLOT);
+ if (element.isMagic(JS_GENERIC_MAGIC)) {
+ return false;
+ }
+ return !getReservedSlot(INTRODUCTION_SCRIPT_SLOT).isMagic(JS_GENERIC_MAGIC);
+ }
+#endif
+
+ enum {
+ SOURCE_SLOT = 0,
+ ELEMENT_PROPERTY_SLOT,
+ INTRODUCTION_SCRIPT_SLOT,
+ PRIVATE_SLOT,
+ RESERVED_SLOTS
+ };
+};
+
+// ScriptWarmUpData represents a pointer-sized field in BaseScript that stores
+// one of the following using low-bit tags:
+//
+// * The enclosing BaseScript. This is only used while this script is lazy and
+// its containing script is also lazy. This outer script must be compiled
+// before the current script can in order to correctly build the scope chain.
+//
+// * The enclosing Scope. This is only used while this script is lazy and its
+// containing script is compiled. This is the outer scope chain that will be
+// used to compile this scipt.
+//
+// * The script's warm-up count. This is only used until the script has a
+// JitScript. The Baseline Interpreter and JITs use the warm-up count stored
+// in JitScript.
+//
+// * A pointer to the JitScript, when the script is warm enough for the Baseline
+// Interpreter.
+//
+class ScriptWarmUpData {
+ uintptr_t data_ = ResetState();
+
+ private:
+ static constexpr uintptr_t NumTagBits = 2;
+ static constexpr uint32_t MaxWarmUpCount = UINT32_MAX >> NumTagBits;
+
+ public:
+ // Public only for the JITs.
+ static constexpr uintptr_t TagMask = (1 << NumTagBits) - 1;
+ static constexpr uintptr_t JitScriptTag = 0;
+ static constexpr uintptr_t EnclosingScriptTag = 1;
+ static constexpr uintptr_t EnclosingScopeTag = 2;
+ static constexpr uintptr_t WarmUpCountTag = 3;
+
+ private:
+ // A gc-safe value to clear to.
+ constexpr uintptr_t ResetState() { return 0 | WarmUpCountTag; }
+
+ template <uintptr_t Tag>
+ inline void setTaggedPtr(void* ptr) {
+ static_assert(Tag <= TagMask, "Tag must fit in TagMask");
+ MOZ_ASSERT((uintptr_t(ptr) & TagMask) == 0);
+ data_ = uintptr_t(ptr) | Tag;
+ }
+
+ template <typename T, uintptr_t Tag>
+ inline T getTaggedPtr() const {
+ static_assert(Tag <= TagMask, "Tag must fit in TagMask");
+ MOZ_ASSERT((data_ & TagMask) == Tag);
+ return reinterpret_cast<T>(data_ & ~TagMask);
+ }
+
+ void setWarmUpCount(uint32_t count) {
+ if (count > MaxWarmUpCount) {
+ count = MaxWarmUpCount;
+ }
+ data_ = (uintptr_t(count) << NumTagBits) | WarmUpCountTag;
+ }
+
+ public:
+ void trace(JSTracer* trc);
+
+ bool isEnclosingScript() const {
+ return (data_ & TagMask) == EnclosingScriptTag;
+ }
+ bool isEnclosingScope() const {
+ return (data_ & TagMask) == EnclosingScopeTag;
+ }
+ bool isWarmUpCount() const { return (data_ & TagMask) == WarmUpCountTag; }
+ bool isJitScript() const { return (data_ & TagMask) == JitScriptTag; }
+
+ // NOTE: To change type safely, 'clear' the old tagged value and then 'init'
+ // the new one. This will notify the GC appropriately.
+
+ BaseScript* toEnclosingScript() const {
+ return getTaggedPtr<BaseScript*, EnclosingScriptTag>();
+ }
+ inline void initEnclosingScript(BaseScript* enclosingScript);
+ inline void clearEnclosingScript();
+
+ Scope* toEnclosingScope() const {
+ return getTaggedPtr<Scope*, EnclosingScopeTag>();
+ }
+ inline void initEnclosingScope(Scope* enclosingScope);
+ inline void clearEnclosingScope();
+
+ uint32_t toWarmUpCount() const {
+ MOZ_ASSERT(isWarmUpCount());
+ return data_ >> NumTagBits;
+ }
+ void resetWarmUpCount(uint32_t count) {
+ MOZ_ASSERT(isWarmUpCount());
+ setWarmUpCount(count);
+ }
+ void incWarmUpCount() {
+ MOZ_ASSERT(isWarmUpCount());
+ data_ += uintptr_t(1) << NumTagBits;
+ }
+
+ jit::JitScript* toJitScript() const {
+ return getTaggedPtr<jit::JitScript*, JitScriptTag>();
+ }
+ void initJitScript(jit::JitScript* jitScript) {
+ MOZ_ASSERT(isWarmUpCount());
+ setTaggedPtr<JitScriptTag>(jitScript);
+ }
+ void clearJitScript() {
+ MOZ_ASSERT(isJitScript());
+ data_ = ResetState();
+ }
+} JS_HAZ_GC_POINTER;
+
+static_assert(sizeof(ScriptWarmUpData) == sizeof(uintptr_t),
+ "JIT code depends on ScriptWarmUpData being pointer-sized");
+
+// [SMDOC] - JSScript data layout (unshared)
+//
+// PrivateScriptData stores variable-length data associated with a script.
+// Abstractly a PrivateScriptData consists of the following:
+//
+// * A non-empty array of GCCellPtr in gcthings()
+//
+// Accessing this array just requires calling the appropriate public
+// Span-computing function.
+//
+// This class doesn't use the GC barrier wrapper classes. BaseScript::swapData
+// performs a manual pre-write barrier when detaching PrivateScriptData from a
+// script.
+class alignas(uintptr_t) PrivateScriptData final : public TrailingArray {
+ private:
+ uint32_t ngcthings = 0;
+
+ // Note: This is only defined for scripts with an enclosing scope. This
+ // excludes lazy scripts with lazy parents.
+ js::MemberInitializers memberInitializers_ =
+ js::MemberInitializers::Invalid();
+
+ // End of fields.
+
+ private:
+ // Layout helpers
+ Offset gcThingsOffset() { return offsetOfGCThings(); }
+ Offset endOffset() const {
+ uintptr_t size = ngcthings * sizeof(JS::GCCellPtr);
+ return offsetOfGCThings() + size;
+ }
+
+ // Initialize header and PackedSpans
+ explicit PrivateScriptData(uint32_t ngcthings);
+
+ public:
+ static constexpr size_t offsetOfGCThings() {
+ return sizeof(PrivateScriptData);
+ }
+
+ // Accessors for typed array spans.
+ mozilla::Span<JS::GCCellPtr> gcthings() {
+ Offset offset = offsetOfGCThings();
+ return mozilla::Span{offsetToPointer<JS::GCCellPtr>(offset), ngcthings};
+ }
+
+ void setMemberInitializers(MemberInitializers memberInitializers) {
+ MOZ_ASSERT(memberInitializers_.valid == false,
+ "Only init MemberInitializers once");
+ memberInitializers_ = memberInitializers;
+ }
+ const MemberInitializers& getMemberInitializers() {
+ return memberInitializers_;
+ }
+
+ // Allocate a new PrivateScriptData. Headers and GCCellPtrs are initialized.
+ static PrivateScriptData* new_(JSContext* cx, uint32_t ngcthings);
+
+ static bool InitFromStencil(
+ JSContext* cx, js::HandleScript script,
+ const js::frontend::CompilationAtomCache& atomCache,
+ const js::frontend::CompilationStencil& stencil,
+ js::frontend::CompilationGCOutput& gcOutput,
+ const js::frontend::ScriptIndex scriptIndex);
+
+ void trace(JSTracer* trc);
+
+ size_t allocationSize() const;
+
+ // PrivateScriptData has trailing data so isn't copyable or movable.
+ PrivateScriptData(const PrivateScriptData&) = delete;
+ PrivateScriptData& operator=(const PrivateScriptData&) = delete;
+};
+
+// [SMDOC] Script Representation (js::BaseScript)
+//
+// A "script" corresponds to a JavaScript function or a top-level (global, eval,
+// module) body that will be executed using SpiderMonkey bytecode. Note that
+// special forms such as asm.js do not use bytecode or the BaseScript type.
+//
+// BaseScript may be generated directly from the parser/emitter, or by cloning
+// or deserializing another script. Cloning is typically used when a script is
+// needed in multiple realms and we would like to avoid re-compiling.
+//
+// A single script may be shared by multiple JSFunctions in a realm when those
+// function objects are used as closure. In this case, a single JSFunction is
+// considered canonical (and often does not escape to script directly).
+//
+// A BaseScript may be in "lazy" form where the parser performs a syntax-only
+// parse and saves minimal information. These lazy scripts must be recompiled
+// from the source (generating bytecode) before they can execute in a process
+// called "delazification". On GC memory pressure, a fully-compiled script may
+// be converted back into lazy form by "relazification".
+//
+// A fully-initialized BaseScript can be identified with `hasBytecode()` and
+// will have bytecode and set of GC-things such as scopes, inner-functions, and
+// object/string literals. This is referred to as a "non-lazy" script.
+//
+// A lazy script has either an enclosing script or scope. Each script needs to
+// know its enclosing scope in order to be fully compiled. If the parent is
+// still lazy we track that script and will need to compile it first to know our
+// own enclosing scope. This is because scope objects are not created until full
+// compilation and bytecode generation.
+//
+//
+// # Script Warm-Up #
+//
+// A script evolves its representation over time. As it becomes "hotter" we
+// attach a stack of additional data-structures generated by the JITs to
+// speed-up execution. This evolution may also be run in reverse, in order to
+// reduce memory usage.
+//
+// +-------------------------------------+
+// | ScriptSource |
+// | Provides: Source |
+// | Engine: Parser |
+// +-------------------------------------+
+// v
+// +-----------------------------------------------+
+// | BaseScript |
+// | Provides: SourceExtent/Bindings |
+// | Engine: CompileLazyFunctionToStencil |
+// | /InstantiateStencilsForDelazify |
+// +-----------------------------------------------+
+// v
+// +-------------------------------------+
+// | ImmutableScriptData |
+// | Provides: Bytecode |
+// | Engine: Interpreter |
+// +-------------------------------------+
+// v
+// +-------------------------------------+
+// | JitScript |
+// | Provides: Inline Caches (ICs) |
+// | Engine: BaselineInterpreter |
+// +-------------------------------------+
+// v
+// +-------------------------------------+
+// | BaselineScript |
+// | Provides: Native Code |
+// | Engine: Baseline |
+// +-------------------------------------+
+// v
+// +-------------------------------------+
+// | IonScript |
+// | Provides: Optimized Native Code |
+// | Engine: IonMonkey |
+// +-------------------------------------+
+//
+// NOTE: Scripts may be directly created with bytecode and skip the lazy script
+// form. This is always the case for top-level scripts.
+class BaseScript : public gc::TenuredCellWithNonGCPointer<uint8_t> {
+ friend class js::gc::CellAllocator;
+
+ public:
+ // Pointer to baseline->method()->raw(), ion->method()->raw(), a wasm jit
+ // entry, the JIT's EnterInterpreter stub, or the lazy link stub. Must be
+ // non-null (except on no-jit builds). This is stored in the cell header.
+ uint8_t* jitCodeRaw() const { return headerPtr(); }
+
+ protected:
+ // Multi-purpose value that changes type as the script warms up from lazy form
+ // to interpreted-bytecode to JITs. See: ScriptWarmUpData type for more info.
+ ScriptWarmUpData warmUpData_ = {};
+
+ // For function scripts this is the canonical function, otherwise nullptr.
+ const GCPtr<JSFunction*> function_ = {};
+
+ // The ScriptSourceObject for this script. This is always same-compartment and
+ // same-realm with this script.
+ const GCPtr<ScriptSourceObject*> sourceObject_ = {};
+
+ // Position of the function in the source buffer. Both in terms of line/column
+ // and code-unit offset.
+ const SourceExtent extent_ = {};
+
+ // Immutable flags are a combination of parser options and bytecode
+ // characteristics. These flags are preserved when serializing or copying this
+ // script.
+ const ImmutableScriptFlags immutableFlags_ = {};
+
+ // Mutable flags store transient information used by subsystems such as the
+ // debugger and the JITs. These flags are *not* preserved when serializing or
+ // cloning since they are based on runtime state.
+ MutableScriptFlags mutableFlags_ = {};
+
+ // Variable-length data owned by this script. This stores one of:
+ // - GC pointers that bytecode references.
+ // - Inner-functions and bindings generated by syntax parse.
+ // - Nullptr, if no bytecode or inner functions.
+ // This is updated as script is delazified and relazified.
+ GCStructPtr<PrivateScriptData*> data_;
+
+ // Shareable script data. This includes runtime-wide atom pointers, bytecode,
+ // and various script note structures. If the script is currently lazy, this
+ // will be nullptr.
+ RefPtr<js::SharedImmutableScriptData> sharedData_ = {};
+
+ // End of fields.
+
+ BaseScript(uint8_t* stubEntry, JSFunction* function,
+ ScriptSourceObject* sourceObject, const SourceExtent& extent,
+ uint32_t immutableFlags);
+
+ void setJitCodeRaw(uint8_t* code) { setHeaderPtr(code); }
+
+ public:
+ static BaseScript* New(JSContext* cx, JS::Handle<JSFunction*> function,
+ JS::Handle<js::ScriptSourceObject*> sourceObject,
+ const js::SourceExtent& extent,
+ uint32_t immutableFlags);
+
+ // Create a lazy BaseScript without initializing any gc-things.
+ static BaseScript* CreateRawLazy(JSContext* cx, uint32_t ngcthings,
+ HandleFunction fun,
+ JS::Handle<ScriptSourceObject*> sourceObject,
+ const SourceExtent& extent,
+ uint32_t immutableFlags);
+
+ bool isUsingInterpreterTrampoline(JSRuntime* rt) const;
+
+ // Canonical function for the script, if it has a function. For top-level
+ // scripts this is nullptr.
+ JSFunction* function() const { return function_; }
+
+ JS::Realm* realm() const { return sourceObject()->realm(); }
+ JS::Compartment* compartment() const { return sourceObject()->compartment(); }
+ JS::Compartment* maybeCompartment() const { return compartment(); }
+ inline JSPrincipals* principals() const;
+
+ ScriptSourceObject* sourceObject() const { return sourceObject_; }
+ ScriptSource* scriptSource() const { return sourceObject()->source(); }
+ ScriptSource* maybeForwardedScriptSource() const;
+
+ bool mutedErrors() const { return scriptSource()->mutedErrors(); }
+
+ const char* filename() const { return scriptSource()->filename(); }
+ HashNumber filenameHash() const { return scriptSource()->filenameHash(); }
+ const char* maybeForwardedFilename() const {
+ return maybeForwardedScriptSource()->filename();
+ }
+
+ uint32_t sourceStart() const { return extent_.sourceStart; }
+ uint32_t sourceEnd() const { return extent_.sourceEnd; }
+ uint32_t sourceLength() const {
+ return extent_.sourceEnd - extent_.sourceStart;
+ }
+ uint32_t toStringStart() const { return extent_.toStringStart; }
+ uint32_t toStringEnd() const { return extent_.toStringEnd; }
+ SourceExtent extent() const { return extent_; }
+
+ [[nodiscard]] bool appendSourceDataForToString(JSContext* cx,
+ js::StringBuffer& buf);
+
+ uint32_t lineno() const { return extent_.lineno; }
+ uint32_t column() const { return extent_.column; }
+
+ JS::DelazificationOption delazificationMode() const {
+ return scriptSource()->delazificationMode();
+ }
+
+ public:
+ ImmutableScriptFlags immutableFlags() const { return immutableFlags_; }
+ RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags_)
+ RW_MUTABLE_SCRIPT_FLAGS(mutableFlags_)
+
+ bool hasEnclosingScript() const { return warmUpData_.isEnclosingScript(); }
+ BaseScript* enclosingScript() const {
+ return warmUpData_.toEnclosingScript();
+ }
+ void setEnclosingScript(BaseScript* enclosingScript);
+
+ // Returns true is the script has an enclosing scope but no bytecode. It is
+ // ready for delazification.
+ // NOTE: The enclosing script must have been successfully compiled at some
+ // point for the enclosing scope to exist. That script may have since been
+ // GC'd, but we kept the scope live so we can still compile ourselves.
+ bool isReadyForDelazification() const {
+ return warmUpData_.isEnclosingScope();
+ }
+
+ Scope* enclosingScope() const;
+ void setEnclosingScope(Scope* enclosingScope);
+ Scope* releaseEnclosingScope();
+
+ bool hasJitScript() const { return warmUpData_.isJitScript(); }
+ jit::JitScript* jitScript() const {
+ MOZ_ASSERT(hasJitScript());
+ return warmUpData_.toJitScript();
+ }
+ jit::JitScript* maybeJitScript() const {
+ return hasJitScript() ? jitScript() : nullptr;
+ }
+
+ inline bool hasBaselineScript() const;
+ inline bool hasIonScript() const;
+
+ bool hasPrivateScriptData() const { return data_ != nullptr; }
+
+ // Update data_ pointer while also informing GC MemoryUse tracking.
+ void swapData(UniquePtr<PrivateScriptData>& other);
+
+ mozilla::Span<const JS::GCCellPtr> gcthings() const {
+ return data_ ? data_->gcthings() : mozilla::Span<JS::GCCellPtr>();
+ }
+
+ // NOTE: This is only used to initialize a fresh script.
+ mozilla::Span<JS::GCCellPtr> gcthingsForInit() {
+ MOZ_ASSERT(!hasBytecode());
+ return data_ ? data_->gcthings() : mozilla::Span<JS::GCCellPtr>();
+ }
+
+ void setMemberInitializers(MemberInitializers memberInitializers) {
+ MOZ_ASSERT(useMemberInitializers());
+ MOZ_ASSERT(data_);
+ data_->setMemberInitializers(memberInitializers);
+ }
+ const MemberInitializers& getMemberInitializers() const {
+ MOZ_ASSERT(data_);
+ return data_->getMemberInitializers();
+ }
+
+ SharedImmutableScriptData* sharedData() const { return sharedData_; }
+ void initSharedData(SharedImmutableScriptData* data) {
+ MOZ_ASSERT(sharedData_ == nullptr);
+ sharedData_ = data;
+ }
+ void freeSharedData() { sharedData_ = nullptr; }
+
+ // NOTE: Script only has bytecode if JSScript::fullyInitFromStencil completes
+ // successfully.
+ bool hasBytecode() const {
+ if (sharedData_) {
+ MOZ_ASSERT(data_);
+ MOZ_ASSERT(warmUpData_.isWarmUpCount() || warmUpData_.isJitScript());
+ return true;
+ }
+ return false;
+ }
+
+ public:
+ static const JS::TraceKind TraceKind = JS::TraceKind::Script;
+
+ void traceChildren(JSTracer* trc);
+ void finalize(JS::GCContext* gcx);
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
+ return mallocSizeOf(data_);
+ }
+
+ inline JSScript* asJSScript();
+
+ // JIT accessors
+ static constexpr size_t offsetOfJitCodeRaw() { return offsetOfHeaderPtr(); }
+ static constexpr size_t offsetOfPrivateData() {
+ return offsetof(BaseScript, data_);
+ }
+ static constexpr size_t offsetOfSharedData() {
+ return offsetof(BaseScript, sharedData_);
+ }
+ static size_t offsetOfImmutableFlags() {
+ static_assert(sizeof(ImmutableScriptFlags) == sizeof(uint32_t));
+ return offsetof(BaseScript, immutableFlags_);
+ }
+ static constexpr size_t offsetOfMutableFlags() {
+ static_assert(sizeof(MutableScriptFlags) == sizeof(uint32_t));
+ return offsetof(BaseScript, mutableFlags_);
+ }
+ static constexpr size_t offsetOfWarmUpData() {
+ return offsetof(BaseScript, warmUpData_);
+ }
+};
+
+extern void SweepScriptData(JSRuntime* rt);
+
+} /* namespace js */
+
+class JSScript : public js::BaseScript {
+ private:
+ friend bool js::PrivateScriptData::InitFromStencil(
+ JSContext* cx, js::HandleScript script,
+ const js::frontend::CompilationAtomCache& atomCache,
+ const js::frontend::CompilationStencil& stencil,
+ js::frontend::CompilationGCOutput& gcOutput,
+ const js::frontend::ScriptIndex scriptIndex);
+
+ private:
+ using js::BaseScript::BaseScript;
+
+ public:
+ static JSScript* Create(JSContext* cx, JS::Handle<JSFunction*> function,
+ JS::Handle<js::ScriptSourceObject*> sourceObject,
+ const js::SourceExtent& extent,
+ js::ImmutableScriptFlags flags);
+
+ // NOTE: This should only be used while delazifying.
+ static JSScript* CastFromLazy(js::BaseScript* lazy) {
+ return static_cast<JSScript*>(lazy);
+ }
+
+ // NOTE: If you use createPrivateScriptData directly instead of via
+ // fullyInitFromStencil, you are responsible for notifying the debugger
+ // after successfully creating the script.
+ static bool createPrivateScriptData(JSContext* cx,
+ JS::Handle<JSScript*> script,
+ uint32_t ngcthings);
+
+ public:
+ static bool fullyInitFromStencil(
+ JSContext* cx, const js::frontend::CompilationAtomCache& atomCache,
+ const js::frontend::CompilationStencil& stencil,
+ js::frontend::CompilationGCOutput& gcOutput, js::HandleScript script,
+ const js::frontend::ScriptIndex scriptIndex);
+
+ // Allocate a JSScript and initialize it with bytecode. This consumes
+ // allocations within the stencil.
+ static JSScript* fromStencil(JSContext* cx,
+ js::frontend::CompilationAtomCache& atomCache,
+ const js::frontend::CompilationStencil& stencil,
+ js::frontend::CompilationGCOutput& gcOutput,
+ js::frontend::ScriptIndex scriptIndex);
+
+#ifdef DEBUG
+ private:
+ // Assert that jump targets are within the code array of the script.
+ void assertValidJumpTargets() const;
+#endif
+
+ public:
+ js::ImmutableScriptData* immutableScriptData() const {
+ return sharedData_->get();
+ }
+
+ // Script bytecode is immutable after creation.
+ jsbytecode* code() const {
+ if (!sharedData_) {
+ return nullptr;
+ }
+ return immutableScriptData()->code();
+ }
+
+ bool hasForceInterpreterOp() const {
+ // JSOp::ForceInterpreter, if present, must be the first op.
+ MOZ_ASSERT(length() >= 1);
+ return JSOp(*code()) == JSOp::ForceInterpreter;
+ }
+
+ js::AllBytecodesIterable allLocations() {
+ return js::AllBytecodesIterable(this);
+ }
+
+ js::BytecodeLocation location() { return js::BytecodeLocation(this, code()); }
+
+ size_t length() const {
+ MOZ_ASSERT(sharedData_);
+ return immutableScriptData()->codeLength();
+ }
+
+ jsbytecode* codeEnd() const { return code() + length(); }
+
+ jsbytecode* lastPC() const {
+ jsbytecode* pc = codeEnd() - js::JSOpLength_RetRval;
+ MOZ_ASSERT(JSOp(*pc) == JSOp::RetRval || JSOp(*pc) == JSOp::Return);
+ return pc;
+ }
+
+ // Note: ArgBytes is optional, but if specified then containsPC will also
+ // check that the opcode arguments are in bounds.
+ template <size_t ArgBytes = 0>
+ bool containsPC(const jsbytecode* pc) const {
+ MOZ_ASSERT_IF(ArgBytes,
+ js::GetBytecodeLength(pc) == sizeof(jsbytecode) + ArgBytes);
+ const jsbytecode* lastByte = pc + ArgBytes;
+ return pc >= code() && lastByte < codeEnd();
+ }
+ template <typename ArgType>
+ bool containsPC(const jsbytecode* pc) const {
+ return containsPC<sizeof(ArgType)>(pc);
+ }
+
+ bool contains(const js::BytecodeLocation& loc) const {
+ return containsPC(loc.toRawBytecode());
+ }
+
+ size_t pcToOffset(const jsbytecode* pc) const {
+ MOZ_ASSERT(containsPC(pc));
+ return size_t(pc - code());
+ }
+
+ jsbytecode* offsetToPC(size_t offset) const {
+ MOZ_ASSERT(offset < length());
+ return code() + offset;
+ }
+
+ size_t mainOffset() const { return immutableScriptData()->mainOffset; }
+
+ // The fixed part of a stack frame is comprised of vars (in function and
+ // module code) and block-scoped locals (in all kinds of code).
+ size_t nfixed() const { return immutableScriptData()->nfixed; }
+
+ // Number of fixed slots reserved for slots that are always live. Only
+ // nonzero for function or module code.
+ size_t numAlwaysLiveFixedSlots() const;
+
+ // Calculate the number of fixed slots that are live at a particular bytecode.
+ size_t calculateLiveFixed(jsbytecode* pc);
+
+ size_t nslots() const { return immutableScriptData()->nslots; }
+
+ unsigned numArgs() const;
+
+ inline js::Shape* initialEnvironmentShape() const;
+
+ bool functionHasParameterExprs() const;
+
+ bool functionAllowsParameterRedeclaration() const {
+ // Parameter redeclaration is only allowed for non-strict functions with
+ // simple parameter lists, which are neither arrow nor method functions. We
+ // don't have a flag at hand to test the function kind, but we can still
+ // test if the function is non-strict and has a simple parameter list by
+ // checking |hasMappedArgsObj()|. (Mapped arguments objects are only
+ // created for non-strict functions with simple parameter lists.)
+ return hasMappedArgsObj();
+ }
+
+ size_t numICEntries() const { return immutableScriptData()->numICEntries; }
+
+ size_t funLength() const { return immutableScriptData()->funLength; }
+
+ void cacheForEval() {
+ MOZ_ASSERT(isForEval());
+ // IsEvalCacheCandidate will make sure that there's nothing in this
+ // script that would prevent reexecution even if isRunOnce is
+ // true. So just pretend like we never ran this script.
+ clearFlag(MutableFlags::HasRunOnce);
+ }
+
+ /*
+ * Arguments access (via JSOp::*Arg* opcodes) must access the canonical
+ * location for the argument. If an arguments object exists AND it's mapped
+ * ('arguments' aliases formals), then all access must go through the
+ * arguments object. Otherwise, the local slot is the canonical location for
+ * the arguments. Note: if a formal is aliased through the scope chain, then
+ * script->formalIsAliased and JSOp::*Arg* opcodes won't be emitted at all.
+ */
+ bool argsObjAliasesFormals() const {
+ return needsArgsObj() && hasMappedArgsObj();
+ }
+
+ void updateJitCodeRaw(JSRuntime* rt);
+
+ bool isModule() const;
+ js::ModuleObject* module() const;
+
+ bool isGlobalCode() const;
+
+ // Returns true if the script may read formal arguments on the stack
+ // directly, via lazy arguments or a rest parameter.
+ bool mayReadFrameArgsDirectly();
+
+ static JSLinearString* sourceData(JSContext* cx, JS::HandleScript script);
+
+#ifdef MOZ_VTUNE
+ // Unique Method ID passed to the VTune profiler. Allows attribution of
+ // different jitcode to the same source script.
+ uint32_t vtuneMethodID();
+#endif
+
+ public:
+ /* Return whether this is a 'direct eval' script in a function scope. */
+ bool isDirectEvalInFunction() const;
+
+ /*
+ * Return whether this script is a top-level script.
+ *
+ * If we evaluate some code which contains a syntax error, then we might
+ * produce a JSScript which has no associated bytecode. Testing with
+ * |code()| filters out this kind of scripts.
+ *
+ * If this script has a function associated to it, then it is not the
+ * top-level of a file.
+ */
+ bool isTopLevel() { return code() && !isFunction(); }
+
+ /* Ensure the script has a JitScript. */
+ inline bool ensureHasJitScript(JSContext* cx, js::jit::AutoKeepJitScripts&);
+
+ void maybeReleaseJitScript(JS::GCContext* gcx);
+ void releaseJitScript(JS::GCContext* gcx);
+ void releaseJitScriptOnFinalize(JS::GCContext* gcx);
+
+ inline js::jit::BaselineScript* baselineScript() const;
+ inline js::jit::IonScript* ionScript() const;
+
+ inline bool isIonCompilingOffThread() const;
+ inline bool canIonCompile() const;
+ inline void disableIon();
+
+ inline bool canBaselineCompile() const;
+ inline void disableBaselineCompile();
+
+ inline js::GlobalObject& global() const;
+ inline bool hasGlobal(const js::GlobalObject* global) const;
+ js::GlobalObject& uninlinedGlobal() const;
+
+ js::GCThingIndex bodyScopeIndex() const {
+ return immutableScriptData()->bodyScopeIndex;
+ }
+
+ js::Scope* bodyScope() const { return getScope(bodyScopeIndex()); }
+
+ js::Scope* outermostScope() const {
+ // The body scope may not be the outermost scope in the script when
+ // the decl env scope is present.
+ return getScope(js::GCThingIndex::outermostScopeIndex());
+ }
+
+ bool functionHasExtraBodyVarScope() const {
+ bool res = BaseScript::functionHasExtraBodyVarScope();
+ MOZ_ASSERT_IF(res, functionHasParameterExprs());
+ return res;
+ }
+
+ js::VarScope* functionExtraBodyVarScope() const;
+
+ bool needsBodyEnvironment() const;
+
+ inline js::LexicalScope* maybeNamedLambdaScope() const;
+
+ // Drop script data and reset warmUpData to reference enclosing scope.
+ void relazify(JSRuntime* rt);
+
+ private:
+ bool createJitScript(JSContext* cx);
+
+ bool shareScriptData(JSContext* cx);
+
+ public:
+ inline uint32_t getWarmUpCount() const;
+ inline void incWarmUpCounter();
+ inline void resetWarmUpCounterForGC();
+
+ void resetWarmUpCounterToDelayIonCompilation();
+
+ unsigned getWarmUpResetCount() const {
+ constexpr uint32_t MASK = uint32_t(MutableFlags::WarmupResets_MASK);
+ return mutableFlags_ & MASK;
+ }
+ void incWarmUpResetCounter() {
+ constexpr uint32_t MASK = uint32_t(MutableFlags::WarmupResets_MASK);
+ uint32_t newCount = getWarmUpResetCount() + 1;
+ if (newCount <= MASK) {
+ mutableFlags_ &= ~MASK;
+ mutableFlags_ |= newCount;
+ }
+ }
+ void resetWarmUpResetCounter() {
+ constexpr uint32_t MASK = uint32_t(MutableFlags::WarmupResets_MASK);
+ mutableFlags_ &= ~MASK;
+ }
+
+ public:
+ bool initScriptCounts(JSContext* cx);
+ js::ScriptCounts& getScriptCounts();
+ js::PCCounts* maybeGetPCCounts(jsbytecode* pc);
+ const js::PCCounts* maybeGetThrowCounts(jsbytecode* pc);
+ js::PCCounts* getThrowCounts(jsbytecode* pc);
+ uint64_t getHitCount(jsbytecode* pc);
+ void addIonCounts(js::jit::IonScriptCounts* ionCounts);
+ js::jit::IonScriptCounts* getIonCounts();
+ void releaseScriptCounts(js::ScriptCounts* counts);
+ void destroyScriptCounts();
+ void resetScriptCounts();
+
+ jsbytecode* main() const { return code() + mainOffset(); }
+
+ js::BytecodeLocation mainLocation() const {
+ return js::BytecodeLocation(this, main());
+ }
+
+ js::BytecodeLocation endLocation() const {
+ return js::BytecodeLocation(this, codeEnd());
+ }
+
+ js::BytecodeLocation offsetToLocation(uint32_t offset) const {
+ return js::BytecodeLocation(this, offsetToPC(offset));
+ }
+
+ void addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf,
+ size_t* sizeOfJitScript,
+ size_t* sizeOfBaselineFallbackStubs) const;
+
+ mozilla::Span<const js::TryNote> trynotes() const {
+ return immutableScriptData()->tryNotes();
+ }
+
+ mozilla::Span<const js::ScopeNote> scopeNotes() const {
+ return immutableScriptData()->scopeNotes();
+ }
+
+ mozilla::Span<const uint32_t> resumeOffsets() const {
+ return immutableScriptData()->resumeOffsets();
+ }
+
+ uint32_t tableSwitchCaseOffset(jsbytecode* pc, uint32_t caseIndex) const {
+ MOZ_ASSERT(containsPC(pc));
+ MOZ_ASSERT(JSOp(*pc) == JSOp::TableSwitch);
+ uint32_t firstResumeIndex = GET_RESUMEINDEX(pc + 3 * JUMP_OFFSET_LEN);
+ return resumeOffsets()[firstResumeIndex + caseIndex];
+ }
+ jsbytecode* tableSwitchCasePC(jsbytecode* pc, uint32_t caseIndex) const {
+ return offsetToPC(tableSwitchCaseOffset(pc, caseIndex));
+ }
+
+ bool hasLoops();
+
+ uint32_t numNotes() const {
+ MOZ_ASSERT(sharedData_);
+ return immutableScriptData()->noteLength();
+ }
+ js::SrcNote* notes() const {
+ MOZ_ASSERT(sharedData_);
+ return immutableScriptData()->notes();
+ }
+
+ JSString* getString(js::GCThingIndex index) const {
+ return &gcthings()[index].as<JSString>();
+ }
+
+ JSString* getString(jsbytecode* pc) const {
+ MOZ_ASSERT(containsPC<js::GCThingIndex>(pc));
+ MOZ_ASSERT(js::JOF_OPTYPE((JSOp)*pc) == JOF_STRING);
+ return getString(GET_GCTHING_INDEX(pc));
+ }
+
+ JSAtom* getAtom(js::GCThingIndex index) const {
+ return &gcthings()[index].as<JSString>().asAtom();
+ }
+
+ JSAtom* getAtom(jsbytecode* pc) const {
+ MOZ_ASSERT(containsPC<js::GCThingIndex>(pc));
+ MOZ_ASSERT(js::JOF_OPTYPE((JSOp)*pc) == JOF_ATOM);
+ return getAtom(GET_GCTHING_INDEX(pc));
+ }
+
+ js::PropertyName* getName(js::GCThingIndex index) {
+ return getAtom(index)->asPropertyName();
+ }
+
+ js::PropertyName* getName(jsbytecode* pc) const {
+ return getAtom(pc)->asPropertyName();
+ }
+
+ JSObject* getObject(js::GCThingIndex index) const {
+ MOZ_ASSERT(gcthings()[index].asCell()->isTenured());
+ return &gcthings()[index].as<JSObject>();
+ }
+
+ JSObject* getObject(const jsbytecode* pc) const {
+ MOZ_ASSERT(containsPC<js::GCThingIndex>(pc));
+ return getObject(GET_GCTHING_INDEX(pc));
+ }
+
+ js::SharedShape* getShape(js::GCThingIndex index) const {
+ return &gcthings()[index].as<js::Shape>().asShared();
+ }
+
+ js::SharedShape* getShape(const jsbytecode* pc) const {
+ MOZ_ASSERT(containsPC<js::GCThingIndex>(pc));
+ return getShape(GET_GCTHING_INDEX(pc));
+ }
+
+ js::Scope* getScope(js::GCThingIndex index) const {
+ return &gcthings()[index].as<js::Scope>();
+ }
+
+ js::Scope* getScope(jsbytecode* pc) const {
+ // This method is used to get a scope directly using a JSOp with an
+ // index. To search through ScopeNotes to look for a Scope using pc,
+ // use lookupScope.
+ MOZ_ASSERT(containsPC<js::GCThingIndex>(pc));
+ MOZ_ASSERT(js::JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPE,
+ "Did you mean to use lookupScope(pc)?");
+ return getScope(GET_GCTHING_INDEX(pc));
+ }
+
+ inline JSFunction* getFunction(js::GCThingIndex index) const;
+ inline JSFunction* getFunction(jsbytecode* pc) const;
+
+ inline js::RegExpObject* getRegExp(js::GCThingIndex index) const;
+ inline js::RegExpObject* getRegExp(jsbytecode* pc) const;
+
+ js::BigInt* getBigInt(js::GCThingIndex index) const {
+ MOZ_ASSERT(gcthings()[index].asCell()->isTenured());
+ return &gcthings()[index].as<js::BigInt>();
+ }
+
+ js::BigInt* getBigInt(jsbytecode* pc) const {
+ MOZ_ASSERT(containsPC<js::GCThingIndex>(pc));
+ MOZ_ASSERT(js::JOF_OPTYPE(JSOp(*pc)) == JOF_BIGINT);
+ return getBigInt(GET_GCTHING_INDEX(pc));
+ }
+
+ // The following 3 functions find the static scope just before the
+ // execution of the instruction pointed to by pc.
+
+ js::Scope* lookupScope(const jsbytecode* pc) const;
+
+ js::Scope* innermostScope(const jsbytecode* pc) const;
+ js::Scope* innermostScope() const { return innermostScope(main()); }
+
+ /*
+ * The isEmpty method tells whether this script has code that computes any
+ * result (not return value, result AKA normal completion value) other than
+ * JSVAL_VOID, or any other effects.
+ */
+ bool isEmpty() const {
+ if (length() > 3) {
+ return false;
+ }
+
+ jsbytecode* pc = code();
+ if (noScriptRval() && JSOp(*pc) == JSOp::False) {
+ ++pc;
+ }
+ return JSOp(*pc) == JSOp::RetRval;
+ }
+
+ bool formalIsAliased(unsigned argSlot);
+ bool anyFormalIsForwarded();
+ bool formalLivesInArgumentsObject(unsigned argSlot);
+
+ // See comment above 'debugMode' in Realm.h for explanation of
+ // invariants of debuggee compartments, scripts, and frames.
+ inline bool isDebuggee() const;
+
+ // Create an allocation site associated with this script/JitScript to track
+ // nursery allocations.
+ js::gc::AllocSite* createAllocSite();
+
+ // A helper class to prevent relazification of the given function's script
+ // while it's holding on to it. This class automatically roots the script.
+ class AutoDelazify;
+ friend class AutoDelazify;
+
+ class AutoDelazify {
+ JS::RootedScript script_;
+ JSContext* cx_;
+ bool oldAllowRelazify_ = false;
+
+ public:
+ explicit AutoDelazify(JSContext* cx, JS::HandleFunction fun = nullptr)
+ : script_(cx), cx_(cx) {
+ holdScript(fun);
+ }
+
+ ~AutoDelazify() { dropScript(); }
+
+ void operator=(JS::HandleFunction fun) {
+ dropScript();
+ holdScript(fun);
+ }
+
+ operator JS::HandleScript() const { return script_; }
+ explicit operator bool() const { return script_; }
+
+ private:
+ void holdScript(JS::HandleFunction fun);
+ void dropScript();
+ };
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ public:
+ struct DumpOptions {
+ bool recursive = false;
+ bool runtimeData = false;
+ };
+
+ void dump(JSContext* cx);
+ void dumpRecursive(JSContext* cx);
+
+ static bool dump(JSContext* cx, JS::Handle<JSScript*> script,
+ DumpOptions& options, js::Sprinter* sp);
+ static bool dumpSrcNotes(JSContext* cx, JS::Handle<JSScript*> script,
+ js::Sprinter* sp);
+ static bool dumpTryNotes(JSContext* cx, JS::Handle<JSScript*> script,
+ js::Sprinter* sp);
+ static bool dumpScopeNotes(JSContext* cx, JS::Handle<JSScript*> script,
+ js::Sprinter* sp);
+ static bool dumpGCThings(JSContext* cx, JS::Handle<JSScript*> script,
+ js::Sprinter* sp);
+#endif
+};
+
+namespace js {
+
+struct ScriptAndCounts {
+ /* This structure is stored and marked from the JSRuntime. */
+ JSScript* script;
+ ScriptCounts scriptCounts;
+
+ inline explicit ScriptAndCounts(JSScript* script);
+ inline ScriptAndCounts(ScriptAndCounts&& sac);
+
+ const PCCounts* maybeGetPCCounts(jsbytecode* pc) const {
+ return scriptCounts.maybeGetPCCounts(script->pcToOffset(pc));
+ }
+ const PCCounts* maybeGetThrowCounts(jsbytecode* pc) const {
+ return scriptCounts.maybeGetThrowCounts(script->pcToOffset(pc));
+ }
+
+ jit::IonScriptCounts* getIonCounts() const { return scriptCounts.ionCounts_; }
+
+ void trace(JSTracer* trc) {
+ TraceRoot(trc, &script, "ScriptAndCounts::script");
+ }
+};
+
+extern JS::UniqueChars FormatIntroducedFilename(const char* filename,
+ unsigned lineno,
+ const char* introducer);
+
+struct GSNCache;
+
+const js::SrcNote* GetSrcNote(GSNCache& cache, JSScript* script,
+ jsbytecode* pc);
+
+extern const js::SrcNote* GetSrcNote(JSContext* cx, JSScript* script,
+ jsbytecode* pc);
+
+extern jsbytecode* LineNumberToPC(JSScript* script, unsigned lineno);
+
+extern JS_PUBLIC_API unsigned GetScriptLineExtent(JSScript* script);
+
+#ifdef JS_CACHEIR_SPEW
+void maybeUpdateWarmUpCount(JSScript* script);
+void maybeSpewScriptFinalWarmUpCount(JSScript* script);
+#endif
+
+} /* namespace js */
+
+namespace js {
+
+extern unsigned PCToLineNumber(JSScript* script, jsbytecode* pc,
+ unsigned* columnp = nullptr);
+
+extern unsigned PCToLineNumber(unsigned startLine, unsigned startCol,
+ SrcNote* notes, jsbytecode* code, jsbytecode* pc,
+ unsigned* columnp = nullptr);
+
+/*
+ * This function returns the file and line number of the script currently
+ * executing on cx. If there is no current script executing on cx (e.g., a
+ * native called directly through JSAPI (e.g., by setTimeout)), nullptr and 0
+ * are returned as the file and line.
+ */
+extern void DescribeScriptedCallerForCompilation(
+ JSContext* cx, MutableHandleScript maybeScript, const char** file,
+ unsigned* linenop, uint32_t* pcOffset, bool* mutedErrors);
+
+/*
+ * Like DescribeScriptedCallerForCompilation, but this function avoids looking
+ * up the script/pc and the full linear scan to compute line number.
+ */
+extern void DescribeScriptedCallerForDirectEval(
+ JSContext* cx, HandleScript script, jsbytecode* pc, const char** file,
+ unsigned* linenop, uint32_t* pcOffset, bool* mutedErrors);
+
+bool CheckCompileOptionsMatch(const JS::ReadOnlyCompileOptions& options,
+ js::ImmutableScriptFlags flags,
+ bool isMultiDecode);
+
+void FillImmutableFlagsFromCompileOptionsForTopLevel(
+ const JS::ReadOnlyCompileOptions& options, js::ImmutableScriptFlags& flags);
+
+void FillImmutableFlagsFromCompileOptionsForFunction(
+ const JS::ReadOnlyCompileOptions& options, js::ImmutableScriptFlags& flags);
+
+} /* namespace js */
+
+namespace JS {
+
+template <>
+struct GCPolicy<js::ScriptLCovEntry>
+ : public IgnoreGCPolicy<js::ScriptLCovEntry> {};
+
+#ifdef JS_CACHEIR_SPEW
+template <>
+struct GCPolicy<js::ScriptFinalWarmUpCountEntry>
+ : public IgnoreGCPolicy<js::ScriptFinalWarmUpCountEntry> {};
+#endif
+
+namespace ubi {
+
+template <>
+class Concrete<JSScript> : public Concrete<js::BaseScript> {};
+
+} // namespace ubi
+} // namespace JS
+
+#endif /* vm_JSScript_h */