diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /js/src/wasm/WasmTypes.h | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/wasm/WasmTypes.h')
-rw-r--r-- | js/src/wasm/WasmTypes.h | 1510 |
1 files changed, 1510 insertions, 0 deletions
diff --git a/js/src/wasm/WasmTypes.h b/js/src/wasm/WasmTypes.h new file mode 100644 index 0000000000..c79af12e5e --- /dev/null +++ b/js/src/wasm/WasmTypes.h @@ -0,0 +1,1510 @@ +/* -*- 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 |