Source code

Revision control

Copy as Markdown

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 */
/* 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/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::LimitedColumnNumberOneOrigin
#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 {
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; }
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>;
using ScriptFinalWarmUpCountEntry = std::tuple<uint32_t, SharedImmutableString>;
using ScriptFinalWarmUpCountMap =
GCRekeyableHashMap<HeapPtr<BaseScript*>, ScriptFinalWarmUpCountEntry,
DefaultHasher<HeapPtr<BaseScript*>>, SystemAllocPolicy>;
// 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 {
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) {
bool valid() const { return ss != nullptr; }
bool operator==(const ScriptSourceChunk& other) const {
return ss == && chunk == other.chunk;
struct ScriptSourceChunkHasher {
using Lookup = ScriptSourceChunk;
static HashNumber hash(const ScriptSourceChunk& ssc) {
return mozilla::AddToHash(DefaultHasher<ScriptSource*>::hash(,
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) {
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,
// 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;
explicit AutoHoldEntry() = default;
~AutoHoldEntry() {
if (cache_) {
template <typename Unit>
void holdUnits(EntryUnits<Unit> units) {
data_ = ToSourceData(std::move(units));
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.
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.
cache_ = nullptr;
sourceChunk_ = ScriptSourceChunk();
data_ = std::move(data);
const ScriptSourceChunk& sourceChunk() const { return sourceChunk_; }
friend class UncompressedSourceCache;
UniquePtr<Map> map_ = nullptr;
AutoHoldEntry* holder_ = nullptr;
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);
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;
// Common base class of the templated variants of PinnedUnits<T>.
class PinnedUnitsBase {
ScriptSource* source_;
explicit PinnedUnitsBase(ScriptSource* source) : source_(source) {}
void addReader();
template <typename Unit>
void removeReader();
// 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_;
PinnedUnits(JSContext* cx, ScriptSource* source,
UncompressedSourceCache::AutoHoldEntry& holder, size_t begin,
size_t len);
const Unit* get() const { return units_; }
const typename SourceTypeTraits<Unit>::CharT* asChars() const {
return SourceTypeTraits<Unit>::toString(get());
template <typename Unit>
class PinnedUnitsIfUncompressed : public PinnedUnitsBase {
const Unit* units_;
PinnedUnitsIfUncompressed(ScriptSource* source, size_t begin, size_t len);
const Unit* get() const { return units_; }
const typename SourceTypeTraits<Unit>::CharT* asChars() const {
return SourceTypeTraits<Unit>::toString(get());
// 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_;
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>;
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>;
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>,
// 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;
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 (1-origin).
uint32_t startLine_ = 0;
// Column number within the file where this source starts,
// in UTF-16 code units.
JS::LimitedColumnNumberOneOrigin startColumn_;
// See: CompileOptions::mutedErrors.
bool mutedErrors_ = false;
// Carry the delazification mode per source.
JS::DelazificationOption delazificationMode_ =
// 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);
template <typename Unit>
const Unit* uncompressedUnits(size_t begin, size_t len);
// 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) {
[[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);
class LoadSourceMatcher;
// 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();
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&) {
"attempting to access uncompressed data in a ScriptSource not "
"containing it");
return nullptr;
template <typename Unit>
const UncompressedData<Unit>* uncompressedData() {
return data.match(UncompressedDataMatcher<Unit>());
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&) {
"attempting to access compressed data in a ScriptSource not "
"containing it");
return nullptr;
template <typename Unit>
const CompressedData<Unit>* compressedData() {
return data.match(CompressedDataMatcher<Unit>());
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; }
bool hasUncompressedSource() const {
return data.match(HasUncompressedSource());
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;
template <typename Unit>
bool isUncompressed() const {
return data.match(IsUncompressed<Unit>());
struct HasCompressedSource {
template <typename Unit, SourceRetrievable CanRetrieve>
bool operator()(const Compressed<Unit, CanRetrieve>&) {
return true;
template <typename T>
bool operator()(const T&) {
return false;
bool hasCompressedSource() const { return data.match(HasCompressedSource()); }
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;
template <typename Unit>
bool isCompressed() const {
return data.match(IsCompressed<Unit>());
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;
template <typename Unit>
bool hasSourceType() const {
return data.match(SourceTypeMatcher<Unit>());
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;
size_t length() const {
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;
// 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);
// 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);
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>&) {
template <typename Unit, SourceRetrievable CanRetrieve>
void operator()(const Compressed<Unit, CanRetrieve>&) {
"can't set compressed source when source is already compressed -- "
"ScriptSource::tryCompressOffThread shouldn't have queued up this "
template <typename Unit>
void operator()(const Retrievable<Unit>&) {
MOZ_CRASH("shouldn't compressing unloaded-but-retrievable source");
void operator()(const Missing&) {
"doesn't make sense to set compressed source for missing source -- "
"ScriptSource::tryCompressOffThread shouldn't have queued up this "
template <typename Unit>
void convertToCompressedSource(SharedImmutableString compressed,
size_t uncompressedLength);
template <typename Unit>
void performDelayedConvertToCompressedSource(
ExclusiveData<ReaderInstances>::Guard& g);
void triggerConvertToCompressedSourceFromTask(
SharedImmutableString compressed);
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);
bool hasIntroducerFilename() const {
return introducerFilename_ ? true : false;
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 {
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_; }
JS::LimitedColumnNumberOneOrigin 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(offset <= (uint32_t)INT32_MAX);
// 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_;
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 {
const Value& v = getReservedSlot(ELEMENT_PROPERTY_SLOT);
return v;
BaseScript* unwrappedIntroductionScript() const {
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 {
Value value = getReservedSlot(PRIVATE_SLOT);
return value;
#ifdef DEBUG
bool isInitialized() const {
Value element = getReservedSlot(ELEMENT_PROPERTY_SLOT);
if (element.isMagic(JS_GENERIC_MAGIC)) {
return false;
enum {
// 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();
static constexpr uintptr_t NumTagBits = 2;
static constexpr uint32_t MaxWarmUpCount = UINT32_MAX >> NumTagBits;
// 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;
// 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;
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 {
return data_ >> NumTagBits;
void resetWarmUpCount(uint32_t count) {
void incWarmUpCount() {
data_ += uintptr_t(1) << NumTagBits;
jit::JitScript* toJitScript() const {
return getTaggedPtr<jit::JitScript*, JitScriptTag>();
void initJitScript(jit::JitScript* jitScript) {
void clearJitScript() {
data_ = ResetState();
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<PrivateScriptData> {
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_ =
// End of fields.
// 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);
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;
// 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(); }
// 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); }
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);
// Line number (1-origin)
uint32_t lineno() const { return extent_.lineno; }
// Column number in UTF-16 code units
JS::LimitedColumnNumberOneOrigin column() const { return extent_.column; }
JS::DelazificationOption delazificationMode() const {
return scriptSource()->delazificationMode();
ImmutableScriptFlags immutableFlags() const { return immutableFlags_; }
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 {
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; }