Changeset 240447 in webkit


Ignore:
Timestamp:
Jan 24, 2019 1:30:55 PM (5 years ago)
Author:
sbarati@apple.com
Message:

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.

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:
Location:
trunk
Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r240432 r240447  
     12019-01-24  Saam Barati  <sbarati@apple.com>
     2
     3        Object Allocation Sinking phase can move a node that walks the stack into a place where the InlineCallFrame is no longer valid
     4        https://bugs.webkit.org/show_bug.cgi?id=193751
     5        <rdar://problem/47280215>
     6
     7        Reviewed by Michael Saboff.
     8
     9        * stress/object-allocation-sinking-phase-must-only-move-allocations-if-stack-trace-is-still-valid.js: Added.
     10        (let.thing):
     11        (foo.let.hello):
     12        (foo):
     13
    1142019-01-24  Guillaume Emont  <guijemont@igalia.com>
    215
  • trunk/Source/JavaScriptCore/ChangeLog

    r240432 r240447  
     12019-01-24  Saam Barati  <sbarati@apple.com>
     2
     3        Object Allocation Sinking phase can move a node that walks the stack into a place where the InlineCallFrame is no longer valid
     4        https://bugs.webkit.org/show_bug.cgi?id=193751
     5        <rdar://problem/47280215>
     6
     7        Reviewed by Michael Saboff.
     8
     9        The Object Allocation Sinking phase may move allocations around inside
     10        of the program. However, it was not ensuring that it's still possible
     11        to walk the stack at the point in the program that it moved the allocation to.
     12        Certain InlineCallFrames rely on data in the stack when taking a stack trace.
     13        All allocation sites can do a stack walk (we do a stack walk when we GC).
     14        Conservatively, this patch says we're ok to move this allocation if we are
     15        moving within the same InlineCallFrame. We could be more precise and do an
     16        analysis of stack writes. However, this scenario is so rare that we just
     17        take the conservative-and-straight-forward approach of checking that the place
     18        we're moving to is the same InlineCallFrame as the allocation site.
     19       
     20        In general, this issue arises anytime we do any kind of code motion.
     21        Interestingly, LICM gets this right. It gets it right because the only
     22        InlineCallFrames we can't move out of are the InlineCallFrames that
     23        have metadata stored on the stack (callee for closure calls and argument
     24        count for varargs calls). LICM doesn't have this issue because it relies
     25        on Clobberize for doing its effects analysis. In clobberize, we model every
     26        node within an InlineCallFrame that meets the above criteria as reading
     27        from those stack fields. Consequently, LICM won't hoist any node in that
     28        InlineCallFrame past the beginning of the InlineCallFrame since the IR
     29        we generate to set up such an InlineCallFrame contains writes to that
     30        stack location.
     31
     32        * dfg/DFGObjectAllocationSinkingPhase.cpp:
     33
    1342019-01-24  Guillaume Emont  <guijemont@igalia.com>
    235
  • trunk/Source/JavaScriptCore/dfg/DFGObjectAllocationSinkingPhase.cpp

    r240023 r240447  
    12161216        }
    12171217
     1218        auto forEachEscapee = [&] (auto callback) {
     1219            for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
     1220                m_heap = m_heapAtHead[block];
     1221                m_heap.setWantEscapees();
     1222
     1223                for (Node* node : *block) {
     1224                    handleNode(
     1225                        node,
     1226                        [] (PromotedHeapLocation, LazyNode) { },
     1227                        [] (PromotedHeapLocation) -> Node* {
     1228                            return nullptr;
     1229                        });
     1230                    auto escapees = m_heap.takeEscapees();
     1231                    escapees.removeIf([&] (const auto& entry) { return !m_sinkCandidates.contains(entry.key); });
     1232                    callback(escapees, node);
     1233                }
     1234
     1235                m_heap.pruneByLiveness(m_combinedLiveness.liveAtTail[block]);
     1236
     1237                {
     1238                    HashMap<Node*, Allocation> escapingOnEdge;
     1239                    for (const auto& entry : m_heap.allocations()) {
     1240                        if (entry.value.isEscapedAllocation())
     1241                            continue;
     1242
     1243                        bool mustEscape = false;
     1244                        for (BasicBlock* successorBlock : block->successors()) {
     1245                            if (!m_heapAtHead[successorBlock].isAllocation(entry.key)
     1246                                || m_heapAtHead[successorBlock].getAllocation(entry.key).isEscapedAllocation())
     1247                                mustEscape = true;
     1248                        }
     1249
     1250                        if (mustEscape && m_sinkCandidates.contains(entry.key))
     1251                            escapingOnEdge.add(entry.key, entry.value);
     1252                    }
     1253                    callback(escapingOnEdge, block->terminal());
     1254                }
     1255            }
     1256        };
     1257
     1258        if (m_sinkCandidates.size()) {
     1259            // If we're moving an allocation to `where` in the program, we need to ensure
     1260            // we can still walk the stack at that point in the program for the
     1261            // InlineCallFrame of the original allocation. Certain InlineCallFrames rely on
     1262            // data in the stack when taking a stack trace. All allocation sites can do a
     1263            // stack walk (we do a stack walk when we GC). Conservatively, we say we're
     1264            // still ok to move this allocation if we are moving within the same InlineCallFrame.
     1265            // We could be more precise here and do an analysis of stack writes. However,
     1266            // this scenario is so rare that we just take the conservative-and-straight-forward
     1267            // approach of checking that we're in the same InlineCallFrame.
     1268
     1269            forEachEscapee([&] (HashMap<Node*, Allocation>& escapees, Node* where) {
     1270                for (Node* allocation : escapees.keys()) {
     1271                    InlineCallFrame* inlineCallFrame = allocation->origin.semantic.inlineCallFrame;
     1272                    if (!inlineCallFrame)
     1273                        continue;
     1274                    if ((inlineCallFrame->isClosureCall || inlineCallFrame->isVarargs()) && inlineCallFrame != where->origin.semantic.inlineCallFrame)
     1275                        m_sinkCandidates.remove(allocation);
     1276                }
     1277            });
     1278        }
     1279
    12181280        // Ensure that the set of sink candidates is closed for put operations
     1281        // This is (2) as described above.
    12191282        Vector<Node*> worklist;
    12201283        worklist.appendRange(m_sinkCandidates.begin(), m_sinkCandidates.end());
     
    12331296            dataLog("Candidates: ", listDump(m_sinkCandidates), "\n");
    12341297
    1235         // Create the materialization nodes
    1236         for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
    1237             m_heap = m_heapAtHead[block];
    1238             m_heap.setWantEscapees();
    1239 
    1240             for (Node* node : *block) {
    1241                 handleNode(
    1242                     node,
    1243                     [] (PromotedHeapLocation, LazyNode) { },
    1244                     [] (PromotedHeapLocation) -> Node* {
    1245                         return nullptr;
    1246                     });
    1247                 auto escapees = m_heap.takeEscapees();
    1248                 if (!escapees.isEmpty())
    1249                     placeMaterializations(escapees, node);
    1250             }
    1251 
    1252             m_heap.pruneByLiveness(m_combinedLiveness.liveAtTail[block]);
    1253 
    1254             {
    1255                 HashMap<Node*, Allocation> escapingOnEdge;
    1256                 for (const auto& entry : m_heap.allocations()) {
    1257                     if (entry.value.isEscapedAllocation())
    1258                         continue;
    1259 
    1260                     bool mustEscape = false;
    1261                     for (BasicBlock* successorBlock : block->successors()) {
    1262                         if (!m_heapAtHead[successorBlock].isAllocation(entry.key)
    1263                             || m_heapAtHead[successorBlock].getAllocation(entry.key).isEscapedAllocation())
    1264                             mustEscape = true;
    1265                     }
    1266 
    1267                     if (mustEscape)
    1268                         escapingOnEdge.add(entry.key, entry.value);
    1269                 }
    1270                 placeMaterializations(WTFMove(escapingOnEdge), block->terminal());
    1271             }
    1272         }
     1298
     1299        // Create the materialization nodes.
     1300        forEachEscapee([&] (HashMap<Node*, Allocation>& escapees, Node* where) {
     1301            placeMaterializations(WTFMove(escapees), where);
     1302        });
    12731303
    12741304        return hasUnescapedReads || !m_sinkCandidates.isEmpty();
     
    12771307    void placeMaterializations(HashMap<Node*, Allocation> escapees, Node* where)
    12781308    {
    1279         // We don't create materializations if the escapee is not a
    1280         // sink candidate
    1281         escapees.removeIf(
    1282             [&] (const auto& entry) {
    1283                 return !m_sinkCandidates.contains(entry.key);
    1284             });
    1285         if (escapees.isEmpty())
    1286             return;
    1287 
    12881309        // First collect the hints that will be needed when the node
    12891310        // we materialize is still stored into other unescaped sink candidates.
Note: See TracChangeset for help on using the changeset viewer.