Title: [203368] trunk
Revision
203368
Author
[email protected]
Date
2016-07-18 13:12:45 -0700 (Mon, 18 Jul 2016)

Log Message

Object.preventExtensions/seal/freeze makes code much slower
https://bugs.webkit.org/show_bug.cgi?id=143247

Reviewed by Michael Saboff.
        
Source/_javascript_Core:

This has been a huge pet peeve of mine for a long time, but I was always afraid of fixing
it because I thought that it would be hard. Well, it looks like it's not hard at all.
        
The problem is that you cannot mutate a structure that participates in transition caching.
You can only clone the structure and mutate that one. But if you do this, you have to make
a hard choice:
        
1) Clone the structure without caching the transition. This is what the code did before
   this change. It's the most obvious choice, but it introduces an uncacheable transition
   that leads to an explosion of structures, which then breaks all inline caches.
        
2) Perform one of the existing cacheable transitions. Cacheable transitions can either add
   properties or they can do one of the NonPropertyTransitions, which until now have been
   restricted to just IndexingType transitions. So, only adding transitions or making
   certain prescribed changes to the indexing type count as cacheable transitions.
        
This change decouples NonPropertyTransition from IndexingType and adds three new kinds of
transitions: PreventExtensions, Seal, and Freeze. We have to give any cacheable transition
a name that fully disambiguates this transition from any other, so that the transition can
be cached. Since we're already giving them names in an enum, I figured that the most
pragmatic way to implement them is to have Structure::nonPropertyTransition() case on the
NonPropertyTransition and implement all of the mutations associated with that transition.
The alternative would have been to allow callers of nonPropertyTransition() to supply
something like a lambda that describes the mutation, but this seemed awkward since each
set of mutations has to anyway be tied to one of the NonPropertyTransition members.
        
This is an enormous speed-up on microbenchmarks that use Object.preventExtensions(),
Object.seal(), or Object.freeze(). I don't know if "real" benchmarks use these features
and I don't really care. This should be fast.

* runtime/JSObject.cpp:
(JSC::JSObject::notifyPresenceOfIndexedAccessors):
(JSC::JSObject::createInitialUndecided):
(JSC::JSObject::createInitialInt32):
(JSC::JSObject::createInitialDouble):
(JSC::JSObject::createInitialContiguous):
(JSC::JSObject::convertUndecidedToInt32):
(JSC::JSObject::convertUndecidedToDouble):
(JSC::JSObject::convertUndecidedToContiguous):
(JSC::JSObject::convertInt32ToDouble):
(JSC::JSObject::convertInt32ToContiguous):
(JSC::JSObject::convertDoubleToContiguous):
(JSC::JSObject::switchToSlowPutArrayStorage):
* runtime/Structure.cpp:
(JSC::Structure::suggestedArrayStorageTransition):
(JSC::Structure::addPropertyTransition):
(JSC::Structure::toUncacheableDictionaryTransition):
(JSC::Structure::sealTransition):
(JSC::Structure::freezeTransition):
(JSC::Structure::preventExtensionsTransition):
(JSC::Structure::takePropertyTableOrCloneIfPinned):
(JSC::Structure::nonPropertyTransition):
(JSC::Structure::pin):
(JSC::Structure::pinForCaching):
(JSC::Structure::allocateRareData):
* runtime/Structure.h:
* runtime/StructureTransitionTable.h:
(JSC::toAttributes):
(JSC::changesIndexingType):
(JSC::newIndexingType):
(JSC::preventsExtensions):
(JSC::setsDontDeleteOnAllProperties):
(JSC::setsReadOnlyOnAllProperties):

LayoutTests:

These tests now run ~25x faster.

* js/regress/freeze-and-do-work-expected.txt: Added.
* js/regress/freeze-and-do-work.html: Added.
* js/regress/prevent-extensions-and-do-work-expected.txt: Added.
* js/regress/prevent-extensions-and-do-work.html: Added.
* js/regress/script-tests/freeze-and-do-work.js: Added.
(Foo):
* js/regress/script-tests/prevent-extensions-and-do-work.js: Added.
(Foo):
* js/regress/script-tests/seal-and-do-work.js: Added.
(Foo):
* js/regress/seal-and-do-work-expected.txt: Added.
* js/regress/seal-and-do-work.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (203367 => 203368)


