/* -*- 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 void extractLane(unsigned lane, T* result) const { MOZ_ASSERT(lane < 16 / sizeof(T)); memcpy(result, bytes + sizeof(T) * lane, sizeof(T)); } template 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; using HandleAnyRef = Handle; using MutableHandleAnyRef = MutableHandle; // 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; using HandleFuncRef = Handle; using MutableHandleFuncRef = MutableHandle; // 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 rval); // See the comment for `ToJSValue` below. bool toJSValue(JSContext* cx, MutableHandleValue rval) const; void trace(JSTracer* trc) const; }; using GCPtrVal = GCPtr; using RootedVal = Rooted; using HandleVal = Handle; using MutableHandleVal = MutableHandle; using ValVector = GCVector; using RootedValVector = Rooted; using HandleValVector = Handle; using MutableHandleValVector = MutableHandle; template using ValVectorN = GCVector; template using RootedValVectorN = Rooted>; // 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 extern bool ToWebAssemblyValue(JSContext* cx, HandleValue val, FieldType type, void* loc, bool mustWrite64, CoercionLevel level = CoercionLevel::Spec); template 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 extern bool ToJSValue(JSContext* cx, const void* src, FieldType type, MutableHandleValue dst, CoercionLevel level = CoercionLevel::Spec); template extern bool ToJSValueMayGC(FieldType type); template extern bool ToJSValue(JSContext* cx, const void* src, ValType type, MutableHandleValue dst, CoercionLevel level = CoercionLevel::Spec); template extern bool ToJSValueMayGC(ValType type); } // namespace wasm template <> struct InternalBarrierMethods { 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 { 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