WasmValType.h (31809B)
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_valtype_h 20 #define wasm_valtype_h 21 22 #include "mozilla/Maybe.h" 23 24 #include "jit/IonTypes.h" 25 #include "wasm/WasmConstants.h" 26 #include "wasm/WasmSerialize.h" 27 #include "wasm/WasmTypeDecls.h" 28 29 namespace js { 30 namespace wasm { 31 32 // A PackedTypeCode represents any value type. 33 union PackedTypeCode { 34 public: 35 using PackedRepr = uint64_t; 36 37 private: 38 static constexpr size_t NullableBits = 1; 39 static constexpr size_t TypeCodeBits = 8; 40 static constexpr size_t TypeDefBits = 48; 41 static constexpr size_t PointerTagBits = 2; 42 static constexpr size_t UnusedBits = 5; 43 44 static_assert(NullableBits + TypeCodeBits + TypeDefBits + PointerTagBits + 45 UnusedBits == 46 (sizeof(PackedRepr) * 8), 47 "enough bits"); 48 49 PackedRepr bits_; 50 struct { 51 PackedRepr nullable_ : NullableBits; 52 PackedRepr typeCode_ : TypeCodeBits; 53 // A pointer to the TypeDef this type references. We use 48-bits for this, 54 // and rely on system memory allocators not allocating outside of this 55 // range. This is also assumed by JS::Value, and so should be safe here. 56 PackedRepr typeDef_ : TypeDefBits; 57 // Reserve the bottom two bits for use as a tagging scheme for BlockType 58 // and ResultType, which can encode a ValType inside themselves in special 59 // cases. 60 PackedRepr pointerTag_ : PointerTagBits; 61 // The remaining bits are unused, but still need to be explicitly 62 // initialized to zero. 63 PackedRepr unused_ : UnusedBits; 64 }; 65 66 explicit constexpr PackedTypeCode(PackedRepr bits) : bits_(bits) {} 67 68 constexpr PackedTypeCode(PackedRepr nullable, PackedRepr typeCode, 69 PackedRepr typeDef) 70 : nullable_(nullable), 71 typeCode_(typeCode), 72 typeDef_(typeDef), 73 pointerTag_(0), 74 unused_(0) {} 75 76 public: 77 PackedTypeCode() = default; 78 79 static constexpr PackedRepr NoTypeCode = ((uint64_t)1 << TypeCodeBits) - 1; 80 81 static constexpr PackedTypeCode invalid() { 82 return PackedTypeCode{false, NoTypeCode, 0}; 83 } 84 85 static constexpr PackedTypeCode fromBits(PackedRepr bits) { 86 return PackedTypeCode{bits}; 87 } 88 89 static PackedTypeCode pack(TypeCode tc, const TypeDef* typeDef, 90 bool isNullable) { 91 MOZ_ASSERT(uint32_t(tc) <= ((1 << TypeCodeBits) - 1)); 92 MOZ_ASSERT_IF(tc != AbstractTypeRefCode, typeDef == nullptr); 93 MOZ_ASSERT_IF(tc == AbstractTypeRefCode, typeDef != nullptr); 94 auto tydef = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(typeDef)); 95 #if defined(JS_64BIT) && defined(DEBUG) 96 // Double check that `typeDef` only has 48 significant bits, with the top 97 // 16 being zero. This is necessary since we will only store the lowest 98 // 48 bits of it, as noted above. There's no equivalent check on 32 bit 99 // targets since we can store the whole pointer. 100 static_assert(sizeof(int64_t) == sizeof(uintptr_t)); 101 MOZ_ASSERT((tydef >> TypeDefBits) == 0); 102 #endif 103 return PackedTypeCode{isNullable, PackedRepr(tc), tydef}; 104 } 105 106 static constexpr PackedTypeCode pack(TypeCode tc, bool nullable) { 107 MOZ_ASSERT(uint32_t(tc) <= ((1 << TypeCodeBits) - 1)); 108 MOZ_ASSERT(tc != AbstractTypeRefCode); 109 return PackedTypeCode{nullable, PackedRepr(tc), 0}; 110 } 111 112 static constexpr PackedTypeCode pack(TypeCode tc) { return pack(tc, false); } 113 114 constexpr bool isValid() const { return typeCode_ != NoTypeCode; } 115 116 PackedRepr bits() const { return bits_; } 117 118 constexpr TypeCode typeCode() const { 119 MOZ_ASSERT(isValid()); 120 return TypeCode(typeCode_); 121 } 122 123 // Return the TypeCode, but return AbstractReferenceTypeCode for any reference 124 // type. 125 // 126 // This function is very, very hot, hence what would normally be a switch on 127 // the value `c` to map the reference types to AbstractReferenceTypeCode has 128 // been distilled into a simple comparison; this is fastest. Should type 129 // codes become too complicated for this to work then a lookup table also has 130 // better performance than a switch. 131 // 132 // An alternative is for the PackedTypeCode to represent something closer to 133 // what ValType needs, so that this decoding step is not necessary, but that 134 // moves complexity elsewhere, and the perf gain here would be only about 1% 135 // for baseline compilation throughput. 136 constexpr TypeCode typeCodeAbstracted() const { 137 TypeCode tc = typeCode(); 138 return tc < LowestPrimitiveTypeCode ? AbstractReferenceTypeCode : tc; 139 } 140 141 // Return whether this type is a reference type. 142 bool isRefType() const { 143 return typeCodeAbstracted() == AbstractReferenceTypeCode; 144 } 145 146 // Return whether this type is represented by a reference at runtime. 147 bool isRefRepr() const { return typeCode() < LowestPrimitiveTypeCode; } 148 149 const TypeDef* typeDef() const { 150 MOZ_ASSERT(isValid()); 151 // On a 64-bit target, this reconstitutes the pointer by zero-extending 152 // the lowest TypeDefBits bits of `typeDef_`. On a 32-bit target, the 153 // pointer is stored exactly in the lowest 32 bits of `typeDef_`. 154 return (const TypeDef*)(uintptr_t)typeDef_; 155 } 156 157 bool isNullable() const { 158 MOZ_ASSERT(isValid()); 159 return bool(nullable_); 160 } 161 162 PackedTypeCode withIsNullable(bool nullable) const { 163 MOZ_ASSERT(isRefType()); 164 PackedTypeCode mutated = *this; 165 mutated.nullable_ = (PackedRepr)nullable; 166 return mutated; 167 } 168 169 bool operator==(const PackedTypeCode& rhs) const { 170 return bits_ == rhs.bits_; 171 } 172 bool operator!=(const PackedTypeCode& rhs) const { 173 return bits_ != rhs.bits_; 174 } 175 }; 176 177 static_assert(sizeof(PackedTypeCode) == sizeof(uint64_t), "packed"); 178 179 // A SerializableTypeCode represents any value type in a form that can be 180 // serialized and deserialized. 181 union SerializableTypeCode { 182 using PackedRepr = uintptr_t; 183 184 static constexpr size_t NullableBits = 1; 185 static constexpr size_t TypeCodeBits = 8; 186 static constexpr size_t TypeIndexBits = 20; 187 188 PackedRepr bits; 189 struct { 190 PackedRepr nullable : NullableBits; 191 PackedRepr typeCode : TypeCodeBits; 192 PackedRepr typeIndex : TypeIndexBits; 193 }; 194 195 WASM_CHECK_CACHEABLE_POD(bits); 196 197 static constexpr PackedRepr NoTypeIndex = (1 << TypeIndexBits) - 1; 198 199 static_assert(NullableBits + TypeCodeBits + TypeIndexBits <= 200 (sizeof(PackedRepr) * 8), 201 "enough bits"); 202 static_assert(NoTypeIndex < (1 << TypeIndexBits), "enough bits"); 203 static_assert(MaxTypes < NoTypeIndex, "enough bits"); 204 205 // Defined in WasmSerialize.cpp 206 static inline SerializableTypeCode serialize(PackedTypeCode ptc, 207 const TypeContext& types); 208 inline PackedTypeCode deserialize(const TypeContext& types); 209 }; 210 211 WASM_DECLARE_CACHEABLE_POD(SerializableTypeCode); 212 static_assert(sizeof(SerializableTypeCode) == sizeof(uintptr_t), "packed"); 213 214 // [SMDOC] Comparing type definitions 215 // 216 // WebAssembly type equality is structural, and we implement canonicalization 217 // such that equality of pointers to type definitions means that the type 218 // definitions are structurally equal. 219 // 220 // 'IsoEquals' is the algorithm used to determine if two types are equal while 221 // canonicalizing types. 222 // 223 // A iso equals type code encodes a type code for use in equality and hashing 224 // matching. It normalizes type references that are local to a recursion group 225 // so that they can be bitwise compared to type references from other recursion 226 // groups. 227 // 228 // This is useful for the following example: 229 // (rec (func $a)) 230 // (rec 231 // (func $b) 232 // (struct 233 // (field (ref $a))) 234 // (field (ref $b))) 235 // ) 236 // (rec 237 // (func $c) 238 // (struct 239 // (field (ref $a))) 240 // (field (ref $c))) 241 // ) 242 // 243 // The last two recursion groups are identical and should canonicalize to the 244 // same instance. However, they will be initially represented as two separate 245 // recursion group instances each with an array type instance with element 246 // types that point to the function type instance before them. A bitwise 247 // comparison of the element type pointers would fail. 248 // 249 // To solve this, we use `IsoEqualsTypeCode` to convert the example to: 250 // (rec (func $a)) 251 // (rec 252 // (func $b) 253 // (struct 254 // (field (ref nonlocal $a))) 255 // (field (ref local 0))) 256 // ) 257 // (rec 258 // (func $c) 259 // (struct 260 // (field (ref nonlocal $a))) 261 // (field (ref local 0))) 262 // ) 263 // 264 // Now, comparing the element types will see that these are local type 265 // references of the same kinds. `IsoEqualsTypeCode` performs the same mechanism 266 // as `tie` in the MVP presentation of type equality [1]. 267 // 268 // [1] 269 // https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP.md#equivalence 270 union IsoEqualsTypeCode { 271 using PackedRepr = uint64_t; 272 273 static constexpr size_t NullableBits = 1; 274 static constexpr size_t TypeCodeBits = 8; 275 static constexpr size_t TypeRefBits = 48; 276 277 PackedRepr bits; 278 struct { 279 PackedRepr nullable : NullableBits; 280 PackedRepr typeCode : TypeCodeBits; 281 PackedRepr typeRef : TypeRefBits; 282 }; 283 284 WASM_CHECK_CACHEABLE_POD(bits); 285 286 static_assert(NullableBits + TypeCodeBits + TypeRefBits <= 287 (sizeof(PackedRepr) * 8), 288 "enough bits"); 289 290 // Defined in WasmTypeDef.h to avoid a cycle while allowing inlining 291 static inline IsoEqualsTypeCode forIsoEquals(PackedTypeCode ptc, 292 const RecGroup* recGroup); 293 294 bool operator==(IsoEqualsTypeCode other) const { return bits == other.bits; } 295 bool operator!=(IsoEqualsTypeCode other) const { return bits != other.bits; } 296 HashNumber hash() const { return HashNumber(bits); } 297 }; 298 299 // An enum that describes the representation classes for tables; The table 300 // element type is mapped into this by Table::repr(). 301 302 enum class TableRepr { Ref, Func }; 303 304 // An enum that describes the different type hierarchies. 305 306 enum class RefTypeHierarchy { Func, Extern, Exn, Any }; 307 308 // The RefType carries more information about types t for which t.isRefType() 309 // is true. 310 311 class RefType { 312 public: 313 enum Kind { 314 Func = uint8_t(TypeCode::FuncRef), 315 Extern = uint8_t(TypeCode::ExternRef), 316 Exn = uint8_t(TypeCode::ExnRef), 317 Any = uint8_t(TypeCode::AnyRef), 318 NoFunc = uint8_t(TypeCode::NullFuncRef), 319 NoExtern = uint8_t(TypeCode::NullExternRef), 320 NoExn = uint8_t(TypeCode::NullExnRef), 321 None = uint8_t(TypeCode::NullAnyRef), 322 Eq = uint8_t(TypeCode::EqRef), 323 I31 = uint8_t(TypeCode::I31Ref), 324 Struct = uint8_t(TypeCode::StructRef), 325 Array = uint8_t(TypeCode::ArrayRef), 326 TypeRef = uint8_t(AbstractTypeRefCode) 327 }; 328 329 private: 330 PackedTypeCode ptc_; 331 332 RefType(Kind kind, bool nullable) 333 : ptc_(PackedTypeCode::pack(TypeCode(kind), nullable)) { 334 MOZ_ASSERT(isValid()); 335 } 336 337 RefType(const TypeDef* typeDef, bool nullable) 338 : ptc_(PackedTypeCode::pack(AbstractTypeRefCode, typeDef, nullable)) { 339 MOZ_ASSERT(isValid()); 340 } 341 342 public: 343 RefType() : ptc_(PackedTypeCode::invalid()) {} 344 explicit RefType(PackedTypeCode ptc) : ptc_(ptc) { MOZ_ASSERT(isValid()); } 345 346 static RefType fromKind(Kind kind, bool nullable) { 347 MOZ_ASSERT(kind != TypeRef); 348 return RefType(kind, nullable); 349 } 350 351 static RefType fromTypeCode(TypeCode tc, bool nullable) { 352 MOZ_ASSERT(tc != AbstractTypeRefCode); 353 return RefType(Kind(tc), nullable); 354 } 355 356 static RefType fromTypeDef(const TypeDef* typeDef, bool nullable) { 357 return RefType(typeDef, nullable); 358 } 359 360 Kind kind() const { return Kind(ptc_.typeCode()); } 361 362 const TypeDef* typeDef() const { return ptc_.typeDef(); } 363 364 PackedTypeCode packed() const { return ptc_; } 365 PackedTypeCode* addressOfPacked() { return &ptc_; } 366 const PackedTypeCode* addressOfPacked() const { return &ptc_; } 367 368 // Further restricts PackedTypeCode::isValid() to only those TypeCodes which 369 // represent a wasm reference type. 370 bool isValid() const { 371 if (!ptc_.isValid()) { 372 return false; 373 } 374 375 MOZ_ASSERT((ptc_.typeCode() == AbstractTypeRefCode) == 376 (ptc_.typeDef() != nullptr)); 377 switch (ptc_.typeCode()) { 378 case TypeCode::FuncRef: 379 case TypeCode::ExternRef: 380 case TypeCode::ExnRef: 381 case TypeCode::AnyRef: 382 case TypeCode::EqRef: 383 case TypeCode::I31Ref: 384 case TypeCode::StructRef: 385 case TypeCode::ArrayRef: 386 case TypeCode::NullFuncRef: 387 case TypeCode::NullExternRef: 388 case TypeCode::NullExnRef: 389 case TypeCode::NullAnyRef: 390 case AbstractTypeRefCode: 391 return true; 392 default: 393 return false; 394 } 395 } 396 397 static RefType func() { return RefType(Func, true); } 398 static RefType extern_() { return RefType(Extern, true); } 399 static RefType exn() { return RefType(Exn, true); } 400 static RefType any() { return RefType(Any, true); } 401 static RefType nofunc() { return RefType(NoFunc, true); } 402 static RefType noextern() { return RefType(NoExtern, true); } 403 static RefType noexn() { return RefType(NoExn, true); } 404 static RefType none() { return RefType(None, true); } 405 static RefType eq() { return RefType(Eq, true); } 406 static RefType i31() { return RefType(I31, true); } 407 static RefType struct_() { return RefType(Struct, true); } 408 static RefType array() { return RefType(Array, true); } 409 410 bool isFunc() const { return kind() == RefType::Func; } 411 bool isExtern() const { return kind() == RefType::Extern; } 412 bool isAny() const { return kind() == RefType::Any; } 413 bool isNoFunc() const { return kind() == RefType::NoFunc; } 414 bool isNoExtern() const { return kind() == RefType::NoExtern; } 415 bool isNoExn() const { return kind() == RefType::NoExn; } 416 bool isNone() const { return kind() == RefType::None; } 417 bool isEq() const { return kind() == RefType::Eq; } 418 bool isI31() const { return kind() == RefType::I31; } 419 bool isStruct() const { return kind() == RefType::Struct; } 420 bool isArray() const { return kind() == RefType::Array; } 421 bool isTypeRef() const { return kind() == RefType::TypeRef; } 422 423 bool isNullable() const { return bool(ptc_.isNullable()); } 424 RefType asNonNullable() const { return withIsNullable(false); } 425 RefType withIsNullable(bool nullable) const { 426 return RefType(ptc_.withIsNullable(nullable)); 427 } 428 429 bool isRefBottom() const { 430 return isNone() || isNoFunc() || isNoExtern() || isNoExn(); 431 } 432 433 // These methods are defined in WasmTypeDef.h to avoid a cycle while allowing 434 // inlining. 435 inline RefTypeHierarchy hierarchy() const; 436 inline TableRepr tableRepr() const; 437 inline bool isFuncHierarchy() const; 438 inline bool isExternHierarchy() const; 439 inline bool isAnyHierarchy() const; 440 inline bool isExnHierarchy() const; 441 static bool isSubTypeOf(RefType subType, RefType superType); 442 static bool castPossible(RefType sourceType, RefType destType); 443 444 // If we have two references, one of type `a` and one of type `b`, return 445 // true if there is any possibility that they might point at the same thing. 446 // That can only happen if either they are the same type or if one type is a 447 // subtype of the other. Note, this can only be used for types in the same 448 // hierarchy. 449 static bool valuesMightAlias(RefType a, RefType b) { 450 MOZ_RELEASE_ASSERT(a.hierarchy() == b.hierarchy()); 451 // The exact-same-type case is subsumed by `isSubTypeOf`. 452 return RefType::isSubTypeOf(a, b) || RefType::isSubTypeOf(b, a); 453 } 454 455 // Gets the top of the given type's hierarchy, e.g. Any for structs and 456 // arrays, and Func for funcs. 457 RefType topType() const; 458 459 // Gets the bottom of the given type's hierarchy, e.g. None for structs and 460 // arrays, and NoFunc for funcs. 461 RefType bottomType() const; 462 463 static RefType leastUpperBound(RefType a, RefType b); 464 465 // Gets the TypeDefKind associated with this RefType, e.g. TypeDefKind::Struct 466 // for RefType::Struct. 467 TypeDefKind typeDefKind() const; 468 469 inline void AddRef() const; 470 inline void Release() const; 471 472 bool operator==(const RefType& that) const { return ptc_ == that.ptc_; } 473 bool operator!=(const RefType& that) const { return ptc_ != that.ptc_; } 474 }; 475 476 using RefTypeVector = Vector<RefType, 0, SystemAllocPolicy>; 477 478 class StorageTypeTraits { 479 public: 480 enum Kind { 481 I8 = uint8_t(TypeCode::I8), 482 I16 = uint8_t(TypeCode::I16), 483 I32 = uint8_t(TypeCode::I32), 484 I64 = uint8_t(TypeCode::I64), 485 F32 = uint8_t(TypeCode::F32), 486 F64 = uint8_t(TypeCode::F64), 487 V128 = uint8_t(TypeCode::V128), 488 Ref = uint8_t(AbstractReferenceTypeCode), 489 }; 490 491 static bool isValidTypeCode(TypeCode tc) { 492 switch (tc) { 493 case TypeCode::I8: 494 case TypeCode::I16: 495 case TypeCode::I32: 496 case TypeCode::I64: 497 case TypeCode::F32: 498 case TypeCode::F64: 499 #ifdef ENABLE_WASM_SIMD 500 case TypeCode::V128: 501 #endif 502 case TypeCode::FuncRef: 503 case TypeCode::ExternRef: 504 case TypeCode::ExnRef: 505 case TypeCode::NullExnRef: 506 case TypeCode::AnyRef: 507 case TypeCode::EqRef: 508 case TypeCode::I31Ref: 509 case TypeCode::StructRef: 510 case TypeCode::ArrayRef: 511 case TypeCode::NullFuncRef: 512 case TypeCode::NullExternRef: 513 case TypeCode::NullAnyRef: 514 case AbstractTypeRefCode: 515 return true; 516 default: 517 return false; 518 } 519 } 520 521 static bool isNumberTypeCode(TypeCode tc) { 522 switch (tc) { 523 case TypeCode::I32: 524 case TypeCode::I64: 525 case TypeCode::F32: 526 case TypeCode::F64: 527 return true; 528 default: 529 return false; 530 } 531 } 532 533 static bool isPackedTypeCode(TypeCode tc) { 534 switch (tc) { 535 case TypeCode::I8: 536 case TypeCode::I16: 537 return true; 538 default: 539 return false; 540 } 541 } 542 543 static bool isVectorTypeCode(TypeCode tc) { 544 switch (tc) { 545 #ifdef ENABLE_WASM_SIMD 546 case TypeCode::V128: 547 return true; 548 #endif 549 default: 550 return false; 551 } 552 } 553 }; 554 555 class ValTypeTraits { 556 public: 557 enum Kind { 558 I32 = uint8_t(TypeCode::I32), 559 I64 = uint8_t(TypeCode::I64), 560 F32 = uint8_t(TypeCode::F32), 561 F64 = uint8_t(TypeCode::F64), 562 V128 = uint8_t(TypeCode::V128), 563 Ref = uint8_t(AbstractReferenceTypeCode), 564 }; 565 566 static const char* KindEnumName(Kind kind) { 567 switch (kind) { 568 case Kind::I32: 569 return "I32"; 570 case Kind::I64: 571 return "I64"; 572 case Kind::F32: 573 return "F32"; 574 case Kind::F64: 575 return "F64"; 576 case Kind::V128: 577 return "V128"; 578 case Kind::Ref: 579 return "Ref"; 580 } 581 MOZ_CRASH("Unknown kind"); 582 } 583 584 static constexpr bool isValidTypeCode(TypeCode tc) { 585 switch (tc) { 586 case TypeCode::I32: 587 case TypeCode::I64: 588 case TypeCode::F32: 589 case TypeCode::F64: 590 #ifdef ENABLE_WASM_SIMD 591 case TypeCode::V128: 592 #endif 593 case TypeCode::FuncRef: 594 case TypeCode::ExternRef: 595 case TypeCode::ExnRef: 596 case TypeCode::NullExnRef: 597 case TypeCode::AnyRef: 598 case TypeCode::EqRef: 599 case TypeCode::I31Ref: 600 case TypeCode::StructRef: 601 case TypeCode::ArrayRef: 602 case TypeCode::NullFuncRef: 603 case TypeCode::NullExternRef: 604 case TypeCode::NullAnyRef: 605 case AbstractTypeRefCode: 606 return true; 607 default: 608 return false; 609 } 610 } 611 612 static bool isNumberTypeCode(TypeCode tc) { 613 switch (tc) { 614 case TypeCode::I32: 615 case TypeCode::I64: 616 case TypeCode::F32: 617 case TypeCode::F64: 618 return true; 619 default: 620 return false; 621 } 622 } 623 624 static bool isPackedTypeCode(TypeCode tc) { return false; } 625 626 static bool isVectorTypeCode(TypeCode tc) { 627 switch (tc) { 628 #ifdef ENABLE_WASM_SIMD 629 case TypeCode::V128: 630 return true; 631 #endif 632 default: 633 return false; 634 } 635 } 636 }; 637 638 // The PackedType represents the storage type of a WebAssembly location, whether 639 // parameter, local, field, or global. See specializations below for ValType and 640 // StorageType. 641 642 template <class T> 643 class PackedType : public T { 644 public: 645 using Kind = typename T::Kind; 646 647 protected: 648 PackedTypeCode tc_; 649 650 explicit PackedType(TypeCode c) : tc_(PackedTypeCode::pack(c)) { 651 MOZ_ASSERT(c != AbstractTypeRefCode); 652 MOZ_ASSERT(isValid()); 653 } 654 655 TypeCode typeCode() const { 656 MOZ_ASSERT(isValid()); 657 return tc_.typeCode(); 658 } 659 660 public: 661 constexpr PackedType() : tc_(PackedTypeCode::invalid()) {} 662 663 MOZ_IMPLICIT constexpr PackedType(Kind c) 664 : tc_(PackedTypeCode::pack(TypeCode(c))) { 665 MOZ_ASSERT(c != Kind::Ref); 666 MOZ_ASSERT(isValid()); 667 } 668 669 MOZ_IMPLICIT PackedType(RefType rt) : tc_(rt.packed()) { 670 MOZ_ASSERT(isValid()); 671 } 672 673 explicit constexpr PackedType(PackedTypeCode ptc) : tc_(ptc) { 674 MOZ_ASSERT(isValid()); 675 } 676 677 inline void AddRef() const; 678 inline void Release() const; 679 680 static constexpr PackedType i32() { return PackedType(PackedType::I32); } 681 static constexpr PackedType f32() { return PackedType(PackedType::F32); } 682 static constexpr PackedType i64() { return PackedType(PackedType::I64); } 683 static constexpr PackedType f64() { return PackedType(PackedType::F64); } 684 685 static PackedType fromMIRType(jit::MIRType mty) { 686 switch (mty) { 687 case jit::MIRType::Int32: 688 return PackedType::I32; 689 break; 690 case jit::MIRType::Int64: 691 return PackedType::I64; 692 break; 693 case jit::MIRType::Float32: 694 return PackedType::F32; 695 break; 696 case jit::MIRType::Double: 697 return PackedType::F64; 698 break; 699 case jit::MIRType::Simd128: 700 return PackedType::V128; 701 break; 702 case jit::MIRType::WasmAnyRef: 703 return PackedType::Ref; 704 default: 705 MOZ_CRASH("fromMIRType: unexpected type"); 706 } 707 } 708 709 static PackedType fromNonRefTypeCode(TypeCode tc) { 710 #ifdef DEBUG 711 switch (tc) { 712 case TypeCode::I8: 713 case TypeCode::I16: 714 case TypeCode::I32: 715 case TypeCode::I64: 716 case TypeCode::F32: 717 case TypeCode::F64: 718 case TypeCode::V128: 719 break; 720 default: 721 MOZ_CRASH("Bad type code"); 722 } 723 #endif 724 return PackedType(tc); 725 } 726 727 static PackedType fromBitsUnsafe(PackedTypeCode::PackedRepr bits) { 728 return PackedType(PackedTypeCode::fromBits(bits)); 729 } 730 731 constexpr bool isValid() const { 732 if (!tc_.isValid()) { 733 return false; 734 } 735 return T::isValidTypeCode(tc_.typeCode()); 736 } 737 738 IsoEqualsTypeCode forIsoEquals(const RecGroup* recGroup) const { 739 return IsoEqualsTypeCode::forIsoEquals(tc_, recGroup); 740 } 741 742 PackedTypeCode packed() const { 743 MOZ_ASSERT(isValid()); 744 return tc_; 745 } 746 PackedTypeCode* addressOfPacked() { return &tc_; } 747 const PackedTypeCode* addressOfPacked() const { return &tc_; } 748 749 PackedTypeCode::PackedRepr bitsUnsafe() const { 750 MOZ_ASSERT(isValid()); 751 return tc_.bits(); 752 } 753 754 bool isNumber() const { return T::isNumberTypeCode(tc_.typeCode()); } 755 756 bool isPacked() const { return T::isPackedTypeCode(tc_.typeCode()); } 757 758 bool isVector() const { return T::isVectorTypeCode(tc_.typeCode()); } 759 760 bool isRefType() const { return tc_.isRefType(); } 761 762 bool isFuncRef() const { return tc_.typeCode() == TypeCode::FuncRef; } 763 764 bool isExternRef() const { return tc_.typeCode() == TypeCode::ExternRef; } 765 766 bool isExnRef() const { return tc_.typeCode() == TypeCode::ExnRef; } 767 768 bool isAnyRef() const { return tc_.typeCode() == TypeCode::AnyRef; } 769 770 bool isNoFunc() const { return tc_.typeCode() == TypeCode::NullFuncRef; } 771 772 bool isNoExtern() const { return tc_.typeCode() == TypeCode::NullExternRef; } 773 774 bool isNoExn() const { return tc_.typeCode() == TypeCode::NullExnRef; } 775 776 bool isNone() const { return tc_.typeCode() == TypeCode::NullAnyRef; } 777 778 bool isEqRef() const { return tc_.typeCode() == TypeCode::EqRef; } 779 780 bool isI31Ref() const { return tc_.typeCode() == TypeCode::I31Ref; } 781 782 bool isStructRef() const { return tc_.typeCode() == TypeCode::StructRef; } 783 784 bool isArrayRef() const { return tc_.typeCode() == TypeCode::ArrayRef; } 785 786 bool isTypeRef() const { return tc_.typeCode() == AbstractTypeRefCode; } 787 788 bool isRefRepr() const { return tc_.isRefRepr(); } 789 790 // Returns whether the type has a default value. 791 bool isDefaultable() const { return !(isRefType() && !isNullable()); } 792 793 // Returns whether the type has a representation in JS. 794 bool isExposable() const { 795 #if defined(ENABLE_WASM_SIMD) 796 return kind() != Kind::V128 && !isExnRef() && !isNoExn(); 797 #else 798 return !isExnRef() && !isNoExn(); 799 #endif 800 } 801 802 bool isNullable() const { return tc_.isNullable(); } 803 804 const TypeDef* typeDef() const { return tc_.typeDef(); } 805 806 Kind kind() const { return Kind(tc_.typeCodeAbstracted()); } 807 808 RefType refType() const { 809 MOZ_ASSERT(isRefType()); 810 return RefType(tc_); 811 } 812 813 RefType::Kind refTypeKind() const { 814 MOZ_ASSERT(isRefType()); 815 return RefType(tc_).kind(); 816 } 817 818 // Some types are encoded as JS::Value when they escape from Wasm (when passed 819 // as parameters to imports or returned from exports). For ExternRef the 820 // Value encoding is pretty much a requirement. For other types it's a choice 821 // that may (temporarily) simplify some code. 822 bool isEncodedAsJSValueOnEscape() const { return isRefType(); } 823 824 uint32_t size() const { 825 switch (tc_.typeCodeAbstracted()) { 826 case TypeCode::I8: 827 return 1; 828 case TypeCode::I16: 829 return 2; 830 case TypeCode::I32: 831 return 4; 832 case TypeCode::I64: 833 return 8; 834 case TypeCode::F32: 835 return 4; 836 case TypeCode::F64: 837 return 8; 838 case TypeCode::V128: 839 return 16; 840 case AbstractReferenceTypeCode: 841 return sizeof(void*); 842 default: 843 MOZ_ASSERT_UNREACHABLE(); 844 return 0; 845 } 846 } 847 uint32_t alignmentInStruct() const { return size(); } 848 uint32_t indexingShift() const { 849 switch (size()) { 850 case 1: 851 return 0; 852 case 2: 853 return 1; 854 case 4: 855 return 2; 856 case 8: 857 return 3; 858 case 16: 859 return 4; 860 default: 861 MOZ_ASSERT_UNREACHABLE(); 862 return 0; 863 } 864 } 865 866 PackedType<ValTypeTraits> widenToValType() const { 867 switch (tc_.typeCodeAbstracted()) { 868 case TypeCode::I8: 869 case TypeCode::I16: 870 return PackedType<ValTypeTraits>::I32; 871 default: 872 return PackedType<ValTypeTraits>(tc_); 873 } 874 } 875 876 PackedType<ValTypeTraits> valType() const { 877 MOZ_ASSERT(isValType()); 878 return PackedType<ValTypeTraits>(tc_); 879 } 880 881 // Note, ToMIRType is only correct within Wasm, where an AnyRef is represented 882 // as a pointer. At the JS/wasm boundary, an AnyRef can be represented as a 883 // JS::Value, and the type translation may have to be handled specially and on 884 // a case-by-case basis. 885 constexpr jit::MIRType toMIRType() const { 886 switch (tc_.typeCodeAbstracted()) { 887 case TypeCode::I32: 888 return jit::MIRType::Int32; 889 case TypeCode::I64: 890 return jit::MIRType::Int64; 891 case TypeCode::F32: 892 return jit::MIRType::Float32; 893 case TypeCode::F64: 894 return jit::MIRType::Double; 895 case TypeCode::V128: 896 return jit::MIRType::Simd128; 897 case AbstractReferenceTypeCode: 898 return jit::MIRType::WasmAnyRef; 899 default: 900 MOZ_CRASH("bad type"); 901 } 902 } 903 904 MaybeRefType toMaybeRefType() const; 905 906 bool isValType() const { 907 switch (tc_.typeCode()) { 908 case TypeCode::I8: 909 case TypeCode::I16: 910 return false; 911 default: 912 return true; 913 } 914 } 915 916 PackedType<StorageTypeTraits> storageType() const { 917 MOZ_ASSERT(isValid()); 918 return PackedType<StorageTypeTraits>(tc_); 919 } 920 921 static bool isSubTypeOf(PackedType subType, PackedType superType) { 922 // Anything is a subtype of itself. 923 if (subType == superType) { 924 return true; 925 } 926 927 // A reference may be a subtype of another reference 928 if (subType.isRefType() && superType.isRefType()) { 929 return RefType::isSubTypeOf(subType.refType(), superType.refType()); 930 } 931 932 return false; 933 } 934 935 bool operator==(const PackedType& that) const { 936 MOZ_ASSERT(isValid() && that.isValid()); 937 return tc_ == that.tc_; 938 } 939 940 bool operator!=(const PackedType& that) const { 941 MOZ_ASSERT(isValid() && that.isValid()); 942 return tc_ != that.tc_; 943 } 944 945 bool operator==(Kind that) const { 946 MOZ_ASSERT(isValid()); 947 MOZ_ASSERT(that != Kind::Ref); 948 return Kind(typeCode()) == that; 949 } 950 951 bool operator!=(Kind that) const { return !(*this == that); } 952 }; 953 954 using StorageType = PackedType<StorageTypeTraits>; 955 956 // The dominant use of this data type is for locals and args, and profiling 957 // with ZenGarden and Tanks suggests an initial size of 16 minimises heap 958 // allocation, both in terms of blocks and bytes. 959 using ValTypeVector = Vector<ValType, 16, SystemAllocPolicy>; 960 961 // A MaybeRefType behaves like a mozilla::Maybe<RefType>, but saves space by 962 // reusing PackedTypeCode. If the inner RefType is not valid, it will be 963 // considered Nothing; otherwise it will be considered Some. 964 class MaybeRefType { 965 private: 966 RefType inner_; 967 968 public: 969 // Creates a MaybeRefType that isNothing(). 970 MaybeRefType() { MOZ_ASSERT(isNothing()); } 971 972 // Creates a MaybeRefType that isSome(). 973 explicit MaybeRefType(RefType type) : inner_(type) { 974 MOZ_RELEASE_ASSERT(isSome()); 975 } 976 977 bool isSome() const { return inner_.isValid(); } 978 bool isNothing() const { return !isSome(); } 979 980 /* Returns the inner RefType by value. Unsafe unless |isSome()|. */ 981 RefType& value() & { 982 MOZ_RELEASE_ASSERT(isSome()); 983 return inner_; 984 }; 985 const RefType& value() const& { 986 MOZ_RELEASE_ASSERT(isSome()); 987 return inner_; 988 }; 989 990 RefType& valueOr(RefType& aDefault) { 991 if (isSome()) { 992 return value(); 993 } 994 return aDefault; 995 } 996 const RefType& valueOr(const RefType& aDefault) { 997 if (isSome()) { 998 return value(); 999 } 1000 return aDefault; 1001 } 1002 1003 bool operator==(const MaybeRefType& other) { return inner_ == other.inner_; } 1004 bool operator!=(const MaybeRefType& other) { return inner_ != other.inner_; } 1005 1006 explicit operator bool() const { return isSome(); } 1007 1008 mozilla::Maybe<wasm::RefTypeHierarchy> hierarchy() const { 1009 if (isSome()) { 1010 return mozilla::Some(value().hierarchy()); 1011 } 1012 return mozilla::Nothing(); 1013 } 1014 1015 static MaybeRefType leastUpperBound(MaybeRefType a, MaybeRefType b) { 1016 if (a.isSome() && b.isSome()) { 1017 return MaybeRefType(RefType::leastUpperBound(a.value(), b.value())); 1018 } 1019 return MaybeRefType(); 1020 } 1021 }; 1022 1023 template <class T> 1024 MaybeRefType PackedType<T>::toMaybeRefType() const { 1025 if (!isRefType()) { 1026 return MaybeRefType(); 1027 } 1028 return MaybeRefType(refType()); 1029 }; 1030 1031 // ValType utilities 1032 1033 extern bool ToValType(JSContext* cx, HandleValue v, ValType* out); 1034 extern bool ToRefType(JSContext* cx, HandleValue v, RefType* out); 1035 1036 extern UniqueChars ToString(RefType type, const TypeContext* types); 1037 extern UniqueChars ToString(ValType type, const TypeContext* types); 1038 extern UniqueChars ToString(StorageType type, const TypeContext* types); 1039 extern UniqueChars ToString(const mozilla::Maybe<ValType>& type, 1040 const TypeContext* types); 1041 extern UniqueChars ToString(const MaybeRefType& type, const TypeContext* types); 1042 1043 } // namespace wasm 1044 } // namespace js 1045 1046 #endif // wasm_valtype_h