Mypal/js/src/wasm/WasmTypes.h

1511 lines
47 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 2015 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_types_h
#define wasm_types_h
#include "mozilla/EnumeratedArray.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/Maybe.h"
#include "mozilla/Move.h"
#include "mozilla/RefCounted.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Unused.h"
#include "NamespaceImports.h"
#include "ds/LifoAlloc.h"
#include "jit/IonTypes.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"
#include "js/Vector.h"
#include "vm/MallocProvider.h"
#include "wasm/WasmBinaryConstants.h"
namespace js {
class PropertyName;
namespace jit { struct BaselineScript; }
// This is a widespread header, so lets keep out the core wasm impl types.
class WasmMemoryObject;
typedef GCPtr<WasmMemoryObject*> GCPtrWasmMemoryObject;
typedef Rooted<WasmMemoryObject*> RootedWasmMemoryObject;
typedef Handle<WasmMemoryObject*> HandleWasmMemoryObject;
typedef MutableHandle<WasmMemoryObject*> MutableHandleWasmMemoryObject;
class WasmModuleObject;
typedef Rooted<WasmModuleObject*> RootedWasmModuleObject;
typedef Handle<WasmModuleObject*> HandleWasmModuleObject;
typedef MutableHandle<WasmModuleObject*> MutableHandleWasmModuleObject;
class WasmInstanceObject;
typedef GCVector<WasmInstanceObject*> WasmInstanceObjectVector;
typedef Rooted<WasmInstanceObject*> RootedWasmInstanceObject;
typedef Handle<WasmInstanceObject*> HandleWasmInstanceObject;
typedef MutableHandle<WasmInstanceObject*> MutableHandleWasmInstanceObject;
class WasmTableObject;
typedef Rooted<WasmTableObject*> RootedWasmTableObject;
typedef Handle<WasmTableObject*> HandleWasmTableObject;
typedef MutableHandle<WasmTableObject*> MutableHandleWasmTableObject;
namespace wasm {
using mozilla::DebugOnly;
using mozilla::EnumeratedArray;
using mozilla::Maybe;
using mozilla::Move;
using mozilla::MallocSizeOf;
using mozilla::Nothing;
using mozilla::PodZero;
using mozilla::PodCopy;
using mozilla::PodEqual;
using mozilla::RefCounted;
using mozilla::Some;
using mozilla::Unused;
typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes;
typedef int8_t I8x16[16];
typedef int16_t I16x8[8];
typedef int32_t I32x4[4];
typedef float F32x4[4];
class Code;
class CodeRange;
class Memory;
class Module;
class Instance;
class Table;
// To call Vector::podResizeToFit, a type must specialize mozilla::IsPod
// which is pretty verbose to do within js::wasm, so factor that process out
// into a macro.
#define WASM_DECLARE_POD_VECTOR(Type, VectorName) \
} } namespace mozilla { \
template <> struct IsPod<js::wasm::Type> : TrueType {}; \
} namespace js { namespace wasm { \
typedef Vector<Type, 0, SystemAllocPolicy> VectorName;
// A wasm Module and everything it contains must support serialization and
// deserialization. Some data can be simply copied as raw bytes and,
// as a convention, is stored in an inline CacheablePod struct. Everything else
// should implement the below methods which are called recusively by the
// containing Module.
#define WASM_DECLARE_SERIALIZABLE(Type) \
size_t serializedSize() const; \
uint8_t* serialize(uint8_t* cursor) const; \
const uint8_t* deserialize(const uint8_t* cursor); \
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
#define WASM_DECLARE_SERIALIZABLE_VIRTUAL(Type) \
virtual size_t serializedSize() const; \
virtual uint8_t* serialize(uint8_t* cursor) const; \
virtual const uint8_t* deserialize(const uint8_t* cursor); \
virtual size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
#define WASM_DECLARE_SERIALIZABLE_OVERRIDE(Type) \
size_t serializedSize() const override; \
uint8_t* serialize(uint8_t* cursor) const override; \
const uint8_t* deserialize(const uint8_t* cursor) override; \
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const override;
// This reusable base class factors out the logic for a resource that is shared
// by multiple instances/modules but should only be counted once when computing
// about:memory stats.
template <class T>
struct ShareableBase : RefCounted<T>
{
using SeenSet = HashSet<const T*, DefaultHasher<const T*>, SystemAllocPolicy>;
size_t sizeOfIncludingThisIfNotSeen(MallocSizeOf mallocSizeOf, SeenSet* seen) const {
const T* self = static_cast<const T*>(this);
typename SeenSet::AddPtr p = seen->lookupForAdd(self);
if (p)
return 0;
bool ok = seen->add(p, self);
(void)ok; // oh well
return mallocSizeOf(self) + self->sizeOfExcludingThis(mallocSizeOf);
}
};
// ValType utilities
static inline bool
IsSimdType(ValType vt)
{
switch (vt) {
case ValType::I8x16:
case ValType::I16x8:
case ValType::I32x4:
case ValType::F32x4:
case ValType::B8x16:
case ValType::B16x8:
case ValType::B32x4:
return true;
default:
return false;
}
}
static inline uint32_t
NumSimdElements(ValType vt)
{
MOZ_ASSERT(IsSimdType(vt));
switch (vt) {
case ValType::I8x16:
case ValType::B8x16:
return 16;
case ValType::I16x8:
case ValType::B16x8:
return 8;
case ValType::I32x4:
case ValType::F32x4:
case ValType::B32x4:
return 4;
default:
MOZ_CRASH("Unhandled SIMD type");
}
}
static inline ValType
SimdElementType(ValType vt)
{
MOZ_ASSERT(IsSimdType(vt));
switch (vt) {
case ValType::I8x16:
case ValType::I16x8:
case ValType::I32x4:
return ValType::I32;
case ValType::F32x4:
return ValType::F32;
case ValType::B8x16:
case ValType::B16x8:
case ValType::B32x4:
return ValType::I32;
default:
MOZ_CRASH("Unhandled SIMD type");
}
}
static inline ValType
SimdBoolType(ValType vt)
{
MOZ_ASSERT(IsSimdType(vt));
switch (vt) {
case ValType::I8x16:
case ValType::B8x16:
return ValType::B8x16;
case ValType::I16x8:
case ValType::B16x8:
return ValType::B16x8;
case ValType::I32x4:
case ValType::F32x4:
case ValType::B32x4:
return ValType::B32x4;
default:
MOZ_CRASH("Unhandled SIMD type");
}
}
static inline bool
IsSimdBoolType(ValType vt)
{
return vt == ValType::B8x16 || vt == ValType::B16x8 || vt == ValType::B32x4;
}
static inline jit::MIRType
ToMIRType(ValType vt)
{
switch (vt) {
case ValType::I32: return jit::MIRType::Int32;
case ValType::I64: return jit::MIRType::Int64;
case ValType::F32: return jit::MIRType::Float32;
case ValType::F64: return jit::MIRType::Double;
case ValType::I8x16: return jit::MIRType::Int8x16;
case ValType::I16x8: return jit::MIRType::Int16x8;
case ValType::I32x4: return jit::MIRType::Int32x4;
case ValType::F32x4: return jit::MIRType::Float32x4;
case ValType::B8x16: return jit::MIRType::Bool8x16;
case ValType::B16x8: return jit::MIRType::Bool16x8;
case ValType::B32x4: return jit::MIRType::Bool32x4;
}
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("bad type");
}
// The ExprType enum represents the type of a WebAssembly expression or return
// value and may either be a value type or void. Soon, expression types will be
// generalized to a list of ValType and this enum will go away, replaced,
// wherever it is used, by a varU32 + list of ValType.
enum class ExprType
{
Void = uint8_t(TypeCode::BlockVoid),
I32 = uint8_t(TypeCode::I32),
I64 = uint8_t(TypeCode::I64),
F32 = uint8_t(TypeCode::F32),
F64 = uint8_t(TypeCode::F64),
I8x16 = uint8_t(TypeCode::I8x16),
I16x8 = uint8_t(TypeCode::I16x8),
I32x4 = uint8_t(TypeCode::I32x4),
F32x4 = uint8_t(TypeCode::F32x4),
B8x16 = uint8_t(TypeCode::B8x16),
B16x8 = uint8_t(TypeCode::B16x8),
B32x4 = uint8_t(TypeCode::B32x4),
Limit = uint8_t(TypeCode::Limit)
};
static inline bool
IsVoid(ExprType et)
{
return et == ExprType::Void;
}
static inline ValType
NonVoidToValType(ExprType et)
{
MOZ_ASSERT(!IsVoid(et));
return ValType(et);
}
static inline ExprType
ToExprType(ValType vt)
{
return ExprType(vt);
}
static inline bool
IsSimdType(ExprType et)
{
return IsVoid(et) ? false : IsSimdType(ValType(et));
}
static inline jit::MIRType
ToMIRType(ExprType et)
{
return IsVoid(et) ? jit::MIRType::None : ToMIRType(ValType(et));
}
static inline const char*
ToCString(ExprType type)
{
switch (type) {
case ExprType::Void: return "void";
case ExprType::I32: return "i32";
case ExprType::I64: return "i64";
case ExprType::F32: return "f32";
case ExprType::F64: return "f64";
case ExprType::I8x16: return "i8x16";
case ExprType::I16x8: return "i16x8";
case ExprType::I32x4: return "i32x4";
case ExprType::F32x4: return "f32x4";
case ExprType::B8x16: return "b8x16";
case ExprType::B16x8: return "b16x8";
case ExprType::B32x4: return "b32x4";
case ExprType::Limit:;
}
MOZ_CRASH("bad expression type");
}
static inline const char*
ToCString(ValType type)
{
return ToCString(ToExprType(type));
}
// Because WebAssembly allows one to define the payload of a NaN value,
// including the signal/quiet bit (highest order bit of payload), another
// represenation of floating-point values is required: on some platforms (x86
// without SSE2), passing a floating-point argument to a function call may use
// the x87 stack, which has the side-effect of clearing the signal/quiet bit.
// Because the signal/quiet bit must be preserved (by spec), we use the raw
// punned integer representation of floating points instead, in function calls.
//
// When we leave the WebAssembly sandbox back to JS, NaNs are canonicalized, so
// this isn't observable from JS.
template<class T>
class Raw
{
typedef typename mozilla::FloatingPoint<T>::Bits Bits;
Bits value_;
public:
Raw() : value_(0) {}
explicit Raw(T value)
: value_(mozilla::BitwiseCast<Bits>(value))
{}
template<class U> MOZ_IMPLICIT Raw(U) = delete;
static Raw fromBits(Bits bits) { Raw r; r.value_ = bits; return r; }
Bits bits() const { return value_; }
T fp() const { return mozilla::BitwiseCast<T>(value_); }
};
using RawF64 = Raw<double>;
using RawF32 = Raw<float>;
// The Val class represents a single WebAssembly value of a given value type,
// mostly for the purpose of numeric literals and initializers. A Val does not
// directly map to a JS value since there is not (currently) a precise
// representation of i64 values. A Val may contain non-canonical NaNs since,
// within WebAssembly, floats are not canonicalized. Canonicalization must
// happen at the JS boundary.
class Val
{
ValType type_;
union U {
uint32_t i32_;
uint64_t i64_;
RawF32 f32_;
RawF64 f64_;
I8x16 i8x16_;
I16x8 i16x8_;
I32x4 i32x4_;
F32x4 f32x4_;
U() {}
} u;
public:
Val() = default;
explicit Val(uint32_t i32) : type_(ValType::I32) { u.i32_ = i32; }
explicit Val(uint64_t i64) : type_(ValType::I64) { u.i64_ = i64; }
explicit Val(RawF32 f32) : type_(ValType::F32) { u.f32_ = f32; }
explicit Val(RawF64 f64) : type_(ValType::F64) { u.f64_ = f64; }
MOZ_IMPLICIT Val(float) = delete;
MOZ_IMPLICIT Val(double) = delete;
explicit Val(const I8x16& i8x16, ValType type = ValType::I8x16) : type_(type) {
MOZ_ASSERT(type_ == ValType::I8x16 || type_ == ValType::B8x16);
memcpy(u.i8x16_, i8x16, sizeof(u.i8x16_));
}
explicit Val(const I16x8& i16x8, ValType type = ValType::I16x8) : type_(type) {
MOZ_ASSERT(type_ == ValType::I16x8 || type_ == ValType::B16x8);
memcpy(u.i16x8_, i16x8, sizeof(u.i16x8_));
}
explicit Val(const I32x4& i32x4, ValType type = ValType::I32x4) : type_(type) {
MOZ_ASSERT(type_ == ValType::I32x4 || type_ == ValType::B32x4);
memcpy(u.i32x4_, i32x4, sizeof(u.i32x4_));
}
explicit Val(const F32x4& f32x4) : type_(ValType::F32x4) {
memcpy(u.f32x4_, f32x4, sizeof(u.f32x4_));
}
ValType type() const { return type_; }
bool isSimd() const { return IsSimdType(type()); }
uint32_t i32() const { MOZ_ASSERT(type_ == ValType::I32); return u.i32_; }
uint64_t i64() const { MOZ_ASSERT(type_ == ValType::I64); return u.i64_; }
RawF32 f32() const { MOZ_ASSERT(type_ == ValType::F32); return u.f32_; }
RawF64 f64() const { MOZ_ASSERT(type_ == ValType::F64); return u.f64_; }
const I8x16& i8x16() const {
MOZ_ASSERT(type_ == ValType::I8x16 || type_ == ValType::B8x16);
return u.i8x16_;
}
const I16x8& i16x8() const {
MOZ_ASSERT(type_ == ValType::I16x8 || type_ == ValType::B16x8);
return u.i16x8_;
}
const I32x4& i32x4() const {
MOZ_ASSERT(type_ == ValType::I32x4 || type_ == ValType::B32x4);
return u.i32x4_;
}
const F32x4& f32x4() const {
MOZ_ASSERT(type_ == ValType::F32x4);
return u.f32x4_;
}
void writePayload(uint8_t* dst) const;
};
typedef Vector<Val, 0, SystemAllocPolicy> ValVector;
// The Sig class represents a WebAssembly function signature which takes a list
// of value types and returns an expression type. The engine uses two in-memory
// representations of the argument Vector's memory (when elements do not fit
// inline): normal malloc allocation (via SystemAllocPolicy) and allocation in
// a LifoAlloc (via LifoAllocPolicy). The former Sig objects can have any
// lifetime since they own the memory. The latter Sig objects must not outlive
// the associated LifoAlloc mark/release interval (which is currently the
// duration of module validation+compilation). Thus, long-lived objects like
// WasmModule must use malloced allocation.
class Sig
{
ValTypeVector args_;
ExprType ret_;
public:
Sig() : args_(), ret_(ExprType::Void) {}
Sig(ValTypeVector&& args, ExprType ret) : args_(Move(args)), ret_(ret) {}
MOZ_MUST_USE bool clone(const Sig& rhs) {
ret_ = rhs.ret_;
MOZ_ASSERT(args_.empty());
return args_.appendAll(rhs.args_);
}
ValType arg(unsigned i) const { return args_[i]; }
const ValTypeVector& args() const { return args_; }
const ExprType& ret() const { return ret_; }
HashNumber hash() const {
return AddContainerToHash(args_, HashNumber(ret_));
}
bool operator==(const Sig& rhs) const {
return ret() == rhs.ret() && EqualContainers(args(), rhs.args());
}
bool operator!=(const Sig& rhs) const {
return !(*this == rhs);
}
WASM_DECLARE_SERIALIZABLE(Sig)
};
struct SigHashPolicy
{
typedef const Sig& Lookup;
static HashNumber hash(Lookup sig) { return sig.hash(); }
static bool match(const Sig* lhs, Lookup rhs) { return *lhs == rhs; }
};
// An InitExpr describes a deferred initializer expression, used to initialize
// a global or a table element offset. Such expressions are created during
// decoding and actually executed on module instantiation.
class InitExpr
{
public:
enum class Kind {
Constant,
GetGlobal
};
private:
Kind kind_;
union U {
Val val_;
struct {
uint32_t index_;
ValType type_;
} global;
U() {}
} u;
public:
InitExpr() = default;
explicit InitExpr(Val val) : kind_(Kind::Constant) {
u.val_ = val;
}
explicit InitExpr(uint32_t globalIndex, ValType type) : kind_(Kind::GetGlobal) {
u.global.index_ = globalIndex;
u.global.type_ = type;
}
Kind kind() const { return kind_; }
bool isVal() const { return kind() == Kind::Constant; }
Val val() const { MOZ_ASSERT(isVal()); return u.val_; }
uint32_t globalIndex() const { MOZ_ASSERT(kind() == Kind::GetGlobal); return u.global.index_; }
ValType type() const {
switch (kind()) {
case Kind::Constant: return u.val_.type();
case Kind::GetGlobal: return u.global.type_;
}
MOZ_CRASH("unexpected initExpr type");
}
};
// CacheableChars is used to cacheably store UniqueChars.
struct CacheableChars : UniqueChars
{
CacheableChars() = default;
explicit CacheableChars(char* ptr) : UniqueChars(ptr) {}
MOZ_IMPLICIT CacheableChars(UniqueChars&& rhs) : UniqueChars(Move(rhs)) {}
WASM_DECLARE_SERIALIZABLE(CacheableChars)
};
typedef Vector<CacheableChars, 0, SystemAllocPolicy> CacheableCharsVector;
// Import describes a single wasm import. An ImportVector describes all
// of a single module's imports.
//
// ImportVector is built incrementally by ModuleGenerator and then stored
// immutably by Module.
struct Import
{
CacheableChars module;
CacheableChars field;
DefinitionKind kind;
Import() = default;
Import(UniqueChars&& module, UniqueChars&& field, DefinitionKind kind)
: module(Move(module)), field(Move(field)), kind(kind)
{}
WASM_DECLARE_SERIALIZABLE(Import)
};
typedef Vector<Import, 0, SystemAllocPolicy> ImportVector;
// A GlobalDesc describes a single global variable. Currently, asm.js and wasm
// exposes mutable and immutable private globals, but can't import nor export
// mutable globals.
enum class GlobalKind
{
Import,
Constant,
Variable
};
class GlobalDesc
{
union V {
struct {
union U {
InitExpr initial_;
struct {
ValType type_;
uint32_t index_;
} import;
U() {}
} val;
unsigned offset_;
bool isMutable_;
} var;
Val cst_;
V() {}
} u;
GlobalKind kind_;
public:
GlobalDesc() = default;
explicit GlobalDesc(InitExpr initial, bool isMutable)
: kind_((isMutable || !initial.isVal()) ? GlobalKind::Variable : GlobalKind::Constant)
{
if (isVariable()) {
u.var.val.initial_ = initial;
u.var.isMutable_ = isMutable;
u.var.offset_ = UINT32_MAX;
} else {
u.cst_ = initial.val();
}
}
explicit GlobalDesc(ValType type, bool isMutable, uint32_t importIndex)
: kind_(GlobalKind::Import)
{
u.var.val.import.type_ = type;
u.var.val.import.index_ = importIndex;
u.var.isMutable_ = isMutable;
u.var.offset_ = UINT32_MAX;
}
void setOffset(unsigned offset) {
MOZ_ASSERT(!isConstant());
MOZ_ASSERT(u.var.offset_ == UINT32_MAX);
u.var.offset_ = offset;
}
unsigned offset() const {
MOZ_ASSERT(!isConstant());
MOZ_ASSERT(u.var.offset_ != UINT32_MAX);
return u.var.offset_;
}
GlobalKind kind() const { return kind_; }
bool isVariable() const { return kind_ == GlobalKind::Variable; }
bool isConstant() const { return kind_ == GlobalKind::Constant; }
bool isImport() const { return kind_ == GlobalKind::Import; }
bool isMutable() const { return !isConstant() && u.var.isMutable_; }
Val constantValue() const { MOZ_ASSERT(isConstant()); return u.cst_; }
const InitExpr& initExpr() const { MOZ_ASSERT(isVariable()); return u.var.val.initial_; }
uint32_t importIndex() const { MOZ_ASSERT(isImport()); return u.var.val.import.index_; }
ValType type() const {
switch (kind_) {
case GlobalKind::Import: return u.var.val.import.type_;
case GlobalKind::Variable: return u.var.val.initial_.type();
case GlobalKind::Constant: return u.cst_.type();
}
MOZ_CRASH("unexpected global kind");
}
};
typedef Vector<GlobalDesc, 0, SystemAllocPolicy> GlobalDescVector;
// DataSegment describes the offset of a data segment in the bytecode that is
// to be copied at a given offset into linear memory upon instantiation.
struct DataSegment
{
InitExpr offset;
uint32_t bytecodeOffset;
uint32_t length;
};
typedef Vector<DataSegment, 0, SystemAllocPolicy> DataSegmentVector;
// SigIdDesc describes a signature id that can be used by call_indirect and
// table-entry prologues to structurally compare whether the caller and callee's
// signatures *structurally* match. To handle the general case, a Sig is
// allocated and stored in a process-wide hash table, so that pointer equality
// implies structural equality. As an optimization for the 99% case where the
// Sig has a small number of parameters, the Sig is bit-packed into a uint32
// immediate value so that integer equality implies structural equality. Both
// cases can be handled with a single comparison by always setting the LSB for
// the immediates (the LSB is necessarily 0 for allocated Sig pointers due to
// alignment).
class SigIdDesc
{
public:
enum class Kind { None, Immediate, Global };
static const uintptr_t ImmediateBit = 0x1;
private:
Kind kind_;
size_t bits_;
SigIdDesc(Kind kind, size_t bits) : kind_(kind), bits_(bits) {}
public:
Kind kind() const { return kind_; }
static bool isGlobal(const Sig& sig);
SigIdDesc() : kind_(Kind::None), bits_(0) {}
static SigIdDesc global(const Sig& sig, uint32_t globalDataOffset);
static SigIdDesc immediate(const Sig& sig);
bool isGlobal() const { return kind_ == Kind::Global; }
size_t immediate() const { MOZ_ASSERT(kind_ == Kind::Immediate); return bits_; }
uint32_t globalDataOffset() const { MOZ_ASSERT(kind_ == Kind::Global); return bits_; }
};
// SigWithId pairs a Sig with SigIdDesc, describing either how to compile code
// that compares this signature's id or, at instantiation what signature ids to
// allocate in the global hash and where to put them.
struct SigWithId : Sig
{
SigIdDesc id;
SigWithId() = default;
explicit SigWithId(Sig&& sig, SigIdDesc id) : Sig(Move(sig)), id(id) {}
void operator=(Sig&& rhs) { Sig::operator=(Move(rhs)); }
WASM_DECLARE_SERIALIZABLE(SigWithId)
};
typedef Vector<SigWithId, 0, SystemAllocPolicy> SigWithIdVector;
typedef Vector<const SigWithId*, 0, SystemAllocPolicy> SigWithIdPtrVector;
// The (,Profiling,Func)Offsets classes are used to record the offsets of
// different key points in a CodeRange during compilation.
struct Offsets
{
explicit Offsets(uint32_t begin = 0, uint32_t end = 0)
: begin(begin), end(end)
{}
// These define a [begin, end) contiguous range of instructions compiled
// into a CodeRange.
uint32_t begin;
uint32_t end;
void offsetBy(uint32_t offset) {
begin += offset;
end += offset;
}
};
struct ProfilingOffsets : Offsets
{
MOZ_IMPLICIT ProfilingOffsets(uint32_t profilingReturn = 0)
: Offsets(), profilingReturn(profilingReturn)
{}
// For CodeRanges with ProfilingOffsets, 'begin' is the offset of the
// profiling entry.
uint32_t profilingEntry() const { return begin; }
// The profiling return is the offset of the return instruction, which
// precedes the 'end' by a variable number of instructions due to
// out-of-line codegen.
uint32_t profilingReturn;
void offsetBy(uint32_t offset) {
Offsets::offsetBy(offset);
profilingReturn += offset;
}
};
struct FuncOffsets : ProfilingOffsets
{
MOZ_IMPLICIT FuncOffsets()
: ProfilingOffsets(),
tableEntry(0),
tableProfilingJump(0),
nonProfilingEntry(0),
profilingJump(0),
profilingEpilogue(0)
{}
// Function CodeRanges have a table entry which takes an extra signature
// argument which is checked against the callee's signature before falling
// through to the normal prologue. When profiling is enabled, a nop on the
// fallthrough is patched to instead jump to the profiling epilogue.
uint32_t tableEntry;
uint32_t tableProfilingJump;
// Function CodeRanges have an additional non-profiling entry that comes
// after the profiling entry and a non-profiling epilogue that comes before
// the profiling epilogue.
uint32_t nonProfilingEntry;
// When profiling is enabled, the 'nop' at offset 'profilingJump' is
// overwritten to be a jump to 'profilingEpilogue'.
uint32_t profilingJump;
uint32_t profilingEpilogue;
void offsetBy(uint32_t offset) {
ProfilingOffsets::offsetBy(offset);
tableEntry += offset;
tableProfilingJump += offset;
nonProfilingEntry += offset;
profilingJump += offset;
profilingEpilogue += offset;
}
};
// A wasm::Trap represents a wasm-defined trap that can occur during execution
// which triggers a WebAssembly.RuntimeError. Generated code may jump to a Trap
// symbolically, passing the bytecode offset to report as the trap offset. The
// generated jump will be bound to a tiny stub which fills the offset and
// then jumps to a per-Trap shared stub at the end of the module.
enum class Trap
{
// The Unreachable opcode has been executed.
Unreachable,
// An integer arithmetic operation led to an overflow.
IntegerOverflow,
// Trying to coerce NaN to an integer.
InvalidConversionToInteger,
// Integer division by zero.
IntegerDivideByZero,
// Out of bounds on wasm memory accesses and asm.js SIMD/atomic accesses.
OutOfBounds,
// call_indirect to null.
IndirectCallToNull,
// call_indirect signature mismatch.
IndirectCallBadSig,
// (asm.js only) SIMD float to int conversion failed because the input
// wasn't in bounds.
ImpreciseSimdConversion,
// The internal stack space was exhausted. For compatibility, this throws
// the same over-recursed error as JS.
StackOverflow,
Limit
};
// A wrapper around the bytecode offset of a wasm instruction within a whole
// module. Trap offsets should refer to the first byte of the instruction that
// triggered the trap and should ultimately derive from OpIter::trapOffset.
struct TrapOffset
{
uint32_t bytecodeOffset;
TrapOffset() = default;
explicit TrapOffset(uint32_t bytecodeOffset) : bytecodeOffset(bytecodeOffset) {}
};
// While the frame-pointer chain allows the stack to be unwound without
// metadata, Error.stack still needs to know the line/column of every call in
// the chain. A CallSiteDesc describes a single callsite to which CallSite adds
// the metadata necessary to walk up to the next frame. Lastly CallSiteAndTarget
// adds the function index of the callee.
class CallSiteDesc
{
uint32_t lineOrBytecode_ : 30;
uint32_t kind_ : 2;
public:
enum Kind {
Func, // pc-relative call to a specific function
Dynamic, // dynamic callee called via register
Symbolic, // call to a single symbolic callee
TrapExit // call to a trap exit
};
CallSiteDesc() {}
explicit CallSiteDesc(Kind kind)
: lineOrBytecode_(0), kind_(kind)
{
MOZ_ASSERT(kind == Kind(kind_));
}
CallSiteDesc(uint32_t lineOrBytecode, Kind kind)
: lineOrBytecode_(lineOrBytecode), kind_(kind)
{
MOZ_ASSERT(kind == Kind(kind_));
MOZ_ASSERT(lineOrBytecode == lineOrBytecode_);
}
uint32_t lineOrBytecode() const { return lineOrBytecode_; }
Kind kind() const { return Kind(kind_); }
};
class CallSite : public CallSiteDesc
{
uint32_t returnAddressOffset_;
uint32_t stackDepth_;
public:
CallSite() {}
CallSite(CallSiteDesc desc, uint32_t returnAddressOffset, uint32_t stackDepth)
: CallSiteDesc(desc),
returnAddressOffset_(returnAddressOffset),
stackDepth_(stackDepth)
{ }
void setReturnAddressOffset(uint32_t r) { returnAddressOffset_ = r; }
void offsetReturnAddressBy(int32_t o) { returnAddressOffset_ += o; }
uint32_t returnAddressOffset() const { return returnAddressOffset_; }
// The stackDepth measures the amount of stack space pushed since the
// function was called. In particular, this includes the pushed return
// address on all archs (whether or not the call instruction pushes the
// return address (x86/x64) or the prologue does (ARM/MIPS)).
uint32_t stackDepth() const { return stackDepth_; }
};
WASM_DECLARE_POD_VECTOR(CallSite, CallSiteVector)
class CallSiteAndTarget : public CallSite
{
uint32_t index_;
public:
explicit CallSiteAndTarget(CallSite cs)
: CallSite(cs)
{
MOZ_ASSERT(cs.kind() != Func);
}
CallSiteAndTarget(CallSite cs, uint32_t funcIndex)
: CallSite(cs), index_(funcIndex)
{
MOZ_ASSERT(cs.kind() == Func);
}
CallSiteAndTarget(CallSite cs, Trap trap)
: CallSite(cs),
index_(uint32_t(trap))
{
MOZ_ASSERT(cs.kind() == TrapExit);
}
uint32_t funcIndex() const { MOZ_ASSERT(kind() == Func); return index_; }
Trap trap() const { MOZ_ASSERT(kind() == TrapExit); return Trap(index_); }
};
typedef Vector<CallSiteAndTarget, 0, SystemAllocPolicy> CallSiteAndTargetVector;
// A wasm::SymbolicAddress represents a pointer to a well-known function or
// object that is embedded in wasm code. Since wasm code is serialized and
// later deserialized into a different address space, symbolic addresses must be
// used for *all* pointers into the address space. The MacroAssembler records a
// list of all SymbolicAddresses and the offsets of their use in the code for
// later patching during static linking.
enum class SymbolicAddress
{
ToInt32,
#if defined(JS_CODEGEN_ARM)
aeabi_idivmod,
aeabi_uidivmod,
AtomicCmpXchg,
AtomicXchg,
AtomicFetchAdd,
AtomicFetchSub,
AtomicFetchAnd,
AtomicFetchOr,
AtomicFetchXor,
#endif
ModD,
SinD,
CosD,
TanD,
ASinD,
ACosD,
ATanD,
CeilD,
CeilF,
FloorD,
FloorF,
TruncD,
TruncF,
NearbyIntD,
NearbyIntF,
ExpD,
LogD,
PowD,
ATan2D,
Context,
InterruptUint32,
ReportOverRecursed,
HandleExecutionInterrupt,
ReportTrap,
ReportOutOfBounds,
ReportUnalignedAccess,
CallImport_Void,
CallImport_I32,
CallImport_I64,
CallImport_F64,
CoerceInPlace_ToInt32,
CoerceInPlace_ToNumber,
DivI64,
UDivI64,
ModI64,
UModI64,
TruncateDoubleToInt64,
TruncateDoubleToUint64,
Uint64ToFloatingPoint,
Int64ToFloatingPoint,
GrowMemory,
CurrentMemory,
Limit
};
void*
AddressOf(SymbolicAddress imm, ExclusiveContext* cx);
// Assumptions captures ambient state that must be the same when compiling and
// deserializing a module for the compiled code to be valid. If it's not, then
// the module must be recompiled from scratch.
struct Assumptions
{
uint32_t cpuId;
JS::BuildIdCharVector buildId;
explicit Assumptions(JS::BuildIdCharVector&& buildId);
// If Assumptions is constructed without arguments, initBuildIdFromContext()
// must be called to complete initialization.
Assumptions();
bool initBuildIdFromContext(ExclusiveContext* cx);
bool clone(const Assumptions& other);
bool operator==(const Assumptions& rhs) const;
bool operator!=(const Assumptions& rhs) const { return !(*this == rhs); }
size_t serializedSize() const;
uint8_t* serialize(uint8_t* cursor) const;
const uint8_t* deserialize(const uint8_t* cursor, size_t limit);
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
};
// A Module can either be asm.js or wasm.
enum ModuleKind
{
Wasm,
AsmJS
};
// Represents the resizable limits of memories and tables.
struct Limits
{
uint32_t initial;
Maybe<uint32_t> maximum;
};
// TableDesc describes a table as well as the offset of the table's base pointer
// in global memory. Currently, wasm only has "any function" and asm.js only
// "typed function".
enum class TableKind
{
AnyFunction,
TypedFunction
};
struct TableDesc
{
TableKind kind;
bool external;
uint32_t globalDataOffset;
Limits limits;
TableDesc() = default;
TableDesc(TableKind kind, Limits limits)
: kind(kind),
external(false),
globalDataOffset(UINT32_MAX),
limits(limits)
{}
};
typedef Vector<TableDesc, 0, SystemAllocPolicy> TableDescVector;
// ExportArg holds the unboxed operands to the wasm entry trampoline which can
// be called through an ExportFuncPtr.
struct ExportArg
{
uint64_t lo;
uint64_t hi;
};
// TLS data for a single module instance.
//
// Every WebAssembly function expects to be passed a hidden TLS pointer argument
// in WasmTlsReg. The TLS pointer argument points to a TlsData struct.
// Compiled functions expect that the TLS pointer does not change for the
// lifetime of the thread.
//
// There is a TlsData per module instance per thread, so inter-module calls need
// to pass the TLS pointer appropriate for the callee module.
//
// After the TlsData struct follows the module's declared TLS variables.
struct TlsData
{
// Pointer to the JSContext that contains this TLS data.
JSContext* cx;
// Pointer to the Instance that contains this TLS data.
Instance* instance;
// Pointer to the global data for this Instance.
uint8_t* globalData;
// Pointer to the base of the default memory (or null if there is none).
uint8_t* memoryBase;
// Stack limit for the current thread. This limit is checked against the
// stack pointer in the prologue of functions that allocate stack space. See
// `CodeGenerator::generateWasm`.
void* stackLimit;
};
typedef int32_t (*ExportFuncPtr)(ExportArg* args, TlsData* tls);
// FuncImportTls describes the region of wasm global memory allocated in the
// instance's thread-local storage for a function import. This is accessed
// directly from JIT code and mutated by Instance as exits become optimized and
// deoptimized.
struct FuncImportTls
{
// The code to call at an import site: a wasm callee, a thunk into C++, or a
// thunk into JIT code.
void* code;
// The callee's TlsData pointer, which must be loaded to WasmTlsReg (along
// with any pinned registers) before calling 'code'.
TlsData* tls;
// If 'code' points into a JIT code thunk, the BaselineScript of the callee,
// for bidirectional registration purposes.
jit::BaselineScript* baselineScript;
// A GC pointer which keeps the callee alive. For imported wasm functions,
// this points to the wasm function's WasmInstanceObject. For all other
// imported functions, 'obj' points to the JSFunction.
GCPtrObject obj;
static_assert(sizeof(GCPtrObject) == sizeof(void*), "for JIT access");
};
// TableTls describes the region of wasm global memory allocated in the
// instance's thread-local storage which is accessed directly from JIT code
// to bounds-check and index the table.
struct TableTls
{
// Length of the table in number of elements (not bytes).
uint32_t length;
// Pointer to the array of elements (of type either ExternalTableElem or
// void*).
void* base;
};
// When a table can contain functions from other instances (it is "external"),
// the internal representation is an array of ExternalTableElem instead of just
// an array of code pointers.
struct ExternalTableElem
{
// The code to call when calling this element. The table ABI is the system
// ABI with the additional ABI requirements that:
// - WasmTlsReg and any pinned registers have been loaded appropriately
// - if this is a heterogeneous table that requires a signature check,
// WasmTableCallSigReg holds the signature id.
void* code;
// The pointer to the callee's instance's TlsData. This must be loaded into
// WasmTlsReg before calling 'code'.
TlsData* tls;
};
// CalleeDesc describes how to compile one of the variety of asm.js/wasm calls.
// This is hoisted into WasmTypes.h for sharing between Ion and Baseline.
class CalleeDesc
{
public:
enum Which {
// Calls a function defined in the same module by its index.
Func,
// Calls the import identified by the offset of its FuncImportTls in
// thread-local data.
Import,
// Calls a WebAssembly table (heterogeneous, index must be bounds
// checked, callee instance depends on TableDesc).
WasmTable,
// Calls an asm.js table (homogeneous, masked index, same-instance).
AsmJSTable,
// Call a C++ function identified by SymbolicAddress.
Builtin,
// Like Builtin, but automatically passes Instance* as first argument.
BuiltinInstanceMethod
};
private:
Which which_;
union U {
U() {}
uint32_t funcIndex_;
struct {
uint32_t globalDataOffset_;
} import;
struct {
uint32_t globalDataOffset_;
bool external_;
SigIdDesc sigId_;
} table;
SymbolicAddress builtin_;
} u;
public:
CalleeDesc() {}
static CalleeDesc function(uint32_t funcIndex) {
CalleeDesc c;
c.which_ = Func;
c.u.funcIndex_ = funcIndex;
return c;
}
static CalleeDesc import(uint32_t globalDataOffset) {
CalleeDesc c;
c.which_ = Import;
c.u.import.globalDataOffset_ = globalDataOffset;
return c;
}
static CalleeDesc wasmTable(const TableDesc& desc, SigIdDesc sigId) {
CalleeDesc c;
c.which_ = WasmTable;
c.u.table.globalDataOffset_ = desc.globalDataOffset;
c.u.table.external_ = desc.external;
c.u.table.sigId_ = sigId;
return c;
}
static CalleeDesc asmJSTable(const TableDesc& desc) {
CalleeDesc c;
c.which_ = AsmJSTable;
c.u.table.globalDataOffset_ = desc.globalDataOffset;
return c;
}
static CalleeDesc builtin(SymbolicAddress callee) {
CalleeDesc c;
c.which_ = Builtin;
c.u.builtin_ = callee;
return c;
}
static CalleeDesc builtinInstanceMethod(SymbolicAddress callee) {
CalleeDesc c;
c.which_ = BuiltinInstanceMethod;
c.u.builtin_ = callee;
return c;
}
Which which() const {
return which_;
}
uint32_t funcIndex() const {
MOZ_ASSERT(which_ == Func);
return u.funcIndex_;
}
uint32_t importGlobalDataOffset() const {
MOZ_ASSERT(which_ == Import);
return u.import.globalDataOffset_;
}
bool isTable() const {
return which_ == WasmTable || which_ == AsmJSTable;
}
uint32_t tableLengthGlobalDataOffset() const {
MOZ_ASSERT(isTable());
return u.table.globalDataOffset_ + offsetof(TableTls, length);
}
uint32_t tableBaseGlobalDataOffset() const {
MOZ_ASSERT(isTable());
return u.table.globalDataOffset_ + offsetof(TableTls, base);
}
bool wasmTableIsExternal() const {
MOZ_ASSERT(which_ == WasmTable);
return u.table.external_;
}
SigIdDesc wasmTableSigId() const {
MOZ_ASSERT(which_ == WasmTable);
return u.table.sigId_;
}
SymbolicAddress builtin() const {
MOZ_ASSERT(which_ == Builtin || which_ == BuiltinInstanceMethod);
return u.builtin_;
}
};
// Because ARM has a fixed-width instruction encoding, ARM can only express a
// limited subset of immediates (in a single instruction).
extern bool
IsValidARMImmediate(uint32_t i);
extern uint32_t
RoundUpToNextValidARMImmediate(uint32_t i);
// The WebAssembly spec hard-codes the virtual page size to be 64KiB and
// requires the size of linear memory to always be a multiple of 64KiB.
static const unsigned PageSize = 64 * 1024;
// Bounds checks always compare the base of the memory access with the bounds
// check limit. If the memory access is unaligned, this means that, even if the
// bounds check succeeds, a few bytes of the access can extend past the end of
// memory. To guard against this, extra space is included in the guard region to
// catch the overflow. MaxMemoryAccessSize is a conservative approximation of
// the maximum guard space needed to catch all unaligned overflows.
static const unsigned MaxMemoryAccessSize = sizeof(Val);
#ifdef JS_CODEGEN_X64
// All other code should use WASM_HUGE_MEMORY instead of JS_CODEGEN_X64 so that
// it is easy to use the huge-mapping optimization for other 64-bit platforms in
// the future.
# define WASM_HUGE_MEMORY
// On WASM_HUGE_MEMORY platforms, every asm.js or WebAssembly memory
// unconditionally allocates a huge region of virtual memory of size
// wasm::HugeMappedSize. This allows all memory resizing to work without
// reallocation and provides enough guard space for all offsets to be folded
// into memory accesses.
static const uint64_t IndexRange = uint64_t(UINT32_MAX) + 1;
static const uint64_t OffsetGuardLimit = uint64_t(INT32_MAX) + 1;
static const uint64_t UnalignedGuardPage = PageSize;
static const uint64_t HugeMappedSize = IndexRange + OffsetGuardLimit + UnalignedGuardPage;
static_assert(MaxMemoryAccessSize <= UnalignedGuardPage, "rounded up to static page size");
#else // !WASM_HUGE_MEMORY
// On !WASM_HUGE_MEMORY platforms:
// - To avoid OOM in ArrayBuffer::prepareForAsmJS, asm.js continues to use the
// original ArrayBuffer allocation which has no guard region at all.
// - For WebAssembly memories, an additional GuardSize is mapped after the
// accessible region of the memory to catch folded (base+offset) accesses
// where `offset < OffsetGuardLimit` as well as the overflow from unaligned
// accesses, as described above for MaxMemoryAccessSize.
static const size_t OffsetGuardLimit = PageSize - MaxMemoryAccessSize;
static const size_t GuardSize = PageSize;
// Return whether the given immediate satisfies the constraints of the platform
// (viz. that, on ARM, IsValidARMImmediate).
extern bool
IsValidBoundsCheckImmediate(uint32_t i);
// For a given WebAssembly/asm.js max size, return the number of bytes to
// map which will necessarily be a multiple of the system page size and greater
// than maxSize. For a returned mappedSize:
// boundsCheckLimit = mappedSize - GuardSize
// IsValidBoundsCheckImmediate(boundsCheckLimit)
extern size_t
ComputeMappedSize(uint32_t maxSize);
#endif // WASM_HUGE_MEMORY
// Metadata for bounds check instructions that are patched at runtime with the
// appropriate bounds check limit. On WASM_HUGE_MEMORY platforms for wasm (and
// SIMD/Atomic) bounds checks, no BoundsCheck is created: the signal handler
// catches everything. On !WASM_HUGE_MEMORY, a BoundsCheck is created for each
// memory access (except when statically eliminated by optimizations) so that
// the length can be patched in as an immediate. This requires that the bounds
// check limit IsValidBoundsCheckImmediate.
class BoundsCheck
{
public:
BoundsCheck() = default;
explicit BoundsCheck(uint32_t cmpOffset)
: cmpOffset_(cmpOffset)
{ }
uint8_t* patchAt(uint8_t* code) const { return code + cmpOffset_; }
void offsetBy(uint32_t offset) { cmpOffset_ += offset; }
private:
uint32_t cmpOffset_;
};
WASM_DECLARE_POD_VECTOR(BoundsCheck, BoundsCheckVector)
// Metadata for memory accesses. On WASM_HUGE_MEMORY platforms, only
// (non-SIMD/Atomic) asm.js loads and stores create a MemoryAccess so that the
// signal handler can implement the semantically-correct wraparound logic; the
// rest simply redirect to the out-of-bounds stub in the signal handler. On x86,
// the base address of memory is baked into each memory access instruction so
// the MemoryAccess records the location of each for patching. On all other
// platforms, no MemoryAccess is created.
class MemoryAccess
{
uint32_t insnOffset_;
uint32_t trapOutOfLineOffset_;
public:
MemoryAccess() = default;
explicit MemoryAccess(uint32_t insnOffset, uint32_t trapOutOfLineOffset = UINT32_MAX)
: insnOffset_(insnOffset),
trapOutOfLineOffset_(trapOutOfLineOffset)
{}
uint32_t insnOffset() const {
return insnOffset_;
}
bool hasTrapOutOfLineCode() const {
return trapOutOfLineOffset_ != UINT32_MAX;
}
uint8_t* trapOutOfLineCode(uint8_t* code) const {
MOZ_ASSERT(hasTrapOutOfLineCode());
return code + trapOutOfLineOffset_;
}
void offsetBy(uint32_t delta) {
insnOffset_ += delta;
if (hasTrapOutOfLineCode())
trapOutOfLineOffset_ += delta;
}
};
WASM_DECLARE_POD_VECTOR(MemoryAccess, MemoryAccessVector)
// Metadata for the offset of an instruction to patch with the base address of
// memory. In practice, this is only used for x86 where the offset points to the
// *end* of the instruction (which is a non-fixed offset from the beginning of
// the instruction). As part of the move away from code patching, this should be
// removed.
struct MemoryPatch
{
uint32_t offset;
MemoryPatch() = default;
explicit MemoryPatch(uint32_t offset) : offset(offset) {}
void offsetBy(uint32_t delta) {
offset += delta;
}
};
WASM_DECLARE_POD_VECTOR(MemoryPatch, MemoryPatchVector)
// Constants:
static const unsigned NaN64GlobalDataOffset = 0;
static const unsigned NaN32GlobalDataOffset = NaN64GlobalDataOffset + sizeof(double);
static const unsigned InitialGlobalDataBytes = NaN32GlobalDataOffset + sizeof(float);
static const unsigned MaxSigs = 4 * 1024;
static const unsigned MaxFuncs = 512 * 1024;
static const unsigned MaxGlobals = 4 * 1024;
static const unsigned MaxLocals = 64 * 1024;
static const unsigned MaxImports = 64 * 1024;
static const unsigned MaxExports = 64 * 1024;
static const unsigned MaxTables = 4 * 1024;
static const unsigned MaxTableElems = 1024 * 1024;
static const unsigned MaxDataSegments = 64 * 1024;
static const unsigned MaxElemSegments = 64 * 1024;
static const unsigned MaxArgsPerFunc = 4 * 1024;
static const unsigned MaxBrTableElems = 4 * 1024 * 1024;
// To be able to assign function indices during compilation while the number of
// imports is still unknown, asm.js sets a maximum number of imports so it can
// immediately start handing out function indices starting at the maximum + 1.
// this means that there is a "hole" between the last import and the first
// definition, but that's fine.
static const unsigned AsmJSMaxImports = 4 * 1024;
static const unsigned AsmJSFirstDefFuncIndex = AsmJSMaxImports + 1;
static_assert(AsmJSMaxImports <= MaxImports, "conservative");
static_assert(AsmJSFirstDefFuncIndex < MaxFuncs, "conservative");
} // namespace wasm
} // namespace js
#endif // wasm_types_h