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 "js/Class.h" // JSClassOps, ClassSpec 23 #include "vm/JSObject.h" 24 #include "vm/NativeObject.h" // NativeObject 25 #include "wasm/WasmValType.h" 26 27 namespace js { 28 namespace wasm { 29 30 // A V128 value. 31 32 struct V128 { 33 uint8_t bytes[16]; // Little-endian 34 V128V12835 V128() { memset(bytes, 0, sizeof(bytes)); } 36 37 template <typename T> extractLaneV12838 T extractLane(unsigned lane) const { 39 T result; 40 MOZ_ASSERT(lane < 16 / sizeof(T)); 41 memcpy(&result, bytes + sizeof(T) * lane, sizeof(T)); 42 return result; 43 } 44 45 template <typename T> insertLaneV12846 void insertLane(unsigned lane, T value) { 47 MOZ_ASSERT(lane < 16 / sizeof(T)); 48 memcpy(bytes + sizeof(T) * lane, &value, sizeof(T)); 49 } 50 51 bool operator==(const V128& rhs) const { 52 for (size_t i = 0; i < sizeof(bytes); i++) { 53 if (bytes[i] != rhs.bytes[i]) { 54 return false; 55 } 56 } 57 return true; 58 } 59 60 bool operator!=(const V128& rhs) const { return !(*this == rhs); } 61 }; 62 63 static_assert(sizeof(V128) == 16, "Invariant"); 64 65 // An AnyRef is a boxed value that can represent any wasm reference type and any 66 // host type that the host system allows to flow into and out of wasm 67 // transparently. It is a pointer-sized datum that has the same representation 68 // as all its subtypes (funcref, externref, eqref, (ref T), et al) due to the 69 // non-coercive subtyping of the wasm type system. Its current representation 70 // is a plain JSObject*, and the private JSObject subtype WasmValueBox is used 71 // to box non-object non-null JS values. 72 // 73 // The C++/wasm boundary always uses a 'void*' type to express AnyRef values, to 74 // emphasize the pointer-ness of the value. The C++ code must transform the 75 // void* into an AnyRef by calling AnyRef::fromCompiledCode(), and transform an 76 // AnyRef into a void* by calling AnyRef::toCompiledCode(). Once in C++, we use 77 // AnyRef everywhere. A JS Value is transformed into an AnyRef by calling 78 // AnyRef::box(), and the AnyRef is transformed into a JS Value by calling 79 // AnyRef::unbox(). 80 // 81 // NOTE that AnyRef values may point to GC'd storage and as such need to be 82 // rooted if they are kept live in boxed form across code that may cause GC! 83 // Use RootedAnyRef / HandleAnyRef / MutableHandleAnyRef where necessary. 84 // 85 // The lowest bits of the pointer value are used for tagging, to allow for some 86 // representation optimizations and to distinguish various types. 87 88 // For version 0, we simply equate AnyRef and JSObject* (this means that there 89 // are technically no tags at all yet). We use a simple boxing scheme that 90 // wraps a JS value that is not already JSObject in a distinguishable JSObject 91 // that holds the value, see WasmTypes.cpp for details. Knowledge of this 92 // mapping is embedded in CodeGenerator.cpp (in WasmBoxValue and 93 // WasmAnyRefFromJSObject) and in WasmStubs.cpp (in functions Box* and Unbox*). 94 95 class AnyRef { 96 // mutable so that tracing may access a JSObject* from a `const Val` or 97 // `const AnyRef`. 98 mutable JSObject* value_; 99 AnyRef()100 explicit AnyRef() : value_((JSObject*)-1) {} AnyRef(JSObject * p)101 explicit AnyRef(JSObject* p) : value_(p) { 102 MOZ_ASSERT(((uintptr_t)p & 0x03) == 0); 103 } 104 105 public: 106 // An invalid AnyRef cannot arise naturally from wasm and so can be used as 107 // a sentinel value to indicate failure from an AnyRef-returning function. invalid()108 static AnyRef invalid() { return AnyRef(); } 109 110 // Given a void* that comes from compiled wasm code, turn it into AnyRef. fromCompiledCode(void * p)111 static AnyRef fromCompiledCode(void* p) { return AnyRef((JSObject*)p); } 112 113 // Given a JSObject* that comes from JS, turn it into AnyRef. fromJSObject(JSObject * p)114 static AnyRef fromJSObject(JSObject* p) { return AnyRef(p); } 115 116 // Generate an AnyRef null pointer. null()117 static AnyRef null() { return AnyRef(nullptr); } 118 isNull()119 bool isNull() const { return value_ == nullptr; } 120 121 bool operator==(const AnyRef& rhs) const { 122 return this->value_ == rhs.value_; 123 } 124 125 bool operator!=(const AnyRef& rhs) const { return !(*this == rhs); } 126 forCompiledCode()127 void* forCompiledCode() const { return value_; } 128 asJSObject()129 JSObject* asJSObject() const { return value_; } 130 asJSObjectAddress()131 JSObject** asJSObjectAddress() const { return &value_; } 132 133 void trace(JSTracer* trc); 134 135 // Tags (to be developed further) 136 static constexpr uintptr_t AnyRefTagMask = 1; 137 static constexpr uintptr_t AnyRefObjTag = 0; 138 }; 139 140 using RootedAnyRef = Rooted<AnyRef>; 141 using HandleAnyRef = Handle<AnyRef>; 142 using MutableHandleAnyRef = MutableHandle<AnyRef>; 143 144 // TODO/AnyRef-boxing: With boxed immediates and strings, these will be defined 145 // as MOZ_CRASH or similar so that we can find all locations that need to be 146 // fixed. 147 148 #define ASSERT_ANYREF_IS_JSOBJECT (void)(0) 149 #define STATIC_ASSERT_ANYREF_IS_JSOBJECT static_assert(1, "AnyRef is JSObject") 150 151 // Given any JS value, box it as an AnyRef and store it in *result. Returns 152 // false on OOM. 153 154 bool BoxAnyRef(JSContext* cx, HandleValue val, MutableHandleAnyRef result); 155 156 // Given a JS value that requires an object box, box it as an AnyRef and return 157 // it, returning nullptr on OOM. 158 // 159 // Currently the values requiring a box are those other than JSObject* or 160 // nullptr, but in the future more values will be represented without an 161 // allocation. 162 JSObject* BoxBoxableValue(JSContext* cx, HandleValue val); 163 164 // Given any AnyRef, unbox it as a JS Value. If it is a reference to a wasm 165 // object it will be reflected as a JSObject* representing some TypedObject 166 // instance. 167 168 Value UnboxAnyRef(AnyRef val); 169 170 class WasmValueBox : public NativeObject { 171 static const unsigned VALUE_SLOT = 0; 172 173 public: 174 static const unsigned RESERVED_SLOTS = 1; 175 static const JSClass class_; 176 177 static WasmValueBox* create(JSContext* cx, HandleValue val); value()178 Value value() const { return getFixedSlot(VALUE_SLOT); } offsetOfValue()179 static size_t offsetOfValue() { 180 return NativeObject::getFixedSlotOffset(VALUE_SLOT); 181 } 182 }; 183 184 // A FuncRef is a JSFunction* and is hence also an AnyRef, and the remarks above 185 // about AnyRef apply also to FuncRef. When 'funcref' is used as a value type 186 // in wasm code, the value that is held is "the canonical function value", which 187 // is a function for which IsWasmExportedFunction() is true, and which has the 188 // correct identity wrt reference equality of functions. Notably, if a function 189 // is imported then its ref.func value compares === in JS to the function that 190 // was passed as an import when the instance was created. 191 // 192 // These rules ensure that casts from funcref to anyref are non-converting 193 // (generate no code), and that no wrapping or unwrapping needs to happen when a 194 // funcref or anyref flows across the JS/wasm boundary, and that functions have 195 // the necessary identity when observed from JS, and in the future, from wasm. 196 // 197 // Functions stored in tables, whether wasm tables or internal tables, can be 198 // stored in a form that optimizes for eg call speed, however. 199 // 200 // Reading a funcref from a funcref table, writing a funcref to a funcref table, 201 // and generating the value for a ref.func instruction are therefore nontrivial 202 // operations that require mapping between the canonical JSFunction and the 203 // optimized table representation. Once we get an instruction to call a 204 // ref.func directly it too will require such a mapping. 205 206 // In many cases, a FuncRef is exactly the same as AnyRef and we can use AnyRef 207 // functionality on funcref values. The FuncRef class exists mostly to add more 208 // checks and to make it clear, when we need to, that we're manipulating funcref 209 // values. FuncRef does not currently subclass AnyRef because there's been no 210 // need to, but it probably could. 211 212 class FuncRef { 213 JSFunction* value_; 214 FuncRef()215 explicit FuncRef() : value_((JSFunction*)-1) {} FuncRef(JSFunction * p)216 explicit FuncRef(JSFunction* p) : value_(p) { 217 MOZ_ASSERT(((uintptr_t)p & 0x03) == 0); 218 } 219 220 public: 221 // Given a void* that comes from compiled wasm code, turn it into FuncRef. fromCompiledCode(void * p)222 static FuncRef fromCompiledCode(void* p) { return FuncRef((JSFunction*)p); } 223 224 // Given a JSFunction* that comes from JS, turn it into FuncRef. fromJSFunction(JSFunction * p)225 static FuncRef fromJSFunction(JSFunction* p) { return FuncRef(p); } 226 227 // Given an AnyRef that represents a possibly-null funcref, turn it into a 228 // FuncRef. 229 static FuncRef fromAnyRefUnchecked(AnyRef p); 230 asAnyRef()231 AnyRef asAnyRef() { return AnyRef::fromJSObject((JSObject*)value_); } 232 forCompiledCode()233 void* forCompiledCode() const { return value_; } 234 asJSFunction()235 JSFunction* asJSFunction() { return value_; } 236 isNull()237 bool isNull() { return value_ == nullptr; } 238 }; 239 240 using RootedFuncRef = Rooted<FuncRef>; 241 using HandleFuncRef = Handle<FuncRef>; 242 using MutableHandleFuncRef = MutableHandle<FuncRef>; 243 244 // Given any FuncRef, unbox it as a JS Value -- always a JSFunction*. 245 246 Value UnboxFuncRef(FuncRef val); 247 248 // The LitVal class represents a single WebAssembly value of a given value 249 // type, mostly for the purpose of numeric literals and initializers. A LitVal 250 // does not directly map to a JS value since there is not (currently) a precise 251 // representation of i64 values. A LitVal may contain non-canonical NaNs since, 252 // within WebAssembly, floats are not canonicalized. Canonicalization must 253 // happen at the JS boundary. 254 255 class LitVal { 256 public: 257 union Cell { 258 int32_t i32_; 259 int64_t i64_; 260 float f32_; 261 double f64_; 262 wasm::V128 v128_; 263 wasm::AnyRef ref_; Cell()264 Cell() : v128_() {} 265 ~Cell() = default; 266 }; 267 268 protected: 269 ValType type_; 270 Cell cell_; 271 272 public: LitVal()273 LitVal() : type_(ValType()), cell_{} {} 274 LitVal(ValType type)275 explicit LitVal(ValType type) : type_(type) { 276 MOZ_ASSERT(type.isDefaultable()); 277 switch (type.kind()) { 278 case ValType::Kind::I32: { 279 cell_.i32_ = 0; 280 break; 281 } 282 case ValType::Kind::I64: { 283 cell_.i64_ = 0; 284 break; 285 } 286 case ValType::Kind::F32: { 287 cell_.f32_ = 0; 288 break; 289 } 290 case ValType::Kind::F64: { 291 cell_.f64_ = 0; 292 break; 293 } 294 case ValType::Kind::V128: { 295 new (&cell_.v128_) V128(); 296 break; 297 } 298 case ValType::Kind::Ref: { 299 cell_.ref_ = AnyRef::null(); 300 break; 301 } 302 case ValType::Kind::Rtt: { 303 MOZ_CRASH("not defaultable"); 304 } 305 } 306 } 307 LitVal(uint32_t i32)308 explicit LitVal(uint32_t i32) : type_(ValType::I32) { cell_.i32_ = i32; } LitVal(uint64_t i64)309 explicit LitVal(uint64_t i64) : type_(ValType::I64) { cell_.i64_ = i64; } 310 LitVal(float f32)311 explicit LitVal(float f32) : type_(ValType::F32) { cell_.f32_ = f32; } LitVal(double f64)312 explicit LitVal(double f64) : type_(ValType::F64) { cell_.f64_ = f64; } 313 LitVal(V128 v128)314 explicit LitVal(V128 v128) : type_(ValType::V128) { cell_.v128_ = v128; } 315 LitVal(ValType type,AnyRef any)316 explicit LitVal(ValType type, AnyRef any) : type_(type) { 317 MOZ_ASSERT(type.isReference()); 318 MOZ_ASSERT(any.isNull(), 319 "use Val for non-nullptr ref types to get tracing"); 320 cell_.ref_ = any; 321 } 322 type()323 ValType type() const { return type_; } sizeofLargestValue()324 static constexpr size_t sizeofLargestValue() { return sizeof(cell_); } 325 cell()326 Cell& cell() { return cell_; } cell()327 const Cell& cell() const { return cell_; } 328 i32()329 uint32_t i32() const { 330 MOZ_ASSERT(type_ == ValType::I32); 331 return cell_.i32_; 332 } i64()333 uint64_t i64() const { 334 MOZ_ASSERT(type_ == ValType::I64); 335 return cell_.i64_; 336 } f32()337 const float& f32() const { 338 MOZ_ASSERT(type_ == ValType::F32); 339 return cell_.f32_; 340 } f64()341 const double& f64() const { 342 MOZ_ASSERT(type_ == ValType::F64); 343 return cell_.f64_; 344 } ref()345 AnyRef ref() const { 346 MOZ_ASSERT(type_.isReference()); 347 return cell_.ref_; 348 } v128()349 const V128& v128() const { 350 MOZ_ASSERT(type_ == ValType::V128); 351 return cell_.v128_; 352 } 353 }; 354 355 // A Val is a LitVal that can contain (non-null) pointers to GC things. All Vals 356 // must be used with the rooting APIs as they may contain JS objects. 357 358 class MOZ_NON_PARAM Val : public LitVal { 359 public: Val()360 Val() : LitVal() {} Val(ValType type)361 explicit Val(ValType type) : LitVal(type) {} 362 explicit Val(const LitVal& val); Val(uint32_t i32)363 explicit Val(uint32_t i32) : LitVal(i32) {} Val(uint64_t i64)364 explicit Val(uint64_t i64) : LitVal(i64) {} Val(float f32)365 explicit Val(float f32) : LitVal(f32) {} Val(double f64)366 explicit Val(double f64) : LitVal(f64) {} Val(V128 v128)367 explicit Val(V128 v128) : LitVal(v128) {} Val(ValType type,AnyRef val)368 explicit Val(ValType type, AnyRef val) : LitVal(type, AnyRef::null()) { 369 MOZ_ASSERT(type.isReference()); 370 cell_.ref_ = val; 371 } Val(ValType type,FuncRef val)372 explicit Val(ValType type, FuncRef val) : LitVal(type, AnyRef::null()) { 373 MOZ_ASSERT(type.isFuncRef()); 374 cell_.ref_ = val.asAnyRef(); 375 } 376 377 Val(const Val&) = default; 378 Val& operator=(const Val&) = default; 379 380 bool operator==(const Val& rhs) const { 381 if (type_ != rhs.type_) { 382 return false; 383 } 384 switch (type_.kind()) { 385 case ValType::I32: 386 return cell_.i32_ == rhs.cell_.i32_; 387 case ValType::I64: 388 return cell_.i64_ == rhs.cell_.i64_; 389 case ValType::F32: 390 return cell_.f32_ == rhs.cell_.f32_; 391 case ValType::F64: 392 return cell_.f64_ == rhs.cell_.f64_; 393 case ValType::V128: 394 return cell_.v128_ == rhs.cell_.v128_; 395 case ValType::Rtt: 396 case ValType::Ref: 397 return cell_.ref_ == rhs.cell_.ref_; 398 } 399 MOZ_ASSERT_UNREACHABLE(); 400 return false; 401 } 402 bool operator!=(const Val& rhs) const { return !(*this == rhs); } 403 isJSObject()404 bool isJSObject() const { 405 return type_.isValid() && type_.isReference() && !cell_.ref_.isNull(); 406 } 407 asJSObject()408 JSObject* asJSObject() const { 409 MOZ_ASSERT(isJSObject()); 410 return cell_.ref_.asJSObject(); 411 } 412 asJSObjectAddress()413 JSObject** asJSObjectAddress() const { 414 return cell_.ref_.asJSObjectAddress(); 415 } 416 417 void readFromRootedLocation(const void* loc); 418 void writeToRootedLocation(void* loc, bool mustWrite64) const; 419 420 // See the comment for `ToWebAssemblyValue` below. 421 static bool fromJSValue(JSContext* cx, ValType targetType, HandleValue val, 422 MutableHandle<Val> rval); 423 // See the comment for `ToJSValue` below. 424 bool toJSValue(JSContext* cx, MutableHandleValue rval) const; 425 426 void trace(JSTracer* trc) const; 427 }; 428 429 using GCPtrVal = GCPtr<Val>; 430 using RootedVal = Rooted<Val>; 431 using HandleVal = Handle<Val>; 432 using MutableHandleVal = MutableHandle<Val>; 433 434 using ValVector = GCVector<Val, 0, SystemAllocPolicy>; 435 using RootedValVector = Rooted<ValVector>; 436 using HandleValVector = Handle<ValVector>; 437 using MutableHandleValVector = MutableHandle<ValVector>; 438 439 // Check a value against the given reference type. If the targetType 440 // is RefType::Extern then the test always passes, but the value may be boxed. 441 // If the test passes then the value is stored either in fnval (for 442 // RefType::Func) or in refval (for other types); this split is not strictly 443 // necessary but is convenient for the users of this function. 444 // 445 // This can return false if the type check fails, or if a boxing into AnyRef 446 // throws an OOM. 447 [[nodiscard]] extern bool CheckRefType(JSContext* cx, RefType targetType, 448 HandleValue v, 449 MutableHandleFunction fnval, 450 MutableHandleAnyRef refval); 451 452 // The same as above for when the target type is 'funcref'. 453 [[nodiscard]] extern bool CheckFuncRefValue(JSContext* cx, HandleValue v, 454 MutableHandleFunction fun); 455 456 // The same as above for when the target type is 'eqref'. 457 [[nodiscard]] extern bool CheckEqRefValue(JSContext* cx, HandleValue v, 458 MutableHandleAnyRef vp); 459 class NoDebug; 460 class DebugCodegenVal; 461 462 // The level of coercion to apply in `ToWebAssemblyValue` and `ToJSValue`. 463 enum class CoercionLevel { 464 // The default coercions given by the JS-API specification. 465 Spec, 466 // Allow for the coercions given by `Spec` but also use WebAssembly.Global 467 // as a container for lossless conversions. This is only available through 468 // the wasmLosslessInvoke testing function and is used in tests. 469 Lossless, 470 }; 471 472 // Coercion function from a JS value to a WebAssembly value [1]. 473 // 474 // This function may fail for any of the following reasons: 475 // * The input value has an incorrect type for the targetType 476 // * The targetType is not exposable 477 // * An OOM ocurred 478 // An error will be set upon failure. 479 // 480 // [1] https://webassembly.github.io/spec/js-api/index.html#towebassemblyvalue 481 template <typename Debug = NoDebug> 482 extern bool ToWebAssemblyValue(JSContext* cx, HandleValue val, FieldType type, 483 void* loc, bool mustWrite64, 484 CoercionLevel level = CoercionLevel::Spec); 485 template <typename Debug = NoDebug> 486 extern bool ToWebAssemblyValue(JSContext* cx, HandleValue val, ValType type, 487 void* loc, bool mustWrite64, 488 CoercionLevel level = CoercionLevel::Spec); 489 490 // Coercion function from a WebAssembly value to a JS value [1]. 491 // 492 // This function will only fail if an OOM ocurred. If the type of WebAssembly 493 // value being coerced is not exposable to JS, then it will be coerced to 494 // 'undefined'. Callers are responsible for guarding against this if this is 495 // not desirable. 496 // 497 // [1] https://webassembly.github.io/spec/js-api/index.html#tojsvalue 498 template <typename Debug = NoDebug> 499 extern bool ToJSValue(JSContext* cx, const void* src, FieldType type, 500 MutableHandleValue dst, 501 CoercionLevel level = CoercionLevel::Spec); 502 template <typename Debug = NoDebug> 503 extern bool ToJSValue(JSContext* cx, const void* src, ValType type, 504 MutableHandleValue dst, 505 CoercionLevel level = CoercionLevel::Spec); 506 507 } // namespace wasm 508 509 template <> 510 struct InternalBarrierMethods<wasm::Val> { 511 STATIC_ASSERT_ANYREF_IS_JSOBJECT; 512 513 static bool isMarkable(const wasm::Val& v) { return v.isJSObject(); } 514 515 static void preBarrier(const wasm::Val& v) { 516 if (v.isJSObject()) { 517 gc::PreWriteBarrier(v.asJSObject()); 518 } 519 } 520 521 static MOZ_ALWAYS_INLINE void postBarrier(wasm::Val* vp, 522 const wasm::Val& prev, 523 const wasm::Val& next) { 524 MOZ_RELEASE_ASSERT(!prev.type().isValid() || prev.type() == next.type()); 525 JSObject* prevObj = prev.isJSObject() ? prev.asJSObject() : nullptr; 526 JSObject* nextObj = next.isJSObject() ? next.asJSObject() : nullptr; 527 if (nextObj) { 528 JSObject::postWriteBarrier(vp->asJSObjectAddress(), prevObj, nextObj); 529 } 530 } 531 532 static void readBarrier(const wasm::Val& v) { 533 if (v.isJSObject()) { 534 gc::ReadBarrier(v.asJSObject()); 535 } 536 } 537 538 #ifdef DEBUG 539 static void assertThingIsNotGray(const wasm::Val& v) { 540 if (v.isJSObject()) { 541 JS::AssertObjectIsNotGray(v.asJSObject()); 542 } 543 } 544 #endif 545 }; 546 547 } // namespace js 548 549 #endif // wasm_val_h 550