diff options
Diffstat (limited to 'js/src/wasm/WasmValue.h')
-rw-r--r-- | js/src/wasm/WasmValue.h | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/js/src/wasm/WasmValue.h b/js/src/wasm/WasmValue.h new file mode 100644 index 0000000000..26ff65e327 --- /dev/null +++ b/js/src/wasm/WasmValue.h @@ -0,0 +1,652 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * + * Copyright 2021 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_val_h +#define wasm_val_h + +#include "js/Class.h" // JSClassOps, ClassSpec +#include "vm/JSObject.h" +#include "vm/NativeObject.h" // NativeObject +#include "wasm/WasmSerialize.h" +#include "wasm/WasmTypeDef.h" + +namespace js { +namespace wasm { + +// A V128 value. + +struct V128 { + uint8_t bytes[16] = {}; // Little-endian + + WASM_CHECK_CACHEABLE_POD(bytes); + + V128() = default; + + explicit V128(uint8_t splatValue) { + memset(bytes, int(splatValue), sizeof(bytes)); + } + + template <typename T> + void extractLane(unsigned lane, T* result) const { + MOZ_ASSERT(lane < 16 / sizeof(T)); + memcpy(result, bytes + sizeof(T) * lane, sizeof(T)); + } + + template <typename T> + void insertLane(unsigned lane, T value) { + MOZ_ASSERT(lane < 16 / sizeof(T)); + memcpy(bytes + sizeof(T) * lane, &value, sizeof(T)); + } + + bool operator==(const V128& rhs) const { + for (size_t i = 0; i < sizeof(bytes); i++) { + if (bytes[i] != rhs.bytes[i]) { + return false; + } + } + return true; + } + + bool operator!=(const V128& rhs) const { return !(*this == rhs); } +}; + +WASM_DECLARE_CACHEABLE_POD(V128); + +static_assert(sizeof(V128) == 16, "Invariant"); + +// An AnyRef is a boxed value that can represent any wasm reference type and any +// host type that the host system allows to flow into and out of wasm +// transparently. It is a pointer-sized datum that has the same representation +// as all its subtypes (funcref, externref, eqref, (ref T), et al) due to the +// non-coercive subtyping of the wasm type system. Its current representation +// is a plain JSObject*, and the private JSObject subtype WasmValueBox is used +// to box non-object non-null JS values. +// +// The C++/wasm boundary always uses a 'void*' type to express AnyRef values, to +// emphasize the pointer-ness of the value. The C++ code must transform the +// void* into an AnyRef by calling AnyRef::fromCompiledCode(), and transform an +// AnyRef into a void* by calling AnyRef::toCompiledCode(). Once in C++, we use +// AnyRef everywhere. A JS Value is transformed into an AnyRef by calling +// AnyRef::box(), and the AnyRef is transformed into a JS Value by calling +// AnyRef::unbox(). +// +// NOTE that AnyRef values may point to GC'd storage and as such need to be +// rooted if they are kept live in boxed form across code that may cause GC! +// Use RootedAnyRef / HandleAnyRef / MutableHandleAnyRef where necessary. +// +// The lowest bits of the pointer value are used for tagging, to allow for some +// representation optimizations and to distinguish various types. + +// For version 0, we simply equate AnyRef and JSObject* (this means that there +// are technically no tags at all yet). We use a simple boxing scheme that +// wraps a JS value that is not already JSObject in a distinguishable JSObject +// that holds the value, see WasmTypes.cpp for details. Knowledge of this +// mapping is embedded in CodeGenerator.cpp (in WasmBoxValue and +// WasmAnyRefFromJSObject) and in WasmStubs.cpp (in functions Box* and Unbox*). + +class AnyRef { + // mutable so that tracing may access a JSObject* from a `const Val` or + // `const AnyRef`. + mutable JSObject* value_; + + explicit AnyRef() : value_((JSObject*)-1) {} + explicit AnyRef(JSObject* p) : value_(p) { + MOZ_ASSERT(((uintptr_t)p & 0x03) == 0); + } + + public: + // An invalid AnyRef cannot arise naturally from wasm and so can be used as + // a sentinel value to indicate failure from an AnyRef-returning function. + static AnyRef invalid() { return AnyRef(); } + + // Given a void* that comes from compiled wasm code, turn it into AnyRef. + static AnyRef fromCompiledCode(void* p) { return AnyRef((JSObject*)p); } + + // Given a JSObject* that comes from JS, turn it into AnyRef. + static AnyRef fromJSObject(JSObject* p) { return AnyRef(p); } + + // Generate an AnyRef null pointer. + static AnyRef null() { return AnyRef(nullptr); } + + bool isNull() const { return value_ == nullptr; } + + bool operator==(const AnyRef& rhs) const { + return this->value_ == rhs.value_; + } + + bool operator!=(const AnyRef& rhs) const { return !(*this == rhs); } + + void* forCompiledCode() const { return value_; } + + JSObject* asJSObject() const { return value_; } + + JSObject** asJSObjectAddress() const { return &value_; } + + void trace(JSTracer* trc); + + // Tags (to be developed further) + static constexpr uintptr_t AnyRefTagMask = 1; + static constexpr uintptr_t AnyRefObjTag = 0; +}; + +using RootedAnyRef = Rooted<AnyRef>; +using HandleAnyRef = Handle<AnyRef>; +using MutableHandleAnyRef = MutableHandle<AnyRef>; + +// TODO/AnyRef-boxing: With boxed immediates and strings, these will be defined +// as MOZ_CRASH or similar so that we can find all locations that need to be +// fixed. + +#define ASSERT_ANYREF_IS_JSOBJECT (void)(0) +#define STATIC_ASSERT_ANYREF_IS_JSOBJECT static_assert(1, "AnyRef is JSObject") + +// Given any JS value, box it as an AnyRef and store it in *result. Returns +// false on OOM. + +bool BoxAnyRef(JSContext* cx, HandleValue val, MutableHandleAnyRef result); + +// Given a JS value that requires an object box, box it as an AnyRef and return +// it, returning nullptr on OOM. +// +// Currently the values requiring a box are those other than JSObject* or +// nullptr, but in the future more values will be represented without an +// allocation. +JSObject* BoxBoxableValue(JSContext* cx, HandleValue val); + +// Given any AnyRef, unbox it as a JS Value. If it is a reference to a wasm +// object it will be reflected as a JSObject* representing some TypedObject +// instance. + +Value UnboxAnyRef(AnyRef val); + +class WasmValueBox : public NativeObject { + static const unsigned VALUE_SLOT = 0; + + public: + static const unsigned RESERVED_SLOTS = 1; + static const JSClass class_; + + static WasmValueBox* create(JSContext* cx, HandleValue val); + Value value() const { return getFixedSlot(VALUE_SLOT); } + static size_t offsetOfValue() { + return NativeObject::getFixedSlotOffset(VALUE_SLOT); + } +}; + +// A FuncRef is a JSFunction* and is hence also an AnyRef, and the remarks above +// about AnyRef apply also to FuncRef. When 'funcref' is used as a value type +// in wasm code, the value that is held is "the canonical function value", which +// is a function for which IsWasmExportedFunction() is true, and which has the +// correct identity wrt reference equality of functions. Notably, if a function +// is imported then its ref.func value compares === in JS to the function that +// was passed as an import when the instance was created. +// +// These rules ensure that casts from funcref to anyref are non-converting +// (generate no code), and that no wrapping or unwrapping needs to happen when a +// funcref or anyref flows across the JS/wasm boundary, and that functions have +// the necessary identity when observed from JS, and in the future, from wasm. +// +// Functions stored in tables, whether wasm tables or internal tables, can be +// stored in a form that optimizes for eg call speed, however. +// +// Reading a funcref from a funcref table, writing a funcref to a funcref table, +// and generating the value for a ref.func instruction are therefore nontrivial +// operations that require mapping between the canonical JSFunction and the +// optimized table representation. Once we get an instruction to call a +// ref.func directly it too will require such a mapping. + +// In many cases, a FuncRef is exactly the same as AnyRef and we can use AnyRef +// functionality on funcref values. The FuncRef class exists mostly to add more +// checks and to make it clear, when we need to, that we're manipulating funcref +// values. FuncRef does not currently subclass AnyRef because there's been no +// need to, but it probably could. + +class FuncRef { + // mutable so that tracing may access a JSFunction* from a `const FuncRef` + mutable JSFunction* value_; + + explicit FuncRef() : value_((JSFunction*)-1) {} + explicit FuncRef(JSFunction* p) : value_(p) { + MOZ_ASSERT(((uintptr_t)p & 0x03) == 0); + } + + public: + // Given a void* that comes from compiled wasm code, turn it into FuncRef. + static FuncRef fromCompiledCode(void* p) { return FuncRef((JSFunction*)p); } + + // Given a JSFunction* that comes from JS, turn it into FuncRef. + static FuncRef fromJSFunction(JSFunction* p) { return FuncRef(p); } + + // Given an AnyRef that represents a possibly-null funcref, turn it into a + // FuncRef. + static FuncRef fromAnyRefUnchecked(AnyRef p); + + AnyRef asAnyRef() { return AnyRef::fromJSObject((JSObject*)value_); } + + void* forCompiledCode() const { return value_; } + + JSFunction* asJSFunction() { return value_; } + + bool isNull() const { return value_ == nullptr; } + + void trace(JSTracer* trc) const; +}; + +using RootedFuncRef = Rooted<FuncRef>; +using HandleFuncRef = Handle<FuncRef>; +using MutableHandleFuncRef = MutableHandle<FuncRef>; + +// Given any FuncRef, unbox it as a JS Value -- always a JSFunction*. + +Value UnboxFuncRef(FuncRef val); + +// The LitVal class represents a single WebAssembly value of a given value +// type, mostly for the purpose of numeric literals and initializers. A LitVal +// does not directly map to a JS value since there is not (currently) a precise +// representation of i64 values. A LitVal may contain non-canonical NaNs since, +// within WebAssembly, floats are not canonicalized. Canonicalization must +// happen at the JS boundary. + +class LitVal { + public: + union Cell { + uint32_t i32_; + uint64_t i64_; + float f32_; + double f64_; + wasm::V128 v128_; + wasm::AnyRef ref_; + + Cell() : v128_() {} + ~Cell() = default; + + WASM_CHECK_CACHEABLE_POD(i32_, i64_, f32_, f64_, v128_); + WASM_ALLOW_NON_CACHEABLE_POD_FIELD( + ref_, + "The pointer value in ref_ is guaranteed to always be null in a " + "LitVal."); + }; + + protected: + ValType type_; + Cell cell_; + + public: + LitVal() : type_(ValType()), cell_{} {} + + explicit LitVal(ValType type) : type_(type) { + switch (type.kind()) { + case ValType::Kind::I32: { + cell_.i32_ = 0; + break; + } + case ValType::Kind::I64: { + cell_.i64_ = 0; + break; + } + case ValType::Kind::F32: { + cell_.f32_ = 0; + break; + } + case ValType::Kind::F64: { + cell_.f64_ = 0; + break; + } + case ValType::Kind::V128: { + new (&cell_.v128_) V128(); + break; + } + case ValType::Kind::Ref: { + cell_.ref_ = AnyRef::null(); + break; + } + } + } + + explicit LitVal(uint32_t i32) : type_(ValType::I32) { cell_.i32_ = i32; } + explicit LitVal(uint64_t i64) : type_(ValType::I64) { cell_.i64_ = i64; } + + explicit LitVal(float f32) : type_(ValType::F32) { cell_.f32_ = f32; } + explicit LitVal(double f64) : type_(ValType::F64) { cell_.f64_ = f64; } + + explicit LitVal(V128 v128) : type_(ValType::V128) { cell_.v128_ = v128; } + + explicit LitVal(ValType type, AnyRef any) : type_(type) { + MOZ_ASSERT(type.isRefRepr()); + MOZ_ASSERT(any.isNull(), + "use Val for non-nullptr ref types to get tracing"); + cell_.ref_ = any; + } + + ValType type() const { return type_; } + static constexpr size_t sizeofLargestValue() { return sizeof(cell_); } + + Cell& cell() { return cell_; } + const Cell& cell() const { return cell_; } + + uint32_t i32() const { + MOZ_ASSERT(type_ == ValType::I32); + return cell_.i32_; + } + uint64_t i64() const { + MOZ_ASSERT(type_ == ValType::I64); + return cell_.i64_; + } + const float& f32() const { + MOZ_ASSERT(type_ == ValType::F32); + return cell_.f32_; + } + const double& f64() const { + MOZ_ASSERT(type_ == ValType::F64); + return cell_.f64_; + } + AnyRef ref() const { + MOZ_ASSERT(type_.isRefRepr()); + return cell_.ref_; + } + const V128& v128() const { + MOZ_ASSERT(type_ == ValType::V128); + return cell_.v128_; + } + + WASM_DECLARE_FRIEND_SERIALIZE(LitVal); +}; + +WASM_DECLARE_CACHEABLE_POD(LitVal::Cell); + +// A Val is a LitVal that can contain (non-null) pointers to GC things. All Vals +// must be used with the rooting APIs as they may contain JS objects. + +class MOZ_NON_PARAM Val : public LitVal { + public: + Val() : LitVal() {} + explicit Val(ValType type) : LitVal(type) {} + explicit Val(const LitVal& val); + explicit Val(uint32_t i32) : LitVal(i32) {} + explicit Val(uint64_t i64) : LitVal(i64) {} + explicit Val(float f32) : LitVal(f32) {} + explicit Val(double f64) : LitVal(f64) {} + explicit Val(V128 v128) : LitVal(v128) {} + explicit Val(ValType type, AnyRef val) : LitVal(type, AnyRef::null()) { + MOZ_ASSERT(type.isRefRepr()); + cell_.ref_ = val; + } + explicit Val(ValType type, FuncRef val) : LitVal(type, AnyRef::null()) { + MOZ_ASSERT(type.refType().isFuncHierarchy()); + cell_.ref_ = val.asAnyRef(); + } + + Val(const Val&) = default; + Val& operator=(const Val&) = default; + + bool operator==(const Val& rhs) const { + if (type_ != rhs.type_) { + return false; + } + switch (type_.kind()) { + case ValType::I32: + return cell_.i32_ == rhs.cell_.i32_; + case ValType::I64: + return cell_.i64_ == rhs.cell_.i64_; + case ValType::F32: + return cell_.f32_ == rhs.cell_.f32_; + case ValType::F64: + return cell_.f64_ == rhs.cell_.f64_; + case ValType::V128: + return cell_.v128_ == rhs.cell_.v128_; + case ValType::Ref: + return cell_.ref_ == rhs.cell_.ref_; + } + MOZ_ASSERT_UNREACHABLE(); + return false; + } + bool operator!=(const Val& rhs) const { return !(*this == rhs); } + + bool isJSObject() const { + return type_.isValid() && type_.isRefRepr() && !cell_.ref_.isNull(); + } + + JSObject* asJSObject() const { + MOZ_ASSERT(isJSObject()); + return cell_.ref_.asJSObject(); + } + + JSObject** asJSObjectAddress() const { + return cell_.ref_.asJSObjectAddress(); + } + + // Read from `loc` which is a rooted location and needs no barriers. + void readFromRootedLocation(const void* loc); + + // Initialize from `loc` which is a rooted location and needs no barriers. + void initFromRootedLocation(ValType type, const void* loc); + void initFromHeapLocation(ValType type, const void* loc); + + // Write to `loc` which is a rooted location and needs no barriers. + void writeToRootedLocation(void* loc, bool mustWrite64) const; + + // Read from `loc` which is in the heap. + void readFromHeapLocation(const void* loc); + // Write to `loc` which is in the heap and must be barriered. + void writeToHeapLocation(void* loc) const; + + // See the comment for `ToWebAssemblyValue` below. + static bool fromJSValue(JSContext* cx, ValType targetType, HandleValue val, + MutableHandle<Val> rval); + // See the comment for `ToJSValue` below. + bool toJSValue(JSContext* cx, MutableHandleValue rval) const; + + void trace(JSTracer* trc) const; +}; + +using GCPtrVal = GCPtr<Val>; +using RootedVal = Rooted<Val>; +using HandleVal = Handle<Val>; +using MutableHandleVal = MutableHandle<Val>; + +using ValVector = GCVector<Val, 0, SystemAllocPolicy>; +using RootedValVector = Rooted<ValVector>; +using HandleValVector = Handle<ValVector>; +using MutableHandleValVector = MutableHandle<ValVector>; + +template <int N> +using ValVectorN = GCVector<Val, N, SystemAllocPolicy>; +template <int N> +using RootedValVectorN = Rooted<ValVectorN<N>>; + +// Check a value against the given reference type. If the targetType +// is RefType::Extern then the test always passes, but the value may be boxed. +// If the test passes then the value is stored either in fnval (for +// RefType::Func) or in refval (for other types); this split is not strictly +// necessary but is convenient for the users of this function. +// +// This can return false if the type check fails, or if a boxing into AnyRef +// throws an OOM. +[[nodiscard]] extern bool CheckRefType(JSContext* cx, RefType targetType, + HandleValue v, + MutableHandleFunction fnval, + MutableHandleAnyRef refval); + +// The same as above for when the target type is 'funcref'. +[[nodiscard]] extern bool CheckFuncRefValue(JSContext* cx, HandleValue v, + MutableHandleFunction fun); + +// The same as above for when the target type is 'anyref'. +[[nodiscard]] extern bool CheckAnyRefValue(JSContext* cx, HandleValue v, + MutableHandleAnyRef vp); + +// The same as above for when the target type is 'nullexternref'. +[[nodiscard]] extern bool CheckNullExternRefValue(JSContext* cx, HandleValue v, + MutableHandleAnyRef vp); + +// The same as above for when the target type is 'nullfuncref'. +[[nodiscard]] extern bool CheckNullFuncRefValue(JSContext* cx, HandleValue v, + MutableHandleFunction fun); + +// The same as above for when the target type is 'nullref'. +[[nodiscard]] extern bool CheckNullRefValue(JSContext* cx, HandleValue v, + MutableHandleAnyRef vp); + +// The same as above for when the target type is 'eqref'. +[[nodiscard]] extern bool CheckEqRefValue(JSContext* cx, HandleValue v, + MutableHandleAnyRef vp); + +// The same as above for when the target type is 'structref'. +[[nodiscard]] extern bool CheckStructRefValue(JSContext* cx, HandleValue v, + MutableHandleAnyRef vp); + +// The same as above for when the target type is 'arrayref'. +[[nodiscard]] extern bool CheckArrayRefValue(JSContext* cx, HandleValue v, + MutableHandleAnyRef vp); + +// The same as above for when the target type is '(ref T)'. +[[nodiscard]] extern bool CheckTypeRefValue(JSContext* cx, + const TypeDef* typeDef, + HandleValue v, + MutableHandleAnyRef vp); +class NoDebug; +class DebugCodegenVal; + +// The level of coercion to apply in `ToWebAssemblyValue` and `ToJSValue`. +enum class CoercionLevel { + // The default coercions given by the JS-API specification. + Spec, + // Allow for the coercions given by `Spec` but also use WebAssembly.Global + // as a container for lossless conversions. This is only available through + // the wasmLosslessInvoke testing function and is used in tests. + Lossless, +}; + +// Coercion function from a JS value to a WebAssembly value [1]. +// +// This function may fail for any of the following reasons: +// * The input value has an incorrect type for the targetType +// * The targetType is not exposable +// * An OOM ocurred +// An error will be set upon failure. +// +// [1] https://webassembly.github.io/spec/js-api/index.html#towebassemblyvalue +template <typename Debug = NoDebug> +extern bool ToWebAssemblyValue(JSContext* cx, HandleValue val, FieldType type, + void* loc, bool mustWrite64, + CoercionLevel level = CoercionLevel::Spec); +template <typename Debug = NoDebug> +extern bool ToWebAssemblyValue(JSContext* cx, HandleValue val, ValType type, + void* loc, bool mustWrite64, + CoercionLevel level = CoercionLevel::Spec); + +// Coercion function from a WebAssembly value to a JS value [1]. +// +// This function will only fail if an OOM ocurred. If the type of WebAssembly +// value being coerced is not exposable to JS, then it will be coerced to +// 'undefined'. Callers are responsible for guarding against this if this is +// not desirable. +// +// [1] https://webassembly.github.io/spec/js-api/index.html#tojsvalue +template <typename Debug = NoDebug> +extern bool ToJSValue(JSContext* cx, const void* src, FieldType type, + MutableHandleValue dst, + CoercionLevel level = CoercionLevel::Spec); +template <typename Debug = NoDebug> +extern bool ToJSValueMayGC(FieldType type); +template <typename Debug = NoDebug> +extern bool ToJSValue(JSContext* cx, const void* src, ValType type, + MutableHandleValue dst, + CoercionLevel level = CoercionLevel::Spec); +template <typename Debug = NoDebug> +extern bool ToJSValueMayGC(ValType type); +} // namespace wasm + +template <> +struct InternalBarrierMethods<wasm::Val> { + STATIC_ASSERT_ANYREF_IS_JSOBJECT; + + static bool isMarkable(const wasm::Val& v) { return v.isJSObject(); } + + static void preBarrier(const wasm::Val& v) { + if (v.isJSObject()) { + gc::PreWriteBarrier(v.asJSObject()); + } + } + + static MOZ_ALWAYS_INLINE void postBarrier(wasm::Val* vp, + const wasm::Val& prev, + const wasm::Val& next) { + MOZ_RELEASE_ASSERT(!prev.type().isValid() || prev.type() == next.type()); + JSObject* prevObj = prev.isJSObject() ? prev.asJSObject() : nullptr; + JSObject* nextObj = next.isJSObject() ? next.asJSObject() : nullptr; + if (nextObj) { + JSObject::postWriteBarrier(vp->asJSObjectAddress(), prevObj, nextObj); + } + } + + static void readBarrier(const wasm::Val& v) { + if (v.isJSObject()) { + gc::ReadBarrier(v.asJSObject()); + } + } + +#ifdef DEBUG + static void assertThingIsNotGray(const wasm::Val& v) { + if (v.isJSObject()) { + JS::AssertObjectIsNotGray(v.asJSObject()); + } + } +#endif +}; + +template <> +struct InternalBarrierMethods<wasm::AnyRef> { + STATIC_ASSERT_ANYREF_IS_JSOBJECT; + + static bool isMarkable(const wasm::AnyRef v) { return !v.isNull(); } + + static void preBarrier(const wasm::AnyRef v) { + if (!v.isNull()) { + gc::PreWriteBarrier(v.asJSObject()); + } + } + + static MOZ_ALWAYS_INLINE void postBarrier(wasm::AnyRef* vp, + const wasm::AnyRef prev, + const wasm::AnyRef next) { + JSObject* prevObj = !prev.isNull() ? prev.asJSObject() : nullptr; + JSObject* nextObj = !next.isNull() ? next.asJSObject() : nullptr; + if (nextObj) { + JSObject::postWriteBarrier(vp->asJSObjectAddress(), prevObj, nextObj); + } + } + + static void readBarrier(const wasm::AnyRef v) { + if (!v.isNull()) { + gc::ReadBarrier(v.asJSObject()); + } + } + +#ifdef DEBUG + static void assertThingIsNotGray(const wasm::AnyRef v) { + if (!v.isNull()) { + JS::AssertObjectIsNotGray(v.asJSObject()); + } + } +#endif +}; + +} // namespace js + +#endif // wasm_val_h |