Title: [265642] trunk
Revision
265642
Author
[email protected]
Date
2020-08-13 21:11:09 -0700 (Thu, 13 Aug 2020)

Log Message

Cache Structure::attributeChangeTransition()
https://bugs.webkit.org/show_bug.cgi?id=214890

Reviewed by Yusuke Suzuki.

JSTests:

* microbenchmarks/redefine-property-previous-attributes.js: Added.

Source/_javascript_Core:

With this change, a non-dictionary structure adds attribute-change transitions
to transition table, making redefinition to previous atttributes a fast path.

After too many transitions, the structure becomes a dictionary, firing the
transition watchpoint. Attribute-change transitions pin their property tables,
preventing forEachPropertyConcurrently() traversal.

This patch advances provided microbenchmark by ~90% and progresses
Speedometer2/EmberJS-Debug-TodoMVC by ~12% (~5% over r264573).

No behavior change.

* dfg/DFGGraph.cpp:
(JSC::DFG::Graph::getRegExpPrototypeProperty):
* runtime/JSObjectInlines.h:
(JSC::JSObject::putDirectInternal):
* runtime/Structure.cpp:
(JSC::Structure::materializePropertyTable):
(JSC::Structure::removeNewPropertyTransition):
(JSC::Structure::attributeChangeTransition):
* runtime/Structure.h:

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (265641 => 265642)


--- trunk/JSTests/ChangeLog	2020-08-14 02:01:56 UTC (rev 265641)
+++ trunk/JSTests/ChangeLog	2020-08-14 04:11:09 UTC (rev 265642)
@@ -1,3 +1,12 @@
+2020-08-13  Alexey Shvayka  <[email protected]>
+
+        Cache Structure::attributeChangeTransition()
+        https://bugs.webkit.org/show_bug.cgi?id=214890
+
+        Reviewed by Yusuke Suzuki.
+
+        * microbenchmarks/redefine-property-previous-attributes.js: Added.
+
 2020-08-12  Saam Barati  <[email protected]>
 
         Inline cache Replace and Setters on PureForwardingProxy

Added: trunk/JSTests/microbenchmarks/redefine-property-previous-attributes.js (0 => 265642)


--- trunk/JSTests/microbenchmarks/redefine-property-previous-attributes.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/redefine-property-previous-attributes.js	2020-08-14 04:11:09 UTC (rev 265642)
@@ -0,0 +1,13 @@
+var desc1 = {value: 1, writable: true, enumerable: false, configurable: true};
+var desc2 = {value: 1, writable: false, enumerable: true, configurable: true};
+
+for (var i = 0; i < 1e5; ++i) {
+    var obj = {foo: 1};
+
+    Object.defineProperty(obj, "foo", desc1);
+    Object.defineProperty(obj, "foo", desc2);
+    Object.defineProperty(obj, "foo", desc1);
+    Object.defineProperty(obj, "foo", desc2);
+    Object.defineProperty(obj, "foo", desc1);
+    Object.defineProperty(obj, "foo", desc2);
+}

Modified: trunk/Source/_javascript_Core/ChangeLog (265641 => 265642)


--- trunk/Source/_javascript_Core/ChangeLog	2020-08-14 02:01:56 UTC (rev 265641)
+++ trunk/Source/_javascript_Core/ChangeLog	2020-08-14 04:11:09 UTC (rev 265642)
@@ -1,5 +1,34 @@
 2020-08-13  Alexey Shvayka  <[email protected]>
 
+        Cache Structure::attributeChangeTransition()
+        https://bugs.webkit.org/show_bug.cgi?id=214890
+
+        Reviewed by Yusuke Suzuki.
+
+        With this change, a non-dictionary structure adds attribute-change transitions
+        to transition table, making redefinition to previous atttributes a fast path.
+
+        After too many transitions, the structure becomes a dictionary, firing the
+        transition watchpoint. Attribute-change transitions pin their property tables,
+        preventing forEachPropertyConcurrently() traversal.
+
+        This patch advances provided microbenchmark by ~90% and progresses
+        Speedometer2/EmberJS-Debug-TodoMVC by ~12% (~5% over r264573).
+
+        No behavior change.
+
+        * dfg/DFGGraph.cpp:
+        (JSC::DFG::Graph::getRegExpPrototypeProperty):
+        * runtime/JSObjectInlines.h:
+        (JSC::JSObject::putDirectInternal):
+        * runtime/Structure.cpp:
+        (JSC::Structure::materializePropertyTable):
+        (JSC::Structure::removeNewPropertyTransition):
+        (JSC::Structure::attributeChangeTransition):
+        * runtime/Structure.h:
+
+2020-08-13  Alexey Shvayka  <[email protected]>
+
         Rework StructureTransitionTable::Hash::Key encoding
         https://bugs.webkit.org/show_bug.cgi?id=215483
 

Modified: trunk/Source/_javascript_Core/dfg/DFGGraph.cpp (265641 => 265642)


--- trunk/Source/_javascript_Core/dfg/DFGGraph.cpp	2020-08-14 02:01:56 UTC (rev 265641)
+++ trunk/Source/_javascript_Core/dfg/DFGGraph.cpp	2020-08-14 04:11:09 UTC (rev 265642)
@@ -1726,8 +1726,7 @@
 
 bool Graph::getRegExpPrototypeProperty(JSObject* regExpPrototype, Structure* regExpPrototypeStructure, UniquedStringImpl* uid, JSValue& returnJSValue)
 {
-    unsigned attributesUnused;
-    PropertyOffset offset = regExpPrototypeStructure->getConcurrently(uid, attributesUnused);
+    PropertyOffset offset = regExpPrototypeStructure->getConcurrently(uid);
     if (!isValidOffset(offset))
         return false;
 

Modified: trunk/Source/_javascript_Core/runtime/JSObjectInlines.h (265641 => 265642)


--- trunk/Source/_javascript_Core/runtime/JSObjectInlines.h	2020-08-14 02:01:56 UTC (rev 265641)
+++ trunk/Source/_javascript_Core/runtime/JSObjectInlines.h	2020-08-14 04:11:09 UTC (rev 265642)
@@ -364,6 +364,10 @@
         return true;
     }
 
+    // We want the structure transition watchpoint to fire after this object has switched structure.
+    // This allows adaptive watchpoints to observe if the new structure is the one we want.
+    DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure);
+
     unsigned currentAttributes;
     offset = structure->get(vm, propertyName, currentAttributes);
     if (offset != invalidOffset) {
@@ -376,7 +380,7 @@
         // FIXME: Check attributes against PropertyAttribute::CustomAccessorOrValue. Changing GetterSetter should work w/o transition.
         // https://bugs.webkit.org/show_bug.cgi?id=214342
         if (mode == PutModeDefineOwnProperty && (attributes != currentAttributes || (attributes & PropertyAttribute::AccessorOrCustomAccessorOrValue)))
-            setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
+            setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes, &deferredWatchpointFire));
         else
             slot.setExistingProperty(this, offset);
 
@@ -385,11 +389,6 @@
 
     if ((mode == PutModePut) && !isStructureExtensible(vm))
         return false;
-
-    // We want the structure transition watchpoint to fire after this object has switched
-    // structure. This allows adaptive watchpoints to observe if the new structure is the one
-    // we want.
-    DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure);
     
     newStructure = Structure::addNewPropertyTransition(
         vm, structure, propertyName, attributes, offset, slot.context(), &deferredWatchpointFire);

Modified: trunk/Source/_javascript_Core/runtime/Structure.cpp (265641 => 265642)


--- trunk/Source/_javascript_Core/runtime/Structure.cpp	2020-08-14 02:01:56 UTC (rev 265641)
+++ trunk/Source/_javascript_Core/runtime/Structure.cpp	2020-08-14 04:11:09 UTC (rev 265642)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2008-2019 Apple Inc. All rights reserved.
+ * Copyright (C) 2020 Alexey Shvayka <[email protected]>.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -444,6 +445,7 @@
             table->addDeletedOffset(structure->transitionOffset());
             continue;
         }
+        ASSERT(structure->isPropertyAdditionTransition());
         PropertyMapEntry entry(structure->m_transitionPropertyName.get(), structure->transitionOffset(), structure->transitionPropertyAttributes());
         auto nextOffset = table->nextOffset(structure->inlineCapacity());
         ASSERT_UNUSED(nextOffset, nextOffset == structure->transitionOffset());
@@ -643,11 +645,7 @@
     ASSERT(!Structure::removePropertyTransitionFromExistingStructure(structure, propertyName, offset));
     ASSERT(structure->getConcurrently(propertyName.uid()) != invalidOffset);
 
-    int transitionCount = 0;
-    for (auto* s = structure; s && transitionCount <= s_maxTransitionLength; s = s->previousID())
-        ++transitionCount;
-
-    if (transitionCount > s_maxTransitionLength) {
+    if (structure->transitionCountHasOverflowed()) {
         ASSERT(!isCopyOnWrite(structure->indexingMode()));
         Structure* transition = toUncacheableDictionaryTransition(vm, structure, deferred);
         ASSERT(structure != transition);
@@ -706,30 +704,79 @@
     return transition;
 }
 
-Structure* Structure::attributeChangeTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes)
+Structure* Structure::attributeChangeTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes, DeferredStructureTransitionWatchpointFire* deferred)
 {
-    if (!structure->isUncacheableDictionary()) {
-        Structure* transition = create(vm, structure);
+    auto setEntryAttributes = [&](Structure* structure) {
+        PropertyMapEntry* entry = structure->ensurePropertyTable(vm)->get(propertyName.uid());
+        ASSERT(entry);
+        entry->attributes = attributes;
+    };
 
-        PropertyTable* table = structure->copyPropertyTableForPinning(vm);
-        transition->pin(holdLock(transition->m_lock), vm, table);
-        transition->setMaxOffset(vm, structure->maxOffset());
-        
-        structure = transition;
+    auto setStructureFlags = [&](Structure* structure) {
+        if (attributes & PropertyAttribute::ReadOnly)
+            structure->setContainsReadOnlyProperties();
+        if (attributes & PropertyAttribute::DontEnum)
+            structure->setIsQuickPropertyAccessAllowedForEnumeration(false);
+    };
+
+    if (structure->isUncacheableDictionary()) {
+        setEntryAttributes(structure);
+        setStructureFlags(structure);
+        structure->checkOffsetConsistency();
+        return structure;
     }
 
-    PropertyMapEntry* entry = structure->ensurePropertyTable(vm)->get(propertyName.uid());
+    if (!structure->isDictionary()) {
+        if (Structure* existingTransition = structure->m_transitionTable.get(propertyName.uid(), attributes, TransitionKind::PropertyAttributeChange)) {
+            validateOffset(existingTransition->transitionOffset(), existingTransition->inlineCapacity());
+            existingTransition->checkOffsetConsistency();
+            return existingTransition;
+        }
+
+        if (structure->transitionCountHasOverflowed()) {
+            ASSERT(!isCopyOnWrite(structure->indexingMode()));
+            Structure* transition = toUncacheableDictionaryTransition(vm, structure, deferred);
+            ASSERT(structure != transition);
+            setEntryAttributes(transition);
+            setStructureFlags(transition);
+            return transition;
+        }
+    }
+
+    Structure* transition = create(vm, structure);
+
+    {
+        ConcurrentJSLocker locker(transition->m_lock);
+        transition->setProtectPropertyTableWhileTransitioning(true);
+    }
+
+    PropertyTable* table = structure->copyPropertyTableForPinning(vm);
+    if (structure->isDictionary())
+        transition->pin(holdLock(transition->m_lock), vm, table);
+    else
+        transition->pinForCaching(holdLock(transition->m_lock), vm, table);
+    transition->m_transitionPropertyName = propertyName.uid();
+    transition->setTransitionPropertyAttributes(attributes);
+    transition->setTransitionKind(TransitionKind::PropertyAttributeChange);
+    transition->setMaxOffset(vm, structure->maxOffset());
+
+    PropertyMapEntry* entry = table->get(propertyName.uid());
     ASSERT(entry);
     entry->attributes = attributes;
+    transition->setTransitionOffset(vm, entry->offset);
+    setStructureFlags(transition);
 
-    if (attributes & PropertyAttribute::ReadOnly)
-        structure->setContainsReadOnlyProperties();
+    WTF::storeStoreFence();
+    transition->setProtectPropertyTableWhileTransitioning(false);
 
-    if (attributes & PropertyAttribute::DontEnum)
-        structure->setIsQuickPropertyAccessAllowedForEnumeration(false);
-
+    checkOffset(transition->transitionOffset(), transition->inlineCapacity());
+    if (!structure->isDictionary()) {
+        GCSafeConcurrentJSLocker locker(structure->m_lock, vm.heap);
+        structure->m_transitionTable.add(vm, transition);
+    }
+    transition->checkOffsetConsistency();
     structure->checkOffsetConsistency();
-    return structure;
+    return transition;
 }
 
 Structure* Structure::toDictionaryTransition(VM& vm, Structure* structure, DictionaryKind kind, DeferredStructureTransitionWatchpointFire* deferred)

Modified: trunk/Source/_javascript_Core/runtime/Structure.h (265641 => 265642)


--- trunk/Source/_javascript_Core/runtime/Structure.h	2020-08-14 02:01:56 UTC (rev 265641)
+++ trunk/Source/_javascript_Core/runtime/Structure.h	2020-08-14 04:11:09 UTC (rev 265642)
@@ -197,7 +197,7 @@
     static Structure* removePropertyTransitionFromExistingStructure(Structure*, PropertyName, PropertyOffset&);
     static Structure* removePropertyTransitionFromExistingStructureConcurrently(Structure*, PropertyName, PropertyOffset&);
     static Structure* changePrototypeTransition(VM&, Structure*, JSValue prototype, DeferredStructureTransitionWatchpointFire&);
-    JS_EXPORT_PRIVATE static Structure* attributeChangeTransition(VM&, Structure*, PropertyName, unsigned attributes);
+    JS_EXPORT_PRIVATE static Structure* attributeChangeTransition(VM&, Structure*, PropertyName, unsigned attributes, DeferredStructureTransitionWatchpointFire* = nullptr);
     JS_EXPORT_PRIVATE static Structure* toCacheableDictionaryTransition(VM&, Structure*, DeferredStructureTransitionWatchpointFire* = nullptr);
     static Structure* toUncacheableDictionaryTransition(VM&, Structure*, DeferredStructureTransitionWatchpointFire* = nullptr);
     JS_EXPORT_PRIVATE static Structure* sealTransition(VM&, Structure*);
@@ -796,6 +796,17 @@
         return numberOfSlotsForMaxOffset(maxOffset(), m_inlineCapacity);
     }
 
+    ALWAYS_INLINE bool transitionCountHasOverflowed() const
+    {
+        int transitionCount = 0;
+        for (auto* structure = this; structure; structure = structure->previousID()) {
+            if (++transitionCount > s_maxTransitionLength)
+                return true;
+        }
+
+        return false;
+    }
+
     bool isValid(JSGlobalObject*, StructureChain* cachedPrototypeChain, JSObject* base) const;
 
     // You have to hold the structure lock to do these.
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to