Title: [293210] trunk
Revision
293210
Author
ysuz...@apple.com
Date
2022-04-21 22:41:35 -0700 (Thu, 21 Apr 2022)

Log Message

[JSC] PropertyTable should have compact mode
https://bugs.webkit.org/show_bug.cgi?id=239451

Reviewed by Saam Barati.

This patch introduces "compact" version of PropertyTable. PropertyTable needs to hold two kind of integers: index and property offset.
But for most of objects, both are pretty small and they can fit in uint8_t. If we can use uint8_t for both, we can significantly reduce
size of allocated memory for PropertyTable (only 40% of memory is required!). This is good for memory, but also good for performance.
Now each CompactPropertyTableEntry is 8bytes while original PropertyMapEntry was 16bytes, so CompactPropertyTableEntry can fit in CPU cache well.
Also, not allocating large amount of memory can reduce memory allocation / deallocation cost. One of costly destruction of GC-managed objects is
PropertyTable (and CodeBlock), and we can reduce that cost by not allocating much memory.

The approach is following.

1. For index vector, we use uint8_t if index can fit within uint8_t.
2. For proprety offset, we use uint8_t and CompactPropertyTableEntry if it is suitable.
3. Once the table gets non-compact, we keep it non-compact since we could have deleted index which has larger than uint8_t. We could improve this
   strategy when deleted indexes are cleared, but for now, we are taking simple approach.
4. We store isCompactFlag 1 bit in the pointer to the table.
5. We encapsulate functions modifying property table entry in PropertyTable itself, so we do not leak internal compact / non-compact mode to the user
   of PropertyTable. We remove begin() / end() iterators and instead use forEachproperty, which can implement iteration for each mode more efficiently.

We have a further opportunity to improve this further: we can deploy 75% load factor only for compact table. Then we can increase threshold of
compact table further and keep more and more tables compact mode. Plus, for small sized tables, small backing memory is better in terms of
CPU cache hit (and that's measured in WTF::HashTable, and that's why WTF::Hashtable deploys 75% load factor only for small tables). This is left
for the subsequent change.

This change is neutral in JetStream2, 0.3% improvement in Speedometer2 with 80% confidence, and 0.41% improvement in RAMification with 95% confidence.

* JSTests/stress/change-attribute-structure-transition.js:
(shouldBe.JSON.stringify.sd):
* Source/_javascript_Core/bytecode/ObjectAllocationProfileInlines.h:
(JSC::ObjectAllocationProfileBase<Derived>::initializeProfile):
* Source/_javascript_Core/dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* Source/_javascript_Core/dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):
* Source/_javascript_Core/dfg/DFGOperations.cpp:
(JSC::DFG::JSC_DEFINE_JIT_OPERATION):
* Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp:
* Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileCompareStrictEq):
* Source/_javascript_Core/ftl/FTLOperations.cpp:
(JSC::FTL::JSC_DEFINE_JIT_OPERATION):
* Source/_javascript_Core/runtime/ClonedArguments.h:
* Source/_javascript_Core/runtime/IteratorOperations.cpp:
(JSC::createIteratorResultObjectStructure):
* Source/_javascript_Core/runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):
* Source/_javascript_Core/runtime/JSGlobalObjectFunctions.cpp:
(JSC::JSC_DEFINE_HOST_FUNCTION):
* Source/_javascript_Core/runtime/JSONObject.cpp:
(JSC::Stringifier::Holder::appendNextProperty):
* Source/_javascript_Core/runtime/JSObject.cpp:
(JSC::JSObject::analyzeHeap):
* Source/_javascript_Core/runtime/JSObject.h:
* Source/_javascript_Core/runtime/ObjectConstructor.h:
(JSC::constructEmptyObject):
(JSC::createDataPropertyDescriptorObjectStructure):
(JSC::createAccessorPropertyDescriptorObjectStructure):
* Source/_javascript_Core/runtime/ObjectConstructorInlines.h:
(JSC::objectAssignFast):
* Source/_javascript_Core/runtime/PropertyOffset.h:
* Source/_javascript_Core/runtime/PropertySlot.h:
* Source/_javascript_Core/runtime/PropertyTable.cpp:
(JSC::PropertyTable::PropertyTable):
(JSC::PropertyTable::finishCreation):
(JSC::PropertyTable::visitChildrenImpl):
(JSC::PropertyTable::~PropertyTable):
(JSC::PropertyTable::seal):
(JSC::PropertyTable::freeze):
(JSC::PropertyTable::isSealed const):
(JSC::PropertyTable::isFrozen const):
(JSC::PropertyTable::renumberPropertyOffsets):
* Source/_javascript_Core/runtime/PropertyTable.h:
(JSC::isPowerOf2):
(JSC::nextPowerOf2):
(JSC::PropertyTable::findImpl):
(JSC::PropertyTable::find):
(JSC::PropertyTable::get):
(JSC::PropertyTable::add):
(JSC::PropertyTable::remove):
(JSC::PropertyTable::take):
(JSC::PropertyTable::updateAttributeIfExists):
(JSC::PropertyTable::sizeInMemory):
(JSC::PropertyTable::reinsert):
(JSC::PropertyTable::rehash):
(JSC::PropertyTable::skipDeletedEntries):
(JSC::PropertyTable::dataSize):
(JSC::PropertyTable::canInsert):
(JSC::PropertyTable::forEachProperty const):
(JSC::PropertyTable::forEachPropertyMutable):
(JSC::PropertyTable::begin): Deleted.
(JSC::PropertyTable::end): Deleted.
(JSC::PropertyTable::begin const): Deleted.
(JSC::PropertyTable::end const): Deleted.
(JSC::PropertyTable::table): Deleted.
(JSC::PropertyTable::table const): Deleted.
* Source/_javascript_Core/runtime/RegExpMatchesArray.h:
* Source/_javascript_Core/runtime/Structure.cpp:
(JSC::Structure::dumpStatistics):
(JSC::Structure::Structure):
(JSC::Structure::materializePropertyTable):
(JSC::Structure::nonPropertyTransitionSlow):
(JSC::Structure::isSealed):
(JSC::Structure::isFrozen):
(JSC::Structure::flattenDictionaryStructure):
(JSC::PropertyTableStatisticsExitLogger::~PropertyTableStatisticsExitLogger):
(JSC::Structure::getConcurrently):
(JSC::Structure::getPropertiesConcurrently):
(JSC::Structure::getPropertyNamesFromStructure):
(JSC::Structure::toStructureShape):
(JSC::Structure::dump const):
* Source/_javascript_Core/runtime/Structure.h:
(JSC::CompactPropertyTableEntry::CompactPropertyTableEntry):
(JSC::CompactPropertyTableEntry::key const):
(JSC::CompactPropertyTableEntry::setKey):
(JSC::CompactPropertyTableEntry::offset const):
(JSC::CompactPropertyTableEntry::setOffset):
(JSC::CompactPropertyTableEntry::attributes const):
(JSC::CompactPropertyTableEntry::setAttributes):
(JSC::PropertyTableEntry::PropertyTableEntry):
(JSC::PropertyTableEntry::key const):
(JSC::PropertyTableEntry::setKey):
(JSC::PropertyTableEntry::offset const):
(JSC::PropertyTableEntry::setOffset):
(JSC::PropertyTableEntry::attributes const):
(JSC::PropertyTableEntry::setAttributes):
(JSC::PropertyMapEntry::PropertyMapEntry): Deleted.
* Source/_javascript_Core/runtime/StructureInlines.h:
(JSC::Structure::get):
(JSC::Structure::forEachPropertyConcurrently):
(JSC::Structure::forEachProperty):
(JSC::Structure::add):
(JSC::Structure::remove):
(JSC::Structure::attributeChange):

Canonical link: https://commits.webkit.org/249881@main

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (293209 => 293210)


--- trunk/JSTests/ChangeLog	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/JSTests/ChangeLog	2022-04-22 05:41:35 UTC (rev 293210)
@@ -1,3 +1,17 @@
+2022-04-21  Yusuke Suzuki  <ysuz...@apple.com>
+
+        [JSC] PropertyTable should have compact mode
+        https://bugs.webkit.org/show_bug.cgi?id=239451
+
+        Reviewed by Saam Barati.
+
+        * stress/change-attribute-structure-transition.js:
+        (shouldBe.JSON.stringify.sd):
+        * stress/compact-to-non-compact-with-delete.js: Added.
+        (shouldBe):
+        * stress/compact-to-non-compact.js: Added.
+        (shouldBe):
+
 2022-04-14  Caitlin Potter  <ca...@igalia.com>
 
         [JSC] ShadowRealm global object has a mutable prototype

Modified: trunk/JSTests/stress/change-attribute-structure-transition.js (293209 => 293210)


--- trunk/JSTests/stress/change-attribute-structure-transition.js	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/JSTests/stress/change-attribute-structure-transition.js	2022-04-22 05:41:35 UTC (rev 293210)
@@ -74,4 +74,4 @@
     enumerable: false
 });
 shouldBe(JSON.stringify(Object.getOwnPropertyDescriptor(object, "test001")), `{"value":42,"writable":false,"enumerable":false,"configurable":true}`);
-shouldBe(JSON.stringify(sd(object)), `[{"offset":-1,"max":1098,"property":null,"type":"unknown"},{"offset":0,"max":1098,"property":"test001","type":"attribute"}]`);
+shouldBe(JSON.stringify(sd(object)), `[{"offset":-1,"max":1062,"property":null,"type":"unknown"},{"offset":0,"max":1062,"property":"test001","type":"attribute"}]`);

Added: trunk/JSTests/stress/compact-to-non-compact-with-delete.js (0 => 293210)


--- trunk/JSTests/stress/compact-to-non-compact-with-delete.js	                        (rev 0)
+++ trunk/JSTests/stress/compact-to-non-compact-with-delete.js	2022-04-22 05:41:35 UTC (rev 293210)
@@ -0,0 +1,23 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+var object = {};
+var properties = [];
+
+// compact
+for (var i = 0; i < 100; ++i) {
+    var key = "Hey" + i;
+    object[key] = i;
+}
+for (var i = 0; i < 16; ++i) {
+    var key = "Hey" + i;
+    delete object[key];
+}
+// compact -> non-compact
+for (var i = 0; i < 150; ++i) {
+    var key = "Hey" + (128 + i);
+    object[key] = i;
+}
+shouldBe(JSON.stringify(Object.keys(object)), `["Hey16","Hey17","Hey18","Hey19","Hey20","Hey21","Hey22","Hey23","Hey24","Hey25","Hey26","Hey27","Hey28","Hey29","Hey30","Hey31","Hey32","Hey33","Hey34","Hey35","Hey36","Hey37","Hey38","Hey39","Hey40","Hey41","Hey42","Hey43","Hey44","Hey45","Hey46","Hey47","Hey48","Hey49","Hey50","Hey51","Hey52","Hey53","Hey54","Hey55","Hey56","Hey57","Hey58","Hey59","Hey60","Hey61","Hey62","Hey63","Hey64","Hey65","Hey66","Hey67","Hey68&q
 uot;,"Hey69","Hey70","Hey71","Hey72","Hey73","Hey74","Hey75","Hey76","Hey77","Hey78","Hey79","Hey80","Hey81","Hey82","Hey83","Hey84","Hey85","Hey86","Hey87","Hey88","Hey89","Hey90","Hey91","Hey92","Hey93","Hey94","Hey95","Hey96","Hey97","Hey98","Hey99","Hey128","Hey129","Hey130","Hey131","Hey132","Hey133","Hey134","Hey135","Hey136","Hey137","Hey138","Hey139","Hey140","Hey141","Hey142","Hey143","Hey144","Hey145","Hey146","Hey147","Hey148","Hey149","Hey150&quo
 t;,"Hey151","Hey152","Hey153","Hey154","Hey155","Hey156","Hey157","Hey158","Hey159","Hey160","Hey161","Hey162","Hey163","Hey164","Hey165","Hey166","Hey167","Hey168","Hey169","Hey170","Hey171","Hey172","Hey173","Hey174","Hey175","Hey176","Hey177","Hey178","Hey179","Hey180","Hey181","Hey182","Hey183","Hey184","Hey185","Hey186","Hey187","Hey188","Hey189","Hey190","Hey191","Hey192","Hey193","Hey194","Hey195","Hey196","Hey197","Hey198","Hey199","Hey200","Hey201","Hey202","
 Hey203","Hey204","Hey205","Hey206","Hey207","Hey208","Hey209","Hey210","Hey211","Hey212","Hey213","Hey214","Hey215","Hey216","Hey217","Hey218","Hey219","Hey220","Hey221","Hey222","Hey223","Hey224","Hey225","Hey226","Hey227","Hey228","Hey229","Hey230","Hey231","Hey232","Hey233","Hey234","Hey235","Hey236","Hey237","Hey238","Hey239","Hey240","Hey241","Hey242","Hey243","Hey244","Hey245","Hey246","Hey247","Hey248","Hey249","Hey250","Hey251","Hey252","Hey253","Hey254","Hey255&qu
 ot;,"Hey256","Hey257","Hey258","Hey259","Hey260","Hey261","Hey262","Hey263","Hey264","Hey265","Hey266","Hey267","Hey268","Hey269","Hey270","Hey271","Hey272","Hey273","Hey274","Hey275","Hey276","Hey277"]`);

