blob: df6d5c93a9bf2e6d552ccaee153933da88c04317 [file] [log] [blame]
/*
* 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)