⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 232742 in webkit


Ignore:
Timestamp:
Jun 11, 2018, 10:05:49 PM (8 years ago)
Author:
sbarati@apple.com
Message:

Reduce graph size by replacing terminal nodes in blocks that have a ForceOSRExit with Unreachable
https://bugs.webkit.org/show_bug.cgi?id=181409
<rdar://problem/36383749>

Reviewed by Keith Miller.

This patch is me redoing r226655. This is a patch I wrote when
profiling Speedometer. Fil rolled this change out in r230928. He
showed this slowed down a sunspider tests by ~2x. This sunspider
regression revealed a real performance bug in the original change:
we would kill blocks that reached OSR entry targets, sometimes leading
us to not do OSR entry into the DFG, since we could end up deleting
entire loops from the CFG. The reason for this is that code that has run
~once and that reaches loops often has ForceOSRExits inside of it. The
solution to this is to not perform this optimization on blocks that can
reach OSR entry targets.

The reason I'm redoing this patch is that it turns out Fil rolling
out the change was a Speedometer 2 regression.

This is a modified version of the original ChangeLog I wrote in r226655:

When I was looking at profiler data for Speedometer, I noticed that one of
the hottest functions in Speedometer is around 1100 bytecode operations long.
Only about 100 of those bytecode ops ever execute. However, we ended up
spending a lot of time compiling basic blocks that never executed. We often
plant ForceOSRExit nodes when we parse bytecodes that have a null value profile.
This is the case when such a node never executes.

This patch makes it so that anytime a block has a ForceOSRExit, and that block
can not reach an OSR entry target, we replace its terminal node with an Unreachable
node, and remove all nodes after the ForceOSRExit. This cuts down the graph
size since it removes control flow edges from the CFG. This allows us to get
rid of huge chunks of the CFG in certain programs. When doing this transformation,
we also insert Flushes/PhantomLocals to ensure we can recover values that are bytecode
live-in to the ForceOSRExit.

Using ForceOSRExit as the signal for this is a bit of a hack. It definitely
does not get rid of all the CFG that it could. If we decide it's worth
it, we could use additional inputs into this mechanism. For example, we could
profile if a basic block ever executes inside the LLInt/Baseline, and
remove parts of the CFG based on that.

When running Speedometer with the concurrent JIT turned off, this patch
improves DFG/FTL compile times by around 5%.

  • dfg/DFGByteCodeParser.cpp:

(JSC::DFG::ByteCodeParser::addToGraph):
(JSC::DFG::ByteCodeParser::inlineCall):
(JSC::DFG::ByteCodeParser::parse):

  • dfg/DFGGraph.cpp:

(JSC::DFG::Graph::blocksInPostOrder):

Location:
trunk/Source/JavaScriptCore
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/ChangeLog

    r232741 r232742  
     12018-06-11  Saam Barati  <sbarati@apple.com>
     2
     3        Reduce graph size by replacing terminal nodes in blocks that have a ForceOSRExit with Unreachable
     4        https://bugs.webkit.org/show_bug.cgi?id=181409
     5        <rdar://problem/36383749>
     6
     7        Reviewed by Keith Miller.
     8
     9        This patch is me redoing r226655. This is a patch I wrote when
     10        profiling Speedometer. Fil rolled this change out in r230928. He
     11        showed this slowed down a sunspider tests by ~2x. This sunspider
     12        regression revealed a real performance bug in the original change:
     13        we would kill blocks that reached OSR entry targets, sometimes leading
     14        us to not do OSR entry into the DFG, since we could end up deleting
     15        entire loops from the CFG. The reason for this is that code that has run
     16        ~once and that reaches loops often has ForceOSRExits inside of it. The
     17        solution to this is to not perform this optimization on blocks that can
     18        reach OSR entry targets.
     19       
     20        The reason I'm redoing this patch is that it turns out Fil rolling
     21        out the change was a Speedometer 2 regression.
     22       
     23        This is a modified version of the original ChangeLog I wrote in r226655:
     24       
     25        When I was looking at profiler data for Speedometer, I noticed that one of
     26        the hottest functions in Speedometer is around 1100 bytecode operations long.
     27        Only about 100 of those bytecode ops ever execute. However, we ended up
     28        spending a lot of time compiling basic blocks that never executed. We often
     29        plant ForceOSRExit nodes when we parse bytecodes that have a null value profile.
     30        This is the case when such a node never executes.
     31       
     32        This patch makes it so that anytime a block has a ForceOSRExit, and that block
     33        can not reach an OSR entry target, we replace its terminal node with an Unreachable
     34        node, and remove all nodes after the ForceOSRExit. This cuts down the graph
     35        size since it removes control flow edges from the CFG. This allows us to get
     36        rid of huge chunks of the CFG in certain programs. When doing this transformation,
     37        we also insert Flushes/PhantomLocals to ensure we can recover values that are bytecode
     38        live-in to the ForceOSRExit.
     39       
     40        Using ForceOSRExit as the signal for this is a bit of a hack. It definitely
     41        does not get rid of all the CFG that it could. If we decide it's worth
     42        it, we could use additional inputs into this mechanism. For example, we could
     43        profile if a basic block ever executes inside the LLInt/Baseline, and
     44        remove parts of the CFG based on that.
     45       
     46        When running Speedometer with the concurrent JIT turned off, this patch
     47        improves DFG/FTL compile times by around 5%.
     48
     49        * dfg/DFGByteCodeParser.cpp:
     50        (JSC::DFG::ByteCodeParser::addToGraph):
     51        (JSC::DFG::ByteCodeParser::inlineCall):
     52        (JSC::DFG::ByteCodeParser::parse):
     53        * dfg/DFGGraph.cpp:
     54        (JSC::DFG::Graph::blocksInPostOrder):
     55
    1562018-06-11  Saam Barati  <sbarati@apple.com>
    257
  • trunk/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp

    r232461 r232742  
    698698    {
    699699        VERBOSE_LOG("        appended ", node, " ", Graph::opName(node->op()), "\n");
     700
     701        m_hasAnyForceOSRExits |= (node->op() == ForceOSRExit);
     702
    700703        m_currentBlock->append(node);
    701704        if (clobbersExitState(m_graph, node))
     
    11511154    Instruction* m_currentInstruction;
    11521155    bool m_hasDebuggerEnabled;
     1156    bool m_hasAnyForceOSRExits { false };
    11531157};
    11541158
     
    68336837    linkBlocks(inlineStackEntry.m_unlinkedBlocks, inlineStackEntry.m_blockLinkingTargets);
    68346838
     6839    if (m_hasAnyForceOSRExits) {
     6840        BlockSet blocksToIgnore;
     6841        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
     6842            if (block->isOSRTarget)
     6843                blocksToIgnore.add(block);
     6844        }
     6845
     6846        {
     6847            bool isSafeToValidate = false;
     6848            auto postOrder = m_graph.blocksInPostOrder(isSafeToValidate); // This algorithm doesn't rely on the predecessors list, which is not yet built.
     6849            bool changed;
     6850            do {
     6851                changed = false;
     6852                for (BasicBlock* block : postOrder) {
     6853                    for (BasicBlock* successor : block->successors()) {
     6854                        if (blocksToIgnore.contains(successor)) {
     6855                            changed |= blocksToIgnore.add(block);
     6856                            break;
     6857                        }
     6858                    }
     6859                }
     6860            } while (changed);
     6861        }
     6862
     6863        InsertionSet insertionSet(m_graph);
     6864        Operands<VariableAccessData*> mapping(OperandsLike, m_graph.block(0)->variablesAtHead);
     6865
     6866        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
     6867            if (blocksToIgnore.contains(block))
     6868                continue;
     6869
     6870            mapping.fill(nullptr);
     6871            if (validationEnabled()) {
     6872                // Verify that it's correct to fill mapping with nullptr.
     6873                for (unsigned i = 0; i < block->variablesAtHead.size(); ++i) {
     6874                    Node* node = block->variablesAtHead.at(i);
     6875                    RELEASE_ASSERT(!node);
     6876                }
     6877            }
     6878
     6879            for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
     6880                Node* node = block->at(nodeIndex);
     6881
     6882                if (node->hasVariableAccessData(m_graph))
     6883                    mapping.operand(node->local()) = node->variableAccessData();
     6884
     6885                if (node->op() == ForceOSRExit) {
     6886                    NodeOrigin endOrigin = node->origin.withExitOK(true);
     6887
     6888                    if (validationEnabled()) {
     6889                        // This verifies that we don't need to change any of the successors's predecessor
     6890                        // list after planting the Unreachable below. At this point in the bytecode
     6891                        // parser, we haven't linked up the predecessor lists yet.
     6892                        for (BasicBlock* successor : block->successors())
     6893                            RELEASE_ASSERT(successor->predecessors.isEmpty());
     6894                    }
     6895
     6896                    block->resize(nodeIndex + 1);
     6897
     6898                    insertionSet.insertNode(block->size(), SpecNone, ExitOK, endOrigin);
     6899
     6900                    auto insertLivenessPreservingOp = [&] (InlineCallFrame* inlineCallFrame, NodeType op, VirtualRegister operand) {
     6901                        VariableAccessData* variable = mapping.operand(operand);
     6902                        if (!variable) {
     6903                            variable = newVariableAccessData(operand);
     6904                            mapping.operand(operand) = variable;
     6905                        }
     6906
     6907                        VirtualRegister argument = operand - (inlineCallFrame ? inlineCallFrame->stackOffset : 0);
     6908                        if (argument.isArgument() && !argument.isHeader()) {
     6909                            const Vector<ArgumentPosition*>& arguments = m_inlineCallFrameToArgumentPositions.get(inlineCallFrame);
     6910                            arguments[argument.toArgument()]->addVariable(variable);
     6911                        }
     6912
     6913                        insertionSet.insertNode(block->size(), SpecNone, op, endOrigin, OpInfo(variable));
     6914                    };
     6915                    auto addFlushDirect = [&] (InlineCallFrame* inlineCallFrame, VirtualRegister operand) {
     6916                        insertLivenessPreservingOp(inlineCallFrame, Flush, operand);
     6917                    };
     6918                    auto addPhantomLocalDirect = [&] (InlineCallFrame* inlineCallFrame, VirtualRegister operand) {
     6919                        insertLivenessPreservingOp(inlineCallFrame, PhantomLocal, operand);
     6920                    };
     6921                    flushForTerminalImpl(endOrigin.semantic, addFlushDirect, addPhantomLocalDirect);
     6922
     6923                    insertionSet.insertNode(block->size(), SpecNone, Unreachable, endOrigin);
     6924                    insertionSet.execute(block);
     6925                    break;
     6926                }
     6927            }
     6928        }
     6929    } else if (validationEnabled()) {
     6930        // Ensure our bookkeeping for ForceOSRExit nodes is working.
     6931        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
     6932            for (Node* node : *block)
     6933                RELEASE_ASSERT(node->op() != ForceOSRExit);
     6934        }
     6935    }
     6936   
    68356937    m_graph.determineReachability();
    68366938    m_graph.killUnreachableBlocks();
  • trunk/Source/JavaScriptCore/dfg/DFGGraph.cpp

    r232741 r232742  
    913913}
    914914
    915 BlockList Graph::blocksInPostOrder()
     915BlockList Graph::blocksInPostOrder(bool isSafeToValidate)
    916916{
    917917    BlockList result;
     
    932932    }
    933933
    934     if (validationEnabled()) {
     934    if (isSafeToValidate && validationEnabled()) { // There are users of this where we haven't yet built of the CFG enough to be able to run dominators.
    935935        auto validateResults = [&] (auto& dominators) {
    936936            // When iterating over reverse post order, we should see dominators
  • trunk/Source/JavaScriptCore/dfg/DFGGraph.h

    r232000 r232742  
    628628   
    629629    BlockList blocksInPreOrder();
    630     BlockList blocksInPostOrder();
     630    BlockList blocksInPostOrder(bool isSafeToValidate = true);
    631631   
    632632    class NaturalBlockIterable {
Note: See TracChangeset for help on using the changeset viewer.