--- trunk/LayoutTests/ChangeLog	2016-07-18 20:12:00 UTC (rev 203367)
+++ trunk/LayoutTests/ChangeLog	2016-07-18 20:12:45 UTC (rev 203368)
@@ -1,3 +1,25 @@
+2016-07-17  Filip Pizlo  <[email protected]>
+
+        Object.preventExtensions/seal/freeze makes code much slower
+        https://bugs.webkit.org/show_bug.cgi?id=143247
+
+        Reviewed by Michael Saboff.
+        
+        These tests now run ~25x faster.
+
+        * js/regress/freeze-and-do-work-expected.txt: Added.
+        * js/regress/freeze-and-do-work.html: Added.
+        * js/regress/prevent-extensions-and-do-work-expected.txt: Added.
+        * js/regress/prevent-extensions-and-do-work.html: Added.
+        * js/regress/script-tests/freeze-and-do-work.js: Added.
+        (Foo):
+        * js/regress/script-tests/prevent-extensions-and-do-work.js: Added.
+        (Foo):
+        * js/regress/script-tests/seal-and-do-work.js: Added.
+        (Foo):
+        * js/regress/seal-and-do-work-expected.txt: Added.
+        * js/regress/seal-and-do-work.html: Added.
+
 2016-07-18  Ryan Haddad  <[email protected]>
 
         Marking imported/w3c/web-platform-tests/XMLHttpRequest/event-readystatechange-loaded.htm as flaky on mac-debug WK1

Added: trunk/LayoutTests/js/regress/freeze-and-do-work-expected.txt (0 => 203368)


--- trunk/LayoutTests/js/regress/freeze-and-do-work-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/regress/freeze-and-do-work-expected.txt	2016-07-18 20:12:45 UTC (rev 203368)
@@ -0,0 +1,10 @@
+JSRegress/freeze-and-do-work
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS no exception thrown
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/regress/freeze-and-do-work.html (0 => 203368)


--- trunk/LayoutTests/js/regress/freeze-and-do-work.html	                        (rev 0)
+++ trunk/LayoutTests/js/regress/freeze-and-do-work.html	2016-07-18 20:12:45 UTC (rev 203368)
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/regress/prevent-extensions-and-do-work-expected.txt (0 => 203368)


--- trunk/LayoutTests/js/regress/prevent-extensions-and-do-work-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/regress/prevent-extensions-and-do-work-expected.txt	2016-07-18 20:12:45 UTC (rev 203368)
@@ -0,0 +1,10 @@
+JSRegress/prevent-extensions-and-do-work
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS no exception thrown
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/regress/prevent-extensions-and-do-work.html (0 => 203368)


--- trunk/LayoutTests/js/regress/prevent-extensions-and-do-work.html	                        (rev 0)
+++ trunk/LayoutTests/js/regress/prevent-extensions-and-do-work.html	2016-07-18 20:12:45 UTC (rev 203368)
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/regress/script-tests/freeze-and-do-work.js (0 => 203368)


--- trunk/LayoutTests/js/regress/script-tests/freeze-and-do-work.js	                        (rev 0)
+++ trunk/LayoutTests/js/regress/script-tests/freeze-and-do-work.js	2016-07-18 20:12:45 UTC (rev 203368)
@@ -0,0 +1,18 @@
+function Foo(f, g)
+{
+    this.f = f;
+    this.g = g;
+    Object.freeze(this);
+}
+
+(function() {
+    var result = 0;
+    for (var i = 0; i < 10000; ++i) {
+        var o = new Foo(i, i + 1);
+        for (var j = 0; j < 1000; ++j)
+            result += o.f * o.g;
+    }
+    
+    if (result != 333333330000000)
+        throw "Error: bad result: " + result;
+})();

Added: trunk/LayoutTests/js/regress/script-tests/prevent-extensions-and-do-work.js (0 => 203368)