Added: trunk/JSTests/stress/compact-to-non-compact.js (0 => 293210)


--- trunk/JSTests/stress/compact-to-non-compact.js	                        (rev 0)
+++ trunk/JSTests/stress/compact-to-non-compact.js	2022-04-22 05:41:35 UTC (rev 293210)
@@ -0,0 +1,21 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+var object = {};
+var properties = [];
+
+for (var i = 0; i < 128; ++i) {
+    var key = "Hey" + i;
+    properties.push(key);
+    object[key] = i;
+}
+object["MakeNonCompact"] = true;
+properties.push("MakeNonCompact");
+shouldBe(JSON.stringify(Object.keys(object)), JSON.stringify(properties));
+for (var i = 0; i < 128; ++i) {
+    var key = "Hey" + i;
+    shouldBe(object[key], i);
+}
+shouldBe(object["MakeNonCompact"], true);

Modified: trunk/Source/_javascript_Core/ChangeLog (293209 => 293210)


--- trunk/Source/_javascript_Core/ChangeLog	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/ChangeLog	2022-04-22 05:41:35 UTC (rev 293210)
@@ -1,5 +1,146 @@
 2022-04-21  Yusuke Suzuki  <ysuz...@apple.com>
 
+        [JSC] PropertyTable should have compact mode
+        https://bugs.webkit.org/show_bug.cgi?id=239451
+
+        Reviewed by Saam Barati.
+
+        This patch introduces "compact" version of PropertyTable. PropertyTable needs to hold two kind of integers: index and property offset.
+        But for most of objects, both are pretty small and they can fit in uint8_t. If we can use uint8_t for both, we can significantly reduce
+        size of allocated memory for PropertyTable (only 40% of memory is required!). This is good for memory, but also good for performance.
+        Now each CompactPropertyTableEntry is 8bytes while original PropertyMapEntry was 16bytes, so CompactPropertyTableEntry can fit in CPU cache well.
+        Also, not allocating large amount of memory can reduce memory allocation / deallocation cost. One of costly destruction of GC-managed objects is
+        PropertyTable (and CodeBlock), and we can reduce that cost by not allocating much memory.
+
+        The approach is following.
+
+        1. For index vector, we use uint8_t if index can fit within uint8_t.
+        2. For proprety offset, we use uint8_t and CompactPropertyTableEntry if it is suitable.
+        3. Once the table gets non-compact, we keep it non-compact since we could have deleted index which has larger than uint8_t. We could improve this
+           strategy when deleted indexes are cleared, but for now, we are taking simple approach.
+        4. We store isCompactFlag 1 bit in the pointer to the table.
+        5. We encapsulate functions modifying property table entry in PropertyTable itself, so we do not leak internal compact / non-compact mode to the user
+           of PropertyTable. We remove begin() / end() iterators and instead use forEachproperty, which can implement iteration for each mode more efficiently.
+
+        We have a further opportunity to improve this further: we can deploy 75% load factor only for compact table. Then we can increase threshold of
+        compact table further and keep more and more tables compact mode. Plus, for small sized tables, small backing memory is better in terms of
+        CPU cache hit (and that's measured in WTF::HashTable, and that's why WTF::Hashtable deploys 75% load factor only for small tables). This is left
+        for the subsequent change.
+
+        This change is neutral in JetStream2, 0.3% improvement in Speedometer2 with 80% confidence, and 0.41% improvement in RAMification with 95% confidence.
+
+        * bytecode/ObjectAllocationProfileInlines.h:
+        (JSC::ObjectAllocationProfileBase<Derived>::initializeProfile):
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGConstantFoldingPhase.cpp:
+        (JSC::DFG::ConstantFoldingPhase::foldConstants):
+        * dfg/DFGOperations.cpp:
+        (JSC::DFG::JSC_DEFINE_JIT_OPERATION):
+        * dfg/DFGSpeculativeJIT.cpp:
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileCompareStrictEq):
+        * ftl/FTLOperations.cpp:
+        (JSC::FTL::JSC_DEFINE_JIT_OPERATION):
+        * runtime/ClonedArguments.h:
+        * runtime/IteratorOperations.cpp:
+        (JSC::createIteratorResultObjectStructure):
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::init):
+        * runtime/JSGlobalObjectFunctions.cpp:
+        (JSC::JSC_DEFINE_HOST_FUNCTION):
+        * runtime/JSONObject.cpp:
+        (JSC::Stringifier::Holder::appendNextProperty):
+        * runtime/JSObject.cpp:
+        (JSC::JSObject::analyzeHeap):
+        * runtime/JSObject.h:
+        * runtime/ObjectConstructor.h:
+        (JSC::constructEmptyObject):
+        (JSC::createDataPropertyDescriptorObjectStructure):
+        (JSC::createAccessorPropertyDescriptorObjectStructure):
+        * runtime/ObjectConstructorInlines.h:
+        (JSC::objectAssignFast):
+        * runtime/PropertyOffset.h:
+        * runtime/PropertySlot.h:
+        * runtime/PropertyTable.cpp:
+        (JSC::PropertyTable::PropertyTable):
+        (JSC::PropertyTable::finishCreation):
+        (JSC::PropertyTable::visitChildrenImpl):
+        (JSC::PropertyTable::~PropertyTable):
+        (JSC::PropertyTable::seal):
+        (JSC::PropertyTable::freeze):
+        (JSC::PropertyTable::isSealed const):
+        (JSC::PropertyTable::isFrozen const):
+        (JSC::PropertyTable::renumberPropertyOffsets):
+        (JSC::PropertyTable::forEachPropertyMutable):
+        * runtime/PropertyTable.h:
+        (JSC::isPowerOf2):
+        (JSC::nextPowerOf2):
+        (JSC::PropertyTable::findImpl):
+        (JSC::PropertyTable::find):
+        (JSC::PropertyTable::get):
+        (JSC::PropertyTable::add):
+        (JSC::PropertyTable::remove):
+        (JSC::PropertyTable::take):
+        (JSC::PropertyTable::updateAttributeIfExists):
+        (JSC::PropertyTable::sizeInMemory):
+        (JSC::PropertyTable::reinsert):
+        (JSC::PropertyTable::rehash):
+        (JSC::PropertyTable::skipDeletedEntries):
+        (JSC::PropertyTable::usedCount const):
+        (JSC::PropertyTable::dataSize):
+        (JSC::PropertyTable::allocateIndexVector):
+        (JSC::PropertyTable::allocateZeroedIndexVector):
+        (JSC::PropertyTable::destroyIndexVector):
+        (JSC::PropertyTable::canInsert):
+        (JSC::PropertyTable::forEachProperty const):
+        (JSC::PropertyTable::begin): Deleted.
+        (JSC::PropertyTable::end): Deleted.
+        (JSC::PropertyTable::begin const): Deleted.
+        (JSC::PropertyTable::end const): Deleted.
+        (JSC::PropertyTable::table): Deleted.
+        (JSC::PropertyTable::table const): Deleted.
+        * runtime/RegExpMatchesArray.h:
+        * runtime/Structure.cpp:
+        (JSC::Structure::dumpStatistics):
+        (JSC::Structure::Structure):
+        (JSC::Structure::materializePropertyTable):
+        (JSC::Structure::nonPropertyTransitionSlow):
+        (JSC::Structure::isSealed):
+        (JSC::Structure::isFrozen):
+        (JSC::Structure::flattenDictionaryStructure):
+        (JSC::PropertyTableStatisticsExitLogger::~PropertyTableStatisticsExitLogger):
+        (JSC::Structure::getConcurrently):
+        (JSC::Structure::getPropertiesConcurrently):
+        (JSC::Structure::getPropertyNamesFromStructure):
+        (JSC::Structure::toStructureShape):
+        (JSC::Structure::dump const):
+        * runtime/Structure.h:
+        (JSC::CompactPropertyTableEntry::CompactPropertyTableEntry):
+        (JSC::CompactPropertyTableEntry::key const):
+        (JSC::CompactPropertyTableEntry::setKey):
+        (JSC::CompactPropertyTableEntry::offset const):
+        (JSC::CompactPropertyTableEntry::setOffset):
+        (JSC::CompactPropertyTableEntry::attributes const):
+        (JSC::CompactPropertyTableEntry::setAttributes):
+        (JSC::PropertyTableEntry::PropertyTableEntry):
+        (JSC::PropertyTableEntry::key const):
+        (JSC::PropertyTableEntry::setKey):
+        (JSC::PropertyTableEntry::offset const):
+        (JSC::PropertyTableEntry::setOffset):
+        (JSC::PropertyTableEntry::attributes const):
+        (JSC::PropertyTableEntry::setAttributes):
+        (JSC::PropertyMapEntry::PropertyMapEntry): Deleted.
+        * runtime/StructureInlines.h:
+        (JSC::Structure::get):
+        (JSC::Structure::forEachPropertyConcurrently):
+        (JSC::Structure::forEachProperty):
+        (JSC::Structure::add):
+        (JSC::Structure::remove):
+        (JSC::Structure::attributeChange):
+
+2022-04-21  Yusuke Suzuki  <ysuz...@apple.com>
+
         [JSC] Remove TempRegisterSet
         https://bugs.webkit.org/show_bug.cgi?id=239578
 

Modified: trunk/Source/_javascript_Core/bytecode/ObjectAllocationProfileInlines.h (293209 => 293210)


--- trunk/Source/_javascript_Core/bytecode/ObjectAllocationProfileInlines.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/bytecode/ObjectAllocationProfileInlines.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -67,7 +67,7 @@
     }
 
     unsigned inlineCapacity = 0;
