tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WasmAnyRef.h (15086B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 *
      4 * Copyright 2023 Mozilla Foundation
      5 *
      6 * Licensed under the Apache License, Version 2.0 (the "License");
      7 * you may not use this file except in compliance with the License.
      8 * You may obtain a copy of the License at
      9 *
     10 *     http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing, software
     13 * distributed under the License is distributed on an "AS IS" BASIS,
     14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 * See the License for the specific language governing permissions and
     16 * limitations under the License.
     17 */
     18 
     19 #ifndef wasm_anyref_h
     20 #define wasm_anyref_h
     21 
     22 #include "mozilla/FloatingPoint.h"
     23 
     24 #include "js/HeapAPI.h"
     25 #include "js/RootingAPI.h"
     26 #include "js/TypeDecls.h"
     27 #include "js/Value.h"
     28 
     29 // #include "NamespaceImports.h"
     30 
     31 class JSObject;
     32 class JSString;
     33 
     34 namespace js {
     35 namespace gc {
     36 struct Cell;
     37 };  // namespace gc
     38 
     39 namespace wasm {
     40 
     41 // [SMDOC] AnyRef
     42 //
     43 // An AnyRef is a boxed value that can represent any wasm reference type and any
     44 // host type that the host system allows to flow into and out of wasm
     45 // transparently.  It is a pointer-sized datum that has the same representation
     46 // as all its subtypes (funcref, externref, eqref, (ref T), et al) due to the
     47 // non-coercive subtyping of the wasm type system.
     48 //
     49 // The C++/wasm boundary always uses a 'void*' type to express AnyRef values, to
     50 // emphasize the pointer-ness of the value.  The C++ code must transform the
     51 // void* into an AnyRef by calling AnyRef::fromCompiledCode(), and transform an
     52 // AnyRef into a void* by calling AnyRef::toCompiledCode().  Once in C++, we use
     53 // AnyRef everywhere.  A JS Value is transformed into an AnyRef by calling
     54 // AnyRef::fromJSValue(), and the AnyRef is transformed into a JS Value by
     55 // calling AnyRef::toJSValue().
     56 //
     57 // NOTE that AnyRef values may point to GC'd storage and as such need to be
     58 // rooted if they are kept live in boxed form across code that may cause GC!
     59 // Use RootedAnyRef / HandleAnyRef / MutableHandleAnyRef where necessary.
     60 //
     61 // The lowest bits of the pointer value are used for tagging, to allow for some
     62 // representation optimizations and to distinguish various types.
     63 //
     64 // The current tagging scheme is:
     65 //   if (pointer == 0) then 'null'
     66 //   if (pointer & 0x1) then 'i31'
     67 //   if (pointer & 0x2) then 'string'
     68 //   else 'object'
     69 //
     70 // NOTE: there is sequencing required when checking tags. If bit 0x1 is set,
     71 // then bit 0x2 is part of the i31 value and does not imply string.
     72 //
     73 // An i31ref value has no sign interpretation within wasm, where instructions
     74 // specify the signedness. When converting to/from a JS value, an i31ref value
     75 // is treated as a signed 31-bit value.
     76 
     77 // The kind of value stored in an AnyRef. This is not 1:1 with the pointer tag
     78 // of AnyRef as this separates the 'Null' and 'Object' cases which are
     79 // collapsed in the pointer tag.
     80 enum class AnyRefKind : uint8_t {
     81  Null,
     82  Object,
     83  String,
     84  I31,
     85 };
     86 
     87 // The pointer tag of an AnyRef.
     88 enum class AnyRefTag : uint8_t {
     89  // This value is either a JSObject& or a null pointer.
     90  ObjectOrNull = 0x0,
     91  // This value is a 31-bit integer.
     92  I31 = 0x1,
     93  // This value is a JSString*.
     94  String = 0x2,
     95 };
     96 
     97 // A reference to any wasm reference type or host (JS) value. AnyRef is
     98 // optimized for efficient access to objects, strings, and 31-bit integers.
     99 //
    100 // See the above documentation comment for more details.
    101 class AnyRef {
    102  uintptr_t value_;
    103 
    104  // Get the pointer tag stored in value_.
    105  AnyRefTag pointerTag() const { return GetUintptrTag(value_); }
    106 
    107  explicit constexpr AnyRef(uintptr_t value) : value_(value) {}
    108 
    109  static constexpr uintptr_t TagUintptr(uintptr_t value, AnyRefTag tag) {
    110    MOZ_ASSERT(!(value & TagMask));
    111    return value | uintptr_t(tag);
    112  }
    113  static constexpr uintptr_t UntagUintptr(uintptr_t value) {
    114    return value & ~TagMask;
    115  }
    116  static constexpr AnyRefTag GetUintptrTag(uintptr_t value) {
    117    // Mask off all but the lowest two-bits (the tag)
    118    uintptr_t rawTag = value & TagMask;
    119    // If the lowest bit is set, we want to normalize and only return
    120    // AnyRefTag::I31. Mask off the high-bit iff the low-bit was set.
    121    uintptr_t normalizedI31 = rawTag & ~(value << 1);
    122    return AnyRefTag(normalizedI31);
    123  }
    124 
    125  // Given a 32-bit signed integer within 31-bit signed bounds, turn it into
    126  // an AnyRef.
    127  static AnyRef fromInt32(int32_t value) {
    128    MOZ_ASSERT(!int32NeedsBoxing(value));
    129    return AnyRef::fromUint32Truncate(uint32_t(value));
    130  }
    131 
    132 public:
    133  static constexpr uintptr_t TagMask = 0x3;
    134  static constexpr uintptr_t TagShift = 2;
    135  static_assert(TagShift <= gc::CellAlignShift, "not enough free bits");
    136  // A mask for getting the GC thing an AnyRef represents.
    137  static constexpr uintptr_t GCThingMask = ~TagMask;
    138  // A combined mask for getting the gc::Chunk for an AnyRef that is a GC
    139  // thing.
    140  static constexpr uintptr_t GCThingChunkMask =
    141      GCThingMask & ~js::gc::ChunkMask;
    142 
    143  // The representation of a null reference value throughout the compiler for
    144  // when we need an integer constant. This is asserted to be equivalent to
    145  // nullptr in wasm::Init.
    146  static constexpr uintptr_t NullRefValue = 0;
    147  static constexpr uintptr_t InvalidRefValue = UINTPTR_MAX << TagShift;
    148 
    149  // The inclusive maximum 31-bit signed integer, 2^30 - 1.
    150  static constexpr int32_t MaxI31Value = (2 << 29) - 1;
    151  // The inclusive minimum 31-bit signed integer, -2^30.
    152  static constexpr int32_t MinI31Value = -(2 << 29);
    153 
    154  explicit constexpr AnyRef() : value_(NullRefValue) {}
    155  MOZ_IMPLICIT constexpr AnyRef(std::nullptr_t) : value_(NullRefValue) {}
    156 
    157  // The null AnyRef value.
    158  static constexpr AnyRef null() { return AnyRef(NullRefValue); }
    159 
    160  // An invalid AnyRef cannot arise naturally from wasm and so can be used as
    161  // a sentinel value to indicate failure from an AnyRef-returning function.
    162  static constexpr AnyRef invalid() { return AnyRef(InvalidRefValue); }
    163 
    164  // Given a JSObject* that comes from JS, turn it into AnyRef.
    165  static AnyRef fromJSObjectOrNull(JSObject* objectOrNull) {
    166    MOZ_ASSERT(GetUintptrTag((uintptr_t)objectOrNull) ==
    167               AnyRefTag::ObjectOrNull);
    168    return AnyRef((uintptr_t)objectOrNull);
    169  }
    170 
    171  // Given a JSObject& that comes from JS, turn it into AnyRef.
    172  static AnyRef fromJSObject(JSObject& object) {
    173    MOZ_ASSERT(GetUintptrTag((uintptr_t)&object) == AnyRefTag::ObjectOrNull);
    174    return AnyRef((uintptr_t)&object);
    175  }
    176 
    177  // Given a JSString* that comes from JS, turn it into AnyRef.
    178  static AnyRef fromJSString(JSString* string) {
    179    return AnyRef(TagUintptr((uintptr_t)string, AnyRefTag::String));
    180  }
    181 
    182  // Given a void* that comes from compiled wasm code, turn it into AnyRef.
    183  static AnyRef fromCompiledCode(void* pointer) {
    184    return AnyRef((uintptr_t)pointer);
    185  }
    186 
    187  // Given a JS value, turn it into AnyRef. This returns false if boxing the
    188  // value failed due to an OOM.
    189  static bool fromJSValue(JSContext* cx, JS::HandleValue value,
    190                          JS::MutableHandle<AnyRef> result);
    191 
    192  // fromUint32Truncate will produce an i31 from an int32 by truncating the
    193  // highest bit. For values in the 31-bit range, this losslessly preserves the
    194  // value. For values outside the 31-bit range, this performs 31-bit
    195  // wraparound.
    196  //
    197  // There are four cases here based on the two high bits:
    198  //   00 - [0, MaxI31Value]
    199  //   01 - (MaxI31Value, INT32_MAX]
    200  //   10 - [INT32_MIN, MinI31Value)
    201  //   11 - [MinI31Value, -1]
    202  //
    203  // The middle two cases can be ruled out if the value is guaranteed to be
    204  // within the i31 range. Therefore if we truncate the high bit upon converting
    205  // to i31 and perform a signed widening upon converting back to i32, we can
    206  // losslessly represent all i31 values.
    207  static AnyRef fromUint32Truncate(uint32_t value) {
    208    // See 64-bit GPRs carrying 32-bit values invariants in MacroAssember.h
    209 #if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_X64) || \
    210    defined(JS_CODEGEN_ARM64)
    211    // Truncate the value to the 31-bit value size.
    212    uintptr_t wideValue = uintptr_t(value & 0x7FFFFFFF);
    213 #elif defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_MIPS64) || \
    214    defined(JS_CODEGEN_RISCV64)
    215    // Sign extend the value to the native pointer size.
    216    uintptr_t wideValue = uintptr_t(int64_t((uint64_t(value) << 33)) >> 33);
    217 #elif !defined(JS_64BIT)
    218    // Transfer 32-bit value as is.
    219    uintptr_t wideValue = (uintptr_t)value;
    220 #else
    221 #  error "unknown architecture"
    222 #endif
    223 
    224    // Left shift the value by 1, truncating the high bit.
    225    uintptr_t shiftedValue = wideValue << 1;
    226    uintptr_t taggedValue = shiftedValue | (uintptr_t)AnyRefTag::I31;
    227 #ifdef JS_64BIT
    228    debugAssertCanonicalInt32(taggedValue);
    229 #endif
    230    return AnyRef(taggedValue);
    231  }
    232 
    233 #ifdef JS_64BIT
    234  // Ensure the value fits into a 32-bits integer on 64-bits platforms.
    235  static void debugAssertCanonicalInt32(uintptr_t value) {
    236 #  ifdef DEBUG
    237 #    if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64)
    238    MOZ_ASSERT(value <= UINT32_MAX);
    239 #    endif
    240 #  endif
    241  }
    242 #endif
    243 
    244  static bool int32NeedsBoxing(int32_t value) {
    245    // We can represent every signed 31-bit number without boxing
    246    return value < MinI31Value || value > MaxI31Value;
    247  }
    248 
    249  static bool doubleNeedsBoxing(double value) {
    250    int32_t intValue;
    251    if (!mozilla::NumberIsInt32(value, &intValue)) {
    252      return true;
    253    }
    254    return int32NeedsBoxing(intValue);
    255  }
    256 
    257  // Returns whether a JS value will need to be boxed.
    258  static bool valueNeedsBoxing(JS::HandleValue value) {
    259    if (value.isObjectOrNull() || value.isString()) {
    260      return false;
    261    }
    262    if (value.isInt32()) {
    263      return int32NeedsBoxing(value.toInt32());
    264    }
    265    if (value.isDouble()) {
    266      return doubleNeedsBoxing(value.toDouble());
    267    }
    268    return true;
    269  }
    270 
    271  // Box a JS Value that needs boxing.
    272  static JSObject* boxValue(JSContext* cx, JS::HandleValue value);
    273 
    274  bool operator==(const AnyRef& rhs) const {
    275    return this->value_ == rhs.value_;
    276  }
    277  bool operator!=(const AnyRef& rhs) const { return !(*this == rhs); }
    278 
    279  // Check if this AnyRef is the invalid value.
    280  bool isInvalid() const { return *this == AnyRef::invalid(); }
    281 
    282  AnyRefKind kind() const {
    283    if (value_ == NullRefValue) {
    284      return AnyRefKind::Null;
    285    }
    286    switch (pointerTag()) {
    287      case AnyRefTag::ObjectOrNull: {
    288        // The invalid pattern uses the ObjectOrNull tag, check for it here.
    289        MOZ_ASSERT(!isInvalid());
    290        // We ruled out the null case above
    291        return AnyRefKind::Object;
    292      }
    293      case AnyRefTag::String: {
    294        return AnyRefKind::String;
    295      }
    296      case AnyRefTag::I31: {
    297        return AnyRefKind::I31;
    298      }
    299      default: {
    300        MOZ_CRASH("unknown AnyRef tag");
    301      }
    302    }
    303  }
    304 
    305  bool isNull() const { return value_ == NullRefValue; }
    306  bool isGCThing() const { return !isNull() && !isI31(); }
    307  bool isJSObject() const { return kind() == AnyRefKind::Object; }
    308  bool isJSString() const { return kind() == AnyRefKind::String; }
    309  bool isI31() const { return kind() == AnyRefKind::I31; }
    310 
    311  gc::Cell* toGCThing() const {
    312    MOZ_ASSERT(isGCThing());
    313    return (gc::Cell*)UntagUintptr(value_);
    314  }
    315  JSObject& toJSObject() const {
    316    MOZ_ASSERT(isJSObject());
    317    return *(JSObject*)value_;
    318  }
    319  JSObject* toJSObjectOrNull() const {
    320    MOZ_ASSERT(!isInvalid());
    321    MOZ_ASSERT(pointerTag() == AnyRefTag::ObjectOrNull);
    322    return (JSObject*)value_;
    323  }
    324  JSString* toJSString() const {
    325    MOZ_ASSERT(isJSString());
    326    return (JSString*)UntagUintptr(value_);
    327  }
    328  // Unpack an i31, interpreting the integer as signed.
    329  int32_t toI31() const {
    330    MOZ_ASSERT(isI31());
    331 #ifdef JS_64BIT
    332    debugAssertCanonicalInt32(value_);
    333 #endif
    334    // On 64-bit targets, we only care about the low 4-bytes.
    335    uint32_t truncatedValue;
    336    memcpy(&truncatedValue, &value_, sizeof(uint32_t));
    337    // Perform a right arithmetic shift (see AnyRef::fromI31 for more details),
    338    // avoiding undefined behavior by using an unsigned type.
    339    uint32_t shiftedValue = value_ >> 1;
    340    if ((truncatedValue & (1 << 31)) != 0) {
    341      shiftedValue |= (1 << 31);
    342    }
    343    // Perform a bitwise cast to see the result as a signed value.
    344    return mozilla::BitwiseCast<int32_t>(shiftedValue);
    345  }
    346 
    347  // Convert from AnyRef to a JS Value. This currently does not require any
    348  // allocation. If this changes in the future, this function will become
    349  // more complicated.
    350  JS::Value toJSValue() const;
    351 
    352  // Get the raw value for returning to wasm code.
    353  void* forCompiledCode() const { return (void*)value_; }
    354 
    355  // Get the raw value for diagnostics.
    356  uintptr_t rawValue() const { return value_; }
    357 
    358  // Internal details of the boxing format used by WasmStubs.cpp
    359  static const JSClass* valueBoxClass();
    360  static size_t valueBoxOffsetOfValue();
    361 };
    362 
    363 using RootedAnyRef = JS::Rooted<AnyRef>;
    364 using HandleAnyRef = JS::Handle<AnyRef>;
    365 using MutableHandleAnyRef = JS::MutableHandle<AnyRef>;
    366 
    367 }  // namespace wasm
    368 
    369 template <class Wrapper>
    370 class WrappedPtrOperations<wasm::AnyRef, Wrapper> {
    371  const wasm::AnyRef& value() const {
    372    return static_cast<const Wrapper*>(this)->get();
    373  }
    374 
    375 public:
    376  bool isNull() const { return value().isNull(); }
    377  bool isI31() const { return value().isI31(); }
    378  bool isJSObject() const { return value().isJSObject(); }
    379  bool isJSString() const { return value().isJSString(); }
    380  JSObject& toJSObject() const { return value().toJSObject(); }
    381  JSString* toJSString() const { return value().toJSString(); }
    382 };
    383 
    384 // If the Value is a GC pointer type, call |f| with the pointer cast to that
    385 // type and return the result wrapped in a Maybe, otherwise return None().
    386 template <typename F>
    387 inline auto MapGCThingTyped(const wasm::AnyRef& val, F&& f) {
    388  switch (val.kind()) {
    389    case wasm::AnyRefKind::Object:
    390      return mozilla::Some(f(&val.toJSObject()));
    391    case wasm::AnyRefKind::String:
    392      return mozilla::Some(f(val.toJSString()));
    393    case wasm::AnyRefKind::I31:
    394    case wasm::AnyRefKind::Null: {
    395      using ReturnType = decltype(f(static_cast<JSObject*>(nullptr)));
    396      return mozilla::Maybe<ReturnType>();
    397    }
    398  }
    399  MOZ_CRASH();
    400 }
    401 
    402 template <typename F>
    403 bool ApplyGCThingTyped(const wasm::AnyRef& val, F&& f) {
    404  return MapGCThingTyped(val,
    405                         [&f](auto t) {
    406                           f(t);
    407                           return true;
    408                         })
    409      .isSome();
    410 }
    411 
    412 }  // namespace js
    413 
    414 namespace JS {
    415 
    416 template <>
    417 struct GCPolicy<js::wasm::AnyRef> {
    418  static void trace(JSTracer* trc, js::wasm::AnyRef* v, const char* name) {
    419    // This should only be called as part of root marking since that's the only
    420    // time we should trace unbarriered GC thing pointers. This will assert if
    421    // called at other times.
    422    TraceRoot(trc, v, name);
    423  }
    424  static bool isValid(const js::wasm::AnyRef& v) {
    425    return !v.isGCThing() || js::gc::IsCellPointerValid(v.toGCThing());
    426  }
    427 };
    428 
    429 }  // namespace JS
    430 
    431 #endif  // wasm_anyref_h