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