-    if (inferredInlineCapacity < JSFinalObject::defaultInlineCapacity()) {
+    if (inferredInlineCapacity < JSFinalObject::defaultInlineCapacity) {
         // Try to shrink the object based on static analysis.
         inferredInlineCapacity += possibleDefaultPropertyCount(vm, prototype);
 
@@ -74,29 +74,29 @@
         if (!inferredInlineCapacity) {
             // Empty objects are rare, so most likely the static analyzer just didn't
             // see the real initializer function. This can happen with helper functions.
-            inferredInlineCapacity = JSFinalObject::defaultInlineCapacity();
-        } else if (inferredInlineCapacity > JSFinalObject::defaultInlineCapacity()) {
+            inferredInlineCapacity = JSFinalObject::defaultInlineCapacity;
+        } else if (inferredInlineCapacity > JSFinalObject::defaultInlineCapacity) {
             // Default properties are weak guesses, so don't allow them to turn a small
             // object into a large object.
-            inferredInlineCapacity = JSFinalObject::defaultInlineCapacity();
+            inferredInlineCapacity = JSFinalObject::defaultInlineCapacity;
         }
 
         inlineCapacity = inferredInlineCapacity;
-        ASSERT(inlineCapacity < JSFinalObject::maxInlineCapacity());
+        ASSERT(inlineCapacity < JSFinalObject::maxInlineCapacity);
     } else {
         // Normal or large object.
         inlineCapacity = inferredInlineCapacity;
-        if (inlineCapacity > JSFinalObject::maxInlineCapacity())
-            inlineCapacity = JSFinalObject::maxInlineCapacity();
+        if (inlineCapacity > JSFinalObject::maxInlineCapacity)
+            inlineCapacity = JSFinalObject::maxInlineCapacity;
     }
 
     if (isPolyProto) {
         ++inlineCapacity;
-        inlineCapacity = std::min(inlineCapacity, JSFinalObject::maxInlineCapacity());
+        inlineCapacity = std::min(inlineCapacity, JSFinalObject::maxInlineCapacity);
     }
 
     ASSERT(inlineCapacity > 0);
-    ASSERT(inlineCapacity <= JSFinalObject::maxInlineCapacity());
+    ASSERT(inlineCapacity <= JSFinalObject::maxInlineCapacity);
 
     size_t allocationSize = JSFinalObject::allocationSize(inlineCapacity);
     Allocator allocator = subspaceFor<JSFinalObject>(vm)->allocatorFor(allocationSize, AllocatorForMode::EnsureAllocator);
@@ -105,8 +105,8 @@
     if (allocator) {
         size_t slop = (allocator.cellSize() - allocationSize) / sizeof(WriteBarrier<Unknown>);
         inlineCapacity += slop;
-        if (inlineCapacity > JSFinalObject::maxInlineCapacity())
-            inlineCapacity = JSFinalObject::maxInlineCapacity();
+        if (inlineCapacity > JSFinalObject::maxInlineCapacity)
+            inlineCapacity = JSFinalObject::maxInlineCapacity;
     }
 
     Structure* structure = globalObject->structureCache().emptyObjectStructureForPrototype(globalObject, prototype, inlineCapacity, isPolyProto, executable);

Modified: trunk/Source/_javascript_Core/dfg/DFGAbstractInterpreterInlines.h (293209 => 293210)


--- trunk/Source/_javascript_Core/dfg/DFGAbstractInterpreterInlines.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/dfg/DFGAbstractInterpreterInlines.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -3141,7 +3141,7 @@
                 WTF::loadLoadFence();
                 if (!isHavingABadTime)
                     m_graph.watchpoints().addLazily(globalObject->havingABadTimeWatchpoint());
-                structure = globalObject->structureCache().emptyObjectStructureConcurrently(base.getObject(), JSFinalObject::defaultInlineCapacity());
+                structure = globalObject->structureCache().emptyObjectStructureConcurrently(base.getObject(), JSFinalObject::defaultInlineCapacity);
             }
 
             if (structure) {

Modified: trunk/Source/_javascript_Core/dfg/DFGConstantFoldingPhase.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/dfg/DFGConstantFoldingPhase.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/dfg/DFGConstantFoldingPhase.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -845,7 +845,7 @@
                         WTF::loadLoadFence();
                         if (!isHavingABadTime)
                             m_graph.watchpoints().addLazily(globalObject->havingABadTimeWatchpoint());
-                        structure = globalObject->structureCache().emptyObjectStructureConcurrently(base.getObject(), JSFinalObject::defaultInlineCapacity());
+                        structure = globalObject->structureCache().emptyObjectStructureConcurrently(base.getObject(), JSFinalObject::defaultInlineCapacity);
                     }
                     
                     if (structure) {

Modified: trunk/Source/_javascript_Core/dfg/DFGOperations.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/dfg/DFGOperations.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/dfg/DFGOperations.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -340,16 +340,16 @@
             // Do not clear since Vector::clear shrinks the backing store.
             properties.resize(0);
             values.clear();
-            source->structure()->forEachProperty(vm, [&] (const PropertyMapEntry& entry) -> bool {
-                if (entry.attributes & PropertyAttribute::DontEnum)
+            source->structure()->forEachProperty(vm, [&] (const PropertyTableEntry& entry) -> bool {
+                if (entry.attributes() & PropertyAttribute::DontEnum)
                     return true;
 
-                PropertyName propertyName(entry.key);
+                PropertyName propertyName(entry.key());
                 if (propertyName.isPrivateName())
                     return true;
 
-                properties.append(entry.key);
-                values.appendWithCrashOnOverflow(source->getDirect(entry.offset));
+                properties.append(entry.key());
+                values.appendWithCrashOnOverflow(source->getDirect(entry.offset()));
 
                 return true;
             });

Modified: trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -13479,15 +13479,15 @@
 
         case NamedPropertyPLoc: {
             StringImpl* uid = m_graph.identifiers()[descriptor.info()];
-            for (PropertyMapEntry entry : structure->getPropertiesConcurrently()) {
-                if (uid != entry.key)
+            for (const PropertyTableEntry& entry : structure->getPropertiesConcurrently()) {
+                if (uid != entry.key())
                     continue;
 
                 JSValueOperand value(this, edge);
-                GPRReg baseGPR = isInlineOffset(entry.offset) ? resultGPR : storageGPR;
+                GPRReg baseGPR = isInlineOffset(entry.offset()) ? resultGPR : storageGPR;
                 m_jit.storeValue(
                     value.jsValueRegs(),
-                    JITCompiler::Address(baseGPR, offsetRelativeToBase(entry.offset)));
+                    JITCompiler::Address(baseGPR, offsetRelativeToBase(entry.offset())));
             }
             break;
         }

Modified: trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -14119,21 +14119,21 @@
             }
 
             BitVector setInlineOffsets;
-            for (PropertyMapEntry entry : structure->getPropertiesConcurrently()) {
+            for (const PropertyTableEntry& entry : structure->getPropertiesConcurrently()) {
                 for (unsigned i = data.m_properties.size(); i--;) {
                     PromotedLocationDescriptor descriptor = data.m_properties[i];
                     if (descriptor.kind() != NamedPropertyPLoc)
                         continue;
-                    if (m_graph.identifiers()[descriptor.info()] != entry.key)
+                    if (m_graph.identifiers()[descriptor.info()] != entry.key())
                         continue;
                     
                     LValue base;
-                    if (isInlineOffset(entry.offset)) {
-                        setInlineOffsets.set(entry.offset);
+                    if (isInlineOffset(entry.offset())) {
+                        setInlineOffsets.set(entry.offset());
                         base = object;
                     } else
                         base = butterfly;
-                    storeProperty(values[i], base, descriptor.info(), entry.offset);
+                    storeProperty(values[i], base, descriptor.info(), entry.offset());
                     break;
                 }
             }

Modified: trunk/Source/_javascript_Core/ftl/FTLOperations.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/ftl/FTLOperations.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/ftl/FTLOperations.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -80,15 +80,15 @@
         // minimal visible effects on the system. Also, don't mind
         // that this is O(n^2). It doesn't matter. We only get here
         // from OSR exit.
-        for (PropertyMapEntry entry : structure->getPropertiesConcurrently()) {
+        for (const PropertyTableEntry& entry : structure->getPropertiesConcurrently()) {
             for (unsigned i = materialization->properties().size(); i--;) {
                 const ExitPropertyValue& property = materialization->properties()[i];
                 if (property.location().kind() != NamedPropertyPLoc)
                     continue;
-                if (codeBlock->identifier(property.location().info()).impl() != entry.key)
+                if (codeBlock->identifier(property.location().info()).impl() != entry.key())
                     continue;
 
-                object->putDirect(vm, entry.offset, JSValue::decode(values[i]));
+                object->putDirect(vm, entry.offset(), JSValue::decode(values[i]));
             }
         }
         break;
@@ -224,8 +224,8 @@
         // field is, for any reason, not filled later.
         // We use a random-ish number instead of a sensible value like
         // undefined to make possible bugs easier to track.
-        for (PropertyMapEntry entry : structure->getPropertiesConcurrently())
-            result->putDirect(vm, entry.offset, jsNumber(19723));
+        for (const PropertyTableEntry& entry : structure->getPropertiesConcurrently())
+            result->putDirect(vm, entry.offset(), jsNumber(19723));
 
         return result;
     }

Modified: trunk/Source/_javascript_Core/runtime/ClonedArguments.h (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/ClonedArguments.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/ClonedArguments.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -82,6 +82,6 @@
     WriteBarrier<JSFunction> m_callee; // Set to nullptr when we materialize all of our special properties.
 };
 
-static const PropertyOffset clonedArgumentsLengthPropertyOffset = 100;
+static constexpr PropertyOffset clonedArgumentsLengthPropertyOffset = firstOutOfLineOffset;
 
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/runtime/IteratorOperations.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/IteratorOperations.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/IteratorOperations.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -132,7 +132,7 @@
 
 Structure* createIteratorResultObjectStructure(VM& vm, JSGlobalObject& globalObject)
 {
-    Structure* iteratorResultStructure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), JSFinalObject::defaultInlineCapacity());
+    Structure* iteratorResultStructure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), JSFinalObject::defaultInlineCapacity);
     PropertyOffset offset;
     iteratorResultStructure = Structure::addPropertyTransition(vm, iteratorResultStructure, vm.propertyNames->value, 0, offset);
     RELEASE_ASSERT(offset == valuePropertyOffset);

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -827,7 +827,7 @@
         JSFunction::create(vm, this, 0, makeString("set ", vm.propertyNames->underscoreProto.string()), globalFuncProtoSetter));
     m_objectPrototype->putDirectNonIndexAccessorWithoutTransition(vm, vm.propertyNames->underscoreProto, protoAccessor, PropertyAttribute::Accessor | PropertyAttribute::DontEnum);
     m_functionPrototype->structure()->setPrototypeWithoutTransition(vm, m_objectPrototype.get());
-    m_objectStructureForObjectConstructor.set(vm, this, m_structureCache.emptyObjectStructureForPrototype(this, m_objectPrototype.get(), JSFinalObject::defaultInlineCapacity()));
+    m_objectStructureForObjectConstructor.set(vm, this, m_structureCache.emptyObjectStructureForPrototype(this, m_objectPrototype.get(), JSFinalObject::defaultInlineCapacity));
     m_objectProtoValueOfFunction.set(vm, this, jsCast<JSFunction*>(objectPrototype()->getDirect(vm, vm.propertyNames->valueOf)));
 
     m_speciesGetterSetter.set(vm, this, GetterSetter::create(vm, this, JSFunction::create(vm, globalOperationsSpeciesGetterCodeGenerator(vm), this), nullptr));
@@ -889,7 +889,7 @@
             init.set(JSWithScope::createStructure(init.vm, init.owner, jsNull()));
         });
     
