tor-browser

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

WasmValue.h (17232B)


      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 2021 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_val_h
     20 #define wasm_val_h
     21 
     22 #include <string.h>
     23 
     24 #include "js/Class.h"  // JSClassOps, ClassSpec
     25 #include "vm/JSObject.h"
     26 #include "vm/NativeObject.h"  // NativeObject
     27 #include "wasm/WasmAnyRef.h"
     28 #include "wasm/WasmSerialize.h"
     29 #include "wasm/WasmTypeDef.h"
     30 
     31 namespace js {
     32 namespace wasm {
     33 
     34 // A V128 value.
     35 
     36 struct V128 {
     37  uint8_t bytes[16] = {};  // Little-endian
     38 
     39  WASM_CHECK_CACHEABLE_POD(bytes);
     40 
     41  V128() = default;
     42 
     43  explicit V128(uint8_t splatValue) {
     44    memset(bytes, int(splatValue), sizeof(bytes));
     45  }
     46 
     47  template <typename T>
     48  void extractLane(unsigned lane, T* result) const {
     49    MOZ_ASSERT(lane < 16 / sizeof(T));
     50    memcpy(result, bytes + sizeof(T) * lane, sizeof(T));
     51  }
     52 
     53  template <typename T>
     54  void insertLane(unsigned lane, T value) {
     55    MOZ_ASSERT(lane < 16 / sizeof(T));
     56    memcpy(bytes + sizeof(T) * lane, &value, sizeof(T));
     57  }
     58 
     59  bool operator==(const V128& rhs) const {
     60    return memcmp(bytes, rhs.bytes, sizeof(bytes)) == 0;
     61  }
     62 
     63  bool operator!=(const V128& rhs) const { return !(*this == rhs); }
     64 };
     65 
     66 WASM_DECLARE_CACHEABLE_POD(V128);
     67 
     68 static_assert(sizeof(V128) == 16, "Invariant");
     69 
     70 // A FuncRef is a JSFunction* and is hence also an AnyRef, and the remarks above
     71 // about AnyRef apply also to FuncRef.  When 'funcref' is used as a value type
     72 // in wasm code, the value that is held is "the canonical function value", which
     73 // is a function for which IsWasmExportedFunction() is true, and which has the
     74 // correct identity wrt reference equality of functions.  Notably, if a function
     75 // is imported then its ref.func value compares === in JS to the function that
     76 // was passed as an import when the instance was created.
     77 //
     78 // These rules ensure that casts from funcref to anyref are non-converting
     79 // (generate no code), and that no wrapping or unwrapping needs to happen when a
     80 // funcref or anyref flows across the JS/wasm boundary, and that functions have
     81 // the necessary identity when observed from JS, and in the future, from wasm.
     82 //
     83 // Functions stored in tables, whether wasm tables or internal tables, can be
     84 // stored in a form that optimizes for eg call speed, however.
     85 //
     86 // Reading a funcref from a funcref table, writing a funcref to a funcref table,
     87 // and generating the value for a ref.func instruction are therefore nontrivial
     88 // operations that require mapping between the canonical JSFunction and the
     89 // optimized table representation.  Once we get an instruction to call a
     90 // ref.func directly it too will require such a mapping.
     91 
     92 // In many cases, a FuncRef is exactly the same as AnyRef and we can use AnyRef
     93 // functionality on funcref values.  The FuncRef class exists mostly to add more
     94 // checks and to make it clear, when we need to, that we're manipulating funcref
     95 // values.  FuncRef does not currently subclass AnyRef because there's been no
     96 // need to, but it probably could.
     97 
     98 class FuncRef {
     99  // mutable so that tracing may access a JSFunction* from a `const FuncRef`
    100  mutable JSFunction* value_;
    101 
    102  explicit FuncRef() : value_((JSFunction*)-1) {}
    103  explicit FuncRef(JSFunction* p) : value_(p) {
    104    MOZ_ASSERT(((uintptr_t)p & 0x03) == 0);
    105  }
    106 
    107 public:
    108  // Given a void* that comes from compiled wasm code, turn it into FuncRef.
    109  static FuncRef fromCompiledCode(void* p) { return FuncRef((JSFunction*)p); }
    110 
    111  // Given a JSFunction* that comes from JS, turn it into FuncRef.
    112  static FuncRef fromJSFunction(JSFunction* p) { return FuncRef(p); }
    113 
    114  // Given an AnyRef that represents a possibly-null funcref, turn it into a
    115  // FuncRef.
    116  static FuncRef fromAnyRefUnchecked(AnyRef p);
    117 
    118  static FuncRef null() { return FuncRef(nullptr); }
    119 
    120  AnyRef toAnyRef() { return AnyRef::fromJSObjectOrNull((JSObject*)value_); }
    121 
    122  void* forCompiledCode() const { return value_; }
    123 
    124  JSFunction* asJSFunction() { return value_; }
    125 
    126  bool isNull() const { return value_ == nullptr; }
    127 
    128  void trace(JSTracer* trc) const;
    129 };
    130 
    131 using RootedFuncRef = Rooted<FuncRef>;
    132 using HandleFuncRef = Handle<FuncRef>;
    133 using MutableHandleFuncRef = MutableHandle<FuncRef>;
    134 
    135 // Given any FuncRef, unbox it as a JS Value -- always a JSFunction*.
    136 
    137 Value UnboxFuncRef(FuncRef val);
    138 
    139 // The LitVal class represents a single WebAssembly value of a given value
    140 // type, mostly for the purpose of numeric literals and initializers. A LitVal
    141 // does not directly map to a JS value since there is not (currently) a precise
    142 // representation of i64 values. A LitVal may contain non-canonical NaNs since,
    143 // within WebAssembly, floats are not canonicalized. Canonicalization must
    144 // happen at the JS boundary.
    145 
    146 class LitVal {
    147 public:
    148  union Cell {
    149    uint32_t i32_;
    150    uint64_t i64_;
    151    float f32_;
    152    double f64_;
    153    wasm::V128 v128_;
    154    // Mutable so that it can be traced
    155    mutable wasm::AnyRef ref_;
    156 
    157    Cell() : v128_() {}
    158    ~Cell() = default;
    159 
    160    WASM_CHECK_CACHEABLE_POD(i32_, i64_, f32_, f64_, v128_);
    161    WASM_ALLOW_NON_CACHEABLE_POD_FIELD(
    162        ref_,
    163        "The pointer value in ref_ is guaranteed to always be null in a "
    164        "LitVal.");
    165  };
    166 
    167 protected:
    168  ValType type_;
    169  Cell cell_;
    170 
    171 public:
    172  LitVal() = default;
    173 
    174  explicit LitVal(ValType type) : type_(type) {
    175    switch (type.kind()) {
    176      case ValType::Kind::I32: {
    177        cell_.i32_ = 0;
    178        break;
    179      }
    180      case ValType::Kind::I64: {
    181        cell_.i64_ = 0;
    182        break;
    183      }
    184      case ValType::Kind::F32: {
    185        cell_.f32_ = 0;
    186        break;
    187      }
    188      case ValType::Kind::F64: {
    189        cell_.f64_ = 0;
    190        break;
    191      }
    192      case ValType::Kind::V128: {
    193        new (&cell_.v128_) V128();
    194        break;
    195      }
    196      case ValType::Kind::Ref: {
    197        cell_.ref_ = nullptr;
    198        break;
    199      }
    200    }
    201  }
    202 
    203  explicit LitVal(uint32_t i32) : type_(ValType::I32) { cell_.i32_ = i32; }
    204  explicit LitVal(uint64_t i64) : type_(ValType::I64) { cell_.i64_ = i64; }
    205 
    206  explicit LitVal(float f32) : type_(ValType::F32) { cell_.f32_ = f32; }
    207  explicit LitVal(double f64) : type_(ValType::F64) { cell_.f64_ = f64; }
    208 
    209  explicit LitVal(V128 v128) : type_(ValType::V128) { cell_.v128_ = v128; }
    210 
    211  explicit LitVal(ValType type, AnyRef any) : type_(type) {
    212    MOZ_ASSERT(type.isRefRepr());
    213    MOZ_ASSERT(any.isNull(),
    214               "use Val for non-nullptr ref types to get tracing");
    215    cell_.ref_ = any;
    216  }
    217 
    218  ValType type() const { return type_; }
    219  static constexpr size_t sizeofLargestValue() { return sizeof(cell_); }
    220 
    221  Cell& cell() { return cell_; }
    222  const Cell& cell() const { return cell_; }
    223 
    224  // Updates the type of the LitVal. Does not check that the type is valid for
    225  // the actual value, so make sure the type is definitely correct via
    226  // validation or something.
    227  void unsafeSetType(ValType type) { type_ = type; }
    228 
    229  uint32_t i32() const {
    230    MOZ_ASSERT(type_ == ValType::I32);
    231    return cell_.i32_;
    232  }
    233  uint64_t i64() const {
    234    MOZ_ASSERT(type_ == ValType::I64);
    235    return cell_.i64_;
    236  }
    237  const float& f32() const {
    238    MOZ_ASSERT(type_ == ValType::F32);
    239    return cell_.f32_;
    240  }
    241  const double& f64() const {
    242    MOZ_ASSERT(type_ == ValType::F64);
    243    return cell_.f64_;
    244  }
    245  AnyRef ref() const {
    246    MOZ_ASSERT(type_.isRefRepr());
    247    return cell_.ref_;
    248  }
    249  const V128& v128() const {
    250    MOZ_ASSERT(type_ == ValType::V128);
    251    return cell_.v128_;
    252  }
    253 
    254  WASM_DECLARE_FRIEND_SERIALIZE(LitVal);
    255 };
    256 
    257 WASM_DECLARE_CACHEABLE_POD(LitVal::Cell);
    258 
    259 // A Val is a LitVal that can contain (non-null) pointers to GC things. All Vals
    260 // must be used with the rooting APIs as they may contain JS objects.
    261 
    262 class MOZ_NON_PARAM Val : public LitVal {
    263 public:
    264  Val() = default;
    265  explicit Val(ValType type) : LitVal(type) {}
    266  explicit Val(const LitVal& val);
    267  explicit Val(uint32_t i32) : LitVal(i32) {}
    268  explicit Val(uint64_t i64) : LitVal(i64) {}
    269  explicit Val(float f32) : LitVal(f32) {}
    270  explicit Val(double f64) : LitVal(f64) {}
    271  explicit Val(V128 v128) : LitVal(v128) {}
    272  explicit Val(ValType type, AnyRef val) : LitVal(type, AnyRef::null()) {
    273    MOZ_ASSERT(type.isRefRepr());
    274    cell_.ref_ = val;
    275  }
    276  explicit Val(ValType type, FuncRef val) : LitVal(type, AnyRef::null()) {
    277    MOZ_ASSERT(type.refType().isFuncHierarchy());
    278    cell_.ref_ = val.toAnyRef();
    279  }
    280 
    281  Val(const Val&) = default;
    282  Val& operator=(const Val&) = default;
    283 
    284  bool operator==(const Val& rhs) const {
    285    if (type_ != rhs.type_) {
    286      return false;
    287    }
    288    switch (type_.kind()) {
    289      case ValType::I32:
    290        return cell_.i32_ == rhs.cell_.i32_;
    291      case ValType::I64:
    292        return cell_.i64_ == rhs.cell_.i64_;
    293      case ValType::F32:
    294        return cell_.f32_ == rhs.cell_.f32_;
    295      case ValType::F64:
    296        return cell_.f64_ == rhs.cell_.f64_;
    297      case ValType::V128:
    298        return cell_.v128_ == rhs.cell_.v128_;
    299      case ValType::Ref:
    300        return cell_.ref_ == rhs.cell_.ref_;
    301    }
    302    MOZ_ASSERT_UNREACHABLE();
    303    return false;
    304  }
    305  bool operator!=(const Val& rhs) const { return !(*this == rhs); }
    306 
    307  bool isInvalid() const { return !type_.isValid(); }
    308  bool isAnyRef() const { return type_.isValid() && type_.isRefRepr(); }
    309  AnyRef& toAnyRef() const {
    310    MOZ_ASSERT(isAnyRef());
    311    return cell_.ref_;
    312  }
    313 
    314  // Initialize from `loc` which is a rooted location and needs no barriers.
    315  void initFromRootedLocation(ValType type, const void* loc);
    316  void initFromHeapLocation(ValType type, const void* loc);
    317 
    318  // Write to `loc` which is a rooted location and needs no barriers.
    319  void writeToRootedLocation(void* loc, bool mustWrite64) const;
    320 
    321  // Read from `loc` which is in the heap.
    322  void readFromHeapLocation(const void* loc);
    323  // Write to `loc` which is in the heap and must be barriered.
    324  void writeToHeapLocation(void* loc) const;
    325 
    326  // See the comment for `ToWebAssemblyValue` below.
    327  static bool fromJSValue(JSContext* cx, ValType targetType, HandleValue val,
    328                          MutableHandle<Val> rval);
    329  // See the comment for `ToJSValue` below.
    330  bool toJSValue(JSContext* cx, MutableHandleValue rval) const;
    331 
    332  void trace(JSTracer* trc) const;
    333 };
    334 
    335 using GCPtrVal = GCPtr<Val>;
    336 using RootedVal = Rooted<Val>;
    337 using HandleVal = Handle<Val>;
    338 using MutableHandleVal = MutableHandle<Val>;
    339 
    340 using ValVector = GCVector<Val, 0, SystemAllocPolicy>;
    341 using RootedValVector = Rooted<ValVector>;
    342 using HandleValVector = Handle<ValVector>;
    343 using MutableHandleValVector = MutableHandle<ValVector>;
    344 
    345 template <int N>
    346 using ValVectorN = GCVector<Val, N, SystemAllocPolicy>;
    347 template <int N>
    348 using RootedValVectorN = Rooted<ValVectorN<N>>;
    349 
    350 // Check if a JS value matches against a given reference type.
    351 // Returns true and gives the corresponding wasm::AnyRef value for the JS value
    352 // if the type check succeeds. Returns false and sets an error if the type
    353 // check fails, or boxing the wasm::AnyRef failed due to an OOM.
    354 [[nodiscard]] extern bool CheckRefType(JSContext* cx, RefType targetType,
    355                                       HandleValue v, MutableHandleAnyRef vp);
    356 // The same as above, but discards the resulting wasm::AnyRef. This may still
    357 // fail due to an OOM.
    358 [[nodiscard]] extern bool CheckRefType(JSContext* cx, RefType targetType,
    359                                       HandleValue v);
    360 
    361 class NoDebug;
    362 class DebugCodegenVal;
    363 
    364 // The level of coercion to apply in `ToWebAssemblyValue` and `ToJSValue`.
    365 enum class CoercionLevel {
    366  // The default coercions given by the JS-API specification.
    367  Spec,
    368  // Allow for the coercions given by `Spec` but also use WebAssembly.Global
    369  // as a container for lossless conversions. This is only available through
    370  // the wasmLosslessInvoke testing function and is used in tests.
    371  Lossless,
    372 };
    373 
    374 // Coercion function from a JS value to a WebAssembly value [1].
    375 //
    376 // This function may fail for any of the following reasons:
    377 //  * The input value has an incorrect type for the targetType
    378 //  * The targetType is not exposable
    379 //  * An OOM ocurred
    380 // An error will be set upon failure.
    381 //
    382 // [1] https://webassembly.github.io/spec/js-api/index.html#towebassemblyvalue
    383 template <typename Debug = NoDebug>
    384 extern bool ToWebAssemblyValue(JSContext* cx, HandleValue val, ValType type,
    385                               void* loc, bool mustWrite64,
    386                               CoercionLevel level = CoercionLevel::Spec);
    387 
    388 // Coercion function from a WebAssembly value to a JS value [1].
    389 //
    390 // This function will only fail if an OOM ocurred. If the type of WebAssembly
    391 // value being coerced is not exposable to JS, then it will be coerced to
    392 // 'undefined'. Callers are responsible for guarding against this if this is
    393 // not desirable.
    394 //
    395 // [1] https://webassembly.github.io/spec/js-api/index.html#tojsvalue
    396 template <typename Debug = NoDebug>
    397 extern bool ToJSValue(JSContext* cx, const void* src, StorageType type,
    398                      MutableHandleValue dst,
    399                      CoercionLevel level = CoercionLevel::Spec);
    400 template <typename Debug = NoDebug>
    401 extern bool ToJSValueMayGC(StorageType type);
    402 template <typename Debug = NoDebug>
    403 extern bool ToJSValue(JSContext* cx, const void* src, ValType type,
    404                      MutableHandleValue dst,
    405                      CoercionLevel level = CoercionLevel::Spec);
    406 template <typename Debug = NoDebug>
    407 extern bool ToJSValueMayGC(ValType type);
    408 
    409 #ifdef DEBUG
    410 void AssertEdgeSourceNotInsideNursery(void* vp);
    411 #endif
    412 
    413 }  // namespace wasm
    414 
    415 template <>
    416 struct InternalBarrierMethods<wasm::AnyRef> {
    417  static bool isMarkable(const wasm::AnyRef v) { return v.isGCThing(); }
    418 
    419  static void preBarrier(const wasm::AnyRef v) {
    420    if (v.isGCThing()) {
    421      gc::PreWriteBarrierImpl(v.toGCThing());
    422    }
    423  }
    424 
    425  static MOZ_ALWAYS_INLINE void postBarrier(wasm::AnyRef* vp,
    426                                            const wasm::AnyRef prev,
    427                                            const wasm::AnyRef next) {
    428    // This assumes that callers have already checked that |vp| is in the
    429    // tenured heap. Don't use GCPtr<AnyRef> for things that could be in the
    430    // nursery!
    431 #ifdef DEBUG
    432    AssertEdgeSourceNotInsideNursery(vp);
    433 #endif
    434 
    435    // If the target needs an entry, add it.
    436    gc::StoreBuffer* sb;
    437    if (next.isGCThing() && (sb = next.toGCThing()->storeBuffer())) {
    438      // If we know that the prev has already inserted an entry, we can
    439      // skip doing the lookup to add the new entry. Note that we cannot
    440      // safely assert the presence of the entry because it may have been
    441      // added via a different store buffer.
    442      if (prev.isGCThing() && prev.toGCThing()->storeBuffer()) {
    443        return;
    444      }
    445      sb->putWasmAnyRefEdgeFromTenured(vp);
    446      return;
    447    }
    448    // Remove the prev entry if the new value does not need it.
    449    if (prev.isGCThing() && (sb = prev.toGCThing()->storeBuffer())) {
    450      sb->unputWasmAnyRef(vp);
    451    }
    452  }
    453 
    454  static void readBarrier(const wasm::AnyRef v) {
    455    if (v.isGCThing()) {
    456      gc::ReadBarrierImpl(v.toGCThing());
    457    }
    458  }
    459 
    460 #ifdef DEBUG
    461  static void assertThingIsNotGray(const wasm::AnyRef v) {
    462    if (v.isGCThing()) {
    463      JS::AssertCellIsNotGray(v.toGCThing());
    464    }
    465  }
    466 #endif
    467 };
    468 
    469 template <>
    470 struct InternalBarrierMethods<wasm::Val> {
    471  static bool isMarkable(const wasm::Val& v) { return v.isAnyRef(); }
    472 
    473  static void preBarrier(const wasm::Val& v) {
    474    if (v.isAnyRef()) {
    475      InternalBarrierMethods<wasm::AnyRef>::preBarrier(v.toAnyRef());
    476    }
    477  }
    478 
    479  static MOZ_ALWAYS_INLINE void postBarrier(wasm::Val* vp,
    480                                            const wasm::Val& prev,
    481                                            const wasm::Val& next) {
    482    // A wasm::Val can transition from being uninitialized to holding an anyref
    483    // but cannot change kind after that.
    484    MOZ_ASSERT_IF(next.isAnyRef(), prev.isAnyRef() || prev.isInvalid());
    485    MOZ_ASSERT_IF(prev.isAnyRef(), next.isAnyRef());
    486 
    487    if (next.isAnyRef()) {
    488      InternalBarrierMethods<wasm::AnyRef>::postBarrier(
    489          &vp->toAnyRef(),
    490          prev.isAnyRef() ? prev.toAnyRef() : wasm::AnyRef::null(),
    491          next.toAnyRef());
    492      return;
    493    }
    494  }
    495 
    496  static void readBarrier(const wasm::Val& v) {
    497    if (v.isAnyRef()) {
    498      InternalBarrierMethods<wasm::AnyRef>::readBarrier(v.toAnyRef());
    499    }
    500  }
    501 
    502 #ifdef DEBUG
    503  static void assertThingIsNotGray(const wasm::Val& v) {
    504    if (v.isAnyRef()) {
    505      InternalBarrierMethods<wasm::AnyRef>::assertThingIsNotGray(v.toAnyRef());
    506    }
    507  }
    508 #endif
    509 };
    510 
    511 }  // namespace js
    512 
    513 template <>
    514 struct JS::SafelyInitialized<js::wasm::AnyRef> {
    515  static constexpr js::wasm::AnyRef create() {
    516    return js::wasm::AnyRef::null();
    517  }
    518 };
    519 
    520 #endif  // wasm_val_h