Ignore:
Timestamp:
Aug 28, 2015, 3:38:41 PM (10 years ago)
Author:
[email protected]
Message:

LICM should be sound even if the CFG has changed
https://bugs.webkit.org/show_bug.cgi?id=148259

Reviewed by Benjamin Poulain.

Source/JavaScriptCore:

Prior to this change, LICM expected a certain CFG shape around a loop: broken critical edges,
a pre-header, and the pre-header's terminal has exitOK. LICM would either crash on an
assertion, or generate code that fails validation, if these conditions weren't met.

The broken critical edge assumption is fine; so far we are assuming that SSA means broken
critical edges. We may revisit this, but we don't have to right now.

The other assumptions are not fine, because it's hard to guarantee that every phase will
preserve the presence of pre-headers. Even if we required that pre-headers are regenerated
before LICM, that regeneration wouldn't be guaranteed to create pre-headers that have exitOK at
the terminal. That's because once in SSA, the loop header probably has exitOK=false at the
head because of Phi's. Pre-header creation has no choice but to use the Node::origin from the
loop header, which means creating a pre-header that has exitOK=false. Regardless of whether
that's a fixable problem, it seems that our best short-term approach is just to be defensive
and turn undesirable pathologies into performance bugs and not crashes.

For the foreseeable future, once pre-headers are created they will probably not be removed. Our
current CFG simplification phase doesn't have a rule for removing pre-headers (since it doesn't
have any jump threading). So, it wouldn't be profitable to put effort towards reneration of
pre-headers for LICM's benefit.

Also, we cannot guarantee that some sequence of CFG transformations will not create a loop that
doesn't have a pre-header. This would be super rare. But you could imagine that some program
has control flow encoded using relooping (like
https://github.com/kripken/Relooper/blob/master/paper.pdf). If that happens, our compiler will
probably incrementally discover the "original" CFG. That may happen only after SSA conversion,
and so after pre-header generation. This is super unlikely for a bunch of reasons, but it
*could* happen.

So, this patch just makes sure that if pre-headers are missing or cannot be exited from, LICM
will simply avoid hoisting out of that block. At some point later, we can worry about a more
comprehensive solution to the pre-header problem. That's covered by this bug:
https://bugs.webkit.org/show_bug.cgi?id=148586

  • dfg/DFGLICMPhase.cpp:

(JSC::DFG::LICMPhase::run):
(JSC::DFG::LICMPhase::attemptHoist):

  • dfg/DFGPlan.cpp:

(JSC::DFG::Plan::compileInThreadImpl):

  • runtime/Options.h:
  • tests/stress/licm-no-pre-header.js: Added.

(foo):

  • tests/stress/licm-pre-header-cannot-exit.js: Added.

(foo):

Tools:

Add a utility for creating tests that set some uncommon option.

  • Scripts/run-jsc-stress-tests:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/dfg/DFGLICMPhase.cpp

    r189070 r189126  
    4747struct LoopData {
    4848    LoopData()
    49         : preHeader(0)
     49        : preHeader(nullptr)
    5050    {
    5151    }
     
    113113            const NaturalLoop& loop = m_graph.m_naturalLoops.loop(loopIndex);
    114114            LoopData& data = m_data[loop.index()];
     115           
    115116            for (
    116117                const NaturalLoop* outerLoop = m_graph.m_naturalLoops.innerMostOuterLoop(loop);
     
    120121           
    121122            BasicBlock* header = loop.header();
    122             BasicBlock* preHeader = 0;
     123            BasicBlock* preHeader = nullptr;
     124            unsigned numberOfPreHeaders = 0; // We're cool if this is 1.
     125
     126            // This is guaranteed because we expect the CFG not to have unreachable code. Therefore, a
     127            // loop header must have a predecessor. (Also, we don't allow the root block to be a loop,
     128            // which cuts out the one other way of having a loop header with only one predecessor.)
     129            DFG_ASSERT(m_graph, header->at(0), header->predecessors.size() > 1);
     130           
    123131            for (unsigned i = header->predecessors.size(); i--;) {
    124132                BasicBlock* predecessor = header->predecessors[i];
    125133                if (m_graph.m_dominators.dominates(header, predecessor))
    126134                    continue;
    127                 DFG_ASSERT(m_graph, nullptr, !preHeader || preHeader == predecessor);
     135
    128136                preHeader = predecessor;
    129             }
    130            
     137                ++numberOfPreHeaders;
     138            }
     139
     140            // We need to validate the pre-header. There are a bunch of things that could be wrong
     141            // about it:
     142            //
     143            // - There might be more than one. This means that pre-header creation either did not run,
     144            //   or some CFG transformation destroyed the pre-headers.
     145            //
     146            // - It may not be legal to exit at the pre-header. That would be a real bummer. Currently,
     147            //   LICM assumes that it can always hoist checks. See
     148            //   https://bugs.webkit.org/show_bug.cgi?id=148545. Though even with that fixed, we anyway
     149            //   would need to check if it's OK to exit at the pre-header since if we can't then we
     150            //   would have to restrict hoisting to non-exiting nodes.
     151
     152            if (numberOfPreHeaders != 1)
     153                continue;
     154
     155            // This is guaranteed because the header has multiple predecessors and critical edges are
     156            // broken. Therefore the predecessors must all have one successor, which implies that they
     157            // must end in a Jump.
    131158            DFG_ASSERT(m_graph, preHeader->terminal(), preHeader->terminal()->op() == Jump);
    132            
    133             // We should validate the pre-header. This currently assumes that it's valid to OSR exit at
    134             // the Jump of the pre-header. That may not always be the case, like if we lowered a Node
    135             // that was ExitInvalid to a loop. This phase should somehow defend against this - at the
    136             // very least with assertions, if not with something better (like only hoisting things that
    137             // cannot exit).
    138             // FIXME: https://bugs.webkit.org/show_bug.cgi?id=148259
     159
     160            if (!preHeader->terminal()->origin.exitOK)
     161                continue;
    139162           
    140163            data.preHeader = preHeader;
     
    147170        // - The node doesn't write anything.
    148171        // - The node doesn't read anything that the loop writes.
     172        // - The preHeader is valid (i.e. it passed the validation above).
    149173        // - The preHeader's state at tail makes the node safe to execute.
    150174        // - The loop's children all belong to nodes that strictly dominate the loop header.
     
    206230        Node* node = nodeRef;
    207231        LoopData& data = m_data[loop->index()];
     232
     233        if (!data.preHeader) {
     234            if (verbose)
     235                dataLog("    Not hoisting ", node, " because the pre-header is invalid.\n");
     236            return false;
     237        }
    208238       
    209239        if (!data.preHeader->cfaDidFinish) {
Note: See TracChangeset for help on using the changeset viewer.