diff options
Diffstat (limited to 'js/src/wasm/WasmValue.h')
-rw-r--r-- | js/src/wasm/WasmValue.h | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/js/src/wasm/WasmValue.h b/js/src/wasm/WasmValue.h new file mode 100644 index 0000000000..66de690537 --- /dev/null +++ b/js/src/wasm/WasmValue.h @@ -0,0 +1,552 @@ +/* -*- 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/WasmAnyRef.h" +#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"); + +// 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); + + static FuncRef null() { return FuncRef(nullptr); } + + AnyRef toAnyRef() { return AnyRef::fromJSObjectOrNull((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_; + // Mutable so that it can be traced + mutable 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() = default; + + 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_ = nullptr; + 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() = default; + 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.toAnyRef(); + } + + 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 isInvalid() const { return !type_.isValid(); } + bool isAnyRef() const { return type_.isValid() && type_.isRefRepr(); } + AnyRef& toAnyRef() const { + MOZ_ASSERT(isAnyRef()); + return cell_.ref_; + } + + // Updates the type of the Val. Does not check that the type is valid for the + // actual value, so make sure the type is definitely correct via validation or + // something. + void unsafeSetType(ValType type) { type_ = type; } + + // 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 'i31ref'. +[[nodiscard]] extern bool CheckI31RefValue(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, 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, StorageType type, + MutableHandleValue dst, + CoercionLevel level = CoercionLevel::Spec); +template <typename Debug = NoDebug> +extern bool ToJSValueMayGC(StorageType 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::AnyRef> { + static bool isMarkable(const wasm::AnyRef v) { return v.isGCThing(); } + + static void preBarrier(const wasm::AnyRef v) { + if (v.isGCThing()) { + gc::PreWriteBarrierImpl(v.toGCThing()); + } + } + + static MOZ_ALWAYS_INLINE void postBarrier(wasm::AnyRef* vp, + const wasm::AnyRef prev, + const wasm::AnyRef next) { + // If the target needs an entry, add it. + gc::StoreBuffer* sb; + if (next.isGCThing() && (sb = next.toGCThing()->storeBuffer())) { + // If we know that the prev has already inserted an entry, we can + // skip doing the lookup to add the new entry. Note that we cannot + // safely assert the presence of the entry because it may have been + // added via a different store buffer. + if (prev.isGCThing() && prev.toGCThing()->storeBuffer()) { + return; + } + sb->putWasmAnyRef(vp); + return; + } + // Remove the prev entry if the new value does not need it. + if (prev.isGCThing() && (sb = prev.toGCThing()->storeBuffer())) { + sb->unputWasmAnyRef(vp); + } + } + + static void readBarrier(const wasm::AnyRef v) { + if (v.isGCThing()) { + gc::ReadBarrierImpl(v.toGCThing()); + } + } + +#ifdef DEBUG + static void assertThingIsNotGray(const wasm::AnyRef v) { + if (v.isGCThing()) { + JS::AssertCellIsNotGray(v.toGCThing()); + } + } +#endif +}; + +template <> +struct InternalBarrierMethods<wasm::Val> { + static bool isMarkable(const wasm::Val& v) { return v.isAnyRef(); } + + static void preBarrier(const wasm::Val& v) { + if (v.isAnyRef()) { + InternalBarrierMethods<wasm::AnyRef>::preBarrier(v.toAnyRef()); + } + } + + static MOZ_ALWAYS_INLINE void postBarrier(wasm::Val* vp, + const wasm::Val& prev, + const wasm::Val& next) { + // A wasm::Val can transition from being uninitialized to holding an anyref + // but cannot change kind after that. + MOZ_ASSERT_IF(next.isAnyRef(), prev.isAnyRef() || prev.isInvalid()); + MOZ_ASSERT_IF(prev.isAnyRef(), next.isAnyRef()); + + if (next.isAnyRef()) { + InternalBarrierMethods<wasm::AnyRef>::postBarrier( + &vp->toAnyRef(), + prev.isAnyRef() ? prev.toAnyRef() : wasm::AnyRef::null(), + next.toAnyRef()); + return; + } + } + + static void readBarrier(const wasm::Val& v) { + if (v.isAnyRef()) { + InternalBarrierMethods<wasm::AnyRef>::readBarrier(v.toAnyRef()); + } + } + +#ifdef DEBUG + static void assertThingIsNotGray(const wasm::Val& v) { + if (v.isAnyRef()) { + InternalBarrierMethods<wasm::AnyRef>::assertThingIsNotGray(v.toAnyRef()); + } + } +#endif +}; + +} // namespace js + +template <> +struct JS::SafelyInitialized<js::wasm::AnyRef> { + static js::wasm::AnyRef create() { return js::wasm::AnyRef::null(); } +}; + +#endif // wasm_val_h |