/* -*- 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/. */ #ifndef vm_SharedStencil_h #define vm_SharedStencil_h #include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_CRASH #include "mozilla/Atomics.h" // mozilla::{Atomic, SequentiallyConsistent} #include "mozilla/CheckedInt.h" // mozilla::CheckedInt #include "mozilla/HashFunctions.h" // mozilla::HahNumber, mozilla::HashBytes #include "mozilla/HashTable.h" // mozilla::HashSet #include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf #include "mozilla/RefPtr.h" // RefPtr #include "mozilla/Span.h" // mozilla::Span #include // size_t #include // uint8_t, uint16_t, uint32_t #include "frontend/SourceNotes.h" // js::SrcNote #include "frontend/TypedIndex.h" // js::frontend::TypedIndex #include "js/AllocPolicy.h" // js::SystemAllocPolicy #include "js/TypeDecls.h" // JSContext,jsbytecode #include "js/UniquePtr.h" // js::UniquePtr #include "js/Vector.h" // js::Vector #include "util/EnumFlags.h" // js::EnumFlags #include "util/TrailingArray.h" // js::TrailingArray #include "vm/GeneratorAndAsyncKind.h" // GeneratorKind, FunctionAsyncKind #include "vm/StencilEnums.h" // js::{TryNoteKind,ImmutableScriptFlagsEnum,MutableScriptFlagsEnum} // // Data structures shared between Stencil and the VM. // namespace js { class FrontendContext; namespace frontend { class StencilXDR; } // namespace frontend // Index into gcthings array. class GCThingIndexType; class GCThingIndex : public frontend::TypedIndex { // Delegate constructors; using Base = frontend::TypedIndex; using Base::Base; public: static constexpr GCThingIndex outermostScopeIndex() { return GCThingIndex(0); } static constexpr GCThingIndex invalid() { return GCThingIndex(UINT32_MAX); } GCThingIndex next() const { return GCThingIndex(index + 1); } }; /* * Exception handling record. */ struct TryNote { uint32_t kind_; /* one of TryNoteKind */ uint32_t stackDepth; /* stack depth upon exception handler entry */ uint32_t start; /* start of the try statement or loop relative to script->code() */ uint32_t length; /* length of the try statement or loop */ TryNote(uint32_t kind, uint32_t stackDepth, uint32_t start, uint32_t length) : kind_(kind), stackDepth(stackDepth), start(start), length(length) {} TryNote() = default; TryNoteKind kind() const { return TryNoteKind(kind_); } bool isLoop() const { switch (kind()) { case TryNoteKind::Loop: case TryNoteKind::ForIn: case TryNoteKind::ForOf: return true; case TryNoteKind::Catch: case TryNoteKind::Finally: case TryNoteKind::ForOfIterClose: case TryNoteKind::Destructuring: return false; } MOZ_CRASH("Unexpected try note kind"); } }; // A block scope has a range in bytecode: it is entered at some offset, and left // at some later offset. Scopes can be nested. Given an offset, the // ScopeNote containing that offset whose with the highest start value // indicates the block scope. The block scope list is sorted by increasing // start value. // // It is possible to leave a scope nonlocally, for example via a "break" // statement, so there may be short bytecode ranges in a block scope in which we // are popping the block chain in preparation for a goto. These exits are also // nested with respect to outer scopes. The scopes in these exits are indicated // by the "index" field, just like any other block. If a nonlocal exit pops the // last block scope, the index will be NoScopeIndex. // struct ScopeNote { // Sentinel index for no Scope. static constexpr GCThingIndex NoScopeIndex = GCThingIndex::invalid(); // Sentinel index for no ScopeNote. static const uint32_t NoScopeNoteIndex = UINT32_MAX; // Index of the js::Scope in the script's gcthings array, or NoScopeIndex if // there is no block scope in this range. GCThingIndex index; // Bytecode offset at which this scope starts relative to script->code(). uint32_t start = 0; // Length of bytecode span this scope covers. uint32_t length = 0; // Index of parent block scope in notes, or NoScopeNoteIndex. uint32_t parent = 0; }; // Range of characters in scriptSource which contains a script's source, // that is, the range used by the Parser to produce a script. // // For most functions the fields point to the following locations. // // function * foo(a, b) { return a + b; } // ^ ^ ^ // | | | // | sourceStart sourceEnd // | | // toStringStart toStringEnd // // For the special case of class constructors, the spec requires us to use an // alternate definition of toStringStart / toStringEnd. // // class C { constructor() { this.field = 42; } } // ^ ^ ^ ^ // | | | | // | sourceStart sourceEnd | // | | // toStringStart toStringEnd // // Implicit class constructors use the following definitions. // // class C { someMethod() { } } // ^ ^ // | | // sourceStart sourceEnd // | | // toStringStart toStringEnd // // Field initializer lambdas are internal details of the engine, but we still // provide a sensible definition of these values. // // class C { static field = 1 } // class C { field = 1 } // class C { somefield } // ^ ^ // | | // sourceStart sourceEnd // // The non-static private class methods (including getters and setters) ALSO // create a hidden initializer lambda in addition to the method itself. These // lambdas are not exposed directly to script. // // class C { #field() { } } // class C { get #field() { } } // class C { async #field() { } } // class C { * #field() { } } // ^ ^ // | | // sourceStart sourceEnd // // NOTE: These are counted in Code Units from the start of the script source. // // Also included in the SourceExtent is the line and column numbers of the // sourceStart position. Compilation options may specify the initial line and // column number. // // NOTE: Column number may saturate and must not be used as unique identifier. struct SourceExtent { SourceExtent() = default; SourceExtent(uint32_t sourceStart, uint32_t sourceEnd, uint32_t toStringStart, uint32_t toStringEnd, uint32_t lineno, uint32_t column) : sourceStart(sourceStart), sourceEnd(sourceEnd), toStringStart(toStringStart), toStringEnd(toStringEnd), lineno(lineno), column(column) {} static SourceExtent makeGlobalExtent(uint32_t len) { return SourceExtent(0, len, 0, len, 1, 0); } static SourceExtent makeGlobalExtent(uint32_t len, uint32_t lineno, uint32_t column) { return SourceExtent(0, len, 0, len, lineno, column); } // FunctionKey is an encoded position of a function within the source text // that is unique and reproducible. using FunctionKey = uint32_t; static constexpr FunctionKey NullFunctionKey = 0; uint32_t sourceStart = 0; uint32_t sourceEnd = 0; uint32_t toStringStart = 0; uint32_t toStringEnd = 0; // Line and column of |sourceStart_| position. uint32_t lineno = 1; // 1-indexed. uint32_t column = 0; // Count of Code Points FunctionKey toFunctionKey() const { // In eval("x=>1"), the arrow function will have a sourceStart of 0 which // conflicts with the NullFunctionKey, so shift all keys by 1 instead. auto result = sourceStart + 1; MOZ_ASSERT(result != NullFunctionKey); return result; } }; class ImmutableScriptFlags : public EnumFlags { public: ImmutableScriptFlags() = default; explicit ImmutableScriptFlags(FieldType rawFlags) : EnumFlags(rawFlags) {} operator FieldType() const { return flags_; } }; class MutableScriptFlags : public EnumFlags { public: MutableScriptFlags() = default; MutableScriptFlags& operator&=(const FieldType rhs) { flags_ &= rhs; return *this; } MutableScriptFlags& operator|=(const FieldType rhs) { flags_ |= rhs; return *this; } operator FieldType() const { return flags_; } }; #define GENERIC_FLAGS_READ_ONLY(Field, Enum) \ [[nodiscard]] bool hasFlag(Enum flag) const { return Field.hasFlag(flag); } #define GENERIC_FLAGS_READ_WRITE(Field, Enum) \ [[nodiscard]] bool hasFlag(Enum flag) const { return Field.hasFlag(flag); } \ void setFlag(Enum flag, bool b = true) { Field.setFlag(flag, b); } \ void clearFlag(Enum flag) { Field.clearFlag(flag); } #define GENERIC_FLAG_GETTER(enumName, lowerName, name) \ bool lowerName() const { return hasFlag(enumName::name); } #define GENERIC_FLAG_GETTER_SETTER(enumName, lowerName, name) \ GENERIC_FLAG_GETTER(enumName, lowerName, name) \ void set##name() { setFlag(enumName::name); } \ void set##name(bool b) { setFlag(enumName::name, b); } \ void clear##name() { clearFlag(enumName::name); } #define IMMUTABLE_SCRIPT_FLAGS_WITH_ACCESSORS(_) \ _(ImmutableFlags, isForEval, IsForEval) \ _(ImmutableFlags, isModule, IsModule) \ _(ImmutableFlags, isFunction, IsFunction) \ _(ImmutableFlags, selfHosted, SelfHosted) \ _(ImmutableFlags, forceStrict, ForceStrict) \ _(ImmutableFlags, hasNonSyntacticScope, HasNonSyntacticScope) \ _(ImmutableFlags, noScriptRval, NoScriptRval) \ _(ImmutableFlags, treatAsRunOnce, TreatAsRunOnce) \ _(ImmutableFlags, strict, Strict) \ _(ImmutableFlags, hasModuleGoal, HasModuleGoal) \ _(ImmutableFlags, hasInnerFunctions, HasInnerFunctions) \ _(ImmutableFlags, hasDirectEval, HasDirectEval) \ _(ImmutableFlags, bindingsAccessedDynamically, BindingsAccessedDynamically) \ _(ImmutableFlags, hasCallSiteObj, HasCallSiteObj) \ _(ImmutableFlags, isAsync, IsAsync) \ _(ImmutableFlags, isGenerator, IsGenerator) \ _(ImmutableFlags, funHasExtensibleScope, FunHasExtensibleScope) \ _(ImmutableFlags, functionHasThisBinding, FunctionHasThisBinding) \ _(ImmutableFlags, needsHomeObject, NeedsHomeObject) \ _(ImmutableFlags, isDerivedClassConstructor, IsDerivedClassConstructor) \ _(ImmutableFlags, isSyntheticFunction, IsSyntheticFunction) \ _(ImmutableFlags, useMemberInitializers, UseMemberInitializers) \ _(ImmutableFlags, hasRest, HasRest) \ _(ImmutableFlags, needsFunctionEnvironmentObjects, \ NeedsFunctionEnvironmentObjects) \ _(ImmutableFlags, functionHasExtraBodyVarScope, \ FunctionHasExtraBodyVarScope) \ _(ImmutableFlags, shouldDeclareArguments, ShouldDeclareArguments) \ _(ImmutableFlags, needsArgsObj, NeedsArgsObj) \ _(ImmutableFlags, hasMappedArgsObj, HasMappedArgsObj) \ _(ImmutableFlags, isInlinableLargeFunction, IsInlinableLargeFunction) \ _(ImmutableFlags, functionHasNewTargetBinding, FunctionHasNewTargetBinding) \ _(ImmutableFlags, usesArgumentsIntrinsics, UsesArgumentsIntrinsics) \ \ GeneratorKind generatorKind() const { \ return isGenerator() ? GeneratorKind::Generator \ : GeneratorKind::NotGenerator; \ } \ \ FunctionAsyncKind asyncKind() const { \ return isAsync() ? FunctionAsyncKind::AsyncFunction \ : FunctionAsyncKind::SyncFunction; \ } \ \ bool isRelazifiable() const { \ /* \ ** A script may not be relazifiable if parts of it can be entrained in \ ** interesting ways: \ ** - Scripts with inner-functions or direct-eval (which can add \ ** inner-functions) should not be relazified as their Scopes may be \ ** part of another scope-chain. \ ** - Generators and async functions may be re-entered in complex ways so \ ** don't discard bytecode. The JIT resume code assumes this. \ ** - Functions with template literals must always return the same object \ ** instance so must not discard it by relazifying. \ */ \ return !hasInnerFunctions() && !hasDirectEval() && !isGenerator() && \ !isAsync() && !hasCallSiteObj(); \ } #define RO_IMMUTABLE_SCRIPT_FLAGS(Field) \ using ImmutableFlags = ImmutableScriptFlagsEnum; \ \ GENERIC_FLAGS_READ_ONLY(Field, ImmutableFlags) \ IMMUTABLE_SCRIPT_FLAGS_WITH_ACCESSORS(GENERIC_FLAG_GETTER) #define MUTABLE_SCRIPT_FLAGS_WITH_ACCESSORS(_) \ _(MutableFlags, hasRunOnce, HasRunOnce) \ _(MutableFlags, hasScriptCounts, HasScriptCounts) \ _(MutableFlags, hasDebugScript, HasDebugScript) \ _(MutableFlags, allowRelazify, AllowRelazify) \ _(MutableFlags, spewEnabled, SpewEnabled) \ _(MutableFlags, needsFinalWarmUpCount, NeedsFinalWarmUpCount) \ _(MutableFlags, failedBoundsCheck, FailedBoundsCheck) \ _(MutableFlags, hadLICMInvalidation, HadLICMInvalidation) \ _(MutableFlags, hadReorderingBailout, HadReorderingBailout) \ _(MutableFlags, hadEagerTruncationBailout, HadEagerTruncationBailout) \ _(MutableFlags, hadUnboxFoldingBailout, HadUnboxFoldingBailout) \ _(MutableFlags, baselineDisabled, BaselineDisabled) \ _(MutableFlags, ionDisabled, IonDisabled) \ _(MutableFlags, uninlineable, Uninlineable) \ _(MutableFlags, noEagerBaselineHint, NoEagerBaselineHint) \ _(MutableFlags, failedLexicalCheck, FailedLexicalCheck) \ _(MutableFlags, hadSpeculativePhiBailout, HadSpeculativePhiBailout) #define RW_MUTABLE_SCRIPT_FLAGS(Field) \ using MutableFlags = MutableScriptFlagsEnum; \ \ GENERIC_FLAGS_READ_WRITE(Field, MutableFlags) \ MUTABLE_SCRIPT_FLAGS_WITH_ACCESSORS(GENERIC_FLAG_GETTER_SETTER) // [SMDOC] JSScript data layout (immutable) // // ImmutableScriptData stores variable-length script data that may be shared // between scripts with the same bytecode, even across different GC Zones. // Abstractly this structure consists of multiple (optional) arrays that are // exposed as mozilla::Span. These arrays exist in a single heap allocation. // // Under the hood, ImmutableScriptData is a fixed-size header class followed // the various array bodies interleaved with metadata to compactly encode the // bounds. These arrays have varying requirements for alignment, performance, // and jit-friendliness which leads to the complex indexing system below. // // Note: The '----' separators are for readability only. // // ---- // // ---- // (REQUIRED) Flags structure // (REQUIRED) Array of jsbytecode constituting code() // (REQUIRED) Array of SrcNote constituting notes() // ---- // (OPTIONAL) Array of uint32_t optional-offsets // optArrayOffset: // ---- // L0: // (OPTIONAL) Array of uint32_t constituting resumeOffsets() // L1: // (OPTIONAL) Array of ScopeNote constituting scopeNotes() // L2: // (OPTIONAL) Array of TryNote constituting tryNotes() // L3: // ---- // // NOTE: The notes() array must have been null-padded such that // flags/code/notes together have uint32_t alignment. // // The labels shown are recorded as byte-offsets relative to 'this'. This is to // reduce memory as well as make ImmutableScriptData easier to share across // processes. // // The L0/L1/L2/L3 labels indicate the start and end of the optional arrays. // Some of these labels may refer to the same location if the array between // them is empty. Each unique label position has an offset stored in the // optional-offsets table. Note that we also avoid entries for labels that // match 'optArrayOffset'. This saves memory when arrays are empty. // // The flags() data indicates (for each optional array) which entry from the // optional-offsets table marks the *end* of array. The array starts where the // previous array ends (with the first array beginning at 'optArrayOffset'). // The optional-offset table is addressed at negative indices from // 'optArrayOffset'. // // In general, the length of each array is computed from subtracting the start // offset of the array from the start offset of the subsequent array. The // notable exception is that bytecode length is stored explicitly. class alignas(uint32_t) ImmutableScriptData final : public TrailingArray { private: Offset optArrayOffset_ = 0; // Length of bytecode uint32_t codeLength_ = 0; public: // Offset of main entry point from code, after predef'ing prologue. uint32_t mainOffset = 0; // Fixed frame slots. uint32_t nfixed = 0; // Slots plus maximum stack depth. uint32_t nslots = 0; // Index into the gcthings array of the body scope. GCThingIndex bodyScopeIndex; // Number of IC entries to allocate in JitScript for Baseline ICs. uint32_t numICEntries = 0; // ES6 function length. uint16_t funLength = 0; // Property Count estimate uint16_t propertyCountEstimate = 0; // NOTE: The raw bytes of this structure are used for hashing so use explicit // padding values as needed for predicatable results across compilers private: struct Flags { uint8_t resumeOffsetsEndIndex : 2; uint8_t scopeNotesEndIndex : 2; uint8_t tryNotesEndIndex : 2; uint8_t _unused : 2; }; static_assert(sizeof(Flags) == sizeof(uint8_t), "Structure packing is broken"); // Offsets (in bytes) from 'this' to each component array. The delta between // each offset and the next offset is the size of each array and is defined // even if an array is empty. Offset flagOffset() const { return offsetOfCode() - sizeof(Flags); } Offset codeOffset() const { return offsetOfCode(); } Offset noteOffset() const { return offsetOfCode() + codeLength_; } Offset optionalOffsetsOffset() const { // Determine the location to beginning of optional-offsets array by looking // at index for try-notes. // // optionalOffsetsOffset(): // (OPTIONAL) tryNotesEndOffset // (OPTIONAL) scopeNotesEndOffset // (OPTIONAL) resumeOffsetsEndOffset // optArrayOffset_: // .... unsigned numOffsets = flags().tryNotesEndIndex; MOZ_ASSERT(numOffsets >= flags().scopeNotesEndIndex); MOZ_ASSERT(numOffsets >= flags().resumeOffsetsEndIndex); return optArrayOffset_ - (numOffsets * sizeof(Offset)); } Offset resumeOffsetsOffset() const { return optArrayOffset_; } Offset scopeNotesOffset() const { return getOptionalOffset(flags().resumeOffsetsEndIndex); } Offset tryNotesOffset() const { return getOptionalOffset(flags().scopeNotesEndIndex); } Offset endOffset() const { return getOptionalOffset(flags().tryNotesEndIndex); } void initOptionalArrays(Offset* cursor, uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes); // Initialize to GC-safe state ImmutableScriptData(uint32_t codeLength, uint32_t noteLength, uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes); void setOptionalOffset(int index, Offset offset) { MOZ_ASSERT(index > 0); MOZ_ASSERT(offset != optArrayOffset_, "Do not store implicit offset"); offsetToPointer(optArrayOffset_)[-index] = offset; } Offset getOptionalOffset(int index) const { // The index 0 represents (implicitly) the offset 'optArrayOffset_'. if (index == 0) { return optArrayOffset_; } ImmutableScriptData* this_ = const_cast(this); return this_->offsetToPointer(optArrayOffset_)[-index]; } public: static js::UniquePtr new_( FrontendContext* fc, uint32_t mainOffset, uint32_t nfixed, uint32_t nslots, GCThingIndex bodyScopeIndex, uint32_t numICEntries, bool isFunction, uint16_t funLength, uint16_t propertyCountEstimate, mozilla::Span code, mozilla::Span notes, mozilla::Span resumeOffsets, mozilla::Span scopeNotes, mozilla::Span tryNotes); static js::UniquePtr new_( FrontendContext* fc, uint32_t codeLength, uint32_t noteLength, uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes); static js::UniquePtr new_(FrontendContext* fc, uint32_t totalSize); // Validate internal offsets of the data structure seems reasonable. This is // for diagnositic purposes only to detect severe corruption. This is not a // security boundary! bool validateLayout(uint32_t expectedSize); private: static mozilla::CheckedInt sizeFor(uint32_t codeLength, uint32_t noteLength, uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes); public: // The code() and note() arrays together maintain an target alignment by // padding the source notes with null. This allows arrays with stricter // alignment requirements to follow them. static constexpr size_t CodeNoteAlign = sizeof(uint32_t); // Compute number of null notes to pad out source notes with. static uint32_t ComputeNotePadding(uint32_t codeLength, uint32_t noteLength) { uint32_t flagLength = sizeof(Flags); uint32_t nullLength = CodeNoteAlign - (flagLength + codeLength + noteLength) % CodeNoteAlign; // The source notes must have at least one null-terminator. MOZ_ASSERT(nullLength >= 1); return nullLength; } // Span over all raw bytes in this struct and its trailing arrays. mozilla::Span immutableData() const { size_t allocSize = endOffset(); return mozilla::Span{reinterpret_cast(this), allocSize}; } private: Flags& flagsRef() { return *offsetToPointer(flagOffset()); } const Flags& flags() const { return const_cast(this)->flagsRef(); } public: uint32_t codeLength() const { return codeLength_; } jsbytecode* code() { return offsetToPointer(codeOffset()); } mozilla::Span codeSpan() { return {code(), codeLength()}; } uint32_t noteLength() const { return numElements(noteOffset(), optionalOffsetsOffset()); } SrcNote* notes() { return offsetToPointer(noteOffset()); } mozilla::Span notesSpan() { return {notes(), noteLength()}; } mozilla::Span resumeOffsets() { return mozilla::Span{offsetToPointer(resumeOffsetsOffset()), offsetToPointer(scopeNotesOffset())}; } mozilla::Span scopeNotes() { return mozilla::Span{offsetToPointer(scopeNotesOffset()), offsetToPointer(tryNotesOffset())}; } mozilla::Span tryNotes() { return mozilla::Span{offsetToPointer(tryNotesOffset()), offsetToPointer(endOffset())}; } // Expose offsets to the JITs. static constexpr size_t offsetOfCode() { return sizeof(ImmutableScriptData) + sizeof(Flags); } static constexpr size_t offsetOfResumeOffsetsOffset() { // Resume-offsets are the first optional array if they exist. Locate the // array with the 'optArrayOffset_' field. static_assert(sizeof(Offset) == sizeof(uint32_t), "JIT expect Offset to be uint32_t"); return offsetof(ImmutableScriptData, optArrayOffset_); } static constexpr size_t offsetOfNfixed() { return offsetof(ImmutableScriptData, nfixed); } static constexpr size_t offsetOfNslots() { return offsetof(ImmutableScriptData, nslots); } static constexpr size_t offsetOfFunLength() { return offsetof(ImmutableScriptData, funLength); } // ImmutableScriptData has trailing data so isn't copyable or movable. ImmutableScriptData(const ImmutableScriptData&) = delete; ImmutableScriptData& operator=(const ImmutableScriptData&) = delete; }; // Wrapper type for ImmutableScriptData to allow sharing across a JSRuntime. // // Note: This is distinct from ImmutableScriptData because it contains a mutable // ref-count while the ImmutableScriptData may live in read-only memory. // // Note: This is *not* directly inlined into the SharedImmutableScriptDataTable // because scripts point directly to object and table resizing moves // entries. This allows for fast finalization by decrementing the // ref-count directly without doing a hash-table lookup. class SharedImmutableScriptData { static constexpr uint32_t IsExternalFlag = 0x80000000; static constexpr uint32_t RefCountBits = 0x7FFFFFFF; // This class is reference counted as follows: each pointer from a JSScript // counts as one reference plus there may be one reference from the shared // script data table. mozilla::Atomic refCountAndExternalFlags_ = {}; mozilla::HashNumber hash_; ImmutableScriptData* isd_ = nullptr; // End of fields. friend class ::JSScript; friend class js::frontend::StencilXDR; public: SharedImmutableScriptData() = default; ~SharedImmutableScriptData() { reset(); } private: bool isExternal() const { return refCountAndExternalFlags_ & IsExternalFlag; } void setIsExternal() { refCountAndExternalFlags_ |= IsExternalFlag; } void unsetIsExternal() { refCountAndExternalFlags_ &= RefCountBits; } void reset() { if (isd_ && !isExternal()) { js_delete(isd_); } isd_ = nullptr; } mozilla::HashNumber calculateHash() const { mozilla::Span immutableData = isd_->immutableData(); return mozilla::HashBytes(immutableData.data(), immutableData.size()); } public: // Hash over the contents of SharedImmutableScriptData and its // ImmutableScriptData. struct Hasher; uint32_t refCount() const { return refCountAndExternalFlags_ & RefCountBits; } void AddRef() { refCountAndExternalFlags_++; } private: uint32_t decrementRef() { MOZ_ASSERT(refCount() != 0); return --refCountAndExternalFlags_ & RefCountBits; } public: void Release() { uint32_t remain = decrementRef(); if (remain == 0) { reset(); js_free(this); } } static constexpr size_t offsetOfISD() { return offsetof(SharedImmutableScriptData, isd_); } private: static SharedImmutableScriptData* create(FrontendContext* fc); public: static SharedImmutableScriptData* createWith( FrontendContext* fc, js::UniquePtr&& isd); size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { size_t isdSize = isExternal() ? 0 : mallocSizeOf(isd_); return mallocSizeOf(this) + isdSize; } // SharedImmutableScriptData has trailing data so isn't copyable or movable. SharedImmutableScriptData(const SharedImmutableScriptData&) = delete; SharedImmutableScriptData& operator=(const SharedImmutableScriptData&) = delete; static bool shareScriptData(FrontendContext* fc, RefPtr& sisd); size_t immutableDataLength() const { return isd_->immutableData().Length(); } uint32_t nfixed() const { return isd_->nfixed; } ImmutableScriptData* get() { return isd_; } mozilla::HashNumber hash() const { return hash_; } void setOwn(js::UniquePtr&& isd) { MOZ_ASSERT(!isd_); isd_ = isd.release(); unsetIsExternal(); hash_ = calculateHash(); } void setOwn(js::UniquePtr&& isd, mozilla::HashNumber hash) { MOZ_ASSERT(!isd_); isd_ = isd.release(); unsetIsExternal(); MOZ_ASSERT(hash == calculateHash()); hash_ = hash; } void setExternal(ImmutableScriptData* isd) { MOZ_ASSERT(!isd_); isd_ = isd; setIsExternal(); hash_ = calculateHash(); } void setExternal(ImmutableScriptData* isd, mozilla::HashNumber hash) { MOZ_ASSERT(!isd_); isd_ = isd; setIsExternal(); MOZ_ASSERT(hash == calculateHash()); hash_ = hash; } }; // Matches SharedImmutableScriptData objects that have the same atoms as well as // contain the same bytes in their ImmutableScriptData. struct SharedImmutableScriptData::Hasher { using Lookup = RefPtr; static mozilla::HashNumber hash(const Lookup& l) { return l->hash(); } static bool match(SharedImmutableScriptData* entry, const Lookup& lookup) { return (entry->isd_->immutableData() == lookup->isd_->immutableData()); } }; using SharedImmutableScriptDataTable = mozilla::HashSet; struct MemberInitializers { static constexpr size_t NumBits = 31; static constexpr uint32_t MaxInitializers = BitMask(NumBits); #ifdef DEBUG bool valid = false; #endif bool hasPrivateBrand : 1; // This struct will eventually have a vector of constant values for optimizing // field initializers. uint32_t numMemberInitializers : NumBits; MemberInitializers(bool hasPrivateBrand, uint32_t numMemberInitializers) : #ifdef DEBUG valid(true), #endif hasPrivateBrand(hasPrivateBrand), numMemberInitializers(numMemberInitializers) { MOZ_ASSERT( this->numMemberInitializers == numMemberInitializers, "numMemberInitializers should easily fit in the 31-bit bitfield"); } static MemberInitializers Invalid() { return MemberInitializers(); } // Singleton to use for class constructors that do not have to initialize any // fields. This is used when we elide the trivial data but still need a valid // set to stop scope walking. static const MemberInitializers& Empty() { static const MemberInitializers zeroInitializers(false, 0); return zeroInitializers; } uint32_t serialize() const { return (hasPrivateBrand << NumBits) | numMemberInitializers; } static MemberInitializers deserialize(uint32_t bits) { return MemberInitializers((bits & Bit(NumBits)) != 0, bits & BitMask(NumBits)); } private: MemberInitializers() : #ifdef DEBUG valid(false), #endif hasPrivateBrand(false), numMemberInitializers(0) { } }; // See JSOp::Lambda for interepretation of this index. using FunctionDeclaration = GCThingIndex; // Defined here to avoid #include cycle with Stencil.h. using FunctionDeclarationVector = Vector; } // namespace js #endif /* vm_SharedStencil_h */