Source code

Revision control

Other Tools

/* -*- 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/Tuple.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 "frontend/SourceNotes.h" // SrcNote
#include "gc/Barrier.h"
#include "gc/Rooting.h"
#include "js/CompileOptions.h"
#include "js/UbiNode.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"
#include "util/StructuredSpewer.h"
#include "util/TrailingArray.h"
#include "vm/BigIntType.h"
#include "vm/BytecodeIterator.h"
#include "vm/BytecodeLocation.h"
#include "vm/BytecodeUtil.h"
#include "vm/GeneratorAndAsyncKind.h" // GeneratorKind, FunctionAsyncKind
#include "vm/JSAtom.h"
#include "vm/NativeObject.h"
#include "vm/ScopeKind.h" // ScopeKind
#include "vm/Shape.h"
#include "vm/SharedImmutableStringsCache.h"
#include "vm/SharedStencil.h" // js::GCThingIndex, js::SourceExtent, js::SharedImmutableScriptData, MemberInitializers
#include "vm/Time.h"
namespace JS {
struct ScriptSourceInfo;
template <typename UnitT>
class SourceText;
} // namespace JS
namespace js {
class VarScope;
class LexicalScope;
namespace coverage {
class LCovSource;
} // namespace coverage
namespace jit {
class AutoKeepJitScripts;
class BaselineScript;
class IonScript;
struct IonScriptCounts;
class JitScript;
} // namespace jit
class ModuleObject;
class RegExpObject;
class SourceCompressionTask;
class Shape;
class DebugScript;
namespace frontend {
struct CompilationStencil;
struct CompilationGCOutput;
} // 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);
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 = HashMap<BaseScript*, UniqueScriptCounts,
DefaultHasher<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 = mozilla::Tuple<coverage::LCovSource*, const char*>;
using ScriptLCovMap = HashMap<BaseScript*, ScriptLCovEntry,
DefaultHasher<BaseScript*>, SystemAllocPolicy>;
#ifdef MOZ_VTUNE
using ScriptVTuneIdMap = HashMap<BaseScript*, uint32_t,
DefaultHasher<BaseScript*>, SystemAllocPolicy>;
#endif
#ifdef JS_CACHEIR_SPEW
using ScriptFinalWarmUpCountEntry = mozilla::Tuple<uint32_t, char*>;
using ScriptFinalWarmUpCountMap =
HashMap<BaseScript*, ScriptFinalWarmUpCountEntry,
DefaultHasher<BaseScript*>, SystemAllocPolicy>;
#endif
class ScriptSource;
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);
// Retrievable source can be retrieved using the source hook (and therefore
// need not be XDR'd, can be discarded if desired because it can always be
// reconstituted later, etc.).
enum class SourceRetrievable { Yes, No };
// [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);
private:
// Common base class of the templated variants of PinnedUnits<T>.
class PinnedUnitsBase {
protected:
PinnedUnitsBase** stack_ = nullptr;
PinnedUnitsBase* prev_ = nullptr;
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 first PinnedUnits (that is, bottom of the stack) will install the
// compressed chars upon destruction.
//
// Retrievability isn't part of the type here because uncompressed->compressed
// transitions must preserve existing retrievability.
PinnedUnitsBase* pinnedUnitsStack_ = nullptr;
mozilla::MaybeOneOf<CompressedData<mozilla::Utf8Unit>,
CompressedData<char16_t>>
pendingCompressed_;
// The filename of this script.
mozilla::Maybe<SharedImmutableString> filename_;
// 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
// filename from above, otherwise it will be mozilla::Nothing.
mozilla::Maybe<SharedImmutableString> introducerFilename_;
mozilla::Maybe<SharedImmutableTwoByteString> displayURL_;
mozilla::Maybe<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.
// This value is logically owned by the canonical ScriptSourceObject, and
// will be released in the canonical SSO's finalizer.
UniquePtr<XDRIncrementalStencilEncoder> xdrEncoder_ = nullptr;
// 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.
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;
// See: CompileOptions::mutedErrors.
bool mutedErrors_ = false;
// Set to true if parser saw asmjs directives.
bool containsAsmJS_ = 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_) {}
void finalizeGCData();
~ScriptSource();
void AddRef() { refs++; }
void Release() {
MOZ_ASSERT(refs != 0);
if (--refs == 0) {
js_delete(this);
}
}
[[nodiscard]] bool initFromOptions(JSContext* cx,
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;
mozilla::Maybe<SharedImmutableString> getOrCreateStringZ(JSContext* cx,
UniqueChars&& str);
mozilla::Maybe<SharedImmutableTwoByteString> getOrCreateStringZ(
JSContext* cx, 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(JSContext* cx,
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 Unit>
[[nodiscard]] bool setUncompressedSourceHelper(JSContext* cx,
EntryUnits<Unit>&& source,
size_t length,
SourceRetrievable retrievable);
public:
// Initialize a fresh |ScriptSource| with unretrievable, uncompressed source.
template <typename Unit>
[[nodiscard]] bool initializeUnretrievableUncompressedSource(
JSContext* cx, 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);
// *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(
JSContext* cx, 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();
void triggerConvertToCompressedSourceFromTask(
SharedImmutableString compressed);
private:
// It'd be better to make this function take <XDRMode, Unit>, as both
// specializations of this function contain nested Unit-parametrized
// helper classes that do everything the function needs to do. But then
// we'd need template function partial specialization to hold XDRMode
// constant while varying Unit, so that idea's no dice.
template <XDRMode mode>
[[nodiscard]] XDRResult xdrUnretrievableUncompressedSource(
XDRState<mode>* xdr, uint8_t sourceCharSize, uint32_t uncompressedLength);
public:
const char* filename() const {
return filename_ ? filename_.ref().chars() : nullptr;
}
[[nodiscard]] bool setFilename(JSContext* cx, const char* filename);
[[nodiscard]] bool setFilename(JSContext* cx, UniqueChars&& filename);
const char* introducerFilename() const {
return introducerFilename_ ? introducerFilename_.ref().chars() : filename();
}
[[nodiscard]] bool setIntroducerFilename(JSContext* cx, const char* filename);
[[nodiscard]] bool setIntroducerFilename(JSContext* cx,
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(JSContext* cx, const char16_t* url);
[[nodiscard]] bool setDisplayURL(JSContext* cx, UniqueTwoByteChars&& url);
bool hasDisplayURL() const { return displayURL_.isSome(); }
const char16_t* displayURL() { return displayURL_.ref().chars(); }
// Source maps
[[nodiscard]] bool setSourceMapURL(JSContext* cx, const char16_t* url);
[[nodiscard]] bool setSourceMapURL(JSContext* cx, UniqueTwoByteChars&& url);
bool hasSourceMapURL() const { return sourceMapURL_.isSome(); }
const char16_t* sourceMapURL() { return sourceMapURL_.ref().chars(); }
bool mutedErrors() const { return mutedErrors_; }
uint32_t startLine() const { return startLine_; }
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);
}
bool containsAsmJS() const { return containsAsmJS_; }
void setContainsAsmJS() { containsAsmJS_ = true; }
// Return wether an XDR encoder is present or not.
bool hasEncoder() const { return bool(xdrEncoder_); }
[[nodiscard]] bool startIncrementalEncoding(
JSContext* cx, const JS::ReadOnlyCompileOptions& options,
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);
private:
template <typename Unit,
template <typename U, SourceRetrievable CanRetrieve> class Data,
XDRMode mode>
static void codeRetrievable(ScriptSource* ss);
template <typename Unit, XDRMode mode>
[[nodiscard]] static XDRResult codeUncompressedData(XDRState<mode>* const xdr,
ScriptSource* const ss);
template <typename Unit, XDRMode mode>
[[nodiscard]] static XDRResult codeCompressedData(XDRState<mode>* const xdr,
ScriptSource* const ss);
template <typename Unit, XDRMode mode>
static void codeRetrievableData(ScriptSource* ss);
template <XDRMode mode>
[[nodiscard]] static XDRResult xdrData(XDRState<mode>* const xdr,
ScriptSource* const ss);
public:
template <XDRMode mode>
[[nodiscard]] static XDRResult XDR(
XDRState<mode>* xdr, const JS::ReadOnlyCompileOptions* maybeOptions,
RefPtr<ScriptSource>& source);
};
// [SMDOC] ScriptSourceObject
//
// ScriptSourceObject stores the ScriptSource and GC pointers related to it.
//
// ScriptSourceObjects can be cloned when we clone the JSScript (in order to
// execute the script in a different compartment). In this case we create a new
// SSO that stores (a wrapper for) the original SSO in its "canonical slot".
// The canonical SSO is always used for the private, introductionScript,
// element, elementAttributeName slots. This means their accessors may return an
// object in a different compartment, hence the "unwrapped" prefix.
//
// Note that we don't clone the SSO when cloning the script for a different
// realm in the same compartment, so sso->realm() does not necessarily match the
// script's realm.
//
// We need ScriptSourceObject (instead of storing these GC pointers in the
// ScriptSource itself) to properly account for cross-zone pointers: the
// canonical SSO will be stored in the wrapper map if necessary so GC will do
// the right thing.
class ScriptSourceObject : public NativeObject {
static const JSClassOps classOps_;
static ScriptSourceObject* createInternal(JSContext* cx, ScriptSource* source,
HandleObject canonical);
bool isCanonical() const {
return &getReservedSlot(CANONICAL_SLOT).toObject() == this;
}
ScriptSourceObject* unwrappedCanonical() const;
public:
static const JSClass class_;
static void finalize(JSFreeOp* fop, JSObject* obj);
static ScriptSourceObject* create(JSContext* cx, ScriptSource* source);
static ScriptSourceObject* clone(JSContext* cx, HandleScriptSourceObject sso);
// Initialize those properties of this ScriptSourceObject whose values
// are provided by |options|, re-wrapping as necessary.
static bool initFromOptions(JSContext* cx, HandleScriptSourceObject source,
const JS::ReadOnlyCompileOptions& options);
static bool initElementProperties(JSContext* cx,
HandleScriptSourceObject 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 {
const Value& v =
unwrappedCanonical()->getReservedSlot(ELEMENT_PROPERTY_SLOT);
MOZ_ASSERT(!v.isMagic());
return v;
}
BaseScript* unwrappedIntroductionScript() const {
Value value =
unwrappedCanonical()->getReservedSlot(INTRODUCTION_SCRIPT_SLOT);
if (value.isUndefined()) {
return nullptr;
}
return value.toGCThing()->as<BaseScript>();
}
void setPrivate(JSRuntime* rt, const Value& value);
Value canonicalPrivate() const {
Value value = getReservedSlot(PRIVATE_SLOT);
MOZ_ASSERT_IF(!isCanonical(), value.isUndefined());
return value;
}
private:
enum {
SOURCE_SLOT = 0,
CANONICAL_SLOT,
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(uint32_t amount) {
MOZ_ASSERT(isWarmUpCount());
data_ += uintptr_t(amount) << 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);
template <XDRMode mode>
[[nodiscard]] static XDRResult XDR(js::XDRState<mode>* xdr,
js::HandleScript script,
js::HandleScriptSourceObject sourceObject,
js::HandleScope scriptEnclosingScope,
js::HandleObject funOrMod);
// Clone src script data into dst script.
static bool Clone(JSContext* cx, js::HandleScript src, js::HandleScript dst,
js::MutableHandle<JS::GCVector<js::Scope*>> scopes);
static bool InitFromStencil(JSContext* cx, js::HandleScript script,
const js::frontend::CompilationInput& input,
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> {
public:
// The definition of flags is shared with the frontend for consistency.
using ImmutableFlags = ImmutableScriptFlagsEnum;
using MutableFlags = MutableScriptFlagsEnum;
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_ = {};
// Object that determines what Realm this script is compiled for. For function
// scripts this is the canonical function, otherwise it is the GlobalObject of
// the realm.
const GCPtrObject functionOrGlobal_ = {};
// The ScriptSourceObject for this script. This is always same-compartment
// with this script, but may be a clone if the original source object is in a
// different compartment.
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.
PrivateScriptData* data_ = nullptr;
// 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, JSObject* functionOrGlobal,
ScriptSourceObject* sourceObject, const SourceExtent& extent,
uint32_t immutableFlags)
: TenuredCellWithNonGCPointer(stubEntry),
functionOrGlobal_(functionOrGlobal),
sourceObject_(sourceObject),
extent_(extent),
immutableFlags_(immutableFlags) {
MOZ_ASSERT(functionOrGlobal->compartment() == sourceObject->compartment());
MOZ_ASSERT(extent_.toStringStart <= extent_.sourceStart);
MOZ_ASSERT(extent_.sourceStart <= extent_.sourceEnd);
MOZ_ASSERT(extent_.sourceEnd <= extent_.toStringEnd);
// The immutableFlags determine if the arguments-analysis will need to be
// run. We track if we need to run the analysis and the result of the
// analysis on the mutableFlags. The analysis is deterministic so we only
// need it once, even if we relazify, etc.
if (argumentsHasVarBinding()) {
setFlag(MutableFlags::NeedsArgsObj, alwaysNeedsArgsObj());
setFlag(MutableFlags::NeedsArgsAnalysis, !alwaysNeedsArgsObj());
} else {
MOZ_ASSERT(!alwaysNeedsArgsObj());
}
}
void setJitCodeRaw(uint8_t* code) { setHeaderPtr(code); }
public:
static BaseScript* New(JSContext* cx, js::HandleObject functionOrGlobal,
js::HandleScriptSourceObject 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,
HandleScriptSourceObject 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 {
if (functionOrGlobal_->is<JSFunction>()) {
return &functionOrGlobal_->as<JSFunction>();
}
return nullptr;
}
JS::Realm* realm() const { return functionOrGlobal_->nonCCWRealm(); }
JS::Compartment* compartment() const {
return functionOrGlobal_->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(); }
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; }
public:
ImmutableScriptFlags immutableFlags() const { return immutableFlags_; }
// ImmutableFlags accessors.
[[nodiscard]] bool hasFlag(ImmutableFlags flag) const {
return immutableFlags_.hasFlag(flag);
}
// MutableFlags accessors.
[[nodiscard]] bool hasFlag(MutableFlags flag) const {
return mutableFlags_.hasFlag(flag);
}
void setFlag(MutableFlags flag, bool b = true) {
mutableFlags_.setFlag(flag, b);
}
void clearFlag(MutableFlags flag) { mutableFlags_.clearFlag(flag); }
// Specific flag accessors
#define FLAG_GETTER(enumName, enumEntry, lowerName) \
public: \
bool lowerName() const { return hasFlag(enumName::enumEntry); }
#define FLAG_GETTER_SETTER(enumName, enumEntry, lowerName, name) \
public: \
bool lowerName() const { return hasFlag(enumName::enumEntry); } \
void set##name() { setFlag(enumName::enumEntry); } \
void set##name(bool b) { setFlag(enumName::enumEntry, b); } \
void clear##name() { clearFlag(enumName::enumEntry); }
#define IMMUTABLE_FLAG_GETTER(lowerName, name) \
FLAG_GETTER(ImmutableFlags, name, lowerName)
#define MUTABLE_FLAG_GETTER_SETTER(lowerName, name) \
FLAG_GETTER_SETTER(MutableFlags, name, lowerName, name)
IMMUTABLE_FLAG_GETTER(isForEval, IsForEval)
IMMUTABLE_FLAG_GETTER(isModule, IsModule)
IMMUTABLE_FLAG_GETTER(isFunction, IsFunction)
IMMUTABLE_FLAG_GETTER(selfHosted, SelfHosted)
IMMUTABLE_FLAG_GETTER(forceStrict, ForceStrict)
IMMUTABLE_FLAG_GETTER(hasNonSyntacticScope, HasNonSyntacticScope)
IMMUTABLE_FLAG_GETTER(noScriptRval, NoScriptRval)
IMMUTABLE_FLAG_GETTER(treatAsRunOnce, TreatAsRunOnce)
IMMUTABLE_FLAG_GETTER(strict, Strict)
IMMUTABLE_FLAG_GETTER(hasModuleGoal, HasModuleGoal)
IMMUTABLE_FLAG_GETTER(hasInnerFunctions, HasInnerFunctions)
IMMUTABLE_FLAG_GETTER(hasDirectEval, HasDirectEval)
IMMUTABLE_FLAG_GETTER(bindingsAccessedDynamically,
BindingsAccessedDynamically)
IMMUTABLE_FLAG_GETTER(hasCallSiteObj, HasCallSiteObj)
IMMUTABLE_FLAG_GETTER(isAsync, IsAsync)
IMMUTABLE_FLAG_GETTER(isGenerator, IsGenerator)
IMMUTABLE_FLAG_GETTER(funHasExtensibleScope, FunHasExtensibleScope)
IMMUTABLE_FLAG_GETTER(functionHasThisBinding, FunctionHasThisBinding)
IMMUTABLE_FLAG_GETTER(needsHomeObject, NeedsHomeObject)
IMMUTABLE_FLAG_GETTER(isDerivedClassConstructor, IsDerivedClassConstructor)
IMMUTABLE_FLAG_GETTER(isSyntheticFunction, IsSyntheticFunction)
IMMUTABLE_FLAG_GETTER(useMemberInitializers, UseMemberInitializers)
IMMUTABLE_FLAG_GETTER(hasRest, HasRest)
IMMUTABLE_FLAG_GETTER(needsFunctionEnvironmentObjects,
NeedsFunctionEnvironmentObjects)
// FunctionHasExtraBodyVarScope: custom logic below.
IMMUTABLE_FLAG_GETTER(shouldDeclareArguments, ShouldDeclareArguments)
IMMUTABLE_FLAG_GETTER(argumentsHasVarBinding, ArgumentsHasVarBinding)
IMMUTABLE_FLAG_GETTER(alwaysNeedsArgsObj, AlwaysNeedsArgsObj)
IMMUTABLE_FLAG_GETTER(hasMappedArgsObj, HasMappedArgsObj)
MUTABLE_FLAG_GETTER_SETTER(hasRunOnce, HasRunOnce)
MUTABLE_FLAG_GETTER_SETTER(hasScriptCounts, HasScriptCounts)
MUTABLE_FLAG_GETTER_SETTER(hasDebugScript, HasDebugScript)
MUTABLE_FLAG_GETTER_SETTER(needsArgsAnalysis, NeedsArgsAnalysis)
// NeedsArgsObj: custom logic below.
MUTABLE_FLAG_GETTER_SETTER(allowRelazify, AllowRelazify)
MUTABLE_FLAG_GETTER_SETTER(spewEnabled, SpewEnabled)
MUTABLE_FLAG_GETTER_SETTER(needsFinalWarmUpCount, NeedsFinalWarmUpCount)
MUTABLE_FLAG_GETTER_SETTER(failedBoundsCheck, FailedBoundsCheck)
MUTABLE_FLAG_GETTER_SETTER(hadLICMInvalidation, HadLICMInvalidation)
MUTABLE_FLAG_GETTER_SETTER(hadReorderingBailout, HadReorderingBailout)
MUTABLE_FLAG_GETTER_SETTER(hadEagerTruncationBailout,
HadEagerTruncationBailout)
MUTABLE_FLAG_GETTER_SETTER(hadUnboxFoldingBailout, HadUnboxFoldingBailout)
MUTABLE_FLAG_GETTER_SETTER(uninlineable, Uninlineable)
MUTABLE_FLAG_GETTER_SETTER(failedLexicalCheck, FailedLexicalCheck)
MUTABLE_FLAG_GETTER_SETTER(hadSpeculativePhiBailout, HadSpeculativePhiBailout)
MUTABLE_FLAG_GETTER_SETTER(isInlinableLargeFunction, IsInlinableLargeFunction)
#undef IMMUTABLE_FLAG_GETTER
#undef MUTABLE_FLAG_GETTER_SETTER
#undef FLAG_GETTER
#undef FLAG_GETTER_SETTER
GeneratorKind generatorKind() const {
return isGenerator() ? GeneratorKind::Generator
: GeneratorKind::NotGenerator;
}
FunctionAsyncKind asyncKind() const {
return isAsync() ? FunctionAsyncKind::AsyncFunction
: FunctionAsyncKind::SyncFunction;
}
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; }