WasmGcObject.h (23188B)
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 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef wasm_WasmGcObject_h 8 #define wasm_WasmGcObject_h 9 10 #include "mozilla/Attributes.h" 11 #include "mozilla/CheckedInt.h" 12 #include "mozilla/Maybe.h" 13 14 #include "gc/GCProbes.h" 15 #include "gc/Pretenuring.h" 16 #include "gc/ZoneAllocator.h" // AddCellMemory 17 #include "vm/JSContext.h" 18 #include "vm/JSObject.h" 19 #include "vm/Probes.h" 20 #include "wasm/WasmInstanceData.h" 21 #include "wasm/WasmMemory.h" 22 #include "wasm/WasmTypeDef.h" 23 #include "wasm/WasmValType.h" 24 25 namespace js { 26 27 //========================================================================= 28 // WasmGcObject 29 30 class WasmGcObject : public JSObject { 31 protected: 32 const wasm::SuperTypeVector* superTypeVector_; 33 34 static const ObjectOps objectOps_; 35 36 [[nodiscard]] static bool obj_lookupProperty(JSContext* cx, HandleObject obj, 37 HandleId id, 38 MutableHandleObject objp, 39 PropertyResult* propp); 40 41 [[nodiscard]] static bool obj_defineProperty(JSContext* cx, HandleObject obj, 42 HandleId id, 43 Handle<PropertyDescriptor> desc, 44 ObjectOpResult& result); 45 46 [[nodiscard]] static bool obj_hasProperty(JSContext* cx, HandleObject obj, 47 HandleId id, bool* foundp); 48 49 [[nodiscard]] static bool obj_getProperty(JSContext* cx, HandleObject obj, 50 HandleValue receiver, HandleId id, 51 MutableHandleValue vp); 52 53 [[nodiscard]] static bool obj_setProperty(JSContext* cx, HandleObject obj, 54 HandleId id, HandleValue v, 55 HandleValue receiver, 56 ObjectOpResult& result); 57 58 [[nodiscard]] static bool obj_getOwnPropertyDescriptor( 59 JSContext* cx, HandleObject obj, HandleId id, 60 MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc); 61 62 [[nodiscard]] static bool obj_deleteProperty(JSContext* cx, HandleObject obj, 63 HandleId id, 64 ObjectOpResult& result); 65 66 // PropOffset is a uint32_t that is used to carry information about the 67 // location of an value from WasmGcObject::lookupProperty to 68 // WasmGcObject::loadValue. It is distinct from a normal uint32_t to 69 // emphasise the fact that it cannot be interpreted as an offset in any 70 // single contiguous area of memory: 71 // 72 // * If the object in question is a WasmStructObject, it is the index of 73 // the relevant field. 74 // 75 // * If the object in question is a WasmArrayObject, then 76 // - u32 == UINT32_MAX (0xFFFF'FFFF) means the "length" property 77 // is requested 78 // - u32 < UINT32_MAX means the array element starting at that byte 79 // offset in WasmArrayObject::data_. It is not an array index value. 80 // See WasmGcObject::lookupProperty for details. 81 class PropOffset { 82 uint32_t u32_; 83 84 public: 85 PropOffset() : u32_(0) {} 86 uint32_t get() const { return u32_; } 87 void set(uint32_t u32) { u32_ = u32; } 88 }; 89 90 [[nodiscard]] static bool lookUpProperty(JSContext* cx, 91 Handle<WasmGcObject*> obj, jsid id, 92 PropOffset* offset, 93 wasm::StorageType* type); 94 95 public: 96 [[nodiscard]] static bool loadValue(JSContext* cx, Handle<WasmGcObject*> obj, 97 jsid id, MutableHandleValue vp); 98 99 const wasm::SuperTypeVector& superTypeVector() const { 100 return *superTypeVector_; 101 } 102 103 static constexpr size_t offsetOfSuperTypeVector() { 104 return offsetof(WasmGcObject, superTypeVector_); 105 } 106 107 // These are both expensive in that they involve a double indirection. 108 // Avoid them if possible. 109 const wasm::TypeDef& typeDef() const { return *superTypeVector().typeDef(); } 110 wasm::TypeDefKind kind() const { return superTypeVector().typeDef()->kind(); } 111 112 [[nodiscard]] bool isRuntimeSubtypeOf( 113 const wasm::TypeDef* parentTypeDef) const; 114 115 [[nodiscard]] static bool obj_newEnumerate(JSContext* cx, HandleObject obj, 116 MutableHandleIdVector properties, 117 bool enumerableOnly); 118 }; 119 120 //========================================================================= 121 // WasmArrayObject 122 123 // Class for a wasm array. It contains a pointer to the array contents and 124 // possibly inline data. Array data is allocated with a DataHeader that tracks 125 // whether the array data is stored inline in a trailing array, or out of line 126 // in heap memory. The array's data pointer will always point at the start of 127 // the array data, and the data header can always be read by subtracting 128 // sizeof(DataHeader). 129 class WasmArrayObject : public WasmGcObject, 130 public TrailingArray<WasmArrayObject> { 131 public: 132 static const JSClass class_; 133 134 // The number of elements in the array. 135 uint32_t numElements_; 136 137 // Owned data pointer, holding `numElements_` entries. This may point to 138 // `inlineStorage` or to an externally-allocated block of memory. It points 139 // to the start of the array data, after the data header. 140 // 141 // This pointer is never null. An empty array will be stored like any other 142 // inline-storage array. 143 uint8_t* data_; 144 145 // The inline (wasm-array-level) data fields, stored as a trailing array. We 146 // request this field to begin at an 8-aligned offset relative to the start of 147 // the object, so as to guarantee that `double` typed fields are not subject 148 // to misaligned-access penalties on any target, whilst wasting at maximum 4 149 // bytes of space. (v128 fields are possible, but we have opted to favor 150 // slightly smaller objects over requiring a 16-byte alignment.) 151 // 152 // If used, the inline storage area will begin with the data header, followed 153 // by the actual array data. See the main comment on WasmArrayObject. 154 // 155 // Remember that `inlineStorage` is in reality a variable length block with 156 // maximum size WasmArrayObject_MaxInlineBytes bytes. Do not add any 157 // (C++-level) fields after this point! 158 uint8_t* inlineStorage() { 159 return offsetToPointer<uint8_t>(offsetOfInlineStorage()); 160 } 161 162 // Actual array data that follows DataHeader. The array data is a part of the 163 // `inlineStorage`. 164 template <typename T> 165 T* inlineArrayElements() { 166 return offsetToPointer<T>(offsetOfInlineArrayData()); 167 } 168 169 // AllocKind for object creation 170 static inline gc::AllocKind allocKindForOOL(); 171 static inline gc::AllocKind allocKindForIL(uint32_t storageBytes); 172 inline gc::AllocKind allocKind() const; 173 174 // Calculate the byte length of the array's data storage, being careful to 175 // check for overflow. This includes the data header, data, and any extra 176 // space for alignment with GC sizes. Note this logic assumes that 177 // MaxArrayPayloadBytes is within uint32_t range. 178 // 179 // This logic is mirrored in WasmArrayObject::maxInlineElementsForElemSize and 180 // MacroAssembler::wasmNewArrayObject. 181 static constexpr mozilla::CheckedUint32 calcStorageBytesChecked( 182 uint32_t elemSize, uint32_t numElements) { 183 static_assert(sizeof(WasmArrayObject) % gc::CellAlignBytes == 0); 184 mozilla::CheckedUint32 storageBytes = elemSize; 185 storageBytes *= numElements; 186 storageBytes += sizeof(WasmArrayObject::DataHeader); 187 // Round total allocation up to gc::CellAlignBytes 188 storageBytes -= 1; 189 storageBytes += gc::CellAlignBytes - (storageBytes % gc::CellAlignBytes); 190 return storageBytes; 191 } 192 // Calculate the byte length of the array's data storage, without checking for 193 // overflow. This includes the data header, data, and any extra space for 194 // alignment with GC sizes. 195 static uint32_t calcStorageBytesUnchecked(uint32_t elemSize, 196 uint32_t numElements) { 197 mozilla::CheckedUint32 storageBytes = 198 calcStorageBytesChecked(elemSize, numElements); 199 MOZ_ASSERT(storageBytes.isValid()); 200 return storageBytes.value(); 201 } 202 // Compute the maximum number of elements that can be stored inline for the 203 // given element size. 204 static inline constexpr uint32_t maxInlineElementsForElemSize( 205 uint32_t elemSize); 206 207 size_t sizeOfExcludingThis() const; 208 209 // These constants can be anything, so long as they are not the same. Use 210 // small but unlikely values in the hope of getting more value from 211 // assertions involving them. 212 using DataHeader = uintptr_t; 213 static const DataHeader DataIsIL = 0x37; 214 static const DataHeader DataIsOOL = 0x71; 215 216 // Creates a new array object with out-of-line storage. Reports an error on 217 // OOM. The element type, shape, class pointer, alloc site and alloc kind are 218 // taken from `typeDefData`; the initial heap must be specified separately. 219 // The size of storage is debug-asserted to be larger than 220 // WasmArrayObject_MaxInlineBytes - generally, C++ code should use 221 // WasmArrayObject::createArray. 222 template <bool ZeroFields> 223 static MOZ_ALWAYS_INLINE WasmArrayObject* createArrayOOL( 224 JSContext* cx, wasm::TypeDefInstanceData* typeDefData, 225 js::gc::AllocSite* allocSite, js::gc::Heap initialHeap, 226 uint32_t numElements, uint32_t storageBytes); 227 228 // Creates a new array object with inline storage. Reports an error on OOM. 229 // The element type, shape, class pointer, alloc site and alloc kind are taken 230 // from `typeDefData`; the initial heap must be specified separately. The size 231 // of storage is debug-asserted to be within WasmArrayObject_MaxInlineBytes - 232 // generally, C++ code should use WasmArrayObject::createArray. 233 template <bool ZeroFields> 234 static MOZ_ALWAYS_INLINE WasmArrayObject* createArrayIL( 235 JSContext* cx, wasm::TypeDefInstanceData* typeDefData, 236 js::gc::AllocSite* allocSite, js::gc::Heap initialHeap, 237 uint32_t numElements, uint32_t storageBytes); 238 239 // This selects one of the above two routines, depending on how much storage 240 // is required for the given type and number of elements. 241 template <bool ZeroFields> 242 static MOZ_ALWAYS_INLINE WasmArrayObject* createArray( 243 JSContext* cx, wasm::TypeDefInstanceData* typeDefData, 244 js::gc::AllocSite* allocSite, js::gc::Heap initialHeap, 245 uint32_t numElements); 246 247 // JIT accessors 248 static constexpr size_t offsetOfNumElements() { 249 return offsetof(WasmArrayObject, numElements_); 250 } 251 static constexpr size_t offsetOfData() { 252 return offsetof(WasmArrayObject, data_); 253 } 254 static const uint32_t inlineStorageAlignment = 8; 255 static constexpr size_t offsetOfInlineStorage() { 256 return AlignBytes(sizeof(WasmArrayObject), inlineStorageAlignment); 257 } 258 static constexpr size_t offsetOfInlineArrayData() { 259 return offsetOfInlineStorage() + sizeof(DataHeader); 260 } 261 262 // Tracing and finalization 263 static void obj_trace(JSTracer* trc, JSObject* object); 264 static void obj_finalize(JS::GCContext* gcx, JSObject* object); 265 static size_t obj_moved(JSObject* objNew, JSObject* objOld); 266 267 void storeVal(const wasm::Val& val, uint32_t itemIndex); 268 void fillVal(const wasm::Val& val, uint32_t itemIndex, uint32_t len); 269 270 static inline DataHeader* dataHeaderFromDataPointer(const uint8_t* data) { 271 MOZ_ASSERT(data); 272 DataHeader* header = (DataHeader*)data; 273 header--; 274 MOZ_ASSERT(*header == DataIsIL || *header == DataIsOOL); 275 return header; 276 } 277 DataHeader* dataHeader() const { 278 return WasmArrayObject::dataHeaderFromDataPointer(data_); 279 } 280 281 static inline uint8_t* dataHeaderToDataPointer(const DataHeader* header) { 282 MOZ_ASSERT(header); 283 MOZ_ASSERT(*header == DataIsIL || *header == DataIsOOL); 284 header++; 285 return (uint8_t*)header; 286 } 287 288 static bool isDataInline(uint8_t* data) { 289 const DataHeader* header = dataHeaderFromDataPointer(data); 290 MOZ_ASSERT(*header == DataIsIL || *header == DataIsOOL); 291 return *header == DataIsIL; 292 } 293 bool isDataInline() const { return WasmArrayObject::isDataInline(data_); } 294 295 static WasmArrayObject* fromInlineDataPointer(uint8_t* data) { 296 MOZ_ASSERT(isDataInline(data)); 297 return (WasmArrayObject*)(data - 298 WasmArrayObject::offsetOfInlineArrayData()); 299 } 300 301 static DataHeader* addressOfInlineDataHeader(WasmArrayObject* base) { 302 return base->offsetToPointer<DataHeader>(offsetOfInlineStorage()); 303 } 304 static uint8_t* addressOfInlineData(WasmArrayObject* base) { 305 return base->offsetToPointer<uint8_t>(offsetOfInlineArrayData()); 306 } 307 }; 308 309 static_assert((WasmArrayObject::offsetOfInlineStorage() % 8) == 0); 310 311 // Helper to mark all locations that assume that the type of 312 // WasmArrayObject::numElements is uint32_t. 313 #define STATIC_ASSERT_WASMARRAYELEMENTS_NUMELEMENTS_IS_U32 \ 314 static_assert(sizeof(js::WasmArrayObject::numElements_) == sizeof(uint32_t)) 315 316 //========================================================================= 317 // WasmStructObject 318 319 // Class for a wasm struct. It has inline data and, if the inline area is 320 // insufficient, a pointer to outline data that lives in the C++ heap. 321 // Computing the field offsets is somewhat tricky; see SMDOC in 322 // WasmStructLayout.h. 323 // 324 // From a C++ viewpoint, WasmStructObject just holds two pointers, a shape 325 // pointer and the supertype vector pointer. Because of class-total-size 326 // roundup effects, it is 16 bytes on both 64- and 32-bit targets. 327 // 328 // For our purposes a WasmStructObject is always followed immediately by an 329 // in-line data area, with maximum size WasmStructObject_MaxInlineBytes. Both 330 // the two-word header and the inline data area have 8-aligned sizes. The GC's 331 // allocation routines only guarantee 8-byte alignment. This means a 332 // WasmStructObject can offer naturally aligned storage for fields of size 8, 333 // 4, 2 and 1, but not for fields of size 16, even though the header size is 16 334 // bytes. 335 // 336 // If the available inline storage is insufficient, some part of the inline 337 // data are will be used as a pointer to the out of line area. This however is 338 // not WasmStructObject's concern: it is unaware of the in-line area layout, 339 // all details of which are stored in the associated StructType, and partially 340 // cached in TypeDefInstanceData.cached.strukt. 341 // 342 // Note that MIR alias analysis assumes the OOL-pointer field, if any, is 343 // readonly for the life of the object; do not change it once the object is 344 // created. See MWasmLoadField::congruentTo. 345 346 class WasmStructObject : public WasmGcObject, 347 public TrailingArray<WasmStructObject> { 348 public: 349 static const JSClass classInline_; 350 static const JSClass classOutline_; 351 352 static const JSClass* classFromOOLness(bool needsOOLstorage) { 353 return needsOOLstorage ? &classOutline_ : &classInline_; 354 } 355 356 size_t sizeOfExcludingThis() const; 357 358 // Creates a new struct typed object, optionally initialized to zero. 359 // Reports if there is an out of memory error. The structure's type, shape, 360 // class pointer, alloc site and alloc kind are taken from `typeDefData`; 361 // the initial heap must be specified separately. It is assumed and debug- 362 // asserted that `typeDefData` refers to a type that does not need OOL 363 // storage. 364 template <bool ZeroFields> 365 static MOZ_ALWAYS_INLINE WasmStructObject* createStructIL( 366 JSContext* cx, wasm::TypeDefInstanceData* typeDefData, 367 gc::AllocSite* allocSite, js::gc::Heap initialHeap); 368 369 // Same as ::createStructIL, except it is assumed and debug-asserted that 370 // `typeDefData` refers to a type that does need OOL storage. 371 template <bool ZeroFields> 372 static MOZ_ALWAYS_INLINE WasmStructObject* createStructOOL( 373 JSContext* cx, wasm::TypeDefInstanceData* typeDefData, 374 gc::AllocSite* allocSite, js::gc::Heap initialHeap); 375 376 // Given the index of a field, return its actual address. 377 uint8_t* fieldIndexToAddress(uint32_t fieldIndex); 378 379 // Operations relating to the OOL block pointer. These involve chain-chasing 380 // starting from `superTypeVector_` and shouldn't be used in very hot paths. 381 bool hasOOLPointer() const; 382 // These will release-assert if called when `!hasOOLPointer()`. 383 uint8_t** addressOfOOLPointer() const; 384 uint8_t* getOOLPointer() const; 385 void setOOLPointer(uint8_t* newOOLpointer); 386 387 // Similar to the above, but find the OOL pointer by looking in the supplied 388 // TypeDefInstanceData. This requires less chain-chasing. 389 uint8_t** addressOfOOLPointer( 390 const wasm::TypeDefInstanceData* typeDefData) const; 391 void setOOLPointer(const wasm::TypeDefInstanceData* typeDefData, 392 uint8_t* newOOLpointer); 393 394 // Gets JS Value of the structure field. 395 bool getField(JSContext* cx, uint32_t index, MutableHandle<Value> val); 396 397 // Tracing and finalization 398 static void obj_trace(JSTracer* trc, JSObject* object); 399 static size_t obj_moved(JSObject* objNew, JSObject* objOld); 400 401 void storeVal(const wasm::Val& val, uint32_t fieldIndex); 402 }; 403 404 // This isn't specifically required. Is merely here to make it obvious when 405 // the size does change. 406 static_assert(sizeof(WasmStructObject) == 16); 407 408 // Both `sizeof(WasmStructObject)` and WasmStructObject_MaxInlineBytes 409 // must be multiples of 8 for reasons described in the comment on 410 // `class WasmStructObject` above. 411 static_assert((sizeof(WasmStructObject) % 8) == 0); 412 413 const size_t WasmStructObject_MaxInlineBytes = 414 ((JSObject::MAX_BYTE_SIZE - sizeof(WasmStructObject)) / 8) * 8; 415 416 static_assert((WasmStructObject_MaxInlineBytes % 8) == 0); 417 418 // These are EXTREMELY IMPORTANT. Do not remove them. Without them, there is 419 // nothing that ensures that the object layouts created by StructType::init() 420 // will actually be in accordance with the WasmStructObject layout constraints 421 // described above. If either fails, the _ASSUMED values are wrong and will 422 // need to be updated. 423 static_assert(wasm::WasmStructObject_Size_ASSUMED == sizeof(WasmStructObject)); 424 static_assert(wasm::WasmStructObject_MaxInlineBytes_ASSUMED == 425 WasmStructObject_MaxInlineBytes); 426 427 const size_t WasmArrayObject_MaxInlineBytes = 428 ((JSObject::MAX_BYTE_SIZE - sizeof(WasmArrayObject)) / 16) * 16; 429 430 static_assert((WasmArrayObject_MaxInlineBytes % 16) == 0); 431 432 /* static */ 433 inline constexpr uint32_t WasmArrayObject::maxInlineElementsForElemSize( 434 uint32_t elemSize) { 435 // This implementation inverts the logic of WasmArrayObject::calcStorageBytes 436 // to compute numElements. 437 MOZ_RELEASE_ASSERT(elemSize > 0); 438 uint32_t result = WasmArrayObject_MaxInlineBytes; 439 static_assert(WasmArrayObject_MaxInlineBytes % gc::CellAlignBytes == 0); 440 result -= sizeof(WasmArrayObject::DataHeader); 441 result /= elemSize; 442 443 MOZ_RELEASE_ASSERT(calcStorageBytesChecked(elemSize, result).isValid()); 444 return result; 445 } 446 447 inline bool WasmStructObject::hasOOLPointer() const { 448 const wasm::SuperTypeVector* stv = superTypeVector_; 449 const wasm::TypeDef* typeDef = stv->typeDef(); 450 MOZ_ASSERT(typeDef->superTypeVector() == stv); 451 const wasm::StructType& structType = typeDef->structType(); 452 uint32_t offset = structType.oolPointerOffset_; 453 return offset != wasm::StructType::InvalidOffset; 454 } 455 456 inline uint8_t** WasmStructObject::addressOfOOLPointer() const { 457 const wasm::SuperTypeVector* stv = superTypeVector_; 458 const wasm::TypeDef* typeDef = stv->typeDef(); 459 MOZ_ASSERT(typeDef->superTypeVector() == stv); 460 const wasm::StructType& structType = typeDef->structType(); 461 uint32_t offset = structType.oolPointerOffset_; 462 MOZ_RELEASE_ASSERT(offset != wasm::StructType::InvalidOffset); 463 return (uint8_t**)((uint8_t*)this + offset); 464 } 465 466 inline uint8_t* WasmStructObject::getOOLPointer() const { 467 return *addressOfOOLPointer(); 468 } 469 470 inline void WasmStructObject::setOOLPointer(uint8_t* newOOLpointer) { 471 *addressOfOOLPointer() = newOOLpointer; 472 } 473 474 inline uint8_t** WasmStructObject::addressOfOOLPointer( 475 const wasm::TypeDefInstanceData* typeDefData) const { 476 uint32_t offset = typeDefData->cached.strukt.oolPointerOffset; 477 MOZ_RELEASE_ASSERT(offset != wasm::StructType::InvalidOffset); 478 uint8_t** addr = (uint8_t**)((uint8_t*)this + offset); 479 // Don't turn this into a release-assert; that would defeat the purpose of 480 // having this method. 481 MOZ_ASSERT(addr == addressOfOOLPointer()); 482 return addr; 483 } 484 485 inline void WasmStructObject::setOOLPointer( 486 const wasm::TypeDefInstanceData* typeDefData, uint8_t* newOOLpointer) { 487 *addressOfOOLPointer(typeDefData) = newOOLpointer; 488 } 489 490 // Ensure that faulting loads/stores for WasmStructObject and WasmArrayObject 491 // are in the NULL pointer guard page. 492 static_assert(WasmStructObject_MaxInlineBytes <= wasm::NullPtrGuardSize); 493 static_assert(sizeof(WasmArrayObject) <= wasm::NullPtrGuardSize); 494 495 // Template to acquire a stable pointer to the elements of a WasmArrayObject 496 // that will not move even if there is a GC. This will create a copy of the 497 // array onto the stack when the array has inline data, and can be expensive. 498 template <typename T> 499 class MOZ_RAII StableWasmArrayObjectElements { 500 static constexpr size_t MaxInlineElements = 501 WasmArrayObject::maxInlineElementsForElemSize(sizeof(T)); 502 Rooted<WasmArrayObject*> array_; 503 T* elements_; 504 mozilla::Maybe<mozilla::Vector<T, MaxInlineElements, SystemAllocPolicy>> 505 ownElements_; 506 507 public: 508 StableWasmArrayObjectElements(JSContext* cx, Handle<WasmArrayObject*> array) 509 : array_(cx, array), elements_(nullptr) { 510 if (array->isDataInline()) { 511 ownElements_.emplace(); 512 if (!ownElements_->resize(array->numElements_)) { 513 // Should not happen as we have inline storage for the maximum needed 514 // elements. 515 MOZ_CRASH(); 516 } 517 const T* src = array->inlineArrayElements<T>(); 518 std::copy(src, src + array->numElements_, ownElements_->begin()); 519 elements_ = ownElements_->begin(); 520 } else { 521 elements_ = reinterpret_cast<T*>(array->data_); 522 } 523 } 524 525 T* elements() { return elements_; } 526 size_t length() const { return array_->numElements_; } 527 }; 528 529 } // namespace js 530 531 //========================================================================= 532 // misc 533 534 namespace js { 535 536 inline bool IsWasmGcObjectClass(const JSClass* class_) { 537 return class_ == &WasmArrayObject::class_ || 538 class_ == &WasmStructObject::classInline_ || 539 class_ == &WasmStructObject::classOutline_; 540 } 541 542 } // namespace js 543 544 template <> 545 inline bool JSObject::is<js::WasmGcObject>() const { 546 return js::IsWasmGcObjectClass(getClass()); 547 } 548 549 template <> 550 inline bool JSObject::is<js::WasmStructObject>() const { 551 const JSClass* class_ = getClass(); 552 return class_ == &js::WasmStructObject::classInline_ || 553 class_ == &js::WasmStructObject::classOutline_; 554 } 555 556 #endif /* wasm_WasmGcObject_h */