// LinkData contains all the metadata necessary to patch all the locations // that depend on the absolute address of a CodeSegment. This happens in a // "linking" step after compilation and after the module's code is serialized. // The LinkData is serialized along with the Module but does not (normally, see // Module::debugLinkData_ comment) persist after (de)serialization, which // distinguishes it from Metadata, which is stored in the Code object.
// A source of machine code for creating an executable code segment. class CodeSource { // The macro assembler to use as the source. If this is set then there is // no `bytes_` pointer.
jit::MacroAssembler* masm_ = nullptr; // A raw pointer to the unlinked machine code bytes. If this is set then // there is no `masm_` pointer. const uint8_t* bytes_ = nullptr;
// The length in bytes for either case. This is always valid and set to // masm.bytesNeeded() if masm_ is present.
uint32_t length_ = 0;
// The link data to use, if any. This is always present if we are linking // from raw bytes. Otherwise it may or may not be present when we are linking // masm. If it is not present for masm we will fall back to basic linking of // code labels and debug symbolic accesses. const LinkData* linkData_;
// The code object to use, if any, for linking. This is optionally present // in either case. This will not be present if we are doing basic linking // without a link data. const Code* code_;
public: // Get the machine code from a macro assembler, optional link data, and // optional code object.
CodeSource(jit::MacroAssembler& masm, const LinkData* linkData, const Code* code);
// Get the machine code from a raw bytes range, link data, and optional code // object.
CodeSource(const uint8_t* bytes, uint32_t length, const LinkData& linkData, const Code* code);
// The length of machine code in bytes.
uint32_t lengthBytes() const { return length_; }
// Copy and link the machine code into `codeStart`. bool copyAndLink(jit::AutoMarkJitCodeWritableForThread& writable,
uint8_t* codeStart) const;
};
// CodeSegment is a fixed-size chunk of executable memory that we can // bump-allocate smaller allocations from. class CodeSegment : public ShareableBase<CodeSegment> { private: const UniqueCodeBytes bytes_;
uint32_t lengthBytes_; const uint32_t capacityBytes_; const Code* code_;
// Create a new, empty code segment with a given capacity. The capacity must // have granularity of ExecutableCodePageSize (64KB). static RefPtr<CodeSegment> create(
mozilla::Maybe<jit::AutoMarkJitCodeWritableForThread>& writable,
size_t capacityBytes, bool allowLastDitchGC = true);
// Returns the alignment that all allocations within a code segment must be. // // If we are write-protecting code, then we must start every new allocation // on a new system page, otherwise we can re-use system pages for new // allocations. static size_t AllocationAlignment(); // Align `bytes` to be at least the allocation alignment. See above. static size_t AlignAllocationBytes(uintptr_t bytes); // Returns whether `bytes` is aligned to the allocation alignment. staticbool IsAligned(uintptr_t bytes);
// Checks if this code segment has enough room for an allocation of bytes. // The bytes must be aligned to allocation alignment. bool hasSpace(size_t bytes) const;
// Claims space in this code segment for an allocation of bytes. The bytes // must be aligned to allocation alignment. void claimSpace(size_t bytes, uint8_t** claimedBase);
// Copies, links, and makes the machine code executable from the given code // source. Returns the code segment the code was allocated into. An optional // pool of code segments may be provided to allocate from. // // There are two important ranges created, an 'allocation' range and a 'code // range'. // // The allocation range is a superset of the code range. The allocation start // offset will be aligned to `AllocationAlignment` which is either the system // page size or just executable code alignment. // // The code range will be within the allocation range and may have some // padding inserted before the start of the allocation. The code start offset // will always be aligned to the executable code alignment. // // Random padding is added before the code range when we are aligning to the // system page size, the start addressess of all the code memories will not // conflict in associative icaches. // // Here's a picture that illustrates the resulting structure of allocations: // // This is an example for a machine with a 4KB page size, for a codeLength // which requires more than one page but less than two, in a segment where // the first page is already allocated. // // Note: if !JitOptions.writeProtectCode, then allocationStart and // allocationLength will be a multiple of jit::CodeAlignment, not the // system page size. // // segment->base() (aligned at 4K = hardware page size) // : // : +4k +8k +12k // : : : : // +-----------------------+ +---------------------------------+ : // | IN USE | | CODE CODE | : // +-----------------------+----------+---------------------------------+---+ // . : : : // : : : allocationLength : // : :<------------------------------------------>: // . : : : // : : padding : codeLength : // :<--------------------->:<-------->:<------------------------------->: // : : : // : : : // :<-------------------------------->: // : : // : codeStart // : // allocationStart static RefPtr<CodeSegment> allocate( const CodeSource& codeSource,
Vector<RefPtr<CodeSegment>, 0, SystemAllocPolicy>* segmentPool, bool allowLastDitchGC, uint8_t** codeStartOut,
uint32_t* allocationLengthOut);
// LazyFuncExport helps to efficiently lookup a CodeRange from a given function // index. It is inserted in a vector sorted by function index, to perform // binary search on it later.
struct LazyFuncExport {
size_t funcIndex;
size_t lazyStubBlockIndex;
size_t funcCodeRangeIndex; // Used to make sure we only upgrade a lazy stub from baseline to ion.
mozilla::DebugOnly<CodeBlockKind> funcKind;
using LazyFuncExportVector = Vector<LazyFuncExport, 0, SystemAllocPolicy>;
// A FuncExport represents a single function definition inside a wasm Module // that has been exported one or more times. A FuncExport represents an // internal entry point that can be called via function definition index by // Instance::callExport(). To allow O(log(n)) lookup of a FuncExport by // function definition index, the FuncExportVector is stored sorted by // function definition index.
using FuncExportVector = Vector<FuncExport, 0, SystemAllocPolicy>;
// A FuncImport contains the runtime metadata needed to implement a call to an // imported function. Each function import has two call stubs: an optimized path // into JIT code and a slow path into the generic C++ js::Invoke and these // offsets of these stubs are stored so that function-import callsites can be // dynamically patched at runtime.
// CodeBlock contains all the data related to a given compilation tier. It is // built during module generation and then immutably stored in a Code. // // Code contains a map from PC to containing code block. The map is thread-safe // to support lookups from multiple threads (see ThreadSafeCodeBlockMap). This // is safe because code blocks are immutable after creation, so there won't // be any concurrent modification during a metadata lookup.
class CodeBlock { public: // Weak reference to the code that owns us, not serialized. const Code* code; // The index we are held inside our containing Code::data::blocks_ vector.
size_t codeBlockIndex;
// The following information is all serialized // Which kind of code is being stored in this block. Most consumers don't // care about this. const CodeBlockKind kind;
// The code segment our JIT code is within.
SharedCodeSegment segment;
// Pointer to the beginning of the CodeBlock.
uint8_t* codeBase;
size_t codeLength;
// Metadata about the code we have contributed to the segment. // // All offsets are relative to `codeBase` not the segment base.
FuncToCodeRangeMap funcToCodeRange;
CodeRangeVector codeRanges;
InliningContext inliningContext;
CallSites callSites;
TrapSites trapSites;
FuncExportVector funcExports;
StackMaps stackMaps;
TryNoteVector tryNotes;
CodeRangeUnwindInfoVector codeRangeUnwindInfos;
// Track whether we are registered in the process map of code blocks. bool unregisterOnDestroy_;
bool initialized() const { if (code) { // Initialize should have given us an index too.
MOZ_ASSERT(codeBlockIndex != (size_t)-1); returntrue;
} returnfalse;
}
// Gets the tier for this code block. Only valid for non-lazy stub code.
Tier tier() const { switch (kind) { case CodeBlockKind::BaselineTier: return Tier::Baseline; case CodeBlockKind::OptimizedTier: return Tier::Optimized; default:
MOZ_CRASH();
}
}
// Returns whether this code block should be considered for serialization. bool isSerializable() const { return kind == CodeBlockKind::SharedStubs ||
kind == CodeBlockKind::OptimizedTier;
}
// Because of profiling, the thread running wasm might need to know to which // CodeBlock the current PC belongs, during a call to lookup(). A lookup // is a read-only operation, and we don't want to take a lock then // (otherwise, we could have a deadlock situation if an async lookup // happened on a given thread that was holding mutatorsMutex_ while getting // sampled). Since the writer could be modifying the data that is getting // looked up, the writer functions use spin-locks to know if there are any // observers (i.e. calls to lookup()) of the atomic data.
class ThreadSafeCodeBlockMap { // Since writes (insertions or removals) can happen on any background // thread at the same time, we need a lock here.
void swapAndWait() { // Both vectors are consistent for lookup at this point although their // contents are different: there is no way for the looked up PC to be // in the code segment that is getting registered, because the code // segment is not even fully created yet.
// If a lookup happens before this instruction, then the // soon-to-become-former read-only pointer is used during the lookup, // which is valid.
// If a lookup happens after this instruction, then the updated vector // is used, which is valid: // - in case of insertion, it means the new vector contains more data, // but it's fine since the code segment is getting registered and thus // isn't even fully created yet, so the code can't be running. // - in case of removal, it means the new vector contains one less // entry, but it's fine since unregistering means the code segment // isn't used by any live instance anymore, thus PC can't be in the // to-be-removed code segment's range.
// A lookup could have happened on any of the two vectors. Wait for // observers to be done using any vector before mutating.
// Although we could simply revert the insertion in the read-only // vector, it is simpler to just crash and given that each CodeBlock // consumes multiple pages, it is unlikely this insert() would OOM in // practice
AutoEnterOOMUnsafeRegion oom; if (!mutableCodeBlocks_->insert(mutableCodeBlocks_->begin() + index, cs)) {
oom.crash("when inserting a CodeBlock in the process-wide map");
}
size_t index; if (!BinarySearchIf(*readonly, 0, readonly->length(), CodeBlockPC(pc),
&index)) { if (codeRange) {
*codeRange = nullptr;
} return nullptr;
}
// It is fine returning a raw CodeBlock*, because we assume we are // looking up a live PC in code which is on the stack, keeping the // CodeBlock alive.
const CodeBlock* result = (*readonly)[index]; if (codeRange) {
*codeRange = result->lookupRange(pc);
} return result;
}
};
// Jump tables that implement function tiering and fast js-to-wasm calls. // // There is one JumpTable object per Code object, holding two jump tables: the // tiering jump table and the jit-entry jump table. The JumpTable is not // serialized with its Code, but is a run-time entity only. At run-time it is // shared across threads with its owning Code (and the Module that owns the // Code). Values in the JumpTable /must/ /always/ be JSContext-agnostic and // Instance-agnostic, because of this sharing. // // Both jump tables have a number of entries equal to the number of functions in // their Module, including imports. In the tiering table, the elements // corresponding to the Module's imported functions are unused; in the jit-entry // table, the elements corresponding to the Module's non-exported functions are // unused. (Functions can be exported explicitly via the exports section or // implicitly via a mention of their indices outside function bodies.) See // comments at JumpTables::init() and WasmInstanceObject::getExportedFunction(). // The entries are void*. Unused entries are null. // // The tiering jump table. // // This table holds code pointers that are used by baseline functions to enter // optimized code. See the large comment block in WasmCompile.cpp for // information about how tiering works. // // The jit-entry jump table. // // The jit-entry jump table entry for a function holds a stub that allows Jitted // JS code to call wasm using the JS JIT ABI. See large comment block at // WasmInstanceObject::getExportedFunction() for more about exported functions // and stubs and the lifecycle of the entries in the jit-entry table - there are // complex invariants.
class JumpTables { using TablePointer = mozilla::UniquePtr<void*[], JS::FreePolicy>;
void setJitEntry(size_t i, void* target) const { // Make sure that write is atomic; see comment in wasm::Module::finishTier2 // to that effect.
MOZ_ASSERT(i < numFuncs_);
__atomic_store_n(&jit_.get()[i], target, __ATOMIC_RELAXED);
} void setJitEntryIfNull(size_t i, void* target) const { // Make sure that compare-and-write is atomic; see comment in // wasm::Module::finishTier2 to that effect.
MOZ_ASSERT(i < numFuncs_); void* expected = nullptr;
(void)__atomic_compare_exchange_n(&jit_.get()[i], &expected, target, /*weak=*/false, /*success_memorder=*/__ATOMIC_RELAXED, /*failure_memorder=*/__ATOMIC_RELAXED);
} void** getAddressOfJitEntry(size_t i) const {
MOZ_ASSERT(i < numFuncs_);
MOZ_ASSERT(jit_.get()[i]); return &jit_.get()[i];
}
uint32_t funcIndexFromJitEntry(void** target) const {
MOZ_ASSERT(target >= &jit_.get()[0]);
MOZ_ASSERT(target <= &(jit_.get()[numFuncs_ - 1]));
size_t index = (intptr_t*)target - (intptr_t*)&jit_.get()[0];
MOZ_ASSERT(index < wasm::MaxFuncs); return (uint32_t)index;
}
void setTieringEntry(size_t i, void* target) const {
MOZ_ASSERT(i < numFuncs_); // See comment in wasm::Module::finishTier2. if (mode_ != CompileMode::Once) {
tiering_.get()[i] = target;
}
} void** tiering() const { return tiering_.get(); }
size_t sizeOfMiscExcludingThis() const { // 2 words per function for the jit entry table, plus maybe 1 per // function if we're tiering. returnsizeof(void*) * (2 + (tiering_ ? 1 : 0)) * numFuncs_;
}
};
// Code objects own executable code and the metadata that describe it. A single // Code object is normally shared between a module and all its instances. // // profilingLabels_ is lazily initialized, but behind a lock.
using SharedCode = RefPtr<const Code>; using MutableCode = RefPtr<Code>; using MetadataAnalysisHashMap =
HashMap<constchar*, uint32_t, mozilla::CStringHasher, SystemAllocPolicy>;
class Code : public ShareableBase<Code> { struct ProtectedData { // A vector of all of the code blocks owned by this code. Each code block // is immutable once added to the vector, but this vector may grow.
UniqueConstCodeBlockVector blocks; // A vector of link data paired 1:1 with `blocks`. Entries may be null if // the code block is not serializable. This is separate from CodeBlock so // that we may clear it out after serialization has happened.
UniqueLinkDataVector blocksLinkData;
// A vector of code segments that we can allocate lazy segments into
SharedCodeSegmentVector lazyStubSegments; // A sorted vector of LazyFuncExport
LazyFuncExportVector lazyExports;
// A vector of code segments that we can lazily allocate functions into
SharedCodeSegmentVector lazyFuncSegments;
// Statistics for tiers of code.
CompileAndLinkStats tier1Stats;
CompileAndLinkStats tier2Stats;
}; using ReadGuard = RWExclusiveData<ProtectedData>::ReadGuard; using WriteGuard = RWExclusiveData<ProtectedData>::WriteGuard;
// The compile mode this code is used with. const CompileMode mode_;
// Core data that is not thread-safe and must acquire a lock in order to // access.
RWExclusiveData<ProtectedData> data_;
// Thread-safe mutable map from code pointer to code block that contains it. mutable ThreadSafeCodeBlockMap blockMap_;
// Metadata for this module that is needed for the lifetime of Code. This is // always non-null.
SharedCodeMetadata codeMeta_; // Metadata for this module that is needed for the lifetime of Code, and is // only available after the whole module has been decoded. This is always // non-null.
SharedCodeTailMetadata codeTailMeta_; // This is null for a wasm module, non-null for asm.js
SharedCodeMetadataForAsmJS codeMetaForAsmJS_;
// [SMDOC] Tier-2 data // // hasCompleteTier2_ and completeTier2_ implement a three-state protocol for // broadcasting tier-2 data; this also amounts to a single-writer/ // multiple-reader setup. // // Initially hasCompleteTier2_ is false and completeTier2_ is null. // // While hasCompleteTier2_ is false, *no* thread may read completeTier2_, but // one thread may make completeTier2_ non-null (this will be the tier-2 // compiler thread). That same thread must then later set hasCompleteTier2_ // to true to broadcast the completeTier2_ value and its availability. Note // that the writing thread may not itself read completeTier2_ before setting // hasCompleteTier2_, in order to simplify reasoning about global invariants. // // Once hasCompleteTier2_ is true, *no* thread may write completeTier2_ and // *no* thread may read completeTier2_ without having observed // hasCompleteTier2_ as true first. Once hasCompleteTier2_ is true, it stays // true. mutableconst CodeBlock* completeTier2_; mutable mozilla::Atomic<bool> hasCompleteTier2_;
// State for every defined function (not imported) in this module. This is // only needed if we're doing partial tiering. mutable FuncStatesPointer funcStates_;
// Returns a pointer to the raw interpreter entry of a given function for // which stubs have been lazily generated.
[[nodiscard]] void* lookupLazyInterpEntry(const WriteGuard& guard,
uint32_t funcIndex) const;
[[nodiscard]] bool createOneLazyEntryStub(const WriteGuard& guard,
uint32_t funcExportIndex, const CodeBlock& tierCodeBlock, void** interpEntry) const;
[[nodiscard]] bool createManyLazyEntryStubs( const WriteGuard& guard, const Uint32Vector& funcExportIndices, const CodeBlock& tierCodeBlock, size_t* stubBlockIndex) const; // Create one lazy stub for all the functions in funcExportIndices, putting // them in a single stub. Jit entries won't be used until // setJitEntries() is actually called, after the Code owner has committed // tier2.
[[nodiscard]] bool createTier2LazyEntryStubs( const WriteGuard& guard, const CodeBlock& tier2Code,
mozilla::Maybe<size_t>* outStubBlockIndex) const;
[[nodiscard]] bool appendProfilingLabels( const ExclusiveData<CacheableCharsVector>::Guard& labels, const CodeBlock& codeBlock) const;
// Atomically claim the right to tier up `funcIndex`. // Returns true if the claim was acquired, false if a tier-up was already // requested. bool tryClaimTierUp(uint32_t funcIndex) const {
MOZ_ASSERT(mode_ == CompileMode::LazyTiering);
FuncState& state = funcStates_[funcIndex - codeMeta_->numFuncImports]; return state.tierUpState.compareExchange(TierUpState::NotRequested,
TierUpState::Requested);
}
bool hasCompleteTier(Tier tier) const; // The 'stable' complete tier of code. This is stable during a run/
Tier stableCompleteTier() const; // The 'best' complete tier of code. This may transition from baseline to ion // at any time.
Tier bestCompleteTier() const; bool hasSerializableCode() const { return hasCompleteTier(Tier::Serialized); }
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.