summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmAnyRef.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/wasm/WasmAnyRef.h
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/wasm/WasmAnyRef.h')
-rw-r--r--js/src/wasm/WasmAnyRef.h412
1 files changed, 412 insertions, 0 deletions
diff --git a/js/src/wasm/WasmAnyRef.h b/js/src/wasm/WasmAnyRef.h
new file mode 100644
index 0000000000..1675a9fa8d
--- /dev/null
+++ b/js/src/wasm/WasmAnyRef.h
@@ -0,0 +1,412 @@
+/* -*- 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 2023 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_anyref_h
+#define wasm_anyref_h
+
+#include "mozilla/FloatingPoint.h"
+
+#include <utility>
+
+#include "js/HeapAPI.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+
+// #include "NamespaceImports.h"
+
+class JSObject;
+class JSString;
+
+namespace js {
+namespace gc {
+struct Cell;
+}; // namespace gc
+
+namespace wasm {
+
+// [SMDOC] AnyRef
+//
+// 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.
+//
+// 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::fromJSValue(), and the AnyRef is transformed into a JS Value by
+// calling AnyRef::toJSValue().
+//
+// 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.
+//
+// The current tagging scheme is:
+// if (pointer == 0) then 'null'
+// if (pointer & 0x1) then 'i31'
+// if (pointer & 0x2) then 'string'
+// else 'object'
+//
+// NOTE: there is sequencing required when checking tags. If bit 0x1 is set,
+// then bit 0x2 is part of the i31 value and does not imply string.
+//
+// An i31ref value has no sign interpretation within wasm, where instructions
+// specify the signedness. When converting to/from a JS value, an i31ref value
+// is treated as a signed 31-bit value.
+
+// The kind of value stored in an AnyRef. This is not 1:1 with the pointer tag
+// of AnyRef as this separates the 'Null' and 'Object' cases which are
+// collapsed in the pointer tag.
+enum class AnyRefKind : uint8_t {
+ Null,
+ Object,
+ String,
+ I31,
+};
+
+// The pointer tag of an AnyRef.
+enum class AnyRefTag : uint8_t {
+ // This value is either a JSObject& or a null pointer.
+ ObjectOrNull = 0x0,
+ // This value is a 31-bit integer.
+ I31 = 0x1,
+ // This value is a JSString*.
+ String = 0x2,
+};
+
+// A reference to any wasm reference type or host (JS) value. AnyRef is
+// optimized for efficient access to objects, strings, and 31-bit integers.
+//
+// See the above documentation comment for more details.
+class AnyRef {
+ uintptr_t value_;
+
+ // Get the pointer tag stored in value_.
+ AnyRefTag pointerTag() const { return GetUintptrTag(value_); }
+
+ explicit AnyRef(uintptr_t value) : value_(value) {}
+
+ static constexpr uintptr_t TagUintptr(uintptr_t value, AnyRefTag tag) {
+ MOZ_ASSERT(!(value & TagMask));
+ return value | uintptr_t(tag);
+ }
+ static constexpr uintptr_t UntagUintptr(uintptr_t value) {
+ return value & ~TagMask;
+ }
+ static constexpr AnyRefTag GetUintptrTag(uintptr_t value) {
+ // Mask off all but the lowest two-bits (the tag)
+ uintptr_t rawTag = value & TagMask;
+ // If the lowest bit is set, we want to normalize and only return
+ // AnyRefTag::I31. Mask off the high-bit iff the low-bit was set.
+ uintptr_t normalizedI31 = rawTag & ~(value << 1);
+ return AnyRefTag(normalizedI31);
+ }
+
+ // Given a 32-bit signed integer within 31-bit signed bounds, turn it into
+ // an AnyRef.
+ static AnyRef fromInt32(int32_t value) {
+ MOZ_ASSERT(!int32NeedsBoxing(value));
+ return AnyRef::fromUint32Truncate(uint32_t(value));
+ }
+
+ public:
+ static constexpr uintptr_t TagMask = 0x3;
+ static constexpr uintptr_t TagShift = 2;
+ static_assert(TagShift <= gc::CellAlignShift, "not enough free bits");
+ // A mask for getting the GC thing an AnyRef represents.
+ static constexpr uintptr_t GCThingMask = ~TagMask;
+ // A combined mask for getting the gc::Chunk for an AnyRef that is a GC
+ // thing.
+ static constexpr uintptr_t GCThingChunkMask =
+ GCThingMask & ~js::gc::ChunkMask;
+
+ // The representation of a null reference value throughout the compiler for
+ // when we need an integer constant. This is asserted to be equivalent to
+ // nullptr in wasm::Init.
+ static constexpr uintptr_t NullRefValue = 0;
+ static constexpr uintptr_t InvalidRefValue = UINTPTR_MAX << TagShift;
+
+ // The inclusive maximum 31-bit signed integer, 2^30 - 1.
+ static constexpr int32_t MaxI31Value = (2 << 29) - 1;
+ // The inclusive minimum 31-bit signed integer, -2^30.
+ static constexpr int32_t MinI31Value = -(2 << 29);
+
+ explicit AnyRef() : value_(NullRefValue) {}
+ MOZ_IMPLICIT AnyRef(std::nullptr_t) : value_(NullRefValue) {}
+
+ // The null AnyRef value.
+ static AnyRef null() { return AnyRef(NullRefValue); }
+
+ // 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(InvalidRefValue); }
+
+ // Given a JSObject* that comes from JS, turn it into AnyRef.
+ static AnyRef fromJSObjectOrNull(JSObject* objectOrNull) {
+ MOZ_ASSERT(GetUintptrTag((uintptr_t)objectOrNull) ==
+ AnyRefTag::ObjectOrNull);
+ return AnyRef((uintptr_t)objectOrNull);
+ }
+
+ // Given a JSObject& that comes from JS, turn it into AnyRef.
+ static AnyRef fromJSObject(JSObject& object) {
+ MOZ_ASSERT(GetUintptrTag((uintptr_t)&object) == AnyRefTag::ObjectOrNull);
+ return AnyRef((uintptr_t)&object);
+ }
+
+ // Given a JSString* that comes from JS, turn it into AnyRef.
+ static AnyRef fromJSString(JSString* string) {
+ return AnyRef(TagUintptr((uintptr_t)string, AnyRefTag::String));
+ }
+
+ // Given a void* that comes from compiled wasm code, turn it into AnyRef.
+ static AnyRef fromCompiledCode(void* pointer) {
+ return AnyRef((uintptr_t)pointer);
+ }
+
+ // Given a JS value, turn it into AnyRef. This returns false if boxing the
+ // value failed due to an OOM.
+ static bool fromJSValue(JSContext* cx, JS::HandleValue value,
+ JS::MutableHandle<AnyRef> result);
+
+ // fromUint32Truncate will produce an i31 from an int32 by truncating the
+ // highest bit. For values in the 31-bit range, this losslessly preserves the
+ // value. For values outside the 31-bit range, this performs 31-bit
+ // wraparound.
+ //
+ // There are four cases here based on the two high bits:
+ // 00 - [0, MaxI31Value]
+ // 01 - (MaxI31Value, INT32_MAX]
+ // 10 - [INT32_MIN, MinI31Value)
+ // 11 - [MinI31Value, -1]
+ //
+ // The middle two cases can be ruled out if the value is guaranteed to be
+ // within the i31 range. Therefore if we truncate the high bit upon converting
+ // to i31 and perform a signed widening upon converting back to i32, we can
+ // losslessly represent all i31 values.
+ static AnyRef fromUint32Truncate(uint32_t value) {
+ // See 64-bit GPRs carrying 32-bit values invariants in MacroAssember.h
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64)
+ // Truncate the value to the 31-bit value size.
+ uintptr_t wideValue = uintptr_t(value & 0x7FFFFFFF);
+#elif defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_MIPS64) || \
+ defined(JS_CODEGEN_RISCV64)
+ // Sign extend the value to the native pointer size.
+ uintptr_t wideValue = uintptr_t(int64_t((uint64_t(value) << 33)) >> 33);
+#else
+ // Transfer 32-bit value as is.
+ uintptr_t wideValue = (uintptr_t)value;
+#endif
+
+ // Left shift the value by 1, truncating the high bit.
+ uintptr_t shiftedValue = wideValue << 1;
+ uintptr_t taggedValue = shiftedValue | (uintptr_t)AnyRefTag::I31;
+ return AnyRef(taggedValue);
+ }
+
+ static bool int32NeedsBoxing(int32_t value) {
+ // We can represent every signed 31-bit number without boxing
+ return value < MinI31Value || value > MaxI31Value;
+ }
+
+ static bool doubleNeedsBoxing(double value) {
+ int32_t intValue;
+ if (!mozilla::NumberIsInt32(value, &intValue)) {
+ return true;
+ }
+ return int32NeedsBoxing(value);
+ }
+
+ // Returns whether a JS value will need to be boxed.
+ static bool valueNeedsBoxing(JS::HandleValue value) {
+ if (value.isObjectOrNull() || value.isString()) {
+ return false;
+ }
+ if (value.isInt32()) {
+ return int32NeedsBoxing(value.toInt32());
+ }
+ if (value.isDouble()) {
+ return doubleNeedsBoxing(value.toDouble());
+ }
+ return true;
+ }
+
+ // Box a JS Value that needs boxing.
+ static JSObject* boxValue(JSContext* cx, JS::HandleValue value);
+
+ bool operator==(const AnyRef& rhs) const {
+ return this->value_ == rhs.value_;
+ }
+ bool operator!=(const AnyRef& rhs) const { return !(*this == rhs); }
+
+ // Check if this AnyRef is the invalid value.
+ bool isInvalid() const { return *this == AnyRef::invalid(); }
+
+ AnyRefKind kind() const {
+ if (value_ == NullRefValue) {
+ return AnyRefKind::Null;
+ }
+ switch (pointerTag()) {
+ case AnyRefTag::ObjectOrNull: {
+ // The invalid pattern uses the ObjectOrNull tag, check for it here.
+ MOZ_ASSERT(!isInvalid());
+ // We ruled out the null case above
+ return AnyRefKind::Object;
+ }
+ case AnyRefTag::String: {
+ return AnyRefKind::String;
+ }
+ case AnyRefTag::I31: {
+ return AnyRefKind::I31;
+ }
+ default: {
+ MOZ_CRASH("unknown AnyRef tag");
+ }
+ }
+ }
+
+ bool isNull() const { return value_ == NullRefValue; }
+ bool isGCThing() const { return !isNull() && !isI31(); }
+ bool isJSObject() const { return kind() == AnyRefKind::Object; }
+ bool isJSString() const { return kind() == AnyRefKind::String; }
+ bool isI31() const { return kind() == AnyRefKind::I31; }
+
+ gc::Cell* toGCThing() const {
+ MOZ_ASSERT(isGCThing());
+ return (gc::Cell*)UntagUintptr(value_);
+ }
+ JSObject& toJSObject() const {
+ MOZ_ASSERT(isJSObject());
+ return *(JSObject*)value_;
+ }
+ JSObject* toJSObjectOrNull() const {
+ MOZ_ASSERT(!isInvalid());
+ MOZ_ASSERT(pointerTag() == AnyRefTag::ObjectOrNull);
+ return (JSObject*)value_;
+ }
+ JSString* toJSString() const {
+ MOZ_ASSERT(isJSString());
+ return (JSString*)UntagUintptr(value_);
+ }
+ // Unpack an i31, interpreting the integer as signed.
+ int32_t toI31() const {
+ MOZ_ASSERT(isI31());
+ // On 64-bit targets, we only care about the low 4-bytes.
+ uint32_t truncatedValue = *reinterpret_cast<const uint32_t*>(&value_);
+ // Perform a right arithmetic shift (see AnyRef::fromI31 for more details),
+ // avoiding undefined behavior by using an unsigned type.
+ uint32_t shiftedValue = value_ >> 1;
+ if ((truncatedValue & (1 << 31)) != 0) {
+ shiftedValue |= (1 << 31);
+ }
+ // Perform a bitwise cast to see the result as a signed value.
+ return *reinterpret_cast<int32_t*>(&shiftedValue);
+ }
+
+ // Convert from AnyRef to a JS Value. This currently does not require any
+ // allocation. If this changes in the future, this function will become
+ // more complicated.
+ JS::Value toJSValue() const;
+
+ // Get the raw value for returning to wasm code.
+ void* forCompiledCode() const { return (void*)value_; }
+
+ // Get the raw value for diagnostics.
+ uintptr_t rawValue() const { return value_; }
+
+ // Internal details of the boxing format used by WasmStubs.cpp
+ static const JSClass* valueBoxClass();
+ static size_t valueBoxOffsetOfValue();
+};
+
+using RootedAnyRef = JS::Rooted<AnyRef>;
+using HandleAnyRef = JS::Handle<AnyRef>;
+using MutableHandleAnyRef = JS::MutableHandle<AnyRef>;
+
+} // namespace wasm
+
+template <class Wrapper>
+class WrappedPtrOperations<wasm::AnyRef, Wrapper> {
+ const wasm::AnyRef& value() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ bool isNull() const { return value().isNull(); }
+ bool isI31() const { return value().isI31(); }
+ bool isJSObject() const { return value().isJSObject(); }
+ bool isJSString() const { return value().isJSString(); }
+ JSObject& toJSObject() const { return value().toJSObject(); }
+ JSString* toJSString() const { return value().toJSString(); }
+};
+
+// If the Value is a GC pointer type, call |f| with the pointer cast to that
+// type and return the result wrapped in a Maybe, otherwise return None().
+template <typename F>
+auto MapGCThingTyped(const wasm::AnyRef& val, F&& f) {
+ switch (val.kind()) {
+ case wasm::AnyRefKind::Object:
+ return mozilla::Some(f(&val.toJSObject()));
+ case wasm::AnyRefKind::String:
+ return mozilla::Some(f(val.toJSString()));
+ case wasm::AnyRefKind::I31:
+ case wasm::AnyRefKind::Null: {
+ using ReturnType = decltype(f(static_cast<JSObject*>(nullptr)));
+ return mozilla::Maybe<ReturnType>();
+ }
+ }
+ MOZ_CRASH();
+}
+
+template <typename F>
+bool ApplyGCThingTyped(const wasm::AnyRef& val, F&& f) {
+ return MapGCThingTyped(val,
+ [&f](auto t) {
+ f(t);
+ return true;
+ })
+ .isSome();
+}
+
+} // namespace js
+
+namespace JS {
+
+template <>
+struct GCPolicy<js::wasm::AnyRef> {
+ static void trace(JSTracer* trc, js::wasm::AnyRef* v, const char* name) {
+ // This should only be called as part of root marking since that's the only
+ // time we should trace unbarriered GC thing pointers. This will assert if
+ // called at other times.
+ TraceRoot(trc, v, name);
+ }
+ static bool isValid(const js::wasm::AnyRef& v) {
+ return !v.isGCThing() || js::gc::IsCellPointerValid(v.toGCThing());
+ }
+};
+
+} // namespace JS
+
+#endif // wasm_anyref_h