Object Allocation Sinking phase can move a node that walks the stack into a place...
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 24 Jan 2019 21:30:55 +0000 (21:30 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 24 Jan 2019 21:30:55 +0000 (21:30 +0000)
https://bugs.webkit.org/show_bug.cgi?id=193751
<rdar://problem/47280215>

Reviewed by Michael Saboff.

JSTests:

* stress/object-allocation-sinking-phase-must-only-move-allocations-if-stack-trace-is-still-valid.js: Added.
(let.thing):
(foo.let.hello):
(foo):

Source/JavaScriptCore:

The Object Allocation Sinking phase may move allocations around inside
of the program. However, it was not ensuring that it's still possible
to walk the stack at the point in the program that it moved the allocation to.
Certain InlineCallFrames rely on data in the stack when taking a stack trace.
All allocation sites can do a stack walk (we do a stack walk when we GC).
Conservatively, this patch says we're ok to move this allocation if we are
moving within the same InlineCallFrame. We could be more precise and do an
analysis of stack writes. However, this scenario is so rare that we just
take the conservative-and-straight-forward approach of checking that the place
we're moving to is the same InlineCallFrame as the allocation site.

In general, this issue arises anytime we do any kind of code motion.
Interestingly, LICM gets this right. It gets it right because the only
InlineCallFrames we can't move out of are the InlineCallFrames that
have metadata stored on the stack (callee for closure calls and argument
count for varargs calls). LICM doesn't have this issue because it relies
on Clobberize for doing its effects analysis. In clobberize, we model every
node within an InlineCallFrame that meets the above criteria as reading
from those stack fields. Consequently, LICM won't hoist any node in that
InlineCallFrame past the beginning of the InlineCallFrame since the IR
we generate to set up such an InlineCallFrame contains writes to that
stack location.

* dfg/DFGObjectAllocationSinkingPhase.cpp:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@240447 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/stress/object-allocation-sinking-phase-must-only-move-allocations-if-stack-trace-is-still-valid.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/dfg/DFGObjectAllocationSinkingPhase.cpp

index e4819f8..eb37acf 100644 (file)
@@ -1,3 +1,16 @@
+2019-01-24  Saam Barati  <sbarati@apple.com>
+
+        Object Allocation Sinking phase can move a node that walks the stack into a place where the InlineCallFrame is no longer valid
+        https://bugs.webkit.org/show_bug.cgi?id=193751
+        <rdar://problem/47280215>
+
+        Reviewed by Michael Saboff.
+
+        * stress/object-allocation-sinking-phase-must-only-move-allocations-if-stack-trace-is-still-valid.js: Added.
+        (let.thing):
+        (foo.let.hello):
+        (foo):
+
 2019-01-24  Guillaume Emont  <guijemont@igalia.com>
 
         [JSC] Reenable baseline JIT on mips
diff --git a/JSTests/stress/object-allocation-sinking-phase-must-only-move-allocations-if-stack-trace-is-still-valid.js b/JSTests/stress/object-allocation-sinking-phase-must-only-move-allocations-if-stack-trace-is-still-valid.js
new file mode 100644 (file)
index 0000000..32ffce3
--- /dev/null
@@ -0,0 +1,30 @@
+//@ runDefault("--useConcurrentJIT=0", "--jitPolicyScale=0", "--collectContinuously=1")
+
+let thing = []
+
+function bar(x) {
+    thing.push(x);
+}
+
+function foo() {
+    let hello = function () {
+        let tmp = 1;
+        return function (num) {
+            if (tmp) {
+                if (num.length) {
+                }
+            }
+        };
+    }();
+
+    bar();
+    for (j = 0; j < 10000; j++) {
+        if (/\s/.test(' ')) {
+            hello(j);
+        }
+    }
+}
+
+for (let i=0; i<100; i++) {
+    foo();
+}
index bdf15f7..bed5aa5 100644 (file)
@@ -1,3 +1,36 @@
+2019-01-24  Saam Barati  <sbarati@apple.com>
+
+        Object Allocation Sinking phase can move a node that walks the stack into a place where the InlineCallFrame is no longer valid
+        https://bugs.webkit.org/show_bug.cgi?id=193751
+        <rdar://problem/47280215>
+
+        Reviewed by Michael Saboff.
+
+        The Object Allocation Sinking phase may move allocations around inside
+        of the program. However, it was not ensuring that it's still possible 
+        to walk the stack at the point in the program that it moved the allocation to.
+        Certain InlineCallFrames rely on data in the stack when taking a stack trace.
+        All allocation sites can do a stack walk (we do a stack walk when we GC).
+        Conservatively, this patch says we're ok to move this allocation if we are
+        moving within the same InlineCallFrame. We could be more precise and do an
+        analysis of stack writes. However, this scenario is so rare that we just
+        take the conservative-and-straight-forward approach of checking that the place
+        we're moving to is the same InlineCallFrame as the allocation site.
+        
+        In general, this issue arises anytime we do any kind of code motion.
+        Interestingly, LICM gets this right. It gets it right because the only
+        InlineCallFrames we can't move out of are the InlineCallFrames that
+        have metadata stored on the stack (callee for closure calls and argument
+        count for varargs calls). LICM doesn't have this issue because it relies
+        on Clobberize for doing its effects analysis. In clobberize, we model every
+        node within an InlineCallFrame that meets the above criteria as reading
+        from those stack fields. Consequently, LICM won't hoist any node in that
+        InlineCallFrame past the beginning of the InlineCallFrame since the IR
+        we generate to set up such an InlineCallFrame contains writes to that
+        stack location.
+
+        * dfg/DFGObjectAllocationSinkingPhase.cpp:
+
 2019-01-24  Guillaume Emont  <guijemont@igalia.com>
 
         [JSC] Reenable baseline JIT on mips
index b99a4dd..0dc646b 100644 (file)
@@ -1215,7 +1215,70 @@ private:
             }
         }
 
+        auto forEachEscapee = [&] (auto callback) {
+            for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
+                m_heap = m_heapAtHead[block];
+                m_heap.setWantEscapees();
+
+                for (Node* node : *block) {
+                    handleNode(
+                        node,
+                        [] (PromotedHeapLocation, LazyNode) { },
+                        [] (PromotedHeapLocation) -> Node* {
+                            return nullptr;
+                        });
+                    auto escapees = m_heap.takeEscapees();
+                    escapees.removeIf([&] (const auto& entry) { return !m_sinkCandidates.contains(entry.key); });
+                    callback(escapees, node);
+                }
+
+                m_heap.pruneByLiveness(m_combinedLiveness.liveAtTail[block]);
+
+                {
+                    HashMap<Node*, Allocation> escapingOnEdge;
+                    for (const auto& entry : m_heap.allocations()) {
+                        if (entry.value.isEscapedAllocation())
+                            continue;
+
+                        bool mustEscape = false;
+                        for (BasicBlock* successorBlock : block->successors()) {
+                            if (!m_heapAtHead[successorBlock].isAllocation(entry.key)
+                                || m_heapAtHead[successorBlock].getAllocation(entry.key).isEscapedAllocation())
+                                mustEscape = true;
+                        }
+
+                        if (mustEscape && m_sinkCandidates.contains(entry.key))
+                            escapingOnEdge.add(entry.key, entry.value);
+                    }
+                    callback(escapingOnEdge, block->terminal());
+                }
+            }
+        };
+
+        if (m_sinkCandidates.size()) {
+            // If we're moving an allocation to `where` in the program, we need to ensure
+            // we can still walk the stack at that point in the program for the
+            // InlineCallFrame of the original allocation. Certain InlineCallFrames rely on
+            // data in the stack when taking a stack trace. All allocation sites can do a
+            // stack walk (we do a stack walk when we GC). Conservatively, we say we're
+            // still ok to move this allocation if we are moving within the same InlineCallFrame.
+            // We could be more precise here and do an analysis of stack writes. However,
+            // this scenario is so rare that we just take the conservative-and-straight-forward 
+            // approach of checking that we're in the same InlineCallFrame.
+
+            forEachEscapee([&] (HashMap<Node*, Allocation>& escapees, Node* where) {
+                for (Node* allocation : escapees.keys()) {
+                    InlineCallFrame* inlineCallFrame = allocation->origin.semantic.inlineCallFrame;
+                    if (!inlineCallFrame)
+                        continue;
+                    if ((inlineCallFrame->isClosureCall || inlineCallFrame->isVarargs()) && inlineCallFrame != where->origin.semantic.inlineCallFrame)
+                        m_sinkCandidates.remove(allocation);
+                }
+            });
+        }
+
         // Ensure that the set of sink candidates is closed for put operations
+        // This is (2) as described above.
         Vector<Node*> worklist;
         worklist.appendRange(m_sinkCandidates.begin(), m_sinkCandidates.end());
 
@@ -1232,59 +1295,17 @@ private:
         if (DFGObjectAllocationSinkingPhaseInternal::verbose)
             dataLog("Candidates: ", listDump(m_sinkCandidates), "\n");
 
-        // Create the materialization nodes
-        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
-            m_heap = m_heapAtHead[block];
-            m_heap.setWantEscapees();
 
-            for (Node* node : *block) {
-                handleNode(
-                    node,
-                    [] (PromotedHeapLocation, LazyNode) { },
-                    [] (PromotedHeapLocation) -> Node* {
-                        return nullptr;
-                    });
-                auto escapees = m_heap.takeEscapees();
-                if (!escapees.isEmpty())
-                    placeMaterializations(escapees, node);
-            }
-
-            m_heap.pruneByLiveness(m_combinedLiveness.liveAtTail[block]);
-
-            {
-                HashMap<Node*, Allocation> escapingOnEdge;
-                for (const auto& entry : m_heap.allocations()) {
-                    if (entry.value.isEscapedAllocation())
-                        continue;
-
-                    bool mustEscape = false;
-                    for (BasicBlock* successorBlock : block->successors()) {
-                        if (!m_heapAtHead[successorBlock].isAllocation(entry.key)
-                            || m_heapAtHead[successorBlock].getAllocation(entry.key).isEscapedAllocation())
-                            mustEscape = true;
-                    }
-
-                    if (mustEscape)
-                        escapingOnEdge.add(entry.key, entry.value);
-                }
-                placeMaterializations(WTFMove(escapingOnEdge), block->terminal());
-            }
-        }
+        // Create the materialization nodes.
+        forEachEscapee([&] (HashMap<Node*, Allocation>& escapees, Node* where) {
+            placeMaterializations(WTFMove(escapees), where);
+        });
 
         return hasUnescapedReads || !m_sinkCandidates.isEmpty();
     }
 
     void placeMaterializations(HashMap<Node*, Allocation> escapees, Node* where)
     {
-        // We don't create materializations if the escapee is not a
-        // sink candidate
-        escapees.removeIf(
-            [&] (const auto& entry) {
-                return !m_sinkCandidates.contains(entry.key);
-            });
-        if (escapees.isEmpty())
-            return;
-
         // First collect the hints that will be needed when the node
         // we materialize is still stored into other unescaped sink candidates.
         // The way to interpret this vector is: