PropMap.h (43145B)
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 vm_PropMap_h 8 #define vm_PropMap_h 9 10 #include "gc/Barrier.h" 11 #include "gc/Cell.h" 12 #include "gc/Policy.h" 13 #include "js/TypeDecls.h" 14 #include "js/UbiNode.h" 15 #include "js/Utility.h" // JS::UniqueChars 16 #include "vm/ObjectFlags.h" 17 #include "vm/PropertyInfo.h" 18 #include "vm/PropertyKey.h" 19 20 // [SMDOC] Property Maps 21 // 22 // Property maps are used to store information about native object properties. 23 // Each property map represents an ordered list of (PropertyKey, PropertyInfo) 24 // tuples. 25 // 26 // Each property map can store up to 8 properties (see PropMap::Capacity). To 27 // store more than eight properties, multiple maps must be linked together with 28 // the |previous| pointer. 29 // 30 // Shapes and Property Maps 31 // ------------------------ 32 // Native object shapes represent property information as a (PropMap*, length) 33 // tuple. When there are no properties yet, the shape's map will be nullptr and 34 // the length is zero. 35 // 36 // For example, consider the following objects: 37 // 38 // o1 = {x: 1, y: 2} 39 // o2 = {x: 3, y: 4, z: 5} 40 // 41 // This is stored as follows: 42 // 43 // +-------------+ +--------------+ +-------------------+ 44 // | JSObject o1 | | Shape S1 | | PropMap M1 | 45 // |-------------+ +--------------+ +-------------------+ 46 // | shape: S1 -+---> | map: M1 -+--+> | key 0: x (slot 0) | 47 // | slot 0: 1 | | mapLength: 2 | | | key 1: y (slot 1) | 48 // | slot 1: 2 | +--------------+ | | key 2: z (slot 2) | 49 // +-------------+ | | ... | 50 // | +-------------------+ 51 // | 52 // +-------------+ +--------------+ | 53 // | JSObject o2 | | Shape S2 | | 54 // |-------------+ +--------------+ | 55 // | shape: S2 -+---> | map: M1 -+--+ 56 // | slot 0: 3 | | mapLength: 3 | 57 // | slot 1: 4 | +--------------+ 58 // | slot 2: 5 | 59 // +-------------+ 60 // 61 // There's a single map M1 shared by shapes S1 and S2. Shape S1 includes only 62 // the first two properties and shape S2 includes all three properties. 63 // 64 // Class Hierarchy 65 // --------------- 66 // Property maps have the following C++ class hierarchy: 67 // 68 // PropMap (abstract) 69 // | 70 // +-- SharedPropMap (abstract) 71 // | | 72 // | +-- CompactPropMap 73 // | | 74 // | +-- NormalPropMap 75 // | 76 // +-- DictionaryPropMap 77 // 78 // * PropMap: base class. It has a flags word and an array of PropertyKeys. 79 // 80 // * SharedPropMap: base class for all shared property maps. See below for more 81 // information on shared maps. 82 // 83 // * CompactPropMap: a shared map that stores its information more compactly 84 // than the other maps. It saves four words by not storing a 85 // PropMapTable, previous pointer, and by using a more compact 86 // PropertyInfo type for slot numbers that fit in one byte. 87 // 88 // * NormalPropMap: a shared map, used when CompactPropMap can't be used. 89 // 90 // * DictionaryPropMap: an unshared map (used by a single object/shape). See 91 // below for more information on dictionary maps. 92 // 93 // Secondary hierarchy 94 // ------------------- 95 // NormalPropMap and DictionaryPropMap store property information in the same 96 // way. This means property lookups don't have to distinguish between these two 97 // types. This is represented with a second class hierarchy: 98 // 99 // PropMap (abstract) 100 // | 101 // +-- CompactPropMap 102 // | 103 // +-- LinkedPropMap (NormalPropMap or DictionaryPropMap) 104 // 105 // Property lookup and property iteration are very performance sensitive and use 106 // this Compact vs Linked "view" so that they don't have to handle the three map 107 // types separately. 108 // 109 // LinkedPropMap also stores the PropMapTable and a pointer to the |previous| 110 // map. Compact maps don't have these fields. 111 // 112 // To summarize these map types: 113 // 114 // +-------------------+-------------+--------+ 115 // | Concrete type | Shared/tree | Linked | 116 // +-------------------+-------------+--------+ 117 // | CompactPropMap | yes | no | 118 // | NormalPropMap | yes | yes | 119 // | DictionaryPropMap | no | yes | 120 // +-------------------+-------------+--------+ 121 // 122 // PropMapTable 123 // ------------ 124 // Finding the PropertyInfo for a particular PropertyKey requires a linear 125 // search if the map is small. For larger maps we can create a PropMapTable, a 126 // hash table that maps from PropertyKey to PropMap + index, to speed up 127 // property lookups. 128 // 129 // To save memory, property map tables can be discarded on GC and recreated when 130 // needed. AutoKeepPropMapTables can be used to avoid discarding tables in a 131 // particular zone. Methods to access a PropMapTable take either an 132 // AutoCheckCannotGC or AutoKeepPropMapTables argument, to help ensure tables 133 // are not purged while we're using them. 134 // 135 // Shared Property Maps 136 // -------------------- 137 // Shared property maps can be shared per-Zone by objects with the same property 138 // keys, flags, and slot numbers. To make this work, shared maps form a tree: 139 // 140 // - Each Zone has a table that maps from first PropertyKey + PropertyInfo to 141 // a SharedPropMap that begins with that property. This is used to lookup the 142 // the map to use when adding the first property. 143 // See ShapeZone::initialPropMaps. 144 // 145 // - When adding a property other than the first one, the property is stored in 146 // the next entry of the same map when possible. If the map is full or the 147 // next entry already stores a different property, a child map is created and 148 // linked to the parent map. 149 // 150 // For example, imagine we want to create these objects: 151 // 152 // o1 = {x: 1, y: 2, z: 3} 153 // o2 = {x: 1, y: 2, foo: 4} 154 // 155 // This will result in the following maps being created: 156 // 157 // +---------------------+ +---------------------+ 158 // | SharedPropMap M1 | | SharedPropMap M2 | 159 // +---------------------+ +---------------------+ 160 // | Child M2 (index 1) -+--> | Parent M1 (index 1) | 161 // +---------------------+ +---------------------+ 162 // | 0: x | | 0: x | 163 // | 1: y | | 1: y | 164 // | 2: z | | 2: foo | 165 // | ... | | ... | 166 // +---------------------+ +---------------------+ 167 // 168 // M1 is the map used for initial property "x". Properties "y" and "z" can be 169 // stored inline. When later adding "foo" following "y", the map has to be 170 // forked: a child map M2 is created and M1 remembers this transition at 171 // property index 1 so that M2 will be used the next time properties "x", "y", 172 // and "foo" are added to an object. 173 // 174 // Shared maps contain a TreeData struct that stores the parent and children 175 // links for the SharedPropMap tree. The parent link is a tagged pointer that 176 // stores both the parent map and the property index of the last used property 177 // in the parent map before the branch. The children are stored similarly: the 178 // parent map can store a single child map and index, or a set of children. 179 // See SharedChildrenPtr. 180 // 181 // Looking up a child map can then be done based on the index of the last 182 // property in the parent map and the new property's key and flags. So for the 183 // example above, the lookup key for M1 => M2 is (index 1, "foo", <flags>). 184 // 185 // Note: shared maps can have both a |previous| map and a |parent| map. They are 186 // equal when the previous map was full, but can be different maps when 187 // branching in the middle of a map like in the example above: M2 has parent M1 188 // but does not have a |previous| map (because it only has three properties). 189 // 190 // Dictionary Property Maps 191 // ------------------------ 192 // Certain operations can't be implemented (efficiently) for shared property 193 // maps, for example changing or deleting a property other than the last one. 194 // When this happens the map is copied as a DictionaryPropMap. 195 // 196 // Dictionary maps are unshared so can be mutated in place (after generating a 197 // new shape for the object). 198 // 199 // Unlike shared maps, dictionary maps can have holes between two property keys 200 // after removing a property. When there are more holes than properties, the 201 // map is compacted. See DictionaryPropMap::maybeCompact. 202 203 namespace js { 204 205 enum class IntegrityLevel; 206 207 class DictionaryPropMap; 208 class SharedPropMap; 209 class LinkedPropMap; 210 class CompactPropMap; 211 class NormalPropMap; 212 213 class JS_PUBLIC_API GenericPrinter; 214 class JSONPrinter; 215 216 // Template class for storing a PropMap* and a property index as tagged pointer. 217 template <typename T> 218 class MapAndIndex { 219 uintptr_t data_ = 0; 220 221 static constexpr uintptr_t IndexMask = 0b111; 222 223 public: 224 MapAndIndex() = default; 225 226 MapAndIndex(const T* map, uint32_t index) : data_(uintptr_t(map) | index) { 227 MOZ_ASSERT((uintptr_t(map) & IndexMask) == 0); 228 MOZ_ASSERT(index <= IndexMask); 229 } 230 explicit MapAndIndex(uintptr_t data) : data_(data) {} 231 232 void setNone() { data_ = 0; } 233 234 bool isNone() const { return data_ == 0; } 235 236 uintptr_t raw() const { return data_; } 237 T* maybeMap() const { return reinterpret_cast<T*>(data_ & ~IndexMask); } 238 239 uint32_t index() const { 240 MOZ_ASSERT(!isNone()); 241 return data_ & IndexMask; 242 } 243 T* map() const { 244 MOZ_ASSERT(!isNone()); 245 return maybeMap(); 246 } 247 248 inline PropertyInfo propertyInfo() const; 249 250 bool operator==(const MapAndIndex<T>& other) const { 251 return data_ == other.data_; 252 } 253 bool operator!=(const MapAndIndex<T>& other) const { 254 return !operator==(other); 255 } 256 } JS_HAZ_GC_POINTER; 257 using PropMapAndIndex = MapAndIndex<PropMap>; 258 using SharedPropMapAndIndex = MapAndIndex<SharedPropMap>; 259 260 struct SharedChildrenHasher; 261 using SharedChildrenSet = 262 HashSet<SharedPropMapAndIndex, SharedChildrenHasher, SystemAllocPolicy>; 263 264 // Children of shared maps. This is either: 265 // 266 // - None (no children) 267 // - SingleMapAndIndex (one child map, including the property index of the last 268 // property before the branch) 269 // - SharedChildrenSet (multiple children) 270 // 271 // Because SingleMapAndIndex use all bits, this relies on the HasChildrenSet 272 // flag in the map to distinguish the latter two cases. 273 class SharedChildrenPtr { 274 uintptr_t data_ = 0; 275 276 public: 277 bool isNone() const { return data_ == 0; } 278 void setNone() { data_ = 0; } 279 280 void setSingleChild(SharedPropMapAndIndex child) { data_ = child.raw(); } 281 void setChildrenSet(SharedChildrenSet* set) { data_ = uintptr_t(set); } 282 283 SharedPropMapAndIndex toSingleChild() const { 284 MOZ_ASSERT(!isNone()); 285 return SharedPropMapAndIndex(data_); 286 } 287 288 SharedChildrenSet* toChildrenSet() const { 289 MOZ_ASSERT(!isNone()); 290 return reinterpret_cast<SharedChildrenSet*>(data_); 291 } 292 } JS_HAZ_GC_POINTER; 293 294 // Ensures no property map tables are purged in the current zone. 295 class MOZ_RAII AutoKeepPropMapTables { 296 JSContext* cx_; 297 bool prev_; 298 299 public: 300 void operator=(const AutoKeepPropMapTables&) = delete; 301 AutoKeepPropMapTables(const AutoKeepPropMapTables&) = delete; 302 explicit inline AutoKeepPropMapTables(JSContext* cx); 303 inline ~AutoKeepPropMapTables(); 304 }; 305 306 // Hash table to optimize property lookups on larger maps. This maps from 307 // PropertyKey to PropMapAndIndex. 308 class PropMapTable { 309 struct Hasher { 310 using Key = PropMapAndIndex; 311 using Lookup = PropertyKey; 312 static MOZ_ALWAYS_INLINE HashNumber hash(PropertyKey key); 313 static MOZ_ALWAYS_INLINE bool match(PropMapAndIndex, PropertyKey key); 314 }; 315 316 // Small lookup cache. This has a hit rate of 30-60% on most workloads and is 317 // a lot faster than the full HashSet lookup. 318 struct CacheEntry { 319 PropertyKey key; 320 PropMapAndIndex result; 321 }; 322 static constexpr uint32_t NumCacheEntries = 2; 323 CacheEntry cacheEntries_[NumCacheEntries]; 324 325 using Set = HashSet<PropMapAndIndex, Hasher, SystemAllocPolicy>; 326 Set set_; 327 328 void setCacheEntry(PropertyKey key, PropMapAndIndex entry) { 329 for (uint32_t i = 0; i < NumCacheEntries; i++) { 330 if (cacheEntries_[i].key == key) { 331 cacheEntries_[i].result = entry; 332 return; 333 } 334 } 335 } 336 bool lookupInCache(PropertyKey key, PropMapAndIndex* result) const { 337 for (uint32_t i = 0; i < NumCacheEntries; i++) { 338 if (cacheEntries_[i].key == key) { 339 *result = cacheEntries_[i].result; 340 #ifdef DEBUG 341 auto p = lookupRaw(key); 342 MOZ_ASSERT(*result == (p ? *p : PropMapAndIndex())); 343 #endif 344 return true; 345 } 346 } 347 return false; 348 } 349 void addToCache(PropertyKey key, Set::Ptr p) { 350 for (uint32_t i = NumCacheEntries - 1; i > 0; i--) { 351 cacheEntries_[i] = cacheEntries_[i - 1]; 352 MOZ_ASSERT(cacheEntries_[i].key != key); 353 } 354 cacheEntries_[0].key = key; 355 cacheEntries_[0].result = p ? *p : PropMapAndIndex(); 356 } 357 358 public: 359 using Ptr = Set::Ptr; 360 361 PropMapTable() = default; 362 ~PropMapTable() = default; 363 364 uint32_t entryCount() const { return set_.count(); } 365 366 // This counts the PropMapTable object itself (which must be heap-allocated) 367 // and its HashSet. 368 size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { 369 return mallocSizeOf(this) + set_.shallowSizeOfExcludingThis(mallocSizeOf); 370 } 371 372 // init() is fallible and reports OOM to the context. 373 bool init(JSContext* cx, LinkedPropMap* map); 374 375 MOZ_ALWAYS_INLINE PropMap* lookup(PropMap* map, uint32_t mapLength, 376 PropertyKey key, uint32_t* index); 377 378 Set::Ptr lookupRaw(PropertyKey key) const { return set_.lookup(key); } 379 #ifdef DEBUG 380 Set::Ptr readonlyThreadsafeLookup(PropertyKey key) const { 381 return set_.readonlyThreadsafeLookup(key); 382 } 383 #endif 384 385 bool add(JSContext* cx, PropertyKey key, PropMapAndIndex entry) { 386 if (!set_.putNew(key, entry)) { 387 ReportOutOfMemory(cx); 388 return false; 389 } 390 setCacheEntry(key, entry); 391 return true; 392 } 393 394 void purgeCache() { 395 for (uint32_t i = 0; i < NumCacheEntries; i++) { 396 cacheEntries_[i] = CacheEntry(); 397 } 398 } 399 400 void remove(Ptr ptr) { 401 set_.remove(ptr); 402 purgeCache(); 403 } 404 405 void replaceEntry(Ptr ptr, PropertyKey key, PropMapAndIndex newEntry) { 406 MOZ_ASSERT(*ptr != newEntry); 407 set_.replaceKey(ptr, key, newEntry); 408 setCacheEntry(key, newEntry); 409 } 410 411 void trace(JSTracer* trc); 412 #ifdef JSGC_HASH_TABLE_CHECKS 413 void checkAfterMovingGC(JS::Zone* zone); 414 #endif 415 }; 416 417 class PropMap : public gc::TenuredCellWithFlags { 418 public: 419 // Number of properties that can be stored in each map. This must be small 420 // enough so that every index fits in a tagged PropMap* pointer (MapAndIndex). 421 static constexpr size_t Capacity = 8; 422 423 protected: 424 static_assert(gc::CellFlagBitsReservedForGC == 3, 425 "PropMap must reserve enough bits for Cell"); 426 427 enum Flags { 428 // Set if this is a CompactPropMap. 429 IsCompactFlag = 1 << 3, 430 431 // Set if this map has a non-null previous map pointer. Never set for 432 // compact maps because they don't have a previous field. 433 HasPrevFlag = 1 << 4, 434 435 // Set if this is a DictionaryPropMap. 436 IsDictionaryFlag = 1 << 5, 437 438 // Set if this map can have a table. Never set for compact maps. Always set 439 // for dictionary maps. 440 CanHaveTableFlag = 1 << 6, 441 442 // If set, this SharedPropMap has a SharedChildrenSet. Else it either has no 443 // children or a single child. See SharedChildrenPtr. Never set for 444 // dictionary maps. 445 HasChildrenSetFlag = 1 << 7, 446 447 // If set, this SharedPropMap was once converted to dictionary mode. This is 448 // only used for heuristics. Never set for dictionary maps. 449 HadDictionaryConversionFlag = 1 << 8, 450 451 // For SharedPropMap this stores the number of previous maps, clamped to 452 // NumPreviousMapsMax. This is used for heuristics. 453 NumPreviousMapsMax = 0x7f, 454 NumPreviousMapsShift = 9, 455 NumPreviousMapsMask = NumPreviousMapsMax << NumPreviousMapsShift, 456 }; 457 458 template <typename KnownF, typename UnknownF> 459 static void forEachPropMapFlag(uintptr_t flags, KnownF known, 460 UnknownF unknown); 461 462 // Flags word, stored in the cell header. Note that this hides the 463 // Cell::flags() method. 464 uintptr_t flags() const { return headerFlagsField(); } 465 466 private: 467 GCPtr<PropertyKey> keys_[Capacity]; 468 469 protected: 470 PropMap() = default; 471 472 void initKey(uint32_t index, PropertyKey key) { 473 MOZ_ASSERT(index < Capacity); 474 keys_[index].init(key); 475 } 476 void setKey(uint32_t index, PropertyKey key) { 477 MOZ_ASSERT(index < Capacity); 478 keys_[index] = key; 479 } 480 481 public: 482 bool isCompact() const { return flags() & IsCompactFlag; } 483 bool isLinked() const { return !isCompact(); } 484 bool isDictionary() const { return flags() & IsDictionaryFlag; } 485 bool isShared() const { return !isDictionary(); } 486 bool isNormal() const { return isShared() && !isCompact(); } 487 488 bool hasPrevious() const { return flags() & HasPrevFlag; } 489 bool canHaveTable() const { return flags() & CanHaveTableFlag; } 490 491 inline CompactPropMap* asCompact(); 492 inline const CompactPropMap* asCompact() const; 493 494 inline LinkedPropMap* asLinked(); 495 inline const LinkedPropMap* asLinked() const; 496 497 inline NormalPropMap* asNormal(); 498 inline const NormalPropMap* asNormal() const; 499 500 inline SharedPropMap* asShared(); 501 inline const SharedPropMap* asShared() const; 502 503 inline DictionaryPropMap* asDictionary(); 504 inline const DictionaryPropMap* asDictionary() const; 505 506 bool hasKey(uint32_t index) const { 507 MOZ_ASSERT(index < Capacity); 508 return !keys_[index].isVoid(); 509 } 510 PropertyKey getKey(uint32_t index) const { 511 MOZ_ASSERT(index < Capacity); 512 return keys_[index]; 513 } 514 515 uint32_t approximateEntryCount() const; 516 517 #if defined(DEBUG) || defined(JS_JITSPEW) 518 void dump() const; 519 void dump(js::GenericPrinter& out) const; 520 void dump(js::JSONPrinter& json) const; 521 522 void dumpFields(js::JSONPrinter& json) const; 523 void dumpFieldsAt(js::JSONPrinter& json, uint32_t index) const; 524 void dumpDescriptorStringContentAt(js::GenericPrinter& out, 525 uint32_t index) const; 526 JS::UniqueChars getPropertyNameAt(uint32_t index) const; 527 #endif 528 529 #ifdef DEBUG 530 void checkConsistency(NativeObject* obj) const; 531 #endif 532 533 void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, 534 size_t* children, size_t* tables) const; 535 536 inline PropertyInfo getPropertyInfo(uint32_t index) const; 537 538 PropertyInfoWithKey getPropertyInfoWithKey(uint32_t index) const { 539 return PropertyInfoWithKey(getPropertyInfo(index), getKey(index)); 540 } 541 542 MOZ_ALWAYS_INLINE PropMap* lookupLinear(uint32_t mapLength, PropertyKey key, 543 uint32_t* index); 544 545 MOZ_ALWAYS_INLINE PropMap* lookupPure(uint32_t mapLength, PropertyKey key, 546 uint32_t* index); 547 548 MOZ_ALWAYS_INLINE PropMap* lookup(JSContext* cx, uint32_t mapLength, 549 PropertyKey key, uint32_t* index); 550 551 static inline bool lookupForRemove(JSContext* cx, PropMap* map, 552 uint32_t mapLength, PropertyKey key, 553 const AutoKeepPropMapTables& keep, 554 PropMap** propMap, uint32_t* propIndex, 555 PropMapTable** table, 556 PropMapTable::Ptr* ptr); 557 558 static const JS::TraceKind TraceKind = JS::TraceKind::PropMap; 559 560 void traceChildren(JSTracer* trc); 561 }; 562 563 class SharedPropMap : public PropMap { 564 friend class PropMap; 565 566 protected: 567 // Shared maps are stored in a tree structure. Each shared map has a TreeData 568 // struct linking the map to its parent and children. Initial maps (the ones 569 // stored in ShapeZone's initialPropMaps table) don't have a parent. 570 struct TreeData { 571 SharedChildrenPtr children; 572 SharedPropMapAndIndex parent; 573 574 void setParent(SharedPropMap* map, uint32_t index) { 575 parent = SharedPropMapAndIndex(map, index); 576 } 577 }; 578 579 private: 580 static SharedPropMap* create(JSContext* cx, Handle<SharedPropMap*> prev, 581 HandleId id, PropertyInfo prop); 582 static SharedPropMap* createInitial(JSContext* cx, HandleId id, 583 PropertyInfo prop); 584 static SharedPropMap* clone(JSContext* cx, Handle<SharedPropMap*> map, 585 uint32_t length); 586 587 inline void initProperty(uint32_t index, PropertyKey key, PropertyInfo prop); 588 589 static bool addPropertyInternal(JSContext* cx, 590 MutableHandle<SharedPropMap*> map, 591 uint32_t* mapLength, HandleId id, 592 PropertyInfo prop); 593 594 bool addChild(JSContext* cx, SharedPropMapAndIndex child, HandleId id, 595 PropertyInfo prop); 596 SharedPropMap* lookupChild(uint32_t length, HandleId id, PropertyInfo prop); 597 598 protected: 599 void initNumPreviousMaps(uint32_t value) { 600 MOZ_ASSERT((flags() >> NumPreviousMapsShift) == 0); 601 // Clamp to NumPreviousMapsMax. This is okay because this value is only used 602 // for heuristics. 603 if (value > NumPreviousMapsMax) { 604 value = NumPreviousMapsMax; 605 } 606 setHeaderFlagBits(value << NumPreviousMapsShift); 607 } 608 609 bool hasChildrenSet() const { return flags() & HasChildrenSetFlag; } 610 void setHasChildrenSet() { setHeaderFlagBits(HasChildrenSetFlag); } 611 void clearHasChildrenSet() { clearHeaderFlagBits(HasChildrenSetFlag); } 612 613 void setHadDictionaryConversion() { 614 setHeaderFlagBits(HadDictionaryConversionFlag); 615 } 616 617 public: 618 // Heuristics used when adding a property via NativeObject::addProperty and 619 // friends: 620 // 621 // * If numPreviousMaps >= NumPrevMapsForAddConsiderDictionary, consider 622 // converting the object to a dictionary object based on other heuristics. 623 // 624 // * If numPreviousMaps >= NumPrevMapsForAddAlwaysDictionary, always convert 625 // the object to a dictionary object. 626 static constexpr size_t NumPrevMapsConsiderDictionary = 32; 627 static constexpr size_t NumPrevMapsAlwaysDictionary = 100; 628 629 static_assert(NumPrevMapsConsiderDictionary < NumPreviousMapsMax); 630 static_assert(NumPrevMapsAlwaysDictionary < NumPreviousMapsMax); 631 632 // The number of properties that can definitely be added to an object without 633 // triggering dictionary mode conversion in NativeObject::addProperty. 634 static constexpr size_t MaxPropsForNonDictionary = 635 NumPrevMapsConsiderDictionary * Capacity; 636 637 bool isDictionary() const = delete; 638 bool isShared() const = delete; 639 SharedPropMap* asShared() = delete; 640 const SharedPropMap* asShared() const = delete; 641 642 bool hadDictionaryConversion() const { 643 return flags() & HadDictionaryConversionFlag; 644 } 645 646 uint32_t numPreviousMaps() const { 647 uint32_t val = (flags() & NumPreviousMapsMask) >> NumPreviousMapsShift; 648 MOZ_ASSERT_IF(hasPrevious(), val > 0); 649 return val; 650 } 651 652 MOZ_ALWAYS_INLINE bool shouldConvertToDictionaryForAdd() const; 653 654 void fixupAfterMovingGC(); 655 inline void sweep(JS::GCContext* gcx); 656 inline void finalize(JS::GCContext* gcx); 657 658 static inline void getPrevious(MutableHandle<SharedPropMap*> map, 659 uint32_t* mapLength); 660 661 bool matchProperty(uint32_t index, PropertyKey key, PropertyInfo prop) const { 662 return getKey(index) == key && getPropertyInfo(index) == prop; 663 } 664 665 inline TreeData& treeDataRef(); 666 inline const TreeData& treeDataRef() const; 667 668 void removeChild(JS::GCContext* gcx, SharedPropMap* child); 669 670 uint32_t lastUsedSlot(uint32_t mapLength) const { 671 return getPropertyInfo(mapLength - 1).maybeSlot(); 672 } 673 674 // Number of slots required for objects with this map/mapLength. 675 static uint32_t slotSpan(const JSClass* clasp, const SharedPropMap* map, 676 uint32_t mapLength) { 677 MOZ_ASSERT(clasp->isNativeObject()); 678 uint32_t numReserved = JSCLASS_RESERVED_SLOTS(clasp); 679 if (!map) { 680 MOZ_ASSERT(mapLength == 0); 681 return numReserved; 682 } 683 uint32_t lastSlot = map->lastUsedSlot(mapLength); 684 if (lastSlot == SHAPE_INVALID_SLOT) { 685 // The object only has custom data properties. 686 return numReserved; 687 } 688 // Some builtin objects store properties in reserved slots. Make sure the 689 // slot span >= numReserved. See addPropertyInReservedSlot. 690 return std::max(lastSlot + 1, numReserved); 691 } 692 693 static uint32_t indexOfNextProperty(uint32_t index) { 694 MOZ_ASSERT(index < PropMap::Capacity); 695 return (index + 1) % PropMap::Capacity; 696 } 697 698 // Add a new property to this map. Returns the new map/mapLength, slot number, 699 // and object flags. 700 static bool addProperty(JSContext* cx, const JSClass* clasp, 701 MutableHandle<SharedPropMap*> map, 702 uint32_t* mapLength, HandleId id, PropertyFlags flags, 703 ObjectFlags* objectFlags, uint32_t* slot); 704 705 // Like addProperty, but for when the slot number is a reserved slot. A few 706 // builtin objects use this for initial properties. 707 static bool addPropertyInReservedSlot(JSContext* cx, const JSClass* clasp, 708 MutableHandle<SharedPropMap*> map, 709 uint32_t* mapLength, HandleId id, 710 PropertyFlags flags, uint32_t slot, 711 ObjectFlags* objectFlags); 712 713 // Like addProperty, but for when the caller already knows the slot number to 714 // use (or wants to assert this exact slot number is used). 715 static bool addPropertyWithKnownSlot(JSContext* cx, const JSClass* clasp, 716 MutableHandle<SharedPropMap*> map, 717 uint32_t* mapLength, HandleId id, 718 PropertyFlags flags, uint32_t slot, 719 ObjectFlags* objectFlags); 720 721 // Like addProperty, but for adding a custom data property. 722 static bool addCustomDataProperty(JSContext* cx, const JSClass* clasp, 723 MutableHandle<SharedPropMap*> map, 724 uint32_t* mapLength, HandleId id, 725 PropertyFlags flags, 726 ObjectFlags* objectFlags); 727 728 // Freeze or seal all properties by creating a new shared map. Returns the new 729 // map and object flags. 730 static bool freezeOrSealProperties(JSContext* cx, IntegrityLevel level, 731 const JSClass* clasp, 732 MutableHandle<SharedPropMap*> map, 733 uint32_t mapLength, 734 ObjectFlags* objectFlags); 735 736 // Create a new dictionary map as copy of this map. 737 static DictionaryPropMap* toDictionaryMap(JSContext* cx, 738 Handle<SharedPropMap*> map, 739 uint32_t length); 740 741 #if defined(DEBUG) || defined(JS_JITSPEW) 742 void dumpOwnFields(js::JSONPrinter& json) const; 743 #endif 744 }; 745 746 class CompactPropMap final : public SharedPropMap { 747 CompactPropertyInfo propInfos_[Capacity]; 748 TreeData treeData_; 749 750 friend class PropMap; 751 friend class SharedPropMap; 752 friend class DictionaryPropMap; 753 friend class js::gc::CellAllocator; 754 755 CompactPropMap(JS::Handle<PropertyKey> key, PropertyInfo prop) { 756 setHeaderFlagBits(IsCompactFlag); 757 initProperty(0, key, prop); 758 } 759 760 CompactPropMap(JS::Handle<CompactPropMap*> orig, uint32_t length) { 761 setHeaderFlagBits(IsCompactFlag); 762 for (uint32_t i = 0; i < length; i++) { 763 initKey(i, orig->getKey(i)); 764 propInfos_[i] = orig->propInfos_[i]; 765 } 766 } 767 768 void initProperty(uint32_t index, PropertyKey key, PropertyInfo prop) { 769 MOZ_ASSERT(!hasKey(index)); 770 initKey(index, key); 771 propInfos_[index] = CompactPropertyInfo(prop); 772 } 773 774 TreeData& treeDataRef() { return treeData_; } 775 const TreeData& treeDataRef() const { return treeData_; } 776 777 public: 778 bool isDictionary() const = delete; 779 bool isShared() const = delete; 780 bool isCompact() const = delete; 781 bool isNormal() const = delete; 782 bool isLinked() const = delete; 783 CompactPropMap* asCompact() = delete; 784 const CompactPropMap* asCompact() const = delete; 785 786 PropertyInfo getPropertyInfo(uint32_t index) const { 787 MOZ_ASSERT(hasKey(index)); 788 return PropertyInfo(propInfos_[index]); 789 } 790 }; 791 792 // Layout shared by NormalPropMap and DictionaryPropMap. 793 class LinkedPropMap final : public PropMap { 794 friend class PropMap; 795 friend class SharedPropMap; 796 friend class NormalPropMap; 797 friend class DictionaryPropMap; 798 799 struct Data { 800 GCPtr<PropMap*> previous; 801 PropMapTable* table = nullptr; 802 PropertyInfo propInfos[Capacity]; 803 804 explicit Data(PropMap* prev) : previous(prev) {} 805 }; 806 Data data_; 807 808 bool createTable(JSContext* cx); 809 void handOffTableTo(LinkedPropMap* next); 810 811 public: 812 bool isCompact() const = delete; 813 bool isLinked() const = delete; 814 LinkedPropMap* asLinked() = delete; 815 const LinkedPropMap* asLinked() const = delete; 816 817 PropMap* previous() const { return data_.previous; } 818 819 bool hasTable() const { return data_.table != nullptr; } 820 821 PropMapTable* maybeTable(JS::AutoCheckCannotGC& nogc) const { 822 return data_.table; 823 } 824 PropMapTable* ensureTable(JSContext* cx, const JS::AutoCheckCannotGC& nogc) { 825 if (!data_.table && MOZ_UNLIKELY(!createTable(cx))) { 826 return nullptr; 827 } 828 return data_.table; 829 } 830 PropMapTable* ensureTable(JSContext* cx, const AutoKeepPropMapTables& keep) { 831 if (!data_.table && MOZ_UNLIKELY(!createTable(cx))) { 832 return nullptr; 833 } 834 return data_.table; 835 } 836 837 void purgeTable(JS::GCContext* gcx); 838 839 void purgeTableCache() { 840 if (data_.table) { 841 data_.table->purgeCache(); 842 } 843 } 844 845 #ifdef DEBUG 846 bool canSkipMarkingTable(); 847 #endif 848 849 PropertyInfo getPropertyInfo(uint32_t index) const { 850 MOZ_ASSERT(hasKey(index)); 851 return data_.propInfos[index]; 852 } 853 854 #if defined(DEBUG) || defined(JS_JITSPEW) 855 void dumpOwnFields(js::JSONPrinter& json) const; 856 #endif 857 }; 858 859 class NormalPropMap final : public SharedPropMap { 860 friend class PropMap; 861 friend class SharedPropMap; 862 friend class DictionaryPropMap; 863 friend class js::gc::CellAllocator; 864 865 LinkedPropMap::Data linkedData_; 866 TreeData treeData_; 867 868 NormalPropMap(JS::Handle<SharedPropMap*> prev, PropertyKey key, 869 PropertyInfo prop) 870 : linkedData_(prev) { 871 if (prev) { 872 setHeaderFlagBits(HasPrevFlag); 873 initNumPreviousMaps(prev->numPreviousMaps() + 1); 874 if (prev->hasPrevious()) { 875 setHeaderFlagBits(CanHaveTableFlag); 876 } 877 } 878 initProperty(0, key, prop); 879 } 880 881 NormalPropMap(JS::Handle<NormalPropMap*> orig, uint32_t length) 882 : linkedData_(orig->previous()) { 883 if (orig->hasPrevious()) { 884 setHeaderFlagBits(HasPrevFlag); 885 } 886 if (orig->canHaveTable()) { 887 setHeaderFlagBits(CanHaveTableFlag); 888 } 889 initNumPreviousMaps(orig->numPreviousMaps()); 890 for (uint32_t i = 0; i < length; i++) { 891 initProperty(i, orig->getKey(i), orig->getPropertyInfo(i)); 892 } 893 } 894 895 void initProperty(uint32_t index, PropertyKey key, PropertyInfo prop) { 896 MOZ_ASSERT(!hasKey(index)); 897 initKey(index, key); 898 linkedData_.propInfos[index] = prop; 899 } 900 901 bool isDictionary() const = delete; 902 bool isShared() const = delete; 903 bool isCompact() const = delete; 904 bool isNormal() const = delete; 905 bool isLinked() const = delete; 906 NormalPropMap* asNormal() = delete; 907 const NormalPropMap* asNormal() const = delete; 908 909 SharedPropMap* previous() const { 910 return static_cast<SharedPropMap*>(linkedData_.previous.get()); 911 } 912 913 TreeData& treeDataRef() { return treeData_; } 914 const TreeData& treeDataRef() const { return treeData_; } 915 916 static void staticAsserts() { 917 static_assert(offsetof(NormalPropMap, linkedData_) == 918 offsetof(LinkedPropMap, data_)); 919 } 920 }; 921 922 class DictionaryPropMap final : public PropMap { 923 friend class PropMap; 924 friend class SharedPropMap; 925 friend class js::gc::CellAllocator; 926 927 LinkedPropMap::Data linkedData_; 928 929 // SHAPE_INVALID_SLOT or head of slot freelist in owning dictionary-mode 930 // object. 931 uint32_t freeList_ = SHAPE_INVALID_SLOT; 932 933 // Number of holes for removed properties in this and previous maps. Used by 934 // compacting heuristics. 935 uint32_t holeCount_ = 0; 936 937 // Empty Dictionary prop map used during reshape. 938 explicit DictionaryPropMap(std::nullptr_t) : linkedData_(nullptr) { 939 setHeaderFlagBits(IsDictionaryFlag | CanHaveTableFlag); 940 } 941 942 DictionaryPropMap(JS::Handle<DictionaryPropMap*> prev, PropertyKey key, 943 PropertyInfo prop) 944 : linkedData_(prev) { 945 setHeaderFlagBits(IsDictionaryFlag | CanHaveTableFlag | 946 (prev ? HasPrevFlag : 0)); 947 initProperty(0, key, prop); 948 } 949 950 DictionaryPropMap(JS::Handle<NormalPropMap*> orig, uint32_t length) 951 : linkedData_(nullptr) { 952 setHeaderFlagBits(IsDictionaryFlag | CanHaveTableFlag); 953 for (uint32_t i = 0; i < length; i++) { 954 initProperty(i, orig->getKey(i), orig->getPropertyInfo(i)); 955 } 956 } 957 958 DictionaryPropMap(JS::Handle<CompactPropMap*> orig, uint32_t length) 959 : linkedData_(nullptr) { 960 setHeaderFlagBits(IsDictionaryFlag | CanHaveTableFlag); 961 for (uint32_t i = 0; i < length; i++) { 962 initProperty(i, orig->getKey(i), orig->getPropertyInfo(i)); 963 } 964 } 965 966 void initProperty(uint32_t index, PropertyKey key, PropertyInfo prop) { 967 MOZ_ASSERT(!hasKey(index)); 968 initKey(index, key); 969 linkedData_.propInfos[index] = prop; 970 } 971 972 void initPrevious(DictionaryPropMap* prev) { 973 MOZ_ASSERT(prev); 974 linkedData_.previous.init(prev); 975 setHeaderFlagBits(HasPrevFlag); 976 } 977 void clearPrevious() { 978 linkedData_.previous = nullptr; 979 clearHeaderFlagBits(HasPrevFlag); 980 } 981 982 void clearProperty(uint32_t index) { setKey(index, PropertyKey::Void()); } 983 984 static void skipTrailingHoles(MutableHandle<DictionaryPropMap*> map, 985 uint32_t* mapLength); 986 987 void handOffLastMapStateTo(DictionaryPropMap* newLast); 988 989 void incHoleCount() { holeCount_++; } 990 void decHoleCount() { 991 MOZ_ASSERT(holeCount_ > 0); 992 holeCount_--; 993 } 994 static void maybeCompact(JSContext* cx, MutableHandle<DictionaryPropMap*> map, 995 uint32_t* mapLength); 996 997 public: 998 bool isDictionary() const = delete; 999 bool isShared() const = delete; 1000 bool isCompact() const = delete; 1001 bool isNormal() const = delete; 1002 bool isLinked() const = delete; 1003 DictionaryPropMap* asDictionary() = delete; 1004 const DictionaryPropMap* asDictionary() const = delete; 1005 1006 void fixupAfterMovingGC() {} 1007 inline void finalize(JS::GCContext* gcx); 1008 1009 DictionaryPropMap* previous() const { 1010 return static_cast<DictionaryPropMap*>(linkedData_.previous.get()); 1011 } 1012 1013 uint32_t freeList() const { return freeList_; } 1014 void setFreeList(uint32_t slot) { freeList_ = slot; } 1015 1016 PropertyInfo getPropertyInfo(uint32_t index) const { 1017 MOZ_ASSERT(hasKey(index)); 1018 return linkedData_.propInfos[index]; 1019 } 1020 1021 // Create an empty prop map for dictionary mode teleporting 1022 static DictionaryPropMap* createEmpty(JSContext* cx); 1023 1024 // Add a new property to this map. Returns the new map/mapLength and object 1025 // flags. The caller is responsible for generating a new dictionary shape. 1026 static bool addProperty(JSContext* cx, const JSClass* clasp, 1027 MutableHandle<DictionaryPropMap*> map, 1028 uint32_t* mapLength, HandleId id, PropertyFlags flags, 1029 uint32_t slot, ObjectFlags* objectFlags); 1030 1031 // Remove the property referenced by the table pointer. Returns the new 1032 // map/mapLength. The caller is responsible for generating a new dictionary 1033 // shape. 1034 static void removeProperty(JSContext* cx, 1035 MutableHandle<DictionaryPropMap*> map, 1036 uint32_t* mapLength, PropMapTable* table, 1037 PropMapTable::Ptr& ptr); 1038 1039 // Turn all sparse elements into dense elements. The caller is responsible 1040 // for checking all sparse elements are plain data properties and must 1041 // generate a new shape for the object. 1042 static void densifyElements(JSContext* cx, 1043 MutableHandle<DictionaryPropMap*> map, 1044 uint32_t* mapLength, NativeObject* obj); 1045 1046 // Freeze or seal all properties in this map. Returns the new object flags. 1047 // The caller is responsible for generating a new shape for the object. 1048 void freezeOrSealProperties(JSContext* cx, IntegrityLevel level, 1049 const JSClass* clasp, uint32_t mapLength, 1050 ObjectFlags* objectFlags); 1051 1052 // Change a property's slot number and/or flags and return the new object 1053 // flags. The caller is responsible for generating a new shape. 1054 void changeProperty(JSContext* cx, const JSClass* clasp, uint32_t index, 1055 PropertyFlags flags, uint32_t slot, 1056 ObjectFlags* objectFlags); 1057 1058 // Like changeProperty, but doesn't change the slot number. 1059 void changePropertyFlags(JSContext* cx, const JSClass* clasp, uint32_t index, 1060 PropertyFlags flags, ObjectFlags* objectFlags) { 1061 uint32_t slot = getPropertyInfo(index).maybeSlot(); 1062 changeProperty(cx, clasp, index, flags, slot, objectFlags); 1063 } 1064 1065 static void staticAsserts() { 1066 static_assert(offsetof(DictionaryPropMap, linkedData_) == 1067 offsetof(LinkedPropMap, data_)); 1068 } 1069 1070 #if defined(DEBUG) || defined(JS_JITSPEW) 1071 void dumpOwnFields(js::JSONPrinter& json) const; 1072 #endif 1073 }; 1074 1075 inline CompactPropMap* PropMap::asCompact() { 1076 MOZ_ASSERT(isCompact()); 1077 return static_cast<CompactPropMap*>(this); 1078 } 1079 inline const CompactPropMap* PropMap::asCompact() const { 1080 MOZ_ASSERT(isCompact()); 1081 return static_cast<const CompactPropMap*>(this); 1082 } 1083 inline LinkedPropMap* PropMap::asLinked() { 1084 MOZ_ASSERT(isLinked()); 1085 return static_cast<LinkedPropMap*>(this); 1086 } 1087 inline const LinkedPropMap* PropMap::asLinked() const { 1088 MOZ_ASSERT(isLinked()); 1089 return static_cast<const LinkedPropMap*>(this); 1090 } 1091 inline NormalPropMap* PropMap::asNormal() { 1092 MOZ_ASSERT(isNormal()); 1093 return static_cast<NormalPropMap*>(this); 1094 } 1095 inline const NormalPropMap* PropMap::asNormal() const { 1096 MOZ_ASSERT(isNormal()); 1097 return static_cast<const NormalPropMap*>(this); 1098 } 1099 inline SharedPropMap* PropMap::asShared() { 1100 MOZ_ASSERT(isShared()); 1101 return static_cast<SharedPropMap*>(this); 1102 } 1103 inline const SharedPropMap* PropMap::asShared() const { 1104 MOZ_ASSERT(isShared()); 1105 return static_cast<const SharedPropMap*>(this); 1106 } 1107 inline DictionaryPropMap* PropMap::asDictionary() { 1108 MOZ_ASSERT(isDictionary()); 1109 return static_cast<DictionaryPropMap*>(this); 1110 } 1111 inline const DictionaryPropMap* PropMap::asDictionary() const { 1112 MOZ_ASSERT(isDictionary()); 1113 return static_cast<const DictionaryPropMap*>(this); 1114 } 1115 1116 inline PropertyInfo PropMap::getPropertyInfo(uint32_t index) const { 1117 return isCompact() ? asCompact()->getPropertyInfo(index) 1118 : asLinked()->getPropertyInfo(index); 1119 } 1120 1121 inline SharedPropMap::TreeData& SharedPropMap::treeDataRef() { 1122 return isCompact() ? asCompact()->treeDataRef() : asNormal()->treeDataRef(); 1123 } 1124 1125 inline const SharedPropMap::TreeData& SharedPropMap::treeDataRef() const { 1126 return isCompact() ? asCompact()->treeDataRef() : asNormal()->treeDataRef(); 1127 } 1128 1129 inline void SharedPropMap::initProperty(uint32_t index, PropertyKey key, 1130 PropertyInfo prop) { 1131 if (isCompact()) { 1132 asCompact()->initProperty(index, key, prop); 1133 } else { 1134 asNormal()->initProperty(index, key, prop); 1135 } 1136 } 1137 1138 template <typename T> 1139 inline PropertyInfo MapAndIndex<T>::propertyInfo() const { 1140 MOZ_ASSERT(!isNone()); 1141 return map()->getPropertyInfo(index()); 1142 } 1143 1144 MOZ_ALWAYS_INLINE HashNumber PropMapTable::Hasher::hash(PropertyKey key) { 1145 return HashPropertyKey(key); 1146 } 1147 MOZ_ALWAYS_INLINE bool PropMapTable::Hasher::match(PropMapAndIndex entry, 1148 PropertyKey key) { 1149 MOZ_ASSERT(entry.map()->hasKey(entry.index())); 1150 return entry.map()->getKey(entry.index()) == key; 1151 } 1152 1153 // Hash policy for SharedPropMap children. 1154 struct SharedChildrenHasher { 1155 using Key = SharedPropMapAndIndex; 1156 1157 struct Lookup { 1158 PropertyKey key; 1159 PropertyInfo prop; 1160 uint8_t index; 1161 1162 Lookup(PropertyKey key, PropertyInfo prop, uint8_t index) 1163 : key(key), prop(prop), index(index) {} 1164 Lookup(PropertyInfoWithKey prop, uint8_t index) 1165 : key(prop.key()), prop(prop), index(index) {} 1166 }; 1167 1168 static HashNumber hash(const Lookup& l) { 1169 HashNumber hash = HashPropertyKey(l.key); 1170 return mozilla::AddToHash(hash, l.prop.toRaw(), l.index); 1171 } 1172 static bool match(SharedPropMapAndIndex k, const Lookup& l) { 1173 SharedPropMap* map = k.map(); 1174 uint32_t index = k.index(); 1175 uint32_t newIndex = SharedPropMap::indexOfNextProperty(index); 1176 return index == l.index && map->matchProperty(newIndex, l.key, l.prop); 1177 } 1178 }; 1179 1180 // An iterator that walks a shared prop map in property definition order 1181 class MOZ_RAII SharedPropMapIter { 1182 public: 1183 // Iterate over all properties of propMap. 1184 SharedPropMapIter(JSContext* cx, SharedPropMapAndIndex propMap); 1185 1186 // Iterate over all properties after (but not including) startAfter. 1187 SharedPropMapIter(JSContext* cx, SharedPropMapAndIndex startAfter, 1188 SharedPropMapAndIndex end); 1189 1190 PropertyKey key() const { 1191 MOZ_ASSERT(!done()); 1192 return maps_[mapIdx_]->getKey(propIdx_); 1193 } 1194 PropertyInfo prop() const { 1195 MOZ_ASSERT(!done()); 1196 return maps_[mapIdx_]->getPropertyInfo(propIdx_); 1197 } 1198 1199 bool done() const { return mapIdx_ == 0 && propIdx_ > endIdx_; } 1200 void next() { 1201 MOZ_ASSERT(!done()); 1202 if (++propIdx_ == PropMap::Capacity && mapIdx_ > 0) { 1203 propIdx_ = 0; 1204 mapIdx_--; 1205 } 1206 } 1207 1208 private: 1209 SharedPropMapIter(JSContext* cx, mozilla::Maybe<SharedPropMapAndIndex> start, 1210 SharedPropMapAndIndex end); 1211 1212 JS::RootedVector<SharedPropMap*> maps_; 1213 uint32_t mapIdx_; 1214 uint32_t propIdx_; 1215 uint32_t endIdx_; 1216 }; 1217 1218 } // namespace js 1219 1220 // JS::ubi::Nodes can point to PropMaps; they're js::gc::Cell instances 1221 // with no associated compartment. 1222 namespace JS { 1223 namespace ubi { 1224 1225 template <> 1226 class Concrete<js::PropMap> : TracerConcrete<js::PropMap> { 1227 protected: 1228 explicit Concrete(js::PropMap* ptr) : TracerConcrete<js::PropMap>(ptr) {} 1229 1230 public: 1231 static void construct(void* storage, js::PropMap* ptr) { 1232 new (storage) Concrete(ptr); 1233 } 1234 1235 Size size(mozilla::MallocSizeOf mallocSizeOf) const override; 1236 1237 const char16_t* typeName() const override { return concreteTypeName; } 1238 static const char16_t concreteTypeName[]; 1239 }; 1240 1241 } // namespace ubi 1242 } // namespace JS 1243 1244 #endif // vm_PropMap_h