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