| /* |
| * Copyright (C) 2013, 2014 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| |
| #if ENABLE(DFG_JIT) |
| |
| #include "DFGSSAConversionPhase.h" |
| |
| #include "DFGBasicBlockInlines.h" |
| #include "DFGGraph.h" |
| #include "DFGInsertionSet.h" |
| #include "DFGPhase.h" |
| #include "Operations.h" |
| |
| namespace JSC { namespace DFG { |
| |
| class SSAConversionPhase : public Phase { |
| static const bool verbose = false; |
| static const bool dumpGraph = false; |
| |
| public: |
| SSAConversionPhase(Graph& graph) |
| : Phase(graph, "SSA conversion") |
| , m_insertionSet(graph) |
| , m_changed(false) |
| { |
| } |
| |
| bool run() |
| { |
| RELEASE_ASSERT(m_graph.m_form == ThreadedCPS); |
| |
| if (dumpGraph) { |
| dataLog("Graph dump at top of SSA conversion:\n"); |
| m_graph.dump(); |
| } |
| |
| // Figure out which SetLocal's need flushing. Need to do this while the |
| // Phi graph is still intact. |
| for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
| BasicBlock* block = m_graph.block(blockIndex); |
| if (!block) |
| continue; |
| for (unsigned nodeIndex = block->size(); nodeIndex--;) { |
| Node* node = block->at(nodeIndex); |
| if (node->op() != Flush) |
| continue; |
| addFlushedLocalOp(node); |
| } |
| } |
| while (!m_flushedLocalOpWorklist.isEmpty()) { |
| Node* node = m_flushedLocalOpWorklist.takeLast(); |
| ASSERT(m_flushedLocalOps.contains(node)); |
| DFG_NODE_DO_TO_CHILDREN(m_graph, node, addFlushedLocalEdge); |
| } |
| |
| // Eliminate all duplicate or self-pointing Phi edges. This means that |
| // we transform: |
| // |
| // p: Phi(@n1, @n2, @n3) |
| // |
| // into: |
| // |
| // p: Phi(@x) |
| // |
| // if each @ni in {@n1, @n2, @n3} is either equal to @p to is equal |
| // to @x, for exactly one other @x. Additionally, trivial Phis (i.e. |
| // p: Phi(@x)) are forwarded, so that if have an edge to such @p, we |
| // replace it with @x. This loop does this for Phis only; later we do |
| // such forwarding for Phi references found in other nodes. |
| // |
| // See Aycock and Horspool in CC'00 for a better description of what |
| // we're doing here. |
| do { |
| m_changed = false; |
| for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
| BasicBlock* block = m_graph.block(blockIndex); |
| if (!block) |
| continue; |
| for (unsigned phiIndex = block->phis.size(); phiIndex--;) { |
| Node* phi = block->phis[phiIndex]; |
| if (phi->variableAccessData()->isCaptured()) |
| continue; |
| forwardPhiChildren(phi); |
| deduplicateChildren(phi); |
| } |
| } |
| } while (m_changed); |
| |
| // For each basic block, for each local live at the head of that block, |
| // figure out what node we should be referring to instead of that local. |
| // If it turns out to be a non-trivial Phi, make sure that we create an |
| // SSA Phi and Upsilons in predecessor blocks. We reuse |
| // BasicBlock::variablesAtHead for tracking which nodes to refer to. |
| Operands<bool> nonTrivialPhis(OperandsLike, m_graph.block(0)->variablesAtHead); |
| for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
| BasicBlock* block = m_graph.block(blockIndex); |
| if (!block) |
| continue; |
| |
| nonTrivialPhis.fill(false); |
| for (unsigned i = block->phis.size(); i--;) { |
| Node* phi = block->phis[i]; |
| if (!phi->children.justOneChild()) |
| nonTrivialPhis.operand(phi->local()) = true; |
| } |
| |
| for (unsigned i = block->variablesAtHead.size(); i--;) { |
| Node* node = block->variablesAtHead[i]; |
| if (!node) |
| continue; |
| |
| if (verbose) |
| dataLog("At block #", blockIndex, " for operand r", block->variablesAtHead.operandForIndex(i), " have node ", node, "\n"); |
| |
| VariableAccessData* variable = node->variableAccessData(); |
| if (variable->isCaptured()) { |
| // Poison this entry in variablesAtHead because we don't |
| // want anyone to try to refer to it, if the variable is |
| // captured. |
| block->variablesAtHead[i] = 0; |
| continue; |
| } |
| |
| switch (node->op()) { |
| case Phi: |
| case SetArgument: |
| break; |
| case Flush: |
| case GetLocal: |
| case PhantomLocal: |
| node = node->child1().node(); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| RELEASE_ASSERT(node->op() == Phi || node->op() == SetArgument); |
| |
| bool isFlushed = m_flushedLocalOps.contains(node); |
| |
| if (node->op() == Phi) { |
| if (!nonTrivialPhis.operand(node->local())) { |
| Edge edge = node->children.justOneChild(); |
| ASSERT(edge); |
| if (verbose) |
| dataLog(" One child: ", edge, ", ", RawPointer(edge.node()), "\n"); |
| node = edge.node(); // It's something from a different basic block. |
| } else { |
| if (verbose) |
| dataLog(" Non-trivial.\n"); |
| // It's a non-trivial Phi. |
| FlushFormat format = variable->flushFormat(); |
| NodeFlags result = resultFor(format); |
| UseKind useKind = useKindFor(format); |
| |
| node = m_insertionSet.insertNode(0, SpecNone, Phi, CodeOrigin()); |
| if (verbose) |
| dataLog(" Inserted new node: ", node, "\n"); |
| node->mergeFlags(result); |
| RELEASE_ASSERT((node->flags() & NodeResultMask) == result); |
| |
| for (unsigned j = block->predecessors.size(); j--;) { |
| BasicBlock* predecessor = block->predecessors[j]; |
| predecessor->appendNonTerminal( |
| m_graph, SpecNone, Upsilon, predecessor->last()->codeOrigin, |
| OpInfo(node), Edge(predecessor->variablesAtTail[i], useKind)); |
| } |
| |
| if (isFlushed) { |
| // Do nothing. For multiple reasons. |
| |
| // Reason #1: If the local is flushed then we don't need to bother |
| // with a MovHint since every path to this point in the code will |
| // have flushed the bytecode variable using a SetLocal and hence |
| // the Availability::flushedAt() will agree, and that will be |
| // sufficient for figuring out how to recover the variable's value. |
| |
| // Reason #2: If we had inserted a MovHint and the Phi function had |
| // died (because the only user of the value was the "flush" - i.e. |
| // some asynchronous runtime thingy) then the MovHint would turn |
| // into a ZombieHint, which would fool us into thinking that the |
| // variable is dead. |
| |
| // Reason #3: If we had inserted a MovHint then even if the Phi |
| // stayed alive, we would still end up generating inefficient code |
| // since we would be telling the OSR exit compiler to use some SSA |
| // value for the bytecode variable rather than just telling it that |
| // the value was already on the stack. |
| } else { |
| m_insertionSet.insertNode( |
| 0, SpecNone, MovHint, CodeOrigin(), |
| OpInfo(variable->local().offset()), Edge(node)); |
| } |
| } |
| } |
| |
| block->variablesAtHead[i] = node; |
| } |
| |
| m_insertionSet.execute(block); |
| } |
| |
| if (verbose) { |
| dataLog("Variables at head after SSA Phi insertion:\n"); |
| for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
| BasicBlock* block = m_graph.block(blockIndex); |
| if (!block) |
| continue; |
| dataLog(" ", *block, ": ", block->variablesAtHead, "\n"); |
| } |
| } |
| |
| // At this point variablesAtHead in each block refers to either: |
| // |
| // 1) A new SSA phi in the current block. |
| // 2) A SetArgument, which will soon get converted into a GetArgument. |
| // 3) An old CPS phi in a different block. |
| // |
| // We don't have to do anything for (1) and (2), but we do need to |
| // do a replacement for (3). |
| |
| // Clear all replacements, since other phases may have used them. |
| m_graph.clearReplacements(); |
| |
| if (dumpGraph) { |
| dataLog("Graph just before identifying replacements:\n"); |
| m_graph.dump(); |
| } |
| |
| // For all of the old CPS Phis, figure out what they correspond to in SSA. |
| for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
| BasicBlock* block = m_graph.block(blockIndex); |
| if (!block) |
| continue; |
| if (verbose) |
| dataLog("Dealing with block #", blockIndex, "\n"); |
| for (unsigned phiIndex = block->phis.size(); phiIndex--;) { |
| Node* phi = block->phis[phiIndex]; |
| if (verbose) { |
| dataLog( |
| "Considering ", phi, " (", RawPointer(phi), "), for r", |
| phi->local(), ", and its replacement in ", *block, ", ", |
| block->variablesAtHead.operand(phi->local()), "\n"); |
| } |
| ASSERT(phi != block->variablesAtHead.operand(phi->local())); |
| phi->misc.replacement = block->variablesAtHead.operand(phi->local()); |
| } |
| } |
| |
| // Now make sure that all variablesAtHead in each block points to the |
| // canonical SSA value. Prior to this, variablesAtHead[local] may point to |
| // an old CPS Phi in a different block. |
| for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
| BasicBlock* block = m_graph.block(blockIndex); |
| if (!block) |
| continue; |
| for (size_t i = block->variablesAtHead.size(); i--;) { |
| Node* node = block->variablesAtHead[i]; |
| if (!node) |
| continue; |
| while (node->misc.replacement) { |
| ASSERT(node != node->misc.replacement); |
| node = node->misc.replacement; |
| } |
| block->variablesAtHead[i] = node; |
| } |
| } |
| |
| if (verbose) { |
| dataLog("Variables at head after convergence:\n"); |
| for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
| BasicBlock* block = m_graph.block(blockIndex); |
| if (!block) |
| continue; |
| dataLog(" ", *block, ": ", block->variablesAtHead, "\n"); |
| } |
| } |
| |
| // Convert operations over locals into operations over SSA nodes. |
| // - GetLocal over captured variables lose their phis. |
| // - GetLocal over uncaptured variables die and get replaced with references |
| // to the node specified by variablesAtHead. |
| // - SetLocal gets NodeMustGenerate if it's flushed, or turns into a |
| // Check otherwise. |
| // - Flush loses its children but remains, because we want to know when a |
| // flushed SetLocal's value is no longer needed. This also makes it simpler |
| // to reason about the format of a local, since we can just do a backwards |
| // analysis (see FlushLivenessAnalysisPhase). As part of the backwards |
| // analysis, we say that the type of a local can be either int32, double, |
| // value, or dead. |
| // - PhantomLocal becomes Phantom, and its child is whatever is specified |
| // by variablesAtHead. |
| // - SetArgument turns into GetArgument unless it's a captured variable. |
| // - Upsilons get their children fixed to refer to the true value of that local |
| // at the end of the block. Prior to this loop, Upsilons will refer to |
| // variableAtTail[operand], which may be any of Flush, PhantomLocal, GetLocal, |
| // SetLocal, SetArgument, or Phi. We accomplish this by setting the |
| // replacement pointers of all of those nodes to refer to either |
| // variablesAtHead[operand], or the child of the SetLocal. |
| for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
| BasicBlock* block = m_graph.block(blockIndex); |
| if (!block) |
| continue; |
| |
| for (unsigned phiIndex = block->phis.size(); phiIndex--;) { |
| block->phis[phiIndex]->misc.replacement = |
| block->variablesAtHead.operand(block->phis[phiIndex]->local()); |
| } |
| for (unsigned nodeIndex = block->size(); nodeIndex--;) |
| ASSERT(!block->at(nodeIndex)->misc.replacement); |
| |
| for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) { |
| Node* node = block->at(nodeIndex); |
| |
| m_graph.performSubstitution(node); |
| |
| switch (node->op()) { |
| case SetLocal: { |
| VariableAccessData* variable = node->variableAccessData(); |
| if (variable->isCaptured() || m_flushedLocalOps.contains(node)) |
| node->mergeFlags(NodeMustGenerate); |
| else |
| node->setOpAndDefaultFlags(Check); |
| node->misc.replacement = node->child1().node(); // Only for Upsilons. |
| break; |
| } |
| |
| case GetLocal: { |
| // It seems tempting to just do forwardPhi(GetLocal), except that we |
| // could have created a new (SSA) Phi, and the GetLocal could still be |
| // referring to an old (CPS) Phi. Uses variablesAtHead to tell us what |
| // to refer to. |
| node->children.reset(); |
| VariableAccessData* variable = node->variableAccessData(); |
| if (variable->isCaptured()) |
| break; |
| node->convertToPhantom(); |
| node->misc.replacement = block->variablesAtHead.operand(variable->local()); |
| break; |
| } |
| |
| case Flush: { |
| node->children.reset(); |
| // This is only for Upsilons. An Upsilon will only refer to a Flush if |
| // there were no SetLocals or GetLocals in the block. |
| node->misc.replacement = block->variablesAtHead.operand(node->local()); |
| break; |
| } |
| |
| case PhantomLocal: { |
| VariableAccessData* variable = node->variableAccessData(); |
| if (variable->isCaptured()) |
| break; |
| node->child1().setNode(block->variablesAtHead.operand(variable->local())); |
| node->convertToPhantom(); |
| // This is only for Upsilons. An Upsilon will only refer to a |
| // PhantomLocal if there were no SetLocals or GetLocals in the block. |
| node->misc.replacement = block->variablesAtHead.operand(variable->local()); |
| break; |
| } |
| |
| case SetArgument: { |
| VariableAccessData* variable = node->variableAccessData(); |
| if (variable->isCaptured()) |
| break; |
| node->setOpAndDefaultFlags(GetArgument); |
| node->mergeFlags(resultFor(node->variableAccessData()->flushFormat())); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| } |
| |
| // Free all CPS phis and reset variables vectors. |
| for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
| BasicBlock* block = m_graph.block(blockIndex); |
| if (!block) |
| continue; |
| for (unsigned phiIndex = block->phis.size(); phiIndex--;) |
| m_graph.m_allocator.free(block->phis[phiIndex]); |
| block->phis.clear(); |
| block->variablesAtHead.clear(); |
| block->variablesAtTail.clear(); |
| block->valuesAtHead.clear(); |
| block->valuesAtHead.clear(); |
| block->ssa = adoptPtr(new BasicBlock::SSAData(block)); |
| } |
| |
| m_graph.m_arguments.clear(); |
| |
| m_graph.m_form = SSA; |
| return true; |
| } |
| |
| private: |
| void forwardPhiChildren(Node* node) |
| { |
| for (unsigned i = 0; i < AdjacencyList::Size; ++i) { |
| Edge& edge = node->children.child(i); |
| if (!edge) |
| break; |
| m_changed |= forwardPhiEdge(edge); |
| } |
| } |
| |
| Node* forwardPhi(Node* node) |
| { |
| for (;;) { |
| switch (node->op()) { |
| case Phi: { |
| Edge edge = node->children.justOneChild(); |
| if (!edge) |
| return node; |
| node = edge.node(); |
| break; |
| } |
| case GetLocal: |
| case SetLocal: |
| if (node->variableAccessData()->isCaptured()) |
| return node; |
| node = node->child1().node(); |
| break; |
| default: |
| return node; |
| } |
| } |
| } |
| |
| bool forwardPhiEdge(Edge& edge) |
| { |
| Node* newNode = forwardPhi(edge.node()); |
| if (newNode == edge.node()) |
| return false; |
| edge.setNode(newNode); |
| return true; |
| } |
| |
| void deduplicateChildren(Node* node) |
| { |
| for (unsigned i = 0; i < AdjacencyList::Size; ++i) { |
| Edge edge = node->children.child(i); |
| if (!edge) |
| break; |
| if (edge == node) { |
| node->children.removeEdge(i--); |
| m_changed = true; |
| continue; |
| } |
| for (unsigned j = i + 1; j < AdjacencyList::Size; ++j) { |
| if (node->children.child(j) == edge) { |
| node->children.removeEdge(j--); |
| m_changed = true; |
| } |
| } |
| } |
| } |
| |
| void addFlushedLocalOp(Node* node) |
| { |
| if (m_flushedLocalOps.contains(node)) |
| return; |
| m_flushedLocalOps.add(node); |
| m_flushedLocalOpWorklist.append(node); |
| } |
| |
| void addFlushedLocalEdge(Node*, Edge edge) |
| { |
| addFlushedLocalOp(edge.node()); |
| } |
| |
| InsertionSet m_insertionSet; |
| HashSet<Node*> m_flushedLocalOps; |
| Vector<Node*> m_flushedLocalOpWorklist; |
| bool m_changed; |
| }; |
| |
| bool performSSAConversion(Graph& graph) |
| { |
| SamplingRegion samplingRegion("DFG SSA Conversion Phase"); |
| return runPhase<SSAConversionPhase>(graph); |
| } |
| |
| } } // namespace JSC::DFG |
| |
| #endif // ENABLE(DFG_JIT) |
| |