summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmCode.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/wasm/WasmCode.h
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/wasm/WasmCode.h')
-rw-r--r--js/src/wasm/WasmCode.h874
1 files changed, 874 insertions, 0 deletions
diff --git a/js/src/wasm/WasmCode.h b/js/src/wasm/WasmCode.h
new file mode 100644
index 0000000000..518495199c
--- /dev/null
+++ b/js/src/wasm/WasmCode.h
@@ -0,0 +1,874 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * Copyright 2016 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_code_h
+#define wasm_code_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <utility>
+
+#include "jstypes.h"
+
+#include "gc/Memory.h"
+#include "js/AllocPolicy.h"
+#include "js/UniquePtr.h"
+#include "js/Utility.h"
+#include "js/Vector.h"
+#include "threading/ExclusiveData.h"
+#include "util/Memory.h"
+#include "vm/MutexIDs.h"
+#include "wasm/WasmBuiltins.h"
+#include "wasm/WasmCodegenConstants.h"
+#include "wasm/WasmCodegenTypes.h"
+#include "wasm/WasmCompileArgs.h"
+#include "wasm/WasmConstants.h"
+#include "wasm/WasmExprType.h"
+#include "wasm/WasmGC.h"
+#include "wasm/WasmLog.h"
+#include "wasm/WasmModuleTypes.h"
+#include "wasm/WasmSerialize.h"
+#include "wasm/WasmShareable.h"
+#include "wasm/WasmTypeDecls.h"
+#include "wasm/WasmTypeDef.h"
+#include "wasm/WasmValType.h"
+
+struct JS_PUBLIC_API JSContext;
+class JSFunction;
+
+namespace js {
+
+struct AsmJSMetadata;
+class ScriptSource;
+
+namespace jit {
+class MacroAssembler;
+};
+
+namespace wasm {
+
+struct MetadataTier;
+struct Metadata;
+
+// LinkData contains all the metadata necessary to patch all the locations
+// that depend on the absolute address of a ModuleSegment. 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.
+
+struct LinkDataCacheablePod {
+ uint32_t trapOffset = 0;
+
+ WASM_CHECK_CACHEABLE_POD(trapOffset);
+
+ LinkDataCacheablePod() = default;
+};
+
+WASM_DECLARE_CACHEABLE_POD(LinkDataCacheablePod);
+
+WASM_CHECK_CACHEABLE_POD_PADDING(LinkDataCacheablePod)
+
+struct LinkData : LinkDataCacheablePod {
+ explicit LinkData(Tier tier) : tier(tier) {}
+
+ LinkDataCacheablePod& pod() { return *this; }
+ const LinkDataCacheablePod& pod() const { return *this; }
+
+ struct InternalLink {
+ uint32_t patchAtOffset;
+ uint32_t targetOffset;
+#ifdef JS_CODELABEL_LINKMODE
+ uint32_t mode;
+#endif
+
+ WASM_CHECK_CACHEABLE_POD(patchAtOffset, targetOffset);
+#ifdef JS_CODELABEL_LINKMODE
+ WASM_CHECK_CACHEABLE_POD(mode)
+#endif
+ };
+ using InternalLinkVector = Vector<InternalLink, 0, SystemAllocPolicy>;
+
+ struct SymbolicLinkArray
+ : EnumeratedArray<SymbolicAddress, SymbolicAddress::Limit, Uint32Vector> {
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ };
+
+ const Tier tier;
+ InternalLinkVector internalLinks;
+ SymbolicLinkArray symbolicLinks;
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+};
+
+WASM_DECLARE_CACHEABLE_POD(LinkData::InternalLink);
+
+using UniqueLinkData = UniquePtr<LinkData>;
+
+// Executable code must be deallocated specially.
+
+struct FreeCode {
+ uint32_t codeLength;
+ FreeCode() : codeLength(0) {}
+ explicit FreeCode(uint32_t codeLength) : codeLength(codeLength) {}
+ void operator()(uint8_t* codeBytes);
+};
+
+using UniqueCodeBytes = UniquePtr<uint8_t, FreeCode>;
+
+class Code;
+class CodeTier;
+class ModuleSegment;
+class LazyStubSegment;
+
+// CodeSegment contains common helpers for determining the base and length of a
+// code segment and if a pc belongs to this segment. It is inherited by:
+// - ModuleSegment, i.e. the code segment of a Module, generated
+// eagerly when a Module is instanciated.
+// - LazyStubSegment, i.e. the code segment of entry stubs that are lazily
+// generated.
+
+class CodeSegment {
+ protected:
+ enum class Kind { LazyStubs, Module };
+
+ CodeSegment(UniqueCodeBytes bytes, uint32_t length, Kind kind)
+ : bytes_(std::move(bytes)),
+ length_(length),
+ kind_(kind),
+ codeTier_(nullptr),
+ unregisterOnDestroy_(false) {}
+
+ bool initialize(const CodeTier& codeTier);
+
+ private:
+ const UniqueCodeBytes bytes_;
+ const uint32_t length_;
+ const Kind kind_;
+ const CodeTier* codeTier_;
+ bool unregisterOnDestroy_;
+
+ public:
+ bool initialized() const { return !!codeTier_; }
+ ~CodeSegment();
+
+ bool isLazyStubs() const { return kind_ == Kind::LazyStubs; }
+ bool isModule() const { return kind_ == Kind::Module; }
+ const ModuleSegment* asModule() const {
+ MOZ_ASSERT(isModule());
+ return (ModuleSegment*)this;
+ }
+ const LazyStubSegment* asLazyStub() const {
+ MOZ_ASSERT(isLazyStubs());
+ return (LazyStubSegment*)this;
+ }
+
+ uint8_t* base() const { return bytes_.get(); }
+ uint32_t length() const {
+ MOZ_ASSERT(length_ != UINT32_MAX);
+ return length_;
+ }
+
+ bool containsCodePC(const void* pc) const {
+ return pc >= base() && pc < (base() + length_);
+ }
+
+ const CodeTier& codeTier() const {
+ MOZ_ASSERT(initialized());
+ return *codeTier_;
+ }
+ const Code& code() const;
+
+ void addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code) const;
+};
+
+// A wasm ModuleSegment owns the allocated executable code for a wasm module.
+
+using UniqueModuleSegment = UniquePtr<ModuleSegment>;
+
+class ModuleSegment : public CodeSegment {
+ const Tier tier_;
+ uint8_t* const trapCode_;
+
+ public:
+ ModuleSegment(Tier tier, UniqueCodeBytes codeBytes, uint32_t codeLength,
+ const LinkData& linkData);
+
+ static UniqueModuleSegment create(Tier tier, jit::MacroAssembler& masm,
+ const LinkData& linkData);
+ static UniqueModuleSegment create(Tier tier, const Bytes& unlinkedBytes,
+ const LinkData& linkData);
+
+ bool initialize(const CodeTier& codeTier, const LinkData& linkData,
+ const Metadata& metadata, const MetadataTier& metadataTier);
+
+ Tier tier() const { return tier_; }
+
+ // Pointers to stubs to which PC is redirected from the signal-handler.
+
+ uint8_t* trapCode() const { return trapCode_; }
+
+ const CodeRange* lookupRange(const void* pc) const;
+
+ void addSizeOfMisc(mozilla::MallocSizeOf mallocSizeOf, size_t* code,
+ size_t* data) const;
+
+ WASM_DECLARE_FRIEND_SERIALIZE(ModuleSegment);
+};
+
+extern UniqueCodeBytes AllocateCodeBytes(uint32_t codeLength);
+extern bool StaticallyLink(const ModuleSegment& ms, const LinkData& linkData);
+extern void StaticallyUnlink(uint8_t* base, const LinkData& linkData);
+
+// 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.
+
+class FuncExport {
+ uint32_t typeIndex_;
+ uint32_t funcIndex_;
+ uint32_t eagerInterpEntryOffset_; // Machine code offset
+ bool hasEagerStubs_;
+
+ WASM_CHECK_CACHEABLE_POD(typeIndex_, funcIndex_, eagerInterpEntryOffset_,
+ hasEagerStubs_);
+
+ public:
+ FuncExport() = default;
+ explicit FuncExport(uint32_t typeIndex, uint32_t funcIndex,
+ bool hasEagerStubs) {
+ typeIndex_ = typeIndex;
+ funcIndex_ = funcIndex;
+ eagerInterpEntryOffset_ = UINT32_MAX;
+ hasEagerStubs_ = hasEagerStubs;
+ }
+ void initEagerInterpEntryOffset(uint32_t entryOffset) {
+ MOZ_ASSERT(eagerInterpEntryOffset_ == UINT32_MAX);
+ MOZ_ASSERT(hasEagerStubs());
+ eagerInterpEntryOffset_ = entryOffset;
+ }
+
+ bool hasEagerStubs() const { return hasEagerStubs_; }
+ uint32_t typeIndex() const { return typeIndex_; }
+ uint32_t funcIndex() const { return funcIndex_; }
+ uint32_t eagerInterpEntryOffset() const {
+ MOZ_ASSERT(eagerInterpEntryOffset_ != UINT32_MAX);
+ MOZ_ASSERT(hasEagerStubs());
+ return eagerInterpEntryOffset_;
+ }
+};
+
+WASM_DECLARE_CACHEABLE_POD(FuncExport);
+
+using FuncExportVector = Vector<FuncExport, 0, SystemAllocPolicy>;
+
+// An 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.
+
+class FuncImport {
+ private:
+ uint32_t typeIndex_;
+ uint32_t instanceOffset_;
+ uint32_t interpExitCodeOffset_; // Machine code offset
+ uint32_t jitExitCodeOffset_; // Machine code offset
+
+ WASM_CHECK_CACHEABLE_POD(typeIndex_, instanceOffset_, interpExitCodeOffset_,
+ jitExitCodeOffset_);
+
+ public:
+ FuncImport()
+ : typeIndex_(0),
+ instanceOffset_(0),
+ interpExitCodeOffset_(0),
+ jitExitCodeOffset_(0) {}
+
+ FuncImport(uint32_t typeIndex, uint32_t instanceOffset) {
+ typeIndex_ = typeIndex;
+ instanceOffset_ = instanceOffset;
+ interpExitCodeOffset_ = 0;
+ jitExitCodeOffset_ = 0;
+ }
+
+ void initInterpExitOffset(uint32_t off) {
+ MOZ_ASSERT(!interpExitCodeOffset_);
+ interpExitCodeOffset_ = off;
+ }
+ void initJitExitOffset(uint32_t off) {
+ MOZ_ASSERT(!jitExitCodeOffset_);
+ jitExitCodeOffset_ = off;
+ }
+
+ uint32_t typeIndex() const { return typeIndex_; }
+ uint32_t instanceOffset() const { return instanceOffset_; }
+ uint32_t interpExitCodeOffset() const { return interpExitCodeOffset_; }
+ uint32_t jitExitCodeOffset() const { return jitExitCodeOffset_; }
+};
+
+WASM_DECLARE_CACHEABLE_POD(FuncImport)
+
+using FuncImportVector = Vector<FuncImport, 0, SystemAllocPolicy>;
+
+// Metadata holds all the data that is needed to describe compiled wasm code
+// at runtime (as opposed to data that is only used to statically link or
+// instantiate a module).
+//
+// Metadata is built incrementally by ModuleGenerator and then shared immutably
+// between modules.
+//
+// The Metadata structure is split into tier-invariant and tier-variant parts;
+// the former points to instances of the latter. Additionally, the asm.js
+// subsystem subclasses the Metadata, adding more tier-invariant data, some of
+// which is serialized. See AsmJS.cpp.
+
+struct MetadataCacheablePod {
+ ModuleKind kind;
+ Maybe<MemoryDesc> memory;
+ uint32_t instanceDataLength;
+ Maybe<uint32_t> startFuncIndex;
+ Maybe<uint32_t> nameCustomSectionIndex;
+ bool filenameIsURL;
+ bool omitsBoundsChecks;
+ uint32_t typeDefsOffsetStart;
+ uint32_t tablesOffsetStart;
+ uint32_t tagsOffsetStart;
+ uint32_t padding;
+
+ WASM_CHECK_CACHEABLE_POD(kind, memory, instanceDataLength, startFuncIndex,
+ nameCustomSectionIndex, filenameIsURL,
+ omitsBoundsChecks, typeDefsOffsetStart,
+ tablesOffsetStart, tagsOffsetStart)
+
+ explicit MetadataCacheablePod(ModuleKind kind)
+ : kind(kind),
+ instanceDataLength(0),
+ filenameIsURL(false),
+ omitsBoundsChecks(false),
+ typeDefsOffsetStart(UINT32_MAX),
+ tablesOffsetStart(UINT32_MAX),
+ tagsOffsetStart(UINT32_MAX),
+ padding(0) {}
+};
+
+WASM_DECLARE_CACHEABLE_POD(MetadataCacheablePod)
+
+WASM_CHECK_CACHEABLE_POD_PADDING(MetadataCacheablePod)
+
+using ModuleHash = uint8_t[8];
+
+struct Metadata : public ShareableBase<Metadata>, public MetadataCacheablePod {
+ SharedTypeContext types;
+ GlobalDescVector globals;
+ TableDescVector tables;
+ TagDescVector tags;
+ CacheableChars filename;
+ CacheableChars sourceMapURL;
+
+ // namePayload points at the name section's CustomSection::payload so that
+ // the Names (which are use payload-relative offsets) can be used
+ // independently of the Module without duplicating the name section.
+ SharedBytes namePayload;
+ Maybe<Name> moduleName;
+ NameVector funcNames;
+
+ // Debug-enabled code is not serialized.
+ bool debugEnabled;
+ Uint32Vector debugFuncTypeIndices;
+ ModuleHash debugHash;
+
+ explicit Metadata(ModuleKind kind = ModuleKind::Wasm)
+ : MetadataCacheablePod(kind), debugEnabled(false), debugHash() {}
+ virtual ~Metadata() = default;
+
+ MetadataCacheablePod& pod() { return *this; }
+ const MetadataCacheablePod& pod() const { return *this; }
+
+ bool usesMemory() const { return memory.isSome(); }
+ bool usesSharedMemory() const {
+ return memory.isSome() && memory->isShared();
+ }
+
+ const FuncType& getFuncImportType(const FuncImport& funcImport) const {
+ return types->type(funcImport.typeIndex()).funcType();
+ }
+ const FuncType& getFuncExportType(const FuncExport& funcExport) const {
+ return types->type(funcExport.typeIndex()).funcType();
+ }
+
+ size_t debugNumFuncs() const { return debugFuncTypeIndices.length(); }
+ const FuncType& debugFuncType(uint32_t funcIndex) const {
+ MOZ_ASSERT(debugEnabled);
+ return types->type(debugFuncTypeIndices[funcIndex]).funcType();
+ }
+
+ // AsmJSMetadata derives Metadata iff isAsmJS(). Mostly this distinction is
+ // encapsulated within AsmJS.cpp, but the additional virtual functions allow
+ // asm.js to override wasm behavior in the handful of cases that can't be
+ // easily encapsulated by AsmJS.cpp.
+
+ bool isAsmJS() const { return kind == ModuleKind::AsmJS; }
+ const AsmJSMetadata& asAsmJS() const {
+ MOZ_ASSERT(isAsmJS());
+ return *(const AsmJSMetadata*)this;
+ }
+ virtual bool mutedErrors() const { return false; }
+ virtual const char16_t* displayURL() const { return nullptr; }
+ virtual ScriptSource* maybeScriptSource() const { return nullptr; }
+
+ // The Developer-Facing Display Conventions section of the WebAssembly Web
+ // API spec defines two cases for displaying a wasm function name:
+ // 1. the function name stands alone
+ // 2. the function name precedes the location
+
+ enum NameContext { Standalone, BeforeLocation };
+
+ virtual bool getFuncName(NameContext ctx, uint32_t funcIndex,
+ UTF8Bytes* name) const;
+
+ bool getFuncNameStandalone(uint32_t funcIndex, UTF8Bytes* name) const {
+ return getFuncName(NameContext::Standalone, funcIndex, name);
+ }
+ bool getFuncNameBeforeLocation(uint32_t funcIndex, UTF8Bytes* name) const {
+ return getFuncName(NameContext::BeforeLocation, funcIndex, name);
+ }
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ WASM_DECLARE_FRIEND_SERIALIZE(Metadata);
+};
+
+using MutableMetadata = RefPtr<Metadata>;
+using SharedMetadata = RefPtr<const Metadata>;
+
+struct MetadataTier {
+ explicit MetadataTier(Tier tier = Tier::Serialized)
+ : tier(tier), debugTrapOffset(0) {}
+
+ const Tier tier;
+
+ Uint32Vector funcToCodeRange;
+ CodeRangeVector codeRanges;
+ CallSiteVector callSites;
+ TrapSiteVectorArray trapSites;
+ FuncImportVector funcImports;
+ FuncExportVector funcExports;
+ StackMaps stackMaps;
+ TryNoteVector tryNotes;
+
+ // Debug information, not serialized.
+ uint32_t debugTrapOffset;
+
+ FuncExport& lookupFuncExport(uint32_t funcIndex,
+ size_t* funcExportIndex = nullptr);
+ const FuncExport& lookupFuncExport(uint32_t funcIndex,
+ size_t* funcExportIndex = nullptr) const;
+
+ const CodeRange& codeRange(const FuncExport& funcExport) const {
+ return codeRanges[funcToCodeRange[funcExport.funcIndex()]];
+ }
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+};
+
+using UniqueMetadataTier = UniquePtr<MetadataTier>;
+
+// LazyStubSegment is a code segment lazily generated for function entry stubs
+// (both interpreter and jit ones).
+//
+// Because a stub is usually small (a few KiB) and an executable code segment
+// isn't (64KiB), a given stub segment can contain entry stubs of many
+// functions.
+
+using UniqueLazyStubSegment = UniquePtr<LazyStubSegment>;
+using LazyStubSegmentVector =
+ Vector<UniqueLazyStubSegment, 0, SystemAllocPolicy>;
+
+class LazyStubSegment : public CodeSegment {
+ CodeRangeVector codeRanges_;
+ size_t usedBytes_;
+
+ public:
+ LazyStubSegment(UniqueCodeBytes bytes, size_t length)
+ : CodeSegment(std::move(bytes), length, CodeSegment::Kind::LazyStubs),
+ usedBytes_(0) {}
+
+ static UniqueLazyStubSegment create(const CodeTier& codeTier,
+ size_t codeLength);
+
+ static size_t AlignBytesNeeded(size_t bytes) {
+ return AlignBytes(bytes, gc::SystemPageSize());
+ }
+
+ bool hasSpace(size_t bytes) const;
+ [[nodiscard]] bool addStubs(const Metadata& metadata, size_t codeLength,
+ const Uint32Vector& funcExportIndices,
+ const FuncExportVector& funcExports,
+ const CodeRangeVector& codeRanges,
+ uint8_t** codePtr,
+ size_t* indexFirstInsertedCodeRange);
+
+ const CodeRangeVector& codeRanges() const { return codeRanges_; }
+ [[nodiscard]] const CodeRange* lookupRange(const void* pc) const;
+
+ void addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code,
+ size_t* data) const;
+};
+
+// 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 lazyStubSegmentIndex;
+ size_t funcCodeRangeIndex;
+ LazyFuncExport(size_t funcIndex, size_t lazyStubSegmentIndex,
+ size_t funcCodeRangeIndex)
+ : funcIndex(funcIndex),
+ lazyStubSegmentIndex(lazyStubSegmentIndex),
+ funcCodeRangeIndex(funcCodeRangeIndex) {}
+};
+
+using LazyFuncExportVector = Vector<LazyFuncExport, 0, SystemAllocPolicy>;
+
+// LazyStubTier contains all the necessary information for lazy function entry
+// stubs that are generated at runtime. None of its data are ever serialized.
+//
+// It must be protected by a lock, because the main thread can both read and
+// write lazy stubs at any time while a background thread can regenerate lazy
+// stubs for tier2 at any time.
+
+class LazyStubTier {
+ LazyStubSegmentVector stubSegments_;
+ LazyFuncExportVector exports_;
+ size_t lastStubSegmentIndex_;
+
+ [[nodiscard]] bool createManyEntryStubs(const Uint32Vector& funcExportIndices,
+ const Metadata& metadata,
+ const CodeTier& codeTier,
+ size_t* stubSegmentIndex);
+
+ public:
+ LazyStubTier() : lastStubSegmentIndex_(0) {}
+
+ // Creates one lazy stub for the exported function, for which the jit entry
+ // will be set to the lazily-generated one.
+ [[nodiscard]] bool createOneEntryStub(uint32_t funcExportIndex,
+ const Metadata& metadata,
+ const CodeTier& codeTier);
+
+ bool entryStubsEmpty() const { return stubSegments_.empty(); }
+ bool hasEntryStub(uint32_t funcIndex) const;
+
+ // Returns a pointer to the raw interpreter entry of a given function for
+ // which stubs have been lazily generated.
+ [[nodiscard]] void* lookupInterpEntry(uint32_t funcIndex) 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 createTier2(const Uint32Vector& funcExportIndices,
+ const Metadata& metadata,
+ const CodeTier& codeTier,
+ Maybe<size_t>* stubSegmentIndex);
+ void setJitEntries(const Maybe<size_t>& stubSegmentIndex, const Code& code);
+
+ void addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code,
+ size_t* data) const;
+};
+
+// CodeTier contains all the data related to a given compilation tier. It is
+// built during module generation and then immutably stored in a Code.
+
+using UniqueCodeTier = UniquePtr<CodeTier>;
+using UniqueConstCodeTier = UniquePtr<const CodeTier>;
+
+class CodeTier {
+ const Code* code_;
+
+ // Serialized information.
+ const UniqueMetadataTier metadata_;
+ const UniqueModuleSegment segment_;
+
+ // Lazy stubs, not serialized.
+ RWExclusiveData<LazyStubTier> lazyStubs_;
+
+ static const MutexId& mutexForTier(Tier tier) {
+ if (tier == Tier::Baseline) {
+ return mutexid::WasmLazyStubsTier1;
+ }
+ MOZ_ASSERT(tier == Tier::Optimized);
+ return mutexid::WasmLazyStubsTier2;
+ }
+
+ public:
+ CodeTier(UniqueMetadataTier metadata, UniqueModuleSegment segment)
+ : code_(nullptr),
+ metadata_(std::move(metadata)),
+ segment_(std::move(segment)),
+ lazyStubs_(mutexForTier(segment_->tier())) {}
+
+ bool initialized() const { return !!code_ && segment_->initialized(); }
+ bool initialize(const Code& code, const LinkData& linkData,
+ const Metadata& metadata);
+
+ Tier tier() const { return segment_->tier(); }
+ const RWExclusiveData<LazyStubTier>& lazyStubs() const { return lazyStubs_; }
+ const MetadataTier& metadata() const { return *metadata_.get(); }
+ const ModuleSegment& segment() const { return *segment_.get(); }
+ const Code& code() const {
+ MOZ_ASSERT(initialized());
+ return *code_;
+ }
+
+ const CodeRange* lookupRange(const void* pc) const;
+ const TryNote* lookupTryNote(const void* pc) const;
+
+ void addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code,
+ size_t* data) const;
+
+ WASM_DECLARE_FRIEND_SERIALIZE_ARGS(CodeTier, const wasm::LinkData& data);
+};
+
+// 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>;
+
+ CompileMode mode_;
+ TablePointer tiering_;
+ TablePointer jit_;
+ size_t numFuncs_;
+
+ static_assert(
+ JumpTableJitEntryOffset == 0,
+ "Each jit entry in table must have compatible layout with BaseScript and"
+ "SelfHostedLazyScript");
+
+ public:
+ bool init(CompileMode mode, const ModuleSegment& ms,
+ const CodeRangeVector& codeRanges);
+
+ 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_);
+ jit_.get()[i] = target;
+ }
+ 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, __ATOMIC_RELAXED,
+ __ATOMIC_RELAXED);
+ }
+ void** getAddressOfJitEntry(size_t i) const {
+ MOZ_ASSERT(i < numFuncs_);
+ MOZ_ASSERT(jit_.get()[i]);
+ return &jit_.get()[i];
+ }
+ size_t funcIndexFromJitEntry(void** target) const {
+ MOZ_ASSERT(target >= &jit_.get()[0]);
+ MOZ_ASSERT(target <= &(jit_.get()[numFuncs_ - 1]));
+ return (intptr_t*)target - (intptr_t*)&jit_.get()[0];
+ }
+
+ void setTieringEntry(size_t i, void* target) const {
+ MOZ_ASSERT(i < numFuncs_);
+ // See comment in wasm::Module::finishTier2.
+ if (mode_ == CompileMode::Tier1) {
+ 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.
+ return sizeof(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>;
+
+class Code : public ShareableBase<Code> {
+ UniqueCodeTier tier1_;
+
+ // [SMDOC] Tier-2 data
+ //
+ // hasTier2_ and tier2_ implement a three-state protocol for broadcasting
+ // tier-2 data; this also amounts to a single-writer/multiple-reader setup.
+ //
+ // Initially hasTier2_ is false and tier2_ is null.
+ //
+ // While hasTier2_ is false, *no* thread may read tier2_, but one thread may
+ // make tier2_ non-null (this will be the tier-2 compiler thread). That same
+ // thread must then later set hasTier2_ to true to broadcast the tier2_ value
+ // and its availability. Note that the writing thread may not itself read
+ // tier2_ before setting hasTier2_, in order to simplify reasoning about
+ // global invariants.
+ //
+ // Once hasTier2_ is true, *no* thread may write tier2_ and *no* thread may
+ // read tier2_ without having observed hasTier2_ as true first. Once
+ // hasTier2_ is true, it stays true.
+ mutable UniqueConstCodeTier tier2_;
+ mutable Atomic<bool> hasTier2_;
+
+ SharedMetadata metadata_;
+ ExclusiveData<CacheableCharsVector> profilingLabels_;
+ JumpTables jumpTables_;
+
+ public:
+ Code(UniqueCodeTier tier1, const Metadata& metadata,
+ JumpTables&& maybeJumpTables);
+ bool initialized() const { return tier1_->initialized(); }
+
+ bool initialize(const LinkData& linkData);
+
+ void setTieringEntry(size_t i, void* target) const {
+ jumpTables_.setTieringEntry(i, target);
+ }
+ void** tieringJumpTable() const { return jumpTables_.tiering(); }
+
+ void setJitEntry(size_t i, void* target) const {
+ jumpTables_.setJitEntry(i, target);
+ }
+ void setJitEntryIfNull(size_t i, void* target) const {
+ jumpTables_.setJitEntryIfNull(i, target);
+ }
+ void** getAddressOfJitEntry(size_t i) const {
+ return jumpTables_.getAddressOfJitEntry(i);
+ }
+ uint32_t getFuncIndex(JSFunction* fun) const;
+
+ // Install the tier2 code without committing it. To maintain the invariant
+ // that tier2_ is never accessed without the tier having been committed, this
+ // returns a pointer to the installed tier that the caller can use for
+ // subsequent operations.
+ bool setAndBorrowTier2(UniqueCodeTier tier2, const LinkData& linkData,
+ const CodeTier** borrowedTier) const;
+ void commitTier2() const;
+
+ bool hasTier2() const { return hasTier2_; }
+ Tiers tiers() const;
+ bool hasTier(Tier t) const;
+
+ Tier stableTier() const; // This is stable during a run
+ Tier bestTier()
+ const; // This may transition from Baseline -> Ion at any time
+
+ const CodeTier& codeTier(Tier tier) const;
+ const Metadata& metadata() const { return *metadata_; }
+
+ const ModuleSegment& segment(Tier iter) const {
+ return codeTier(iter).segment();
+ }
+ const MetadataTier& metadata(Tier iter) const {
+ return codeTier(iter).metadata();
+ }
+
+ // Metadata lookup functions:
+
+ const CallSite* lookupCallSite(void* returnAddress) const;
+ const CodeRange* lookupFuncRange(void* pc) const;
+ const StackMap* lookupStackMap(uint8_t* nextPC) const;
+ const TryNote* lookupTryNote(void* pc, Tier* tier) const;
+ bool containsCodePC(const void* pc) const;
+ bool lookupTrap(void* pc, Trap* trap, BytecodeOffset* bytecode) const;
+
+ // To save memory, profilingLabels_ are generated lazily when profiling mode
+ // is enabled.
+
+ void ensureProfilingLabels(bool profilingEnabled) const;
+ const char* profilingLabel(uint32_t funcIndex) const;
+
+ // Wasm disassembly support
+
+ void disassemble(JSContext* cx, Tier tier, int kindSelection,
+ PrintCallback printString) const;
+
+ // about:memory reporting:
+
+ void addSizeOfMiscIfNotSeen(MallocSizeOf mallocSizeOf,
+ Metadata::SeenSet* seenMetadata,
+ Code::SeenSet* seenCode, size_t* code,
+ size_t* data) const;
+
+ WASM_DECLARE_FRIEND_SERIALIZE_ARGS(SharedCode, const wasm::LinkData& data);
+};
+
+void PatchDebugSymbolicAccesses(uint8_t* codeBase, jit::MacroAssembler& masm);
+
+} // namespace wasm
+} // namespace js
+
+#endif // wasm_code_h