--- trunk/LayoutTests/js/regress/script-tests/prevent-extensions-and-do-work.js	                        (rev 0)
+++ trunk/LayoutTests/js/regress/script-tests/prevent-extensions-and-do-work.js	2016-07-18 20:12:45 UTC (rev 203368)
@@ -0,0 +1,18 @@
+function Foo(f, g)
+{
+    this.f = f;
+    this.g = g;
+    Object.preventExtensions(this);
+}
+
+(function() {
+    var result = 0;
+    for (var i = 0; i < 10000; ++i) {
+        var o = new Foo(i, i + 1);
+        for (var j = 0; j < 1000; ++j)
+            result += o.f * o.g;
+    }
+    
+    if (result != 333333330000000)
+        throw "Error: bad result: " + result;
+})();

Added: trunk/LayoutTests/js/regress/script-tests/seal-and-do-work.js (0 => 203368)


--- trunk/LayoutTests/js/regress/script-tests/seal-and-do-work.js	                        (rev 0)
+++ trunk/LayoutTests/js/regress/script-tests/seal-and-do-work.js	2016-07-18 20:12:45 UTC (rev 203368)
@@ -0,0 +1,18 @@
+function Foo(f, g)
+{
+    this.f = f;
+    this.g = g;
+    Object.seal(this);
+}
+
+(function() {
+    var result = 0;
+    for (var i = 0; i < 10000; ++i) {
+        var o = new Foo(i, i + 1);
+        for (var j = 0; j < 1000; ++j)
+            result += o.f * o.g;
+    }
+    
+    if (result != 333333330000000)
+        throw "Error: bad result: " + result;
+})();

Added: trunk/LayoutTests/js/regress/seal-and-do-work-expected.txt (0 => 203368)


--- trunk/LayoutTests/js/regress/seal-and-do-work-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/regress/seal-and-do-work-expected.txt	2016-07-18 20:12:45 UTC (rev 203368)
@@ -0,0 +1,10 @@
+JSRegress/seal-and-do-work
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS no exception thrown
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/regress/seal-and-do-work.html (0 => 203368)


--- trunk/LayoutTests/js/regress/seal-and-do-work.html	                        (rev 0)
+++ trunk/LayoutTests/js/regress/seal-and-do-work.html	2016-07-18 20:12:45 UTC (rev 203368)
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+</body>
+</html>

Modified: trunk/Source/_javascript_Core/ChangeLog (203367 => 203368)


--- trunk/Source/_javascript_Core/ChangeLog	2016-07-18 20:12:00 UTC (rev 203367)
+++ trunk/Source/_javascript_Core/ChangeLog	2016-07-18 20:12:45 UTC (rev 203368)
@@ -1,5 +1,76 @@
 2016-07-17  Filip Pizlo  <[email protected]>
 
