Mypal/js/src/wasm/WasmCode.h

555 lines
18 KiB
C++

/* -*- 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<CodeSegment> 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 <class> 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<ShareableBytes>
{
// 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<ShareableBytes> MutableBytes;
typedef RefPtr<const ShareableBytes> 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<FuncExport, 0, SystemAllocPolicy> 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<FuncImport, 0, SystemAllocPolicy> 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<NameInBytecode, 0, SystemAllocPolicy> NameInBytecodeVector;
typedef Vector<char16_t, 64> 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<uint32_t> maxMemoryLength;
Maybe<uint32_t> startFuncIndex;
explicit MetadataCacheablePod(ModuleKind kind)
: kind(kind),
memoryUsage(MemoryUsage::None),
minMemoryLength(0)
{}
};
struct Metadata : ShareableBase<Metadata>, 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<Metadata> MutableMetadata;
typedef RefPtr<const Metadata> 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<uint32_t>& 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<Code> UniqueCode;
} // namespace wasm
} // namespace js
#endif // wasm_code_h