-    m_nullPrototypeObjectStructure.set(vm, this, JSFinalObject::createStructure(vm, this, jsNull(), JSFinalObject::defaultInlineCapacity()));
+    m_nullPrototypeObjectStructure.set(vm, this, JSFinalObject::createStructure(vm, this, jsNull(), JSFinalObject::defaultInlineCapacity));
     
     m_callbackFunctionStructure.initLater(
         [] (const Initializer<Structure>& init) {

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -925,19 +925,19 @@
         // that ends up transitioning the structure underneath us.
         // https://bugs.webkit.org/show_bug.cgi?id=187837
 
-        source->structure()->forEachProperty(vm, [&] (const PropertyMapEntry& entry) -> bool {
-            PropertyName propertyName(entry.key);
+        source->structure()->forEachProperty(vm, [&] (const PropertyTableEntry& entry) -> bool {
+            PropertyName propertyName(entry.key());
             if (propertyName.isPrivateName())
                 return true;
 
-            if (entry.attributes & PropertyAttribute::DontEnum)
+            if (entry.attributes() & PropertyAttribute::DontEnum)
                 return true;
 
             if (isPropertyNameExcluded(propertyName))
                 return true;
 
-            properties.append(entry.key);
-            values.appendWithCrashOnOverflow(source->getDirect(entry.offset));
+            properties.append(entry.key());
+            values.appendWithCrashOnOverflow(source->getDirect(entry.offset()));
             return true;
         });
 

Modified: trunk/Source/_javascript_Core/runtime/JSONObject.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/JSONObject.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/JSONObject.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -511,14 +511,14 @@
                 m_propertyNames = stringifier.m_arrayReplacerPropertyNames.data();
                 m_size = m_propertyNames->propertyNameVector().size();
             } else if (m_structure && m_object->structureID() == m_structure->id() && canPerformFastPropertyEnumerationForJSONStringify(m_structure)) {
-                m_structure->forEachProperty(vm, [&](const PropertyMapEntry& entry) -> bool {
-                    if (entry.attributes & PropertyAttribute::DontEnum)
+                m_structure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool {
+                    if (entry.attributes() & PropertyAttribute::DontEnum)
                         return true;
 
-                    PropertyName propertyName(entry.key);
+                    PropertyName propertyName(entry.key());
                     if (propertyName.isSymbol())
                         return true;
-                    m_propertiesAndOffsets.constructAndAppend(Identifier::fromUid(vm, entry.key), entry.offset);
+                    m_propertiesAndOffsets.constructAndAppend(Identifier::fromUid(vm, entry.key()), entry.offset());
                     return true;
                 });
                 m_hasFastObjectProperties = true;

Modified: trunk/Source/_javascript_Core/runtime/JSObject.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/JSObject.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/JSObject.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -430,10 +430,10 @@
     Base::analyzeHeap(cell, analyzer);
 
     Structure* structure = thisObject->structure();
-    for (auto& entry : structure->getPropertiesConcurrently()) {
-        JSValue toValue = thisObject->getDirect(entry.offset);
+    for (const auto& entry : structure->getPropertiesConcurrently()) {
+        JSValue toValue = thisObject->getDirect(entry.offset());
         if (toValue && toValue.isCell())
-            analyzer.analyzePropertyNameEdge(thisObject, toValue.asCell(), entry.key);
+            analyzer.analyzePropertyNameEdge(thisObject, toValue.asCell(), entry.key());
     }
 
     Butterfly* butterfly = thisObject->butterfly();

Modified: trunk/Source/_javascript_Core/runtime/JSObject.h (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/JSObject.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/JSObject.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -1241,17 +1241,13 @@
     static inline const TypeInfo typeInfo() { return TypeInfo(FinalObjectType, StructureFlags); }
     static constexpr IndexingType defaultIndexingType = NonArray;
         
-    static constexpr unsigned defaultSize = 64;
-    static inline unsigned defaultInlineCapacity()
-    {
-        return (defaultSize - allocationSize(0)) / sizeof(WriteBarrier<Unknown>);
-    }
+    static constexpr unsigned defaultSizeInBytes = 64;
+    static constexpr unsigned defaultInlineCapacity = (defaultSizeInBytes - sizeof(JSObject)) / sizeof(WriteBarrier<Unknown>);
+    static_assert(defaultInlineCapacity < firstOutOfLineOffset);
 
-    static constexpr unsigned maxSize = 512;
-    static inline unsigned maxInlineCapacity()
-    {
-        return (maxSize - allocationSize(0)) / sizeof(WriteBarrier<Unknown>);
-    }
+    static constexpr unsigned maxSizeInBytes = 512;
+    static constexpr unsigned maxInlineCapacity = (maxSizeInBytes - sizeof(JSObject)) / sizeof(WriteBarrier<Unknown>);
+    static_assert(maxInlineCapacity < firstOutOfLineOffset);
 
     static JSFinalObject* create(VM&, Structure*);
     static JSFinalObject* createWithButterfly(VM&, Structure*, Butterfly*);

Modified: trunk/Source/_javascript_Core/runtime/ObjectConstructor.h (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/ObjectConstructor.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/ObjectConstructor.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -73,7 +73,7 @@
 
 inline JSFinalObject* constructEmptyObject(JSGlobalObject* globalObject, JSObject* prototype)
 {
-    return constructEmptyObject(globalObject, prototype, JSFinalObject::defaultInlineCapacity());
+    return constructEmptyObject(globalObject, prototype, JSFinalObject::defaultInlineCapacity);
 }
 
 inline JSFinalObject* constructEmptyObject(JSGlobalObject* globalObject)
@@ -102,7 +102,7 @@
 
 inline Structure* createDataPropertyDescriptorObjectStructure(VM& vm, JSGlobalObject& globalObject)
 {
-    Structure* structure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), JSFinalObject::defaultInlineCapacity());
+    Structure* structure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), JSFinalObject::defaultInlineCapacity);
     PropertyOffset offset;
     structure = Structure::addPropertyTransition(vm, structure, vm.propertyNames->value, 0, offset);
     RELEASE_ASSERT(offset == dataPropertyDescriptorValuePropertyOffset);
@@ -117,7 +117,7 @@
 
 inline Structure* createAccessorPropertyDescriptorObjectStructure(VM& vm, JSGlobalObject& globalObject)
 {
-    Structure* structure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), JSFinalObject::defaultInlineCapacity());
+    Structure* structure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), JSFinalObject::defaultInlineCapacity);
     PropertyOffset offset;
     structure = Structure::addPropertyTransition(vm, structure, vm.propertyNames->get, 0, offset);
     RELEASE_ASSERT(offset == accessorPropertyDescriptorGetPropertyOffset);

Modified: trunk/Source/_javascript_Core/runtime/ObjectConstructorInlines.h (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/ObjectConstructorInlines.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/ObjectConstructorInlines.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -76,16 +76,16 @@
     // Do not clear since Vector::clear shrinks the backing store.
     properties.resize(0);
     values.clear();
-    source->structure()->forEachProperty(vm, [&] (const PropertyMapEntry& entry) -> bool {
-        if (entry.attributes & PropertyAttribute::DontEnum)
+    source->structure()->forEachProperty(vm, [&] (const PropertyTableEntry& entry) -> bool {
+        if (entry.attributes() & PropertyAttribute::DontEnum)
             return true;
 
-        PropertyName propertyName(entry.key);
+        PropertyName propertyName(entry.key());
         if (propertyName.isPrivateName())
             return true;
 
-        properties.append(entry.key);
-        values.appendWithCrashOnOverflow(source->getDirect(entry.offset));
+        properties.append(entry.key());
+        values.appendWithCrashOnOverflow(source->getDirect(entry.offset()));
 
         return true;
     });

Modified: trunk/Source/_javascript_Core/runtime/PropertyOffset.h (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/PropertyOffset.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/PropertyOffset.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -32,7 +32,7 @@
 typedef int PropertyOffset;
 
 static constexpr PropertyOffset invalidOffset = -1;
-static constexpr PropertyOffset firstOutOfLineOffset = 100;
+static constexpr PropertyOffset firstOutOfLineOffset = 64;
 static constexpr PropertyOffset knownPolyProtoOffset = 0;
 static_assert(knownPolyProtoOffset < firstOutOfLineOffset, "We assume in all the JITs that the poly proto offset is an inline offset");
 

Modified: trunk/Source/_javascript_Core/runtime/PropertySlot.h (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/PropertySlot.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/PropertySlot.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -49,7 +49,7 @@
     AccessorOrCustomAccessorOrValue = Accessor | CustomAccessor | CustomValue,
     ReadOnlyOrAccessorOrCustomAccessor = ReadOnly | Accessor | CustomAccessor,
 
-    // Things that are used by static hashtables are not in the attributes byte in PropertyMapEntry.
+    // Things that are used by static hashtables are not in the attributes byte in PropertyTableEntry.
     Function          = 1 << 8,  // property is a function - only used by static hashtables
     Builtin           = 1 << 9,  // property is a builtin function - only used by static hashtables
     ConstantInteger   = 1 << 10, // property is a constant integer - only used by static hashtables

Modified: trunk/Source/_javascript_Core/runtime/PropertyTable.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/PropertyTable.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/PropertyTable.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -59,11 +59,14 @@
     : JSCell(vm, vm.propertyTableStructure.get())
     , m_indexSize(sizeForCapacity(initialCapacity))
     , m_indexMask(m_indexSize - 1)
-    , m_index(static_cast<unsigned*>(PropertyTableMalloc::zeroedMalloc(dataSize())))
+    , m_indexVector()
     , m_keyCount(0)
     , m_deletedCount(0)
 {
     ASSERT(isPowerOf2(m_indexSize));
+    bool isCompact = tableCapacity() < UINT8_MAX;
+    m_indexVector = allocateZeroedIndexVector(isCompact, m_indexSize);
+    ASSERT(isCompact == this->isCompact());
 }
 
 PropertyTable::PropertyTable(VM& vm, const PropertyTable& other)
@@ -70,18 +73,19 @@
     : JSCell(vm, vm.propertyTableStructure.get())
     , m_indexSize(other.m_indexSize)
     , m_indexMask(other.m_indexMask)
-    , m_index(static_cast<unsigned*>(PropertyTableMalloc::malloc(dataSize())))
+    , m_indexVector(allocateIndexVector(other.isCompact(), other.m_indexSize))
     , m_keyCount(other.m_keyCount)
     , m_deletedCount(other.m_deletedCount)
 {
     ASSERT(isPowerOf2(m_indexSize));
+    ASSERT(isCompact() == other.isCompact());
+    memcpy(bitwise_cast<void*>(m_indexVector & indexVectorMask), bitwise_cast<void*>(other.m_indexVector & indexVectorMask), dataSize(isCompact()));
 
-    memcpy(m_index, other.m_index, dataSize());
+    forEachProperty([&](auto& entry) {
+        entry.key()->ref();
+        return IterationStatus::Continue;
+    });
 
-    iterator end = this->end();
-    for (iterator iter = begin(); iter != end; ++iter)
-        iter->key->ref();
-
     // Copy the m_deletedOffsets vector.
     Vector<PropertyOffset>* otherDeletedOffsets = other.m_deletedOffsets.get();
     if (otherDeletedOffsets)
@@ -92,19 +96,25 @@
     : JSCell(vm, vm.propertyTableStructure.get())
     , m_indexSize(sizeForCapacity(initialCapacity))
     , m_indexMask(m_indexSize - 1)
-    , m_index(static_cast<unsigned*>(PropertyTableMalloc::zeroedMalloc(dataSize())))
+    , m_indexVector()
     , m_keyCount(0)
     , m_deletedCount(0)
 {
     ASSERT(isPowerOf2(m_indexSize));
     ASSERT(initialCapacity >= other.m_keyCount);
+    bool isCompact = other.isCompact() && tableCapacity() < UINT8_MAX;
+    m_indexVector = allocateZeroedIndexVector(isCompact, m_indexSize);
+    ASSERT(this->isCompact() == isCompact);
 
-    const_iterator end = other.end();
-    for (const_iterator iter = other.begin(); iter != end; ++iter) {
-        ASSERT(canInsert());
-        reinsert(*iter);
-        iter->key->ref();
-    }
+    withIndexVector([&](auto* vector) {
+        auto* table = tableFromIndexVector(vector);
+        other.forEachProperty([&](auto& entry) {
+            ASSERT(canInsert(entry));
+            reinsert(vector, table, entry);
+            entry.key()->ref();
+            return IterationStatus::Continue;
+        });
+    });
 
     // Copy the m_deletedOffsets vector.
     Vector<PropertyOffset>* otherDeletedOffsets = other.m_deletedOffsets.get();
@@ -115,7 +125,7 @@
 void PropertyTable::finishCreation(VM& vm)
 {
     Base::finishCreation(vm);
-    vm.heap.reportExtraMemoryAllocated(dataSize());
+    vm.heap.reportExtraMemoryAllocated(dataSize(isCompact()));
 }
 
 template<typename Visitor>
@@ -124,7 +134,7 @@
     auto* thisObject = jsCast<PropertyTable*>(cell);
     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
     Base::visitChildren(cell, visitor);
-    visitor.reportExtraMemoryVisited(thisObject->dataSize());
+    visitor.reportExtraMemoryVisited(thisObject->dataSize(thisObject->isCompact()));
 }
 
 DEFINE_VISIT_CHILDREN(PropertyTable);
@@ -136,13 +146,92 @@
 
 PropertyTable::~PropertyTable()
 {
-    iterator end = this->end();
-    for (iterator iter = begin(); iter != end; ++iter)
-        iter->key->deref();
+    forEachProperty([&](auto& entry) {
+        entry.key()->deref();
+        return IterationStatus::Continue;
+    });
+    destroyIndexVector(m_indexVector);
+}
 
-    PropertyTableMalloc::free(m_index);
+void PropertyTable::seal()
+{
+    forEachPropertyMutable([&](auto& entry) {
+        entry.setAttributes(entry.attributes() | static_cast<unsigned>(PropertyAttribute::DontDelete));
+        return IterationStatus::Continue;
+    });
+}
 
+void PropertyTable::freeze()
+{
+    forEachPropertyMutable([&](auto& entry) {
+        if (!(entry.attributes() & PropertyAttribute::Accessor))
+            entry.setAttributes(entry.attributes() | static_cast<unsigned>(PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly));
+        else
+            entry.setAttributes(entry.attributes() | static_cast<unsigned>(PropertyAttribute::DontDelete));
+        return IterationStatus::Continue;
+    });
 }
 
+bool PropertyTable::isSealed() const
+{
+    bool result = true;
+    forEachProperty([&](const auto& entry) {
+        if ((entry.attributes() & PropertyAttribute::DontDelete) != static_cast<unsigned>(PropertyAttribute::DontDelete)) {
+            result = false;
+            return IterationStatus::Done;
+        }
+        return IterationStatus::Continue;
+    });
+    return result;
+}
+
+bool PropertyTable::isFrozen() const
+{
+    bool result = true;
+    forEachProperty([&](const auto& entry) {
+        if (!(entry.attributes() & PropertyAttribute::DontDelete)) {
+            result = false;
+            return IterationStatus::Done;
+        }
+        if (!(entry.attributes() & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor))) {
+            result = false;
+            return IterationStatus::Done;
+        }
+        return IterationStatus::Continue;
+    });
+    return result;
+}
+
+PropertyOffset PropertyTable::renumberPropertyOffsets(JSObject* object, unsigned inlineCapacity, Vector<JSValue>& values)
+{
+    ASSERT(values.size() == size());
+    unsigned i = 0;
+    PropertyOffset offset = invalidOffset;
+    forEachPropertyMutable([&](auto& entry) {
+        values[i] = object->getDirect(entry.offset());
+        offset = offsetForPropertyNumber(i, inlineCapacity);
+        entry.setOffset(offset);
+        ++i;
+        return IterationStatus::Continue;
+    });
+    clearDeletedOffsets();
+    return offset;
+}
+
+template<typename Functor>
+inline void PropertyTable::forEachPropertyMutable(const Functor& functor)
+{
+    withIndexVector([&](auto* vector) {
+        auto* cursor = tableFromIndexVector(vector);
+        auto* end = tableEndFromIndexVector(vector);
+        for (; cursor != end; ++cursor) {
+            if (cursor->key() == PROPERTY_MAP_DELETED_ENTRY_KEY)
+                continue;
+            if (functor(*cursor) == IterationStatus::Done)
+                return;
+        }
+    });
+}
+
 } // namespace JSC
 

Modified: trunk/Source/_javascript_Core/runtime/PropertyTable.h (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/PropertyTable.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/PropertyTable.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -57,12 +57,12 @@
 
 #endif
 
-inline bool isPowerOf2(unsigned v)
+inline constexpr bool isPowerOf2(unsigned v)
 {
     return hasOneBitSet(v);
 }
 
-inline unsigned nextPowerOf2(unsigned v)
+inline constexpr unsigned nextPowerOf2(unsigned v)
 {
     // Taken from http://www.cs.utk.edu/~vose/c-stuff/bithacks.html
     // Devised by Sean Anderson, Sepember 14, 2001
@@ -78,52 +78,33 @@
     return v;
 }
 
+// compact <-> non-compact PropertyTable
+// We need to maintain two things, one is PropertyOffset and one is unsigned index in index buffer of PropertyTable.
+// But both are typically small. It is possible that we can get optimized table if both are fit in uint8_t, that's
+// compact PropertyTable.
+//
+// PropertyOffset can be offseted with firstOutOfLineOffset since we can get out-of-line property easily, but this
+// offset is small enough (64 currently), so that we can still assume that most of property offsets are < 256.
+//
+// 1. If property offset gets larger than 255, then we get non-compact PropertyTable. It requires at least 191 (255 - 64) properties.
+//    In that case, PropertyTable size should be 256 since it is power-of-two.
+// 2. If index gets larger than 255, then we get non-compact PropertyTable. But we are using 0 and 255 for markers. Thus, if we get 253
+//    used counts, then we need to change the table.
+//
+// So, typical scenario is that, once 128th property is added, then we extend the table via rehashing. At that time, we change the
+// table from compact to non-compact mode.
+//
+//  index-size  table-capacity    compact   v.s. non-compact
+//     16             8              80              192
+//     32            16             160              384
+//     64            32             320              768
+//    128            64             640             1536
+//    256           128            1280             3072
+//    512           256             N/A             6144     // After 512 size, compact PropertyTable does not work. All table gets non-compact.
+
 class PropertyTable final : public JSCell {
-
-    // This is the implementation for 'iterator' and 'const_iterator',
-    // used for iterating over the table in insertion order.
-    template<typename T>
-    class ordered_iterator {
-    public:
-        ordered_iterator<T>& operator++()
-        {
-            m_valuePtr = skipDeletedEntries(m_valuePtr + 1, m_endValuePtr);
-            return *this;
-        }
-
-        bool operator==(const ordered_iterator<T>& other) const
-        {
-            return m_valuePtr == other.m_valuePtr;
-        }
-
-        bool operator!=(const ordered_iterator<T>& other) const
-        {
-            return m_valuePtr != other.m_valuePtr;
-        }
-
-        T& operator*()
-        {
-            return *m_valuePtr;
-        }
-
-        T* operator->()
-        {
-            return m_valuePtr;
-        }
-
-        ordered_iterator(T* valuePtr, T* endValuePtr)
-            : m_valuePtr(valuePtr)
-            , m_endValuePtr(endValuePtr)
-        {
-        }
-
-    private:
-        T* m_valuePtr;
-        T* m_endValuePtr;
-    };
-
 public:
-    typedef JSCell Base;
+    using Base = JSCell;
     static constexpr unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal;
 
     template<typename CellType, SubspaceAccess>
@@ -143,18 +124,9 @@
         return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info());
     }
 
-    typedef UniquedStringImpl* KeyType;
-    typedef PropertyMapEntry ValueType;
+    using KeyType = UniquedStringImpl*;
+    using ValueType = PropertyTableEntry;
 
-    // The in order iterator provides overloaded * and -> to access the Value at the current position.
-    typedef ordered_iterator<ValueType> iterator;
-    typedef ordered_iterator<const ValueType> const_iterator;
-
-    // The find_iterator is a pair of a pointer to a Value* an the entry in the index.
-    // If 'find' does not find an entry then iter.first will be 0, and iter.second will
-    // give the point in m_index where an entry should be inserted.
-    typedef std::pair<ValueType*, unsigned> find_iterator;
-
     // Constructor is passed an initial capacity, a PropertyTable to copy, or both.
     static PropertyTable* create(VM&, unsigned initialCapacity);
     static PropertyTable* clone(VM&, const PropertyTable&);
@@ -161,21 +133,22 @@
     static PropertyTable* clone(VM&, unsigned initialCapacity, const PropertyTable&);
     ~PropertyTable();
 
-    // Ordered iteration methods.
-    iterator begin();
-    iterator end();
-    const_iterator begin() const;
-    const_iterator end() const;
-
     // Find a value in the table.
-    find_iterator find(const KeyType&);
-    ValueType* get(const KeyType&);
+    std::tuple<PropertyOffset, unsigned> get(const KeyType&);
     // Add a value to the table
-    std::pair<find_iterator, bool> WARN_UNUSED_RETURN add(VM&, const ValueType& entry);
+    std::tuple<PropertyOffset, unsigned, bool> WARN_UNUSED_RETURN add(VM&, const ValueType& entry);
     // Remove a value from the table.
-    void remove(VM&, const find_iterator&);
-    void remove(VM&, const KeyType&);
+    std::tuple<PropertyOffset, unsigned> take(VM&, const KeyType&);
+    PropertyOffset updateAttributeIfExists(const KeyType&, unsigned attributes);
 
+    PropertyOffset renumberPropertyOffsets(JSObject*, unsigned inlineCapacity, Vector<JSValue>&);
+
+    void seal();
+    void freeze();
+
+    bool isSealed() const;
+    bool isFrozen() const;
+
     // Returns the number of values in the hashtable.
     unsigned size() const;
 
@@ -200,11 +173,10 @@
     size_t sizeInMemory();
     void checkConsistency();
 #endif
-    
-    static ptrdiff_t offsetOfIndexSize() { return OBJECT_OFFSETOF(PropertyTable, m_indexSize); }
-    static ptrdiff_t offsetOfIndexMask() { return OBJECT_OFFSETOF(PropertyTable, m_indexMask); }
-    static ptrdiff_t offsetOfIndex() { return OBJECT_OFFSETOF(PropertyTable, m_index); }
 
+    template<typename Functor>
+    void forEachProperty(const Functor&) const;
+
     static constexpr unsigned EmptyEntryIndex = 0;
 
 private:
@@ -217,10 +189,13 @@
     void finishCreation(VM&);
 
     // Used to insert a value known not to be in the table, and where we know capacity to be available.
-    void reinsert(const ValueType& entry);
+    template<typename Index, typename Entry>
+    void reinsert(Index*, Entry*, const ValueType& entry);
 
+    static bool canFitInCompact(const ValueType& entry) { return entry.offset() <= UINT8_MAX; }
+
     // Rehash the table. Used to grow, or to recover deleted slots.
-    void rehash(VM&, unsigned newCapacity);
+    void rehash(VM&, unsigned newCapacity, bool canStayCompact);
 
     // The capacity of the table of values is half of the size of the index.
     unsigned tableCapacity() const;
@@ -238,63 +213,113 @@
     template<typename T>
     static T* skipDeletedEntries(T* valuePtr, T* endValuePtr);
 
-    // The table of values lies after the hash index.
-    ValueType* table();
-    const ValueType* table() const;
-
-    ValueType* tableEnd() { return table() + usedCount(); }
-    const ValueType* tableEnd() const { return table() + usedCount(); }
-
     // total number of  used entries in the values array - by either valid entries, or deleted ones.
     unsigned usedCount() const;
 
     // The size in bytes of data needed for by the table.
-    size_t dataSize();
+    size_t dataSize(bool isCompact);
+    static size_t dataSize(bool isCompact, unsigned indexSize);
 
     // Calculates the appropriate table size (rounds up to a power of two).
     static unsigned sizeForCapacity(unsigned capacity);
 
     // Check if capacity is available.
-    bool canInsert();
+    bool canInsert(const ValueType&);
 
+    void remove(VM&, KeyType, unsigned entryIndex, unsigned index);
+
+    struct FindResult {
+        unsigned entryIndex;
+        unsigned index;
+        PropertyOffset offset;
+        unsigned attributes;
+    };
+
+    FindResult find(const KeyType&);
+
+    template<typename Index, typename Entry>
+    ALWAYS_INLINE FindResult findImpl(const Index*, const Entry*, const KeyType&);
+
+    bool isCompact() const { return m_indexVector & isCompactFlag; }
+
+    template<typename Functor>
+    void forEachPropertyMutable(const Functor&);
+
+    // The table of values lies after the hash index.
+    static CompactPropertyTableEntry* tableFromIndexVector(uint8_t* index, unsigned indexSize)
+    {
+        return bitwise_cast<CompactPropertyTableEntry*>(index + indexSize);
+    }
+    static const CompactPropertyTableEntry* tableFromIndexVector(const uint8_t* index, unsigned indexSize)
+    {
+        return bitwise_cast<const CompactPropertyTableEntry*>(index + indexSize);
+    }
+    static PropertyTableEntry* tableFromIndexVector(uint32_t* index, unsigned indexSize)
+    {
+        return bitwise_cast<PropertyTableEntry*>(index + indexSize);
+    }
+    static const PropertyTableEntry* tableFromIndexVector(const uint32_t* index, unsigned indexSize)
+    {
+        return bitwise_cast<const PropertyTableEntry*>(index + indexSize);
+    }
+
+    CompactPropertyTableEntry* tableFromIndexVector(uint8_t* index) { return tableFromIndexVector(index, m_indexSize); }
+    const CompactPropertyTableEntry* tableFromIndexVector(const uint8_t* index) const { return tableFromIndexVector(index, m_indexSize); }
+    PropertyTableEntry* tableFromIndexVector(uint32_t* index) { return tableFromIndexVector(index, m_indexSize); }
+    const PropertyTableEntry* tableFromIndexVector(const uint32_t* index) const { return tableFromIndexVector(index, m_indexSize); }
+
+    CompactPropertyTableEntry* tableEndFromIndexVector(uint8_t* index)
+    {
+        return tableFromIndexVector(index) + usedCount();
+    }
+    const CompactPropertyTableEntry* tableEndFromIndexVector(const uint8_t* index) const
+    {
+        return tableFromIndexVector(index) + usedCount();
+    }
+    PropertyTableEntry* tableEndFromIndexVector(uint32_t* index)
+    {
+        return tableFromIndexVector(index) + usedCount();
+    }
+    const PropertyTableEntry* tableEndFromIndexVector(const uint32_t* index) const
+    {
+        return tableFromIndexVector(index) + usedCount();
+    }
+
+    static uintptr_t allocateIndexVector(bool isCompact, unsigned indexSize);
+    static uintptr_t allocateZeroedIndexVector(bool isCompact, unsigned indexSize);
+    static void destroyIndexVector(uintptr_t indexVector);
+
+    template<typename Func>
+    static ALWAYS_INLINE auto withIndexVector(uintptr_t indexVector, Func&& function) -> decltype(auto)
+    {
+        if (indexVector & isCompactFlag)
+            return function(bitwise_cast<uint8_t*>(indexVector & indexVectorMask));
+        return function(bitwise_cast<uint32_t*>(indexVector & indexVectorMask));
+    }
+
+    template<typename Func>
+    ALWAYS_INLINE auto withIndexVector(Func&& function) const -> decltype(auto)
+    {
+        return withIndexVector(m_indexVector, std::forward<Func>(function));
+    }
+
+    static constexpr uintptr_t isCompactFlag = 0x1;
+    static constexpr uintptr_t indexVectorMask = ~isCompactFlag;
+
     unsigned m_indexSize;
     unsigned m_indexMask;
-    unsigned* m_index;
+    uintptr_t m_indexVector;
     unsigned m_keyCount;
     unsigned m_deletedCount;
     std::unique_ptr<Vector<PropertyOffset>> m_deletedOffsets;
 
     static constexpr unsigned MinimumTableSize = 16;
+    static_assert(MinimumTableSize >= 16, "compact index is uint8_t and we should keep 16 byte aligned entries after this array");
 };
 
-inline PropertyTable::iterator PropertyTable::begin()
+template<typename Index, typename Entry>
+PropertyTable::FindResult PropertyTable::findImpl(const Index* indexVector, const Entry* table, const KeyType& key)
 {
-    auto* tableEnd = this->tableEnd();
-    return iterator(skipDeletedEntries(table(), tableEnd), tableEnd);
-}
-
-inline PropertyTable::iterator PropertyTable::end()
-{
-    auto* tableEnd = this->tableEnd();
-    return iterator(tableEnd, tableEnd);
-}
-
-inline PropertyTable::const_iterator PropertyTable::begin() const
-{
-    auto* tableEnd = this->tableEnd();
-    return const_iterator(skipDeletedEntries(table(), tableEnd), tableEnd);
-}
-
-inline PropertyTable::const_iterator PropertyTable::end() const
-{
-    auto* tableEnd = this->tableEnd();
-    return const_iterator(tableEnd, tableEnd);
-}
-
-inline PropertyTable::find_iterator PropertyTable::find(const KeyType& key)
-{
-    ASSERT(key);
-    ASSERT(key->isAtom() || key->isSymbol());
     unsigned hash = IdentifierRepHash::hash(key);
 
 #if DUMP_PROPERTYMAP_STATS
@@ -302,11 +327,15 @@
 #endif
 
     while (true) {
-        unsigned entryIndex = m_index[hash & m_indexMask];
+        unsigned index = hash & m_indexMask;
+        unsigned entryIndex = indexVector[index];
         if (entryIndex == EmptyEntryIndex)
-            return std::make_pair((ValueType*)nullptr, hash & m_indexMask);
-        if (key == table()[entryIndex - 1].key)
-            return std::make_pair(&table()[entryIndex - 1], hash & m_indexMask);
+            return FindResult { entryIndex, index, invalidOffset, 0 };
+        const auto& entry = table[entryIndex - 1];
+        if (key == entry.key()) {
+            ASSERT(!m_deletedOffsets || !m_deletedOffsets->contains(entry.offset()));
+            return FindResult { entryIndex, index, entry.offset(), entry.attributes() };
+        }
 
 #if DUMP_PROPERTYMAP_STATS
         ++propertyTableStats->numCollisions;
@@ -314,7 +343,7 @@
 
 #if DUMP_PROPERTYMAP_COLLISIONS
         dataLog("PropertyTable collision for ", key, " (", hash, ")\n");
-        dataLog("Collided with ", table()[entryIndex - 1].key, "(", IdentifierRepHash::hash(table()[entryIndex - 1].key), ")\n");
+        dataLog("Collided with ", entry.key(), "(", IdentifierRepHash::hash(entry.key()), ")\n");
 #endif
 
         hash++;
@@ -321,46 +350,36 @@
     }
 }
 
-inline PropertyTable::ValueType* PropertyTable::get(const KeyType& key)
+inline PropertyTable::FindResult PropertyTable::find(const KeyType& key)
 {
     ASSERT(key);
     ASSERT(key->isAtom() || key->isSymbol());
+    return withIndexVector([&](auto* vector) {
+        return findImpl(vector, tableFromIndexVector(vector), key);
+    });
+}
+
+inline std::tuple<PropertyOffset, unsigned> PropertyTable::get(const KeyType& key)
+{
+    ASSERT(key);
+    ASSERT(key->isAtom() || key->isSymbol());
     ASSERT(key != PROPERTY_MAP_DELETED_ENTRY_KEY);
 
     if (!m_keyCount)
-        return nullptr;
+        return std::tuple { invalidOffset, 0 };
 
-    unsigned hash = IdentifierRepHash::hash(key);
-
-#if DUMP_PROPERTYMAP_STATS
-    ++propertyTableStats->numLookups;
-#endif
-
-    while (true) {
-        unsigned entryIndex = m_index[hash & m_indexMask];
-        if (entryIndex == EmptyEntryIndex)
-            return nullptr;
-        if (key == table()[entryIndex - 1].key) {
-            ASSERT(!m_deletedOffsets || !m_deletedOffsets->contains(table()[entryIndex - 1].offset));
-            return &table()[entryIndex - 1];
-        }
-
-#if DUMP_PROPERTYMAP_STATS
-        ++propertyTableStats->numLookupProbing;
-#endif
-
-        hash++;
-    }
+    FindResult result = find(key);
+    return std::tuple { result.offset, result.attributes };
 }
 
-inline std::pair<PropertyTable::find_iterator, bool> WARN_UNUSED_RETURN PropertyTable::add(VM& vm, const ValueType& entry)
+inline std::tuple<PropertyOffset, unsigned, bool> WARN_UNUSED_RETURN PropertyTable::add(VM& vm, const ValueType& entry)
 {
-    ASSERT(!m_deletedOffsets || !m_deletedOffsets->contains(entry.offset));
+    ASSERT(!m_deletedOffsets || !m_deletedOffsets->contains(entry.offset()));
 
     // Look for a value with a matching key already in the array.
-    find_iterator iter = find(entry.key);
-    if (iter.first)
-        return std::make_pair(iter, false);
+    FindResult result = find(entry.key());
+    if (result.offset != invalidOffset)
+        return std::tuple { result.offset, result.attributes, false };
 
 #if DUMP_PROPERTYMAP_STATS
     ++propertyTableStats->numAdds;
@@ -367,32 +386,32 @@
 #endif
 
     // Ref the key
-    entry.key->ref();
+    entry.key()->ref();
 
     // ensure capacity is available.
-    if (!canInsert()) {
-        rehash(vm, m_keyCount + 1);
-        iter = find(entry.key);
-        ASSERT(!iter.first);
+    if (!canInsert(entry)) {
+        rehash(vm, m_keyCount + 1, canFitInCompact(entry));
+        result = find(entry.key());
+        ASSERT(result.offset == invalidOffset);
+        ASSERT(result.entryIndex == EmptyEntryIndex);
     }
 
     // Allocate a slot in the hashtable, and set the index to reference this.
+    ASSERT(!isCompact() || usedCount() < UINT8_MAX);
+    unsigned index = result.index;
     unsigned entryIndex = usedCount() + 1;
-    m_index[iter.second] = entryIndex;
-    iter.first = &table()[entryIndex - 1];
-    *iter.first = entry;
+    withIndexVector([&](auto* vector) {
+        vector[index] = entryIndex;
+        tableFromIndexVector(vector)[entryIndex - 1] = entry;
+    });
 
     ++m_keyCount;
     
-    return std::make_pair(iter, true);
+    return std::tuple { entry.offset(), entry.attributes(), true };
 }
 
-inline void PropertyTable::remove(VM& vm, const find_iterator& iter)
+inline void PropertyTable::remove(VM& vm, KeyType key, unsigned entryIndex, unsigned index)
 {
-    // Removing a key that doesn't exist does nothing!
-    if (!iter.first)
-        return;
-
 #if DUMP_PROPERTYMAP_STATS
     ++propertyTableStats->numRemoves;
 #endif
@@ -399,9 +418,11 @@
 
     // Replace this one element with the deleted sentinel. Also clear out
     // the entry so we can iterate all the entries as needed.
-    m_index[iter.second] = deletedEntryIndex();
-    iter.first->key->deref();
-    iter.first->key = PROPERTY_MAP_DELETED_ENTRY_KEY;
+    withIndexVector([&](auto* vector) {
+        vector[index] = deletedEntryIndex();
+        tableFromIndexVector(vector)[entryIndex - 1].setKey(PROPERTY_MAP_DELETED_ENTRY_KEY);
+    });
+    key->deref();
 
     ASSERT(m_keyCount >= 1);
     --m_keyCount;
@@ -408,14 +429,29 @@
     ++m_deletedCount;
 
     if (m_deletedCount * 4 >= m_indexSize)
-        rehash(vm, m_keyCount);
+        rehash(vm, m_keyCount, true);
 }
 
-inline void PropertyTable::remove(VM& vm, const KeyType& key)
+inline std::tuple<PropertyOffset, unsigned> PropertyTable::take(VM& vm, const KeyType& key)
 {
-    remove(vm, find(key));
+    FindResult result = find(key);
+    if (result.offset != invalidOffset)
+        remove(vm, key, result.entryIndex, result.index);
+    return std::tuple { result.offset, result.attributes };
 }
 
+inline PropertyOffset PropertyTable::updateAttributeIfExists(const KeyType& key, unsigned attributes)
+{
+    return withIndexVector([&](auto* vector) -> PropertyOffset {
+        auto* table = tableFromIndexVector(vector);
+        FindResult result = findImpl(vector, table, key);
+        if (result.offset == invalidOffset)
+            return invalidOffset;
+        table[result.entryIndex - 1].setAttributes(attributes);
+        return result.offset;
+    });
+}
+
 // returns the number of values in the hashtable.
 inline unsigned PropertyTable::size() const
 {
@@ -479,7 +515,7 @@
 #ifndef NDEBUG
 inline size_t PropertyTable::sizeInMemory()
 {
-    size_t result = sizeof(PropertyTable) + dataSize();
+    size_t result = sizeof(PropertyTable) + dataSize(isCompact());
     if (m_deletedOffsets)
         result += (m_deletedOffsets->capacity() * sizeof(PropertyOffset));
     return result;
@@ -486,7 +522,8 @@
 }
 #endif
 
-inline void PropertyTable::reinsert(const ValueType& entry)
+template<typename Index, typename Entry>
+inline void PropertyTable::reinsert(Index* indexVector, Entry* table, const ValueType& entry)
 {
 #if DUMP_PROPERTYMAP_STATS
     ++propertyTableStats->numReinserts;
@@ -494,27 +531,30 @@
 
     // Used to insert a value known not to be in the table, and where
     // we know capacity to be available.
-    ASSERT(canInsert());
-    find_iterator iter = find(entry.key);
-    ASSERT(!iter.first);
+    ASSERT(canInsert(entry));
+    FindResult result = findImpl(indexVector, table, entry.key());
+    ASSERT(result.offset == invalidOffset);
+    ASSERT(result.entryIndex == EmptyEntryIndex);
 
+    ASSERT(!isCompact() || usedCount() < UINT8_MAX);
     unsigned entryIndex = usedCount() + 1;
-    m_index[iter.second] = entryIndex;
-    table()[entryIndex - 1] = entry;
+    indexVector[result.index] = entryIndex;
+    table[entryIndex - 1] = entry;
 
     ++m_keyCount;
 }
 
-inline void PropertyTable::rehash(VM& vm, unsigned newCapacity)
+inline void PropertyTable::rehash(VM& vm, unsigned newCapacity, bool canStayCompact)
 {
 #if DUMP_PROPERTYMAP_STATS
     ++propertyTableStats->numRehashes;
 #endif
 
-    size_t oldDataSize = dataSize();
-    unsigned* oldEntryIndices = m_index;
-    iterator iter = this->begin();
-    iterator end = this->end();
+    uintptr_t oldIndexVector = m_indexVector;
+    bool oldIsCompact = oldIndexVector & isCompactFlag;
+    unsigned oldIndexSize = m_indexSize;
+    unsigned oldUsedCount = usedCount();
+    size_t oldDataSize = dataSize(oldIsCompact, oldIndexSize);
 
     m_indexSize = sizeForCapacity(newCapacity);
     m_indexMask = m_indexSize - 1;
@@ -521,17 +561,28 @@
     m_keyCount = 0;
     m_deletedCount = 0;
 
-    m_index = static_cast<unsigned*>(PropertyTableMalloc::zeroedMalloc(dataSize()));
+    // Once table gets non-compact, we do not change it back to compact again.
+    // This is because some of property offset can be larger than UINT8_MAX already.
+    bool isCompact = canStayCompact && oldIsCompact && tableCapacity() < UINT8_MAX;
+    m_indexVector = allocateZeroedIndexVector(isCompact, m_indexSize);
+    withIndexVector([&](auto* vector) {
+        auto* table = tableFromIndexVector(vector);
+        withIndexVector(oldIndexVector, [&](const auto* oldVector) {
+            const auto* oldCursor = tableFromIndexVector(oldVector, oldIndexSize);
+            const auto* oldEnd = oldCursor + oldUsedCount;
+            for (; oldCursor != oldEnd; ++oldCursor) {
+                if (oldCursor->key() == PROPERTY_MAP_DELETED_ENTRY_KEY)
+                    continue;
+                ASSERT(canInsert(*oldCursor));
+                reinsert(vector, table, *oldCursor);
+            }
+        });
+    });
+    destroyIndexVector(oldIndexVector);
 
-    for (; iter != end; ++iter) {
-        ASSERT(canInsert());
-        reinsert(*iter);
-    }
-
-    PropertyTableMalloc::free(oldEntryIndices);
-
-    if (oldDataSize < dataSize())
-        vm.heap.reportExtraMemoryAllocated(dataSize() - oldDataSize);
+    size_t newDataSize = dataSize(this->isCompact());
+    if (oldDataSize < newDataSize)
+        vm.heap.reportExtraMemoryAllocated(newDataSize - oldDataSize);
 }
 
 inline unsigned PropertyTable::tableCapacity() const { return m_indexSize >> 1; }
@@ -541,37 +592,46 @@
 template<typename T>
 inline T* PropertyTable::skipDeletedEntries(T* valuePtr, T* endValuePtr)
 {
-    while (valuePtr < endValuePtr && valuePtr->key == PROPERTY_MAP_DELETED_ENTRY_KEY)
+    while (valuePtr < endValuePtr && valuePtr->key() == PROPERTY_MAP_DELETED_ENTRY_KEY)
         ++valuePtr;
     return valuePtr;
 }
 
-inline PropertyTable::ValueType* PropertyTable::table()
+inline unsigned PropertyTable::usedCount() const
 {
-    // The table of values lies after the hash index.
-    return reinterpret_cast_ptr<ValueType*>(m_index + m_indexSize);
+    // Total number of  used entries in the values array - by either valid entries, or deleted ones.
+    return m_keyCount + m_deletedCount;
 }
 
-inline const PropertyTable::ValueType* PropertyTable::table() const
+inline size_t PropertyTable::dataSize(bool isCompact, unsigned indexSize)
 {
-    // The table of values lies after the hash index.
-    return reinterpret_cast_ptr<const ValueType*>(m_index + m_indexSize);
+    if (isCompact)
+        return indexSize * sizeof(uint8_t) + ((indexSize >> 1) + 1) * sizeof(CompactPropertyTableEntry);
+    return indexSize * sizeof(uint32_t) + ((indexSize >> 1) + 1) * sizeof(PropertyTableEntry);
 }
 
-inline unsigned PropertyTable::usedCount() const
+inline size_t PropertyTable::dataSize(bool isCompact)
 {
-    // Total number of  used entries in the values array - by either valid entries, or deleted ones.
-    return m_keyCount + m_deletedCount;
+    // The size in bytes of data needed for by the table.
+    // Ensure that this function can be called concurrently.
+    return dataSize(isCompact, m_indexSize);
 }
 
-inline size_t PropertyTable::dataSize()
+ALWAYS_INLINE uintptr_t PropertyTable::allocateIndexVector(bool isCompact, unsigned indexSize)
 {
-    // The size in bytes of data needed for by the table.
-    // Ensure that this function can be called concurrently.
-    unsigned indexSize = m_indexSize;
-    return indexSize * sizeof(unsigned) + ((indexSize >> 1) + 1) * sizeof(ValueType);
+    return bitwise_cast<uintptr_t>(PropertyTableMalloc::malloc(PropertyTable::dataSize(isCompact, indexSize))) | (isCompact ? isCompactFlag : 0);
 }
 
+ALWAYS_INLINE uintptr_t PropertyTable::allocateZeroedIndexVector(bool isCompact, unsigned indexSize)
+{
+    return bitwise_cast<uintptr_t>(PropertyTableMalloc::zeroedMalloc(PropertyTable::dataSize(isCompact, indexSize))) | (isCompact ? isCompactFlag : 0);
+}
+
+ALWAYS_INLINE void PropertyTable::destroyIndexVector(uintptr_t indexVector)
+{
+    PropertyTableMalloc::free(bitwise_cast<void*>(indexVector & indexVectorMask));
+}
+
 inline unsigned PropertyTable::sizeForCapacity(unsigned capacity)
 {
     if (capacity < MinimumTableSize / 2)
@@ -579,9 +639,28 @@
     return nextPowerOf2(capacity + 1) * 2;
 }
 
-inline bool PropertyTable::canInsert()
+inline bool PropertyTable::canInsert(const ValueType& entry)
 {
-    return usedCount() < tableCapacity();
+    if (usedCount() >= tableCapacity())
+        return false;
+    if (!isCompact())
+        return true;
+    return canFitInCompact(entry);
 }
 
+template<typename Functor>
+inline void PropertyTable::forEachProperty(const Functor& functor) const
+{
+    withIndexVector([&](const auto* vector) {
+        const auto* cursor = tableFromIndexVector(vector);
+        const auto* end = tableEndFromIndexVector(vector);
+        for (; cursor != end; ++cursor) {
+            if (cursor->key() == PROPERTY_MAP_DELETED_ENTRY_KEY)
+                continue;
+            if (functor(*cursor) == IterationStatus::Done)
+                return;
+        }
+    });
+}
+
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/runtime/RegExpMatchesArray.h (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/RegExpMatchesArray.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/RegExpMatchesArray.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -30,11 +30,11 @@
 
 namespace JSC {
 
-static const PropertyOffset RegExpMatchesArrayIndexPropertyOffset = 100;
-static const PropertyOffset RegExpMatchesArrayInputPropertyOffset = 101;
-static const PropertyOffset RegExpMatchesArrayGroupsPropertyOffset = 102;
-static const PropertyOffset RegExpMatchesArrayIndicesPropertyOffset = 103;
-static const PropertyOffset RegExpMatchesIndicesGroupsPropertyOffset = 100;
+static constexpr PropertyOffset RegExpMatchesArrayIndexPropertyOffset = firstOutOfLineOffset;
+static constexpr PropertyOffset RegExpMatchesArrayInputPropertyOffset = firstOutOfLineOffset + 1;
+static constexpr PropertyOffset RegExpMatchesArrayGroupsPropertyOffset = firstOutOfLineOffset + 2;
+static constexpr PropertyOffset RegExpMatchesArrayIndicesPropertyOffset = firstOutOfLineOffset + 3;
+static constexpr PropertyOffset RegExpMatchesIndicesGroupsPropertyOffset = firstOutOfLineOffset;
 
 ALWAYS_INLINE JSArray* tryCreateUninitializedRegExpMatchesArray(ObjectInitializationScope& scope, GCDeferralContext* deferralContext, Structure* structure, unsigned initialLength)
 {

Modified: trunk/Source/_javascript_Core/runtime/Structure.cpp (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/Structure.cpp	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/Structure.cpp	2022-04-22 05:41:35 UTC (rev 293210)
@@ -106,8 +106,8 @@
     unsigned numberLeaf = 0;
     unsigned numberUsingSingleSlot = 0;
     unsigned numberSingletons = 0;
-    unsigned numberWithPropertyMaps = 0;
-    unsigned totalPropertyMapsSize = 0;
+    unsigned numberWithPropertyTables = 0;
+    unsigned totalPropertyTablesSize = 0;
 
     HashSet<Structure*>::const_iterator end = liveStructureSet.end();
     for (HashSet<Structure*>::const_iterator it = liveStructureSet.begin(); it != end; ++it) {
@@ -126,8 +126,8 @@
         }
 
         if (PropertyTable* table = structure->propertyTableOrNull()) {
-            ++numberWithPropertyMaps;
-            totalPropertyMapsSize += table->sizeInMemory();
+            ++numberWithPropertyTables;
+            totalPropertyTablesSize += table->sizeInMemory();
         }
     }
 
@@ -135,11 +135,11 @@
     dataLogF("Number of Structures using the single item optimization for transition map: %d\n", numberUsingSingleSlot);
     dataLogF("Number of Structures that are leaf nodes: %d\n", numberLeaf);
     dataLogF("Number of Structures that singletons: %d\n", numberSingletons);
-    dataLogF("Number of Structures with PropertyMaps: %d\n", numberWithPropertyMaps);
+    dataLogF("Number of Structures with PropertyTables: %d\n", numberWithPropertyTables);
 
     dataLogF("Size of a single Structures: %d\n", static_cast<unsigned>(sizeof(Structure)));
-    dataLogF("Size of sum of all property maps: %d\n", totalPropertyMapsSize);
-    dataLogF("Size of average of all property maps: %f\n", static_cast<double>(totalPropertyMapsSize) / static_cast<double>(liveStructureSet.size()));
+    dataLogF("Size of sum of all property maps: %d\n", totalPropertyTablesSize);
+    dataLogF("Size of average of all property maps: %f\n", static_cast<double>(totalPropertyTablesSize) / static_cast<double>(liveStructureSet.size()));
 #else
     dataLogF("Dumping Structure statistics is not enabled.\n");
 #endif
@@ -225,7 +225,7 @@
     setTransitionOffset(vm, invalidOffset);
     setMaxOffset(vm, invalidOffset);
  
-    ASSERT(inlineCapacity <= JSFinalObject::maxInlineCapacity());
+    ASSERT(inlineCapacity <= JSFinalObject::maxInlineCapacity);
     ASSERT(static_cast<PropertyOffset>(inlineCapacity) < firstOutOfLineOffset);
     ASSERT(!hasRareData());
     ASSERT(hasReadOnlyOrGetterSetterPropertiesExcludingProto() == m_classInfo->hasStaticSetterOrReadonlyProperties());
@@ -416,25 +416,25 @@
             continue;
         switch (structure->transitionKind()) {
         case TransitionKind::PropertyAddition: {
-            PropertyMapEntry entry(structure->m_transitionPropertyName.get(), structure->transitionOffset(), structure->transitionPropertyAttributes());
+            PropertyTableEntry entry(structure->m_transitionPropertyName.get(), structure->transitionOffset(), structure->transitionPropertyAttributes());
             auto nextOffset = table->nextOffset(structure->inlineCapacity());
             ASSERT_UNUSED(nextOffset, nextOffset == structure->transitionOffset());
-            auto result = table->add(vm, entry);
-            ASSERT_UNUSED(result, result.second);
-            ASSERT_UNUSED(result, result.first.first->offset == nextOffset);
+            auto [offset, attribute, result] = table->add(vm, entry);
+            ASSERT_UNUSED(result, result);
+            ASSERT_UNUSED(offset, offset == nextOffset);
+            UNUSED_VARIABLE(attribute);
             break;
         }
         case TransitionKind::PropertyDeletion: {
-            auto item = table->find(structure->m_transitionPropertyName.get());
-            ASSERT(item.first);
-            table->remove(vm, item);
+            auto [offset, attributes] = table->take(vm, structure->m_transitionPropertyName.get());
+            ASSERT_UNUSED(offset, offset != invalidOffset);
+            UNUSED_VARIABLE(attributes);
             table->addDeletedOffset(structure->transitionOffset());
             break;
         }
         case TransitionKind::PropertyAttributeChange: {
-            PropertyMapEntry* entry = table->get(structure->m_transitionPropertyName.get());
-            entry->attributes = structure->transitionPropertyAttributes();
-            ASSERT(entry->offset == structure->transitionOffset());
+            PropertyOffset offset = table->updateAttributeIfExists(structure->m_transitionPropertyName.get(), structure->transitionPropertyAttributes());
+            ASSERT_UNUSED(offset, offset == structure->transitionOffset());
             break;
         }
         default:
@@ -831,6 +831,8 @@
         // table, since our logic for walking the property transition chain to rematerialize the
         // table doesn't know how to take into account such wholesale edits.
 
+        ASSERT(transitionKind == TransitionKind::Seal || transitionKind == TransitionKind::Freeze);
+
         PropertyTable* table = structure->copyPropertyTableForPinning(vm);
         transition->pinForCaching(Locker { transition->m_lock }, vm, table);
         transition->setMaxOffset(vm, structure->maxOffset());
@@ -837,12 +839,10 @@
         
         table = transition->propertyTableOrNull();
         RELEASE_ASSERT(table);
-        for (auto& entry : *table) {
-            if (setsDontDeleteOnAllProperties(transitionKind))
-                entry.attributes |= static_cast<unsigned>(PropertyAttribute::DontDelete);
-            if (setsReadOnlyOnNonAccessorProperties(transitionKind) && !(entry.attributes & PropertyAttribute::Accessor))
-                entry.attributes |= static_cast<unsigned>(PropertyAttribute::ReadOnly);
-        }
+        if (transitionKind == TransitionKind::Seal)
+            table->seal();
+        else
+            table->freeze();
     } else {
         transition->setPropertyTable(vm, structure->takePropertyTableOrCloneIfPinned(vm));
         transition->setMaxOffset(vm, structure->maxOffset());
@@ -874,13 +874,7 @@
     PropertyTable* table = ensurePropertyTableIfNotEmpty(vm);
     if (!table)
         return true;
-    
-    PropertyTable::iterator end = table->end();
-    for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) {
-        if ((iter->attributes & PropertyAttribute::DontDelete) != static_cast<unsigned>(PropertyAttribute::DontDelete))
-            return false;
-    }
-    return true;
+    return table->isSealed();
 }
 
 // In future we may want to cache this property.
@@ -892,15 +886,7 @@
     PropertyTable* table = ensurePropertyTableIfNotEmpty(vm);
     if (!table)
         return true;
-    
-    PropertyTable::iterator end = table->end();
-    for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) {
-        if (!(iter->attributes & PropertyAttribute::DontDelete))
-            return false;
-        if (!(iter->attributes & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor)))
-            return false;
-    }
-    return true;
+    return table->isFrozen();
 }
 
 Structure* Structure::flattenDictionaryStructure(VM& vm, JSObject* object)
@@ -921,17 +907,11 @@
 
         size_t propertyCount = table->size();
 
-        // Holds our values compacted by insertion order.
+        // Holds our values compacted by insertion order. This is OK since GC is deferred.
         Vector<JSValue> values(propertyCount);
 
         // Copies out our values from their hashed locations, compacting property table offsets as we go.
-        unsigned i = 0;
-        PropertyTable::iterator end = table->end();
-        auto offset = invalidOffset;
-        for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter, ++i) {
-            values[i] = object->getDirect(iter->offset);
-            offset = iter->offset = offsetForPropertyNumber(i, m_inlineCapacity);
-        }
+        PropertyOffset offset = table->renumberPropertyOffsets(object, m_inlineCapacity, values);
         setMaxOffset(vm, offset);
         ASSERT(transitionOffset() == invalidOffset);
         
@@ -939,8 +919,6 @@
         for (unsigned i = 0; i < propertyCount; i++)
             object->putDirect(vm, offsetForPropertyNumber(i, m_inlineCapacity), values[i]);
 
-        table->clearDeletedOffsets();
-
         // We need to zero our unused property space; otherwise the GC might see a
         // stale pointer when we add properties in the future.
         gcSafeZeroMemory(
@@ -1067,7 +1045,7 @@
 {
     unsigned finds = propertyTableStats->numFinds;
     unsigned collisions = propertyTableStats->numCollisions;
-    dataLogF("\nJSC::PropertyMap statistics for process %d\n\n", getCurrentProcessID());
+    dataLogF("\nJSC::PropertyTable statistics for process %d\n\n", getCurrentProcessID());
     dataLogF("%d finds\n", finds);
     dataLogF("%d collisions (%.1f%%)\n", collisions, 100.0 * collisions / finds);
     dataLogF("%d lookups\n", propertyTableStats->numLookups.load());
@@ -1137,9 +1115,10 @@
         assertIsHeld(tableStructure->m_lock); // Sadly Clang needs some help here.
         // Because uid is UniquedStringImpl, it is guaranteed that the hash is already computed.
         // So we can use PropertyTable::get even from the concurrent compilers.
-        if (auto* entry = table->get(uid)) {
-            result = entry->offset;
-            attributes = entry->attributes;
+        auto [offset, entryAttributes] = table->get(uid);
+        if (offset != invalidOffset) {
+            result = offset;
+            attributes = entryAttributes;
         }
         tableStructure->m_lock.unlock();
     }
@@ -1147,12 +1126,12 @@
     return result;
 }
 
-Vector<PropertyMapEntry> Structure::getPropertiesConcurrently()
+Vector<PropertyTableEntry> Structure::getPropertiesConcurrently()
 {
-    Vector<PropertyMapEntry> result;
+    Vector<PropertyTableEntry> result;
 
     forEachPropertyConcurrently(
-        [&] (const PropertyMapEntry& entry) -> bool {
+        [&] (const PropertyTableEntry& entry) -> bool {
             result.append(entry);
             return true;
         });
@@ -1194,34 +1173,35 @@
     bool knownUnique = propertyNames.canAddKnownUniqueForStructure();
     bool foundSymbol = false;
 
-    auto checkDontEnumAndAdd = [&](PropertyTable::iterator iter) {
-        if (mode == DontEnumPropertiesMode::Include || !(iter->attributes & PropertyAttribute::DontEnum)) {
+    auto checkDontEnumAndAdd = [&](const auto& entry) {
+        if (mode == DontEnumPropertiesMode::Include || !(entry.attributes() & PropertyAttribute::DontEnum)) {
             if (knownUnique)
-                propertyNames.addUnchecked(iter->key);
+                propertyNames.addUnchecked(entry.key());
             else
-                propertyNames.add(iter->key);
+                propertyNames.add(entry.key());
         }
     };
     
-    PropertyTable::iterator end = table->end();
-    for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) {
-        ASSERT(!isQuickPropertyAccessAllowedForEnumeration() || !(iter->attributes & PropertyAttribute::DontEnum));
-        ASSERT(!isQuickPropertyAccessAllowedForEnumeration() || !iter->key->isSymbol());
-        if (iter->key->isSymbol()) {
+    table->forEachProperty([&](const auto& entry) {
+        ASSERT(!isQuickPropertyAccessAllowedForEnumeration() || !(entry.attributes() & PropertyAttribute::DontEnum));
+        ASSERT(!isQuickPropertyAccessAllowedForEnumeration() || !entry.key()->isSymbol());
+        if (entry.key()->isSymbol()) {
             foundSymbol = true;
             if (propertyNames.propertyNameMode() != PropertyNameMode::Symbols)
-                continue;
+                return IterationStatus::Continue;
         }
-        checkDontEnumAndAdd(iter);
-    }
+        checkDontEnumAndAdd(entry);
+        return IterationStatus::Continue;
+    });
 
     if (foundSymbol && propertyNames.propertyNameMode() == PropertyNameMode::StringsAndSymbols) {
         // To ensure the order defined in the spec, we append symbols at the last elements of keys.
         // https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
-        for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) {
-            if (iter->key->isSymbol())
-                checkDontEnumAndAdd(iter);
-        }
+        table->forEachProperty([&](const auto& entry) {
+            if (entry.key()->isSymbol())
+                checkDontEnumAndAdd(entry);
+            return IterationStatus::Continue;
+        });
     }
 }
 
@@ -1344,9 +1324,9 @@
     while (curStructure) {
         sawPolyProtoStructure |= curStructure->hasPolyProto();
         curStructure->forEachPropertyConcurrently(
-            [&] (const PropertyMapEntry& entry) -> bool {
-                if (!PropertyName(entry.key).isPrivateName())
-                    curShape->addProperty(*entry.key);
+            [&] (const PropertyTableEntry& entry) -> bool {
+                if (!PropertyName(entry.key()).isPrivateName())
+                    curShape->addProperty(*entry.key());
                 return true;
             });
 
@@ -1389,8 +1369,8 @@
     CommaPrinter comma;
     
     const_cast<Structure*>(this)->forEachPropertyConcurrently(
-        [&] (const PropertyMapEntry& entry) -> bool {
-            out.print(comma, entry.key, ":", static_cast<int>(entry.offset));
+        [&] (const PropertyTableEntry& entry) -> bool {
+            out.print(comma, entry.key(), ":", static_cast<int>(entry.offset()));
             return true;
         });
     

Modified: trunk/Source/_javascript_Core/runtime/Structure.h (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/Structure.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/Structure.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -45,6 +45,7 @@
 #include "Watchpoint.h"
 #include "WriteBarrierInlines.h"
 #include <wtf/Atomics.h>
+#include <wtf/CompactPointerTuple.h>
 #include <wtf/PrintStream.h>
 
 namespace WTF {
@@ -77,27 +78,80 @@
 // initial allocation.
 static constexpr unsigned outOfLineGrowthFactor = 2;
 
-struct PropertyMapEntry {
-    UniquedStringImpl* key;
-    PropertyOffset offset;
-    uint8_t attributes;
+class PropertyTableEntry;
+class CompactPropertyTableEntry {
+public:
+    CompactPropertyTableEntry()
+        : m_data(nullptr, 0)
+    {
+    }
 
-    PropertyMapEntry()
-        : key(nullptr)
-        , offset(invalidOffset)
-        , attributes(0)
+    CompactPropertyTableEntry(UniquedStringImpl* key, PropertyOffset offset, unsigned attributes)
+        : m_data(key, ((offset << 8) | attributes))
     {
+        ASSERT(this->attributes() == attributes);
+        ASSERT(this->offset() == offset);
     }
-    
-    PropertyMapEntry(UniquedStringImpl* key, PropertyOffset offset, unsigned attributes)
-        : key(key)
-        , offset(offset)
-        , attributes(attributes)
+
+    CompactPropertyTableEntry(const PropertyTableEntry&);
+
+    UniquedStringImpl* key() const { return m_data.pointer(); }
+    void setKey(UniquedStringImpl* key) { m_data.setPointer(key); }
+    PropertyOffset offset() const { return m_data.type() >> 8; }
+    void setOffset(PropertyOffset offset)
     {
-        ASSERT(this->attributes == attributes);
+        m_data.setType((m_data.type() & 0x00ffU) | (offset << 8));
+        ASSERT(this->offset() == offset);
     }
+    uint8_t attributes() const { return m_data.type(); }
+    void setAttributes(uint8_t attributes)
+    {
+        m_data.setType((m_data.type() & 0xff00U) | attributes);
+        ASSERT(this->attributes() == attributes);
+    }
+
+private:
+    CompactPointerTuple<UniquedStringImpl*, uint16_t> m_data;
 };
 
+class PropertyTableEntry {
+public:
+    PropertyTableEntry() = default;
+
+    PropertyTableEntry(UniquedStringImpl* key, PropertyOffset offset, unsigned attributes)
+        : m_key(key)
+        , m_offset(offset)
+        , m_attributes(attributes)
+    {
+        ASSERT(this->attributes() == attributes);
+    }
+
+    PropertyTableEntry(const CompactPropertyTableEntry& entry)
+        : m_key(entry.key())
+        , m_offset(entry.offset())
+        , m_attributes(entry.attributes())
+    {
+    }
+
+    UniquedStringImpl* key() const { return m_key; }
+    void setKey(UniquedStringImpl* key) { m_key = key; }
+    PropertyOffset offset() const { return m_offset; }
+    void setOffset(PropertyOffset offset) { m_offset = offset; }
+    uint8_t attributes() const { return m_attributes; }
+    void setAttributes(uint8_t attributes) { m_attributes = attributes; }
+
+private:
+    UniquedStringImpl* m_key { nullptr };
+    PropertyOffset m_offset { 0 };
+    uint8_t m_attributes { 0 };
+};
+
+
+inline CompactPropertyTableEntry::CompactPropertyTableEntry(const PropertyTableEntry& entry)
+    : m_data(entry.key(), ((entry.offset() << 8) | entry.attributes()))
+{
+}
+
 class StructureFireDetail final : public FireDetail {
 public:
     StructureFireDetail(const Structure* structure)
@@ -547,7 +601,7 @@
     PropertyOffset getConcurrently(UniquedStringImpl* uid);
     PropertyOffset getConcurrently(UniquedStringImpl* uid, unsigned& attributes);
     
-    Vector<PropertyMapEntry> getPropertiesConcurrently();
+    Vector<PropertyTableEntry> getPropertiesConcurrently();
     
     void setHasGetterSetterPropertiesWithProtoCheck(bool is__proto__)
     {

Modified: trunk/Source/_javascript_Core/runtime/StructureInlines.h (293209 => 293210)


--- trunk/Source/_javascript_Core/runtime/StructureInlines.h	2022-04-22 05:35:33 UTC (rev 293209)
+++ trunk/Source/_javascript_Core/runtime/StructureInlines.h	2022-04-22 05:41:35 UTC (rev 293210)
@@ -171,12 +171,10 @@
     if (!propertyTable)
         return invalidOffset;
 
-    PropertyMapEntry* entry = propertyTable->get(propertyName.uid());
-    if (!entry)
-        return invalidOffset;
-
-    attributes = entry->attributes;
-    return entry->offset;
+    auto [offset, entryAttributes] = propertyTable->get(propertyName.uid());
+    if (offset != invalidOffset)
+        attributes = entryAttributes;
+    return offset;
 }
 
 template<typename Functor>
@@ -208,7 +206,7 @@
             break;
         }
 
-        if (!functor(PropertyMapEntry(structure->m_transitionPropertyName.get(), structure->transitionOffset(), structure->transitionPropertyAttributes()))) {
+        if (!functor(PropertyTableEntry(structure->m_transitionPropertyName.get(), structure->transitionOffset(), structure->transitionPropertyAttributes()))) {
             if (didFindStructure) {
                 assertIsHeld(tableStructure->m_lock); // Sadly Clang needs some help here.
                 tableStructure->m_lock.unlock();
@@ -219,15 +217,15 @@
     
     if (didFindStructure) {
         assertIsHeld(tableStructure->m_lock); // Sadly Clang needs some help here.
-        for (auto& entry : *table) {
-            if (seenProperties.contains(entry.key))
-                continue;
+        table->forEachProperty([&](const auto& entry) {
+            if (seenProperties.contains(entry.key()))
+                return IterationStatus::Continue;
 
-            if (!functor(entry)) {
-                tableStructure->m_lock.unlock();
-                return;
-            }
-        }
+            if (!functor(entry))
+                return IterationStatus::Done;
+
+            return IterationStatus::Continue;
+        });
         tableStructure->m_lock.unlock();
     }
 }
@@ -236,10 +234,11 @@
 void Structure::forEachProperty(VM& vm, const Functor& functor)
 {
     if (PropertyTable* table = ensurePropertyTableIfNotEmpty(vm)) {
-        for (auto& entry : *table) {
+        table->forEachProperty([&](const auto& entry) {
             if (!functor(entry))
-                return;
-        }
+                return IterationStatus::Done;
+            return IterationStatus::Continue;
+        });
         ensureStillAliveHere(table);
     }
 }
@@ -492,9 +491,10 @@
     m_propertyHash = m_propertyHash ^ rep->existingSymbolAwareHash();
     m_seenProperties.add(bitwise_cast<uintptr_t>(rep));
 
-    auto result = table->add(vm, PropertyMapEntry(rep, newOffset, attributes));
-    ASSERT_UNUSED(result, result.second);
-    ASSERT_UNUSED(result, result.first.first->offset == newOffset);
+    auto [offset, attribute, result] = table->add(vm, PropertyTableEntry(rep, newOffset, attributes));
+    ASSERT_UNUSED(result, result);
+    ASSERT_UNUSED(offset, offset == newOffset);
+    UNUSED_VARIABLE(attribute);
     auto newMaxOffset = std::max(newOffset, maxOffset());
     
     func(locker, newOffset, newMaxOffset);
@@ -526,15 +526,13 @@
 
     auto rep = propertyName.uid();
 
-    PropertyTable::find_iterator position = table->find(rep);
-    if (!position.first)
+    auto [offset, attributes] = table->take(vm, rep);
+    UNUSED_VARIABLE(attributes);
+    if (offset == invalidOffset)
         return invalidOffset;
 
     setIsQuickPropertyAccessAllowedForEnumeration(false);
-    
-    PropertyOffset offset = position.first->offset;
 
-    table->remove(vm, position);
     table->addDeletedOffset(offset);
 
     PropertyOffset newMaxOffset = maxOffset();
@@ -567,19 +565,15 @@
     ASSERT(JSC::isValidOffset(get(vm, propertyName)));
 
     checkConsistency();
-    PropertyMapEntry* entry = table->get(propertyName.uid());
-    if (!entry)
-        return invalidOffset;
+    PropertyOffset offset = table->updateAttributeIfExists(propertyName.uid(), attributes);
+    if (offset == invalidOffset)
+        return offset;
 
-    PropertyOffset offset = entry->offset;
-
     if (attributes & PropertyAttribute::DontEnum)
         setIsQuickPropertyAccessAllowedForEnumeration(false);
     if (attributes & PropertyAttribute::ReadOnly)
         setContainsReadOnlyProperties();
 
-    entry->attributes = attributes;
-
     PropertyOffset newMaxOffset = maxOffset();
 
     func(locker, offset, newMaxOffset);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to