+        Object.preventExtensions/seal/freeze makes code much slower
+        https://bugs.webkit.org/show_bug.cgi?id=143247
+
+        Reviewed by Michael Saboff.
+        
+        This has been a huge pet peeve of mine for a long time, but I was always afraid of fixing
+        it because I thought that it would be hard. Well, it looks like it's not hard at all.
+        
+        The problem is that you cannot mutate a structure that participates in transition caching.
+        You can only clone the structure and mutate that one. But if you do this, you have to make
+        a hard choice:
+        
+        1) Clone the structure without caching the transition. This is what the code did before
+           this change. It's the most obvious choice, but it introduces an uncacheable transition
+           that leads to an explosion of structures, which then breaks all inline caches.
+        
+        2) Perform one of the existing cacheable transitions. Cacheable transitions can either add
+           properties or they can do one of the NonPropertyTransitions, which until now have been
+           restricted to just IndexingType transitions. So, only adding transitions or making
+           certain prescribed changes to the indexing type count as cacheable transitions.
+        
+        This change decouples NonPropertyTransition from IndexingType and adds three new kinds of
+        transitions: PreventExtensions, Seal, and Freeze. We have to give any cacheable transition
+        a name that fully disambiguates this transition from any other, so that the transition can
+        be cached. Since we're already giving them names in an enum, I figured that the most
+        pragmatic way to implement them is to have Structure::nonPropertyTransition() case on the
+        NonPropertyTransition and implement all of the mutations associated with that transition.
+        The alternative would have been to allow callers of nonPropertyTransition() to supply
+        something like a lambda that describes the mutation, but this seemed awkward since each
+        set of mutations has to anyway be tied to one of the NonPropertyTransition members.
+        
+        This is an enormous speed-up on microbenchmarks that use Object.preventExtensions(),
+        Object.seal(), or Object.freeze(). I don't know if "real" benchmarks use these features
+        and I don't really care. This should be fast.
+
+        * runtime/JSObject.cpp:
+        (JSC::JSObject::notifyPresenceOfIndexedAccessors):
+        (JSC::JSObject::createInitialUndecided):
+        (JSC::JSObject::createInitialInt32):
+        (JSC::JSObject::createInitialDouble):
+        (JSC::JSObject::createInitialContiguous):
+        (JSC::JSObject::convertUndecidedToInt32):
+        (JSC::JSObject::convertUndecidedToDouble):
+        (JSC::JSObject::convertUndecidedToContiguous):
+        (JSC::JSObject::convertInt32ToDouble):
+        (JSC::JSObject::convertInt32ToContiguous):
+        (JSC::JSObject::convertDoubleToContiguous):
+        (JSC::JSObject::switchToSlowPutArrayStorage):
+        * runtime/Structure.cpp:
+        (JSC::Structure::suggestedArrayStorageTransition):
+        (JSC::Structure::addPropertyTransition):
+        (JSC::Structure::toUncacheableDictionaryTransition):
+        (JSC::Structure::sealTransition):
+        (JSC::Structure::freezeTransition):
+        (JSC::Structure::preventExtensionsTransition):
+        (JSC::Structure::takePropertyTableOrCloneIfPinned):
+        (JSC::Structure::nonPropertyTransition):
+        (JSC::Structure::pin):
+        (JSC::Structure::pinForCaching):
+        (JSC::Structure::allocateRareData):
+        * runtime/Structure.h:
+        * runtime/StructureTransitionTable.h:
+        (JSC::toAttributes):
+        (JSC::changesIndexingType):
+        (JSC::newIndexingType):
+        (JSC::preventsExtensions):
+        (JSC::setsDontDeleteOnAllProperties):
+        (JSC::setsReadOnlyOnAllProperties):
+
+2016-07-17  Filip Pizlo  <[email protected]>
+
         RegisterSet should use a Bitmap instead of a BitVector so that it never allocates memory and is trivial to copy
         https://bugs.webkit.org/show_bug.cgi?id=159863
 

Modified: trunk/Source/_javascript_Core/runtime/JSObject.cpp (203367 => 203368)


--- trunk/Source/_javascript_Core/runtime/JSObject.cpp	2016-07-18 20:12:00 UTC (rev 203367)
+++ trunk/Source/_javascript_Core/runtime/JSObject.cpp	2016-07-18 20:12:45 UTC (rev 203368)
@@ -770,7 +770,7 @@
     if (mayInterceptIndexedAccesses())
         return;
     
-    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AddIndexedAccessors));
+    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AddIndexedAccessors));
     
     if (!vm.prototypeMap.isPrototype(this))
         return;
@@ -798,7 +798,7 @@
 {
     DeferGC deferGC(vm.heap);
     Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(EncodedJSValue));
-    Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), AllocateUndecided);
+    Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateUndecided);
     setStructureAndButterfly(vm, newStructure, newButterfly);
     return newButterfly;
 }
@@ -807,7 +807,7 @@
 {
     DeferGC deferGC(vm.heap);
     Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(EncodedJSValue));
-    Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), AllocateInt32);
+    Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateInt32);
     setStructureAndButterfly(vm, newStructure, newButterfly);
     return newButterfly->contiguousInt32();
 }
@@ -818,7 +818,7 @@
     Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(double));
     for (unsigned i = newButterfly->vectorLength(); i--;)
         newButterfly->contiguousDouble()[i] = PNaN;
-    Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), AllocateDouble);
+    Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateDouble);
     setStructureAndButterfly(vm, newStructure, newButterfly);
     return newButterfly->contiguousDouble();
 }
@@ -827,7 +827,7 @@
 {
     DeferGC deferGC(vm.heap);
     Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(EncodedJSValue));
-    Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), AllocateContiguous);
+    Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateContiguous);
     setStructureAndButterfly(vm, newStructure, newButterfly);
     return newButterfly->contiguous();
 }
@@ -862,7 +862,7 @@
 ContiguousJSValues JSObject::convertUndecidedToInt32(VM& vm)
 {
     ASSERT(hasUndecided(indexingType()));
-    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateInt32));
+    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateInt32));
     return m_butterfly.get()->contiguousInt32();
 }
 
@@ -874,7 +874,7 @@
     for (unsigned i = butterfly->vectorLength(); i--;)
         butterfly->contiguousDouble()[i] = PNaN;
     
-    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateDouble));
+    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateDouble));
     return m_butterfly.get()->contiguousDouble();
 }
 
@@ -881,7 +881,7 @@
 ContiguousJSValues JSObject::convertUndecidedToContiguous(VM& vm)
 {
     ASSERT(hasUndecided(indexingType()));
-    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateContiguous));
+    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateContiguous));
     return m_butterfly.get()->contiguous();
 }
 
@@ -946,7 +946,7 @@
         *currentAsDouble = v.asInt32();
     }
     
-    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateDouble));
+    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateDouble));
     return m_butterfly.get()->contiguousDouble();
 }
 
@@ -954,7 +954,7 @@
 {
     ASSERT(hasInt32(indexingType()));
     
-    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateContiguous));
+    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateContiguous));
     return m_butterfly.get()->contiguous();
 }
 
@@ -1002,7 +1002,7 @@
         currentAsValue->setWithoutWriteBarrier(v);
     }
     
-    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateContiguous));
+    setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateContiguous));
     return m_butterfly.get()->contiguous();
 }
 
@@ -1290,24 +1290,24 @@
 {
     switch (indexingType()) {
     case ALL_UNDECIDED_INDEXING_TYPES:
-        convertUndecidedToArrayStorage(vm, AllocateSlowPutArrayStorage);
+        convertUndecidedToArrayStorage(vm, NonPropertyTransition::AllocateSlowPutArrayStorage);
         break;
         
     case ALL_INT32_INDEXING_TYPES:
-        convertInt32ToArrayStorage(vm, AllocateSlowPutArrayStorage);
+        convertInt32ToArrayStorage(vm, NonPropertyTransition::AllocateSlowPutArrayStorage);
         break;
         
     case ALL_DOUBLE_INDEXING_TYPES:
-        convertDoubleToArrayStorage(vm, AllocateSlowPutArrayStorage);
+        convertDoubleToArrayStorage(vm, NonPropertyTransition::AllocateSlowPutArrayStorage);
         break;
         
     case ALL_CONTIGUOUS_INDEXING_TYPES:
-        convertContiguousToArrayStorage(vm, AllocateSlowPutArrayStorage);
+        convertContiguousToArrayStorage(vm, NonPropertyTransition::AllocateSlowPutArrayStorage);
         break;
         
     case NonArrayWithArrayStorage:
     case ArrayWithArrayStorage: {
-        Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), SwitchToSlowPutArrayStorage);
+        Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::SwitchToSlowPutArrayStorage);
         setStructure(vm, newStructure);
         break;
     }

Modified: trunk/Source/_javascript_Core/runtime/Structure.cpp (203367 => 203368)


--- trunk/Source/_javascript_Core/runtime/Structure.cpp	2016-07-18 20:12:00 UTC (rev 203367)
+++ trunk/Source/_javascript_Core/runtime/Structure.cpp	2016-07-18 20:12:45 UTC (rev 203368)
@@ -435,9 +435,9 @@
 NonPropertyTransition Structure::suggestedArrayStorageTransition() const
 {
     if (needsSlowPutIndexing())
-        return AllocateSlowPutArrayStorage;
+        return NonPropertyTransition::AllocateSlowPutArrayStorage;
     
-    return AllocateArrayStorage;
+    return NonPropertyTransition::AllocateArrayStorage;
 }
 
 Structure* Structure::addPropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset)
@@ -587,57 +587,19 @@
     return toDictionaryTransition(vm, structure, UncachedDictionaryKind);
 }
 
-// In future we may want to cache this transition.
 Structure* Structure::sealTransition(VM& vm, Structure* structure)
 {
-    Structure* transition = preventExtensionsTransition(vm, structure);
-
-    if (transition->propertyTable()) {
-        PropertyTable::iterator end = transition->propertyTable()->end();
-        for (PropertyTable::iterator iter = transition->propertyTable()->begin(); iter != end; ++iter)
-            iter->attributes |= DontDelete;
-    }
-
-    transition->checkOffsetConsistency();
-    return transition;
+    return nonPropertyTransition(vm, structure, NonPropertyTransition::Seal);
 }
 
-// In future we may want to cache this transition.
 Structure* Structure::freezeTransition(VM& vm, Structure* structure)
 {
-    Structure* transition = preventExtensionsTransition(vm, structure);
-
-    if (transition->propertyTable()) {
-        PropertyTable::iterator iter = transition->propertyTable()->begin();
-        PropertyTable::iterator end = transition->propertyTable()->end();
-        if (iter != end)
-            transition->setHasReadOnlyOrGetterSetterPropertiesExcludingProto(true);
-        for (; iter != end; ++iter)
-            iter->attributes |= iter->attributes & Accessor ? DontDelete : (DontDelete | ReadOnly);
-    }
-
-    ASSERT(transition->hasReadOnlyOrGetterSetterPropertiesExcludingProto() || !transition->classInfo()->hasStaticSetterOrReadonlyProperties());
-    ASSERT(transition->hasGetterSetterProperties() || !transition->classInfo()->hasStaticSetterOrReadonlyProperties());
-    transition->checkOffsetConsistency();
-    return transition;
+    return nonPropertyTransition(vm, structure, NonPropertyTransition::Freeze);
 }
 
-// In future we may want to cache this transition.
 Structure* Structure::preventExtensionsTransition(VM& vm, Structure* structure)
 {
-    Structure* transition = create(vm, structure);
-
-    // Don't set m_offset, as one cannot transition to this.
-
-    DeferGC deferGC(vm.heap);
-    structure->materializePropertyMapIfNecessary(vm, deferGC);
-    transition->propertyTable().set(vm, transition, structure->copyPropertyTableForPinning(vm));
-    transition->m_offset = structure->m_offset;
-    transition->setDidPreventExtensions(true);
-    transition->pin();
-
-    transition->checkOffsetConsistency();
-    return transition;
+    return nonPropertyTransition(vm, structure, NonPropertyTransition::PreventExtensions);
 }
 
 PropertyTable* Structure::takePropertyTableOrCloneIfPinned(VM& vm)
@@ -662,12 +624,14 @@
     unsigned attributes = toAttributes(transitionKind);
     IndexingType indexingType = newIndexingType(structure->indexingTypeIncludingHistory(), transitionKind);
     
-    if (JSGlobalObject* globalObject = structure->m_globalObject.get()) {
-        if (globalObject->isOriginalArrayStructure(structure)) {
-            Structure* result = globalObject->originalArrayStructureForIndexingType(indexingType);
-            if (result->indexingTypeIncludingHistory() == indexingType) {
-                structure->didTransitionFromThisStructure();
-                return result;
+    if (changesIndexingType(transitionKind)) {
+        if (JSGlobalObject* globalObject = structure->m_globalObject.get()) {
+            if (globalObject->isOriginalArrayStructure(structure)) {
+                Structure* result = globalObject->originalArrayStructureForIndexingType(indexingType);
+                if (result->indexingTypeIncludingHistory() == indexingType) {
+                    structure->didTransitionFromThisStructure();
+                    return result;
+                }
             }
         }
     }
@@ -679,13 +643,45 @@
         return existingTransition;
     }
     
+    DeferGC deferGC(vm.heap);
+    
     Structure* transition = create(vm, structure);
     transition->setAttributesInPrevious(attributes);
     transition->m_blob.setIndexingType(indexingType);
-    transition->propertyTable().set(vm, transition, structure->takePropertyTableOrCloneIfPinned(vm));
-    transition->m_offset = structure->m_offset;
-    checkOffset(transition->m_offset, transition->inlineCapacity());
     
+    if (preventsExtensions(transitionKind))
+        transition->setDidPreventExtensions(true);
+    
+    unsigned additionalPropertyAttributes = 0;
+    if (setsDontDeleteOnAllProperties(transitionKind))
+        additionalPropertyAttributes |= DontDelete;
+    if (setsReadOnlyOnAllProperties(transitionKind))
+        additionalPropertyAttributes |= ReadOnly;
+    if (additionalPropertyAttributes) {
+        // We pin the property table on transitions that do wholesale editing of the property
+        // 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.
+        
+        structure->materializePropertyMapIfNecessary(vm, deferGC);
+        transition->propertyTable().set(vm, transition, structure->copyPropertyTableForPinning(vm));
+        transition->m_offset = structure->m_offset;
+        transition->pinForCaching();
+        
+        if (transition->propertyTable()) {
+            for (auto& entry : *transition->propertyTable().get())
+                entry.attributes |= additionalPropertyAttributes;
+        }
+    } else {
+        transition->propertyTable().set(vm, transition, structure->takePropertyTableOrCloneIfPinned(vm));
+        transition->m_offset = structure->m_offset;
+        checkOffset(transition->m_offset, transition->inlineCapacity());
+    }
+    
+    if (setsReadOnlyOnAllProperties(transitionKind)
+        && transition->propertyTable()
+        && !transition->propertyTable()->isEmpty())
+        transition->setHasReadOnlyOrGetterSetterPropertiesExcludingProto(true);
+    
     if (structure->isDictionary())
         transition->pin();
     else {
@@ -692,6 +688,7 @@
         ConcurrentJITLocker locker(structure->m_lock);
         structure->m_transitionTable.add(vm, transition);
     }
+
     transition->checkOffsetConsistency();
     return transition;
 }
@@ -817,6 +814,13 @@
     m_nameInPrevious = nullptr;
 }
 
+void Structure::pinForCaching()
+{
+    ASSERT(propertyTable());
+    setIsPinnedPropertyTable(true);
+    m_nameInPrevious = nullptr;
+}
+
 void Structure::allocateRareData(VM& vm)
 {
     ASSERT(!hasRareData());

Modified: trunk/Source/_javascript_Core/runtime/Structure.h (203367 => 203368)


--- trunk/Source/_javascript_Core/runtime/Structure.h	2016-07-18 20:12:00 UTC (rev 203367)
+++ trunk/Source/_javascript_Core/runtime/Structure.h	2016-07-18 20:12:45 UTC (rev 203368)
@@ -700,6 +700,7 @@
     bool isValid(ExecState*, StructureChain* cachedPrototypeChain) const;
         
     void pin();
+    void pinForCaching();
     
     bool isRareData(JSCell* cell) const
     {

Modified: trunk/Source/_javascript_Core/runtime/StructureTransitionTable.h (203367 => 203368)


--- trunk/Source/_javascript_Core/runtime/StructureTransitionTable.h	2016-07-18 20:12:00 UTC (rev 203367)
+++ trunk/Source/_javascript_Core/runtime/StructureTransitionTable.h	2016-07-18 20:12:45 UTC (rev 203368)
@@ -40,7 +40,7 @@
 
 // Support for attributes used to indicate transitions not related to properties.
 // If any of these are used, the string portion of the key should be 0.
-enum NonPropertyTransition {
+enum class NonPropertyTransition : unsigned {
     AllocateUndecided,
     AllocateInt32,
     AllocateDouble,
@@ -48,46 +48,98 @@
     AllocateArrayStorage,
     AllocateSlowPutArrayStorage,
     SwitchToSlowPutArrayStorage,
-    AddIndexedAccessors
+    AddIndexedAccessors,
+    PreventExtensions,
+    Seal,
+    Freeze
 };
 
 inline unsigned toAttributes(NonPropertyTransition transition)
 {
-    return transition + FirstInternalAttribute;
+    return static_cast<unsigned>(transition) + FirstInternalAttribute;
 }
 
+inline bool changesIndexingType(NonPropertyTransition transition)
+{
+    switch (transition) {
+    case NonPropertyTransition::AllocateUndecided:
+    case NonPropertyTransition::AllocateInt32:
+    case NonPropertyTransition::AllocateDouble:
+    case NonPropertyTransition::AllocateContiguous:
+    case NonPropertyTransition::AllocateArrayStorage:
+    case NonPropertyTransition::AllocateSlowPutArrayStorage:
+    case NonPropertyTransition::SwitchToSlowPutArrayStorage:
+    case NonPropertyTransition::AddIndexedAccessors:
+        return true;
+    default:
+        return false;
+    }
+}
+
 inline IndexingType newIndexingType(IndexingType oldType, NonPropertyTransition transition)
 {
     switch (transition) {
-    case AllocateUndecided:
+    case NonPropertyTransition::AllocateUndecided:
         ASSERT(!hasIndexedProperties(oldType));
         return oldType | UndecidedShape;
-    case AllocateInt32:
+    case NonPropertyTransition::AllocateInt32:
         ASSERT(!hasIndexedProperties(oldType) || hasUndecided(oldType));
         return (oldType & ~IndexingShapeMask) | Int32Shape;
-    case AllocateDouble:
+    case NonPropertyTransition::AllocateDouble:
         ASSERT(!hasIndexedProperties(oldType) || hasUndecided(oldType) || hasInt32(oldType));
         return (oldType & ~IndexingShapeMask) | DoubleShape;
-    case AllocateContiguous:
+    case NonPropertyTransition::AllocateContiguous:
         ASSERT(!hasIndexedProperties(oldType) || hasUndecided(oldType) || hasInt32(oldType) || hasDouble(oldType));
         return (oldType & ~IndexingShapeMask) | ContiguousShape;
-    case AllocateArrayStorage:
+    case NonPropertyTransition::AllocateArrayStorage:
         ASSERT(!hasIndexedProperties(oldType) || hasUndecided(oldType) || hasInt32(oldType) || hasDouble(oldType) || hasContiguous(oldType));
         return (oldType & ~IndexingShapeMask) | ArrayStorageShape;
-    case AllocateSlowPutArrayStorage:
+    case NonPropertyTransition::AllocateSlowPutArrayStorage:
         ASSERT(!hasIndexedProperties(oldType) || hasUndecided(oldType) || hasInt32(oldType) || hasDouble(oldType) || hasContiguous(oldType) || hasContiguous(oldType));
         return (oldType & ~IndexingShapeMask) | SlowPutArrayStorageShape;
-    case SwitchToSlowPutArrayStorage:
+    case NonPropertyTransition::SwitchToSlowPutArrayStorage:
         ASSERT(hasArrayStorage(oldType));
         return (oldType & ~IndexingShapeMask) | SlowPutArrayStorageShape;
-    case AddIndexedAccessors:
+    case NonPropertyTransition::AddIndexedAccessors:
         return oldType | MayHaveIndexedAccessors;
     default:
-        RELEASE_ASSERT_NOT_REACHED();
         return oldType;
     }
 }
 
+inline bool preventsExtensions(NonPropertyTransition transition)
+{
+    switch (transition) {
+    case NonPropertyTransition::PreventExtensions:
+    case NonPropertyTransition::Seal:
+    case NonPropertyTransition::Freeze:
+        return true;
+    default:
+        return false;
+    }
+}
+
+inline bool setsDontDeleteOnAllProperties(NonPropertyTransition transition)
+{
+    switch (transition) {
+    case NonPropertyTransition::Seal:
+    case NonPropertyTransition::Freeze:
+        return true;
+    default:
+        return false;
+    }
+}
+
+inline bool setsReadOnlyOnAllProperties(NonPropertyTransition transition)
+{
+    switch (transition) {
+    case NonPropertyTransition::Freeze:
+        return true;
+    default:
+        return false;
+    }
+}
+
 class StructureTransitionTable {
     static const intptr_t UsingSingleSlotFlag = 1;
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to