/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * * 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 "wasm/WasmGeneratedSourceMap.h" #include "wasm/WasmTypes.h" namespace js { struct AsmJSMetadata; namespace wasm { struct LinkData; struct Metadata; // A wasm CodeSegment owns the allocated executable code for a wasm module. // This allocation also currently includes the global data segment, which allows // RIP-relative access to global data on some architectures, but this will // change in the future to give global data its own allocation. class CodeSegment; typedef UniquePtr UniqueCodeSegment; class CodeSegment { // bytes_ points to a single allocation with two contiguous ranges: // executable machine code in the range [0, codeLength) and global data in // the range [codeLength, codeLength + globalDataLength). The range // [0, functionCodeLength) is the subrange of [0, codeLength) which contains // function code. uint8_t* bytes_; uint32_t functionCodeLength_; uint32_t codeLength_; uint32_t globalDataLength_; // These are pointers into code for stubs used for asynchronous // signal-handler control-flow transfer. uint8_t* interruptCode_; uint8_t* outOfBoundsCode_; uint8_t* unalignedAccessCode_; // The profiling mode may be changed dynamically. bool profilingEnabled_; CodeSegment() { PodZero(this); } template friend struct js::MallocProvider; CodeSegment(const CodeSegment&) = delete; CodeSegment(CodeSegment&&) = delete; void operator=(const CodeSegment&) = delete; void operator=(CodeSegment&&) = delete; public: static UniqueCodeSegment create(JSContext* cx, const Bytes& code, const LinkData& linkData, const Metadata& metadata, HandleWasmMemoryObject memory); ~CodeSegment(); uint8_t* base() const { return bytes_; } uint8_t* globalData() const { return bytes_ + codeLength_; } uint32_t codeLength() const { return codeLength_; } uint32_t globalDataLength() const { return globalDataLength_; } uint32_t totalLength() const { return codeLength_ + globalDataLength_; } uint8_t* interruptCode() const { return interruptCode_; } uint8_t* outOfBoundsCode() const { return outOfBoundsCode_; } uint8_t* unalignedAccessCode() const { return unalignedAccessCode_; } // The range [0, functionBytes) is a subrange of [0, codeBytes) that // contains only function body code, not the stub code. This distinction is // used by the async interrupt handler to only interrupt when the pc is in // function code which, in turn, simplifies reasoning about how stubs // enter/exit. bool containsFunctionPC(const void* pc) const { return pc >= base() && pc < (base() + functionCodeLength_); } bool containsCodePC(const void* pc) const { return pc >= base() && pc < (base() + codeLength_); } // onMovingGrow must be called if the memory passed to 'create' performs a // moving grow operation. void onMovingGrow(uint8_t* prevMemoryBase, const Metadata& metadata, ArrayBufferObject& buffer); }; // ShareableBytes is a ref-counted vector of bytes which are incrementally built // during compilation and then immutably shared. struct ShareableBytes : ShareableBase { // Vector is 'final', so instead make Vector a member and add boilerplate. Bytes bytes; size_t sizeOfExcludingThis(MallocSizeOf m) const { return bytes.sizeOfExcludingThis(m); } const uint8_t* begin() const { return bytes.begin(); } const uint8_t* end() const { return bytes.end(); } size_t length() const { return bytes.length(); } bool append(const uint8_t *p, uint32_t ct) { return bytes.append(p, ct); } }; typedef RefPtr MutableBytes; typedef RefPtr SharedBytes; // 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 { Sig sig_; MOZ_INIT_OUTSIDE_CTOR struct CacheablePod { uint32_t funcIndex_; uint32_t codeRangeIndex_; uint32_t entryOffset_; } pod; public: FuncExport() = default; explicit FuncExport(Sig&& sig, uint32_t funcIndex, uint32_t codeRangeIndex) : sig_(Move(sig)) { pod.funcIndex_ = funcIndex; pod.codeRangeIndex_ = codeRangeIndex; pod.entryOffset_ = UINT32_MAX; } void initEntryOffset(uint32_t entryOffset) { MOZ_ASSERT(pod.entryOffset_ == UINT32_MAX); pod.entryOffset_ = entryOffset; } const Sig& sig() const { return sig_; } uint32_t funcIndex() const { return pod.funcIndex_; } uint32_t codeRangeIndex() const { return pod.codeRangeIndex_; } uint32_t entryOffset() const { MOZ_ASSERT(pod.entryOffset_ != UINT32_MAX); return pod.entryOffset_; } WASM_DECLARE_SERIALIZABLE(FuncExport) }; typedef Vector FuncExportVector; // 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 { Sig sig_; struct CacheablePod { uint32_t tlsDataOffset_; uint32_t interpExitCodeOffset_; uint32_t jitExitCodeOffset_; } pod; public: FuncImport() { memset(&pod, 0, sizeof(CacheablePod)); } FuncImport(Sig&& sig, uint32_t tlsDataOffset) : sig_(Move(sig)) { pod.tlsDataOffset_ = tlsDataOffset; pod.interpExitCodeOffset_ = 0; pod.jitExitCodeOffset_ = 0; } void initInterpExitOffset(uint32_t off) { MOZ_ASSERT(!pod.interpExitCodeOffset_); pod.interpExitCodeOffset_ = off; } void initJitExitOffset(uint32_t off) { MOZ_ASSERT(!pod.jitExitCodeOffset_); pod.jitExitCodeOffset_ = off; } const Sig& sig() const { return sig_; } uint32_t tlsDataOffset() const { return pod.tlsDataOffset_; } uint32_t interpExitCodeOffset() const { return pod.interpExitCodeOffset_; } uint32_t jitExitCodeOffset() const { return pod.jitExitCodeOffset_; } WASM_DECLARE_SERIALIZABLE(FuncImport) }; typedef Vector FuncImportVector; // A CodeRange describes a single contiguous range of code within a wasm // module's code segment. A CodeRange describes what the code does and, for // function bodies, the name and source coordinates of the function. class CodeRange { public: enum Kind { Function, // function definition Entry, // calls into wasm from C++ ImportJitExit, // fast-path calling from wasm into JIT code ImportInterpExit, // slow-path calling from wasm into C++ interp TrapExit, // calls C++ to report and jumps to throw stub FarJumpIsland, // inserted to connect otherwise out-of-range insns Inline // stub that is jumped-to, not called, and thus // replaces/loses preceding innermost frame }; private: // All fields are treated as cacheable POD: uint32_t begin_; uint32_t profilingReturn_; uint32_t end_; uint32_t funcIndex_; uint32_t funcLineOrBytecode_; uint8_t funcBeginToTableEntry_; uint8_t funcBeginToTableProfilingJump_; uint8_t funcBeginToNonProfilingEntry_; uint8_t funcProfilingJumpToProfilingReturn_; uint8_t funcProfilingEpilogueToProfilingReturn_; Kind kind_ : 8; public: CodeRange() = default; CodeRange(Kind kind, Offsets offsets); CodeRange(Kind kind, ProfilingOffsets offsets); CodeRange(uint32_t funcIndex, uint32_t lineOrBytecode, FuncOffsets offsets); // All CodeRanges have a begin and end. uint32_t begin() const { return begin_; } uint32_t end() const { return end_; } // Other fields are only available for certain CodeRange::Kinds. Kind kind() const { return kind_; } bool isFunction() const { return kind() == Function; } bool isImportExit() const { return kind() == ImportJitExit || kind() == ImportInterpExit; } bool isTrapExit() const { return kind() == TrapExit; } bool isInline() const { return kind() == Inline; } // Every CodeRange except entry and inline stubs has a profiling return // which is used for asynchronous profiling to determine the frame pointer. uint32_t profilingReturn() const { MOZ_ASSERT(isFunction() || isImportExit() || isTrapExit()); return profilingReturn_; } // Functions have offsets which allow patching to selectively execute // profiling prologues/epilogues. uint32_t funcProfilingEntry() const { MOZ_ASSERT(isFunction()); return begin(); } uint32_t funcTableEntry() const { MOZ_ASSERT(isFunction()); return begin_ + funcBeginToTableEntry_; } uint32_t funcTableProfilingJump() const { MOZ_ASSERT(isFunction()); return begin_ + funcBeginToTableProfilingJump_; } uint32_t funcNonProfilingEntry() const { MOZ_ASSERT(isFunction()); return begin_ + funcBeginToNonProfilingEntry_; } uint32_t funcProfilingJump() const { MOZ_ASSERT(isFunction()); return profilingReturn_ - funcProfilingJumpToProfilingReturn_; } uint32_t funcProfilingEpilogue() const { MOZ_ASSERT(isFunction()); return profilingReturn_ - funcProfilingEpilogueToProfilingReturn_; } uint32_t funcIndex() const { MOZ_ASSERT(isFunction()); return funcIndex_; } uint32_t funcLineOrBytecode() const { MOZ_ASSERT(isFunction()); return funcLineOrBytecode_; } // A sorted array of CodeRanges can be looked up via BinarySearch and PC. struct PC { size_t offset; explicit PC(size_t offset) : offset(offset) {} bool operator==(const CodeRange& rhs) const { return offset >= rhs.begin() && offset < rhs.end(); } bool operator<(const CodeRange& rhs) const { return offset < rhs.begin(); } }; }; WASM_DECLARE_POD_VECTOR(CodeRange, CodeRangeVector) // A CallThunk describes the offset and target of thunks so that they may be // patched at runtime when profiling is toggled. Thunks are emitted to connect // callsites that are too far away from callees to fit in a single call // instruction's relative offset. struct CallThunk { uint32_t offset; union { uint32_t funcIndex; uint32_t codeRangeIndex; } u; CallThunk(uint32_t offset, uint32_t funcIndex) : offset(offset) { u.funcIndex = funcIndex; } CallThunk() = default; }; WASM_DECLARE_POD_VECTOR(CallThunk, CallThunkVector) // A wasm module can either use no memory, a unshared memory (ArrayBuffer) or // shared memory (SharedArrayBuffer). enum class MemoryUsage { None = false, Unshared = 1, Shared = 2 }; static inline bool UsesMemory(MemoryUsage memoryUsage) { return bool(memoryUsage); } // NameInBytecode represents a name that is embedded in the wasm bytecode. // The presence of NameInBytecode implies that bytecode has been kept. struct NameInBytecode { uint32_t offset; uint32_t length; NameInBytecode() = default; NameInBytecode(uint32_t offset, uint32_t length) : offset(offset), length(length) {} }; typedef Vector NameInBytecodeVector; typedef Vector TwoByteName; // 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. struct MetadataCacheablePod { ModuleKind kind; MemoryUsage memoryUsage; uint32_t minMemoryLength; Maybe maxMemoryLength; Maybe startFuncIndex; explicit MetadataCacheablePod(ModuleKind kind) : kind(kind), memoryUsage(MemoryUsage::None), minMemoryLength(0) {} }; struct Metadata : ShareableBase, MetadataCacheablePod { explicit Metadata(ModuleKind kind = ModuleKind::Wasm) : MetadataCacheablePod(kind) {} virtual ~Metadata() {} MetadataCacheablePod& pod() { return *this; } const MetadataCacheablePod& pod() const { return *this; } FuncImportVector funcImports; FuncExportVector funcExports; SigWithIdVector sigIds; GlobalDescVector globals; TableDescVector tables; MemoryAccessVector memoryAccesses; MemoryPatchVector memoryPatches; BoundsCheckVector boundsChecks; CodeRangeVector codeRanges; CallSiteVector callSites; CallThunkVector callThunks; NameInBytecodeVector funcNames; CacheableChars filename; bool usesMemory() const { return UsesMemory(memoryUsage); } bool hasSharedMemory() const { return memoryUsage == MemoryUsage::Shared; } const FuncExport& lookupFuncExport(uint32_t funcIndex) const; // 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; } virtual bool getFuncName(JSContext* cx, const Bytes* maybeBytecode, uint32_t funcIndex, TwoByteName* name) const; WASM_DECLARE_SERIALIZABLE_VIRTUAL(Metadata); }; typedef RefPtr MutableMetadata; typedef RefPtr SharedMetadata; // Code objects own executable code and the metadata that describes it. At the // moment, Code objects are owned uniquely by instances since CodeSegments are // not shareable. However, once this restriction is removed, a single Code // object will be shared between a module and all its instances. class Code { const UniqueCodeSegment segment_; const SharedMetadata metadata_; const SharedBytes maybeBytecode_; UniqueGeneratedSourceMap maybeSourceMap_; CacheableCharsVector funcLabels_; bool profilingEnabled_; public: Code(UniqueCodeSegment segment, const Metadata& metadata, const ShareableBytes* maybeBytecode); CodeSegment& segment() { return *segment_; } const CodeSegment& segment() const { return *segment_; } const Metadata& metadata() const { return *metadata_; } // Frame iterator support: const CallSite* lookupCallSite(void* returnAddress) const; const CodeRange* lookupRange(void* pc) const; const MemoryAccess* lookupMemoryAccess(void* pc) const; // Return the name associated with a given function index, or generate one // if none was given by the module. bool getFuncName(JSContext* cx, uint32_t funcIndex, TwoByteName* name) const; JSAtom* getFuncAtom(JSContext* cx, uint32_t funcIndex) const; // If the source bytecode was saved when this Code was constructed, this // method will render the binary as text. Otherwise, a diagnostic string // will be returned. JSString* createText(JSContext* cx); bool getLineOffsets(size_t lineno, Vector& offsets) const; // Each Code has a profiling mode that is updated to match the runtime's // profiling mode when there are no other activations of the code live on // the stack. Once in profiling mode, ProfilingFrameIterator can be used to // asynchronously walk the stack. Otherwise, the ProfilingFrameIterator will // skip any activations of this code. MOZ_MUST_USE bool ensureProfilingState(JSContext* cx, bool enabled); bool profilingEnabled() const { return profilingEnabled_; } const char* profilingLabel(uint32_t funcIndex) const { return funcLabels_[funcIndex].get(); } // about:memory reporting: void addSizeOfMisc(MallocSizeOf mallocSizeOf, Metadata::SeenSet* seenMetadata, ShareableBytes::SeenSet* seenBytes, size_t* code, size_t* data) const; WASM_DECLARE_SERIALIZABLE(Code); }; typedef UniquePtr UniqueCode; } // namespace wasm } // namespace js #endif // wasm_code_h