blob: a93a6b3d33fbf26fa9cdf56934c928950b0993db [file] [log] [blame]
/*
* Copyright (C) 2013-2020 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"
#include "DFGSSAConversionPhase.h"
#if ENABLE(DFG_JIT)
#include "DFGBasicBlockInlines.h"
#include "DFGBlockInsertionSet.h"
#include "DFGGraph.h"
#include "DFGInsertionSet.h"
#include "DFGPhase.h"
#include "DFGSSACalculator.h"
#include "DFGVariableAccessDataDump.h"
#include "JSCJSValueInlines.h"
#include "OperandsInlines.h"
#undef RELEASE_ASSERT
#define RELEASE_ASSERT(assertion) do { \
if (!(assertion)) { \
WTFReportAssertionFailure(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, #assertion); \
CRASH(); \
} \
} while (0)
namespace JSC { namespace DFG {
class SSAConversionPhase : public Phase {
static constexpr bool verbose = false;
public:
SSAConversionPhase(Graph& graph)
: Phase(graph, "SSA conversion")
, m_insertionSet(graph)
{
}
bool run()
{
RELEASE_ASSERT(m_graph.m_form == ThreadedCPS);
RELEASE_ASSERT(!m_graph.m_isInSSAConversion);
m_graph.m_isInSSAConversion = true;
m_graph.clearReplacements();
m_graph.clearCPSCFGData();
HashMap<unsigned, BasicBlock*, WTF::IntHash<unsigned>, WTF::UnsignedWithZeroKeyHashTraits<unsigned>> entrypointIndexToArgumentsBlock;
m_graph.m_numberOfEntrypoints = m_graph.m_roots.size();
m_graph.m_argumentFormats.resize(m_graph.m_numberOfEntrypoints);
for (unsigned entrypointIndex = 0; entrypointIndex < m_graph.m_numberOfEntrypoints; ++entrypointIndex) {
BasicBlock* oldRoot = m_graph.m_roots[entrypointIndex];
entrypointIndexToArgumentsBlock.add(entrypointIndex, oldRoot);
NodeOrigin origin = oldRoot->at(0)->origin;
m_insertionSet.insertNode(
0, SpecNone, InitializeEntrypointArguments, origin, OpInfo(entrypointIndex));
m_insertionSet.insertNode(
0, SpecNone, ExitOK, origin);
m_insertionSet.execute(oldRoot);
}
if (m_graph.m_numberOfEntrypoints > 1) {
BlockInsertionSet blockInsertionSet(m_graph);
BasicBlock* newRoot = blockInsertionSet.insert(0, 1.0f);
EntrySwitchData* entrySwitchData = m_graph.m_entrySwitchData.add();
for (unsigned entrypointIndex = 0; entrypointIndex < m_graph.m_numberOfEntrypoints; ++entrypointIndex) {
BasicBlock* oldRoot = m_graph.m_roots[entrypointIndex];
entrySwitchData->cases.append(oldRoot);
ASSERT(oldRoot->predecessors.isEmpty());
oldRoot->predecessors.append(newRoot);
if (oldRoot->isCatchEntrypoint) {
ASSERT(!!entrypointIndex);
m_graph.m_entrypointIndexToCatchBytecodeIndex.add(entrypointIndex, oldRoot->bytecodeBegin);
}
}
RELEASE_ASSERT(entrySwitchData->cases[0] == m_graph.block(0)); // We strongly assume the normal call entrypoint is the first item in the list.
const bool exitOK = false;
NodeOrigin origin { CodeOrigin(BytecodeIndex(0)), CodeOrigin(BytecodeIndex(0)), exitOK };
newRoot->appendNode(
m_graph, SpecNone, EntrySwitch, origin, OpInfo(entrySwitchData));
m_graph.m_roots.clear();
m_graph.m_roots.append(newRoot);
blockInsertionSet.execute();
}
SSACalculator calculator(m_graph);
m_graph.ensureSSADominators();
if (verbose) {
dataLog("Graph before SSA transformation:\n");
m_graph.dump();
}
// Create a SSACalculator::Variable for every root VariableAccessData.
for (VariableAccessData& variable : m_graph.m_variableAccessData) {
if (!variable.isRoot())
continue;
SSACalculator::Variable* ssaVariable = calculator.newVariable();
ASSERT(ssaVariable->index() == m_variableForSSAIndex.size());
m_variableForSSAIndex.append(&variable);
m_ssaVariableForVariable.add(&variable, ssaVariable);
}
// Find all SetLocals and create Defs for them. We handle SetArgumentDefinitely by creating a
// GetLocal, and recording the flush format.
for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
BasicBlock* block = m_graph.block(blockIndex);
if (!block)
continue;
// Must process the block in forward direction because we want to see the last
// assignment for every local.
for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
Node* node = block->at(nodeIndex);
if (node->op() != SetLocal && node->op() != SetArgumentDefinitely)
continue;
VariableAccessData* variable = node->variableAccessData();
Node* childNode;
if (node->op() == SetLocal)
childNode = node->child1().node();
else {
ASSERT(node->op() == SetArgumentDefinitely);
childNode = m_insertionSet.insertNode(
nodeIndex, node->variableAccessData()->prediction(),
GetStack, node->origin,
OpInfo(m_graph.m_stackAccessData.add(variable->operand(), variable->flushFormat())));
if (ASSERT_ENABLED)
m_argumentGetters.add(childNode);
m_argumentMapping.add(node, childNode);
}
calculator.newDef(
m_ssaVariableForVariable.get(variable), block, childNode);
}
m_insertionSet.execute(block);
}
// Decide where Phis are to be inserted. This creates the Phi's but doesn't insert them
// yet. We will later know where to insert based on where SSACalculator tells us to.
calculator.computePhis(
[&] (SSACalculator::Variable* ssaVariable, BasicBlock* block) -> Node* {
VariableAccessData* variable = m_variableForSSAIndex[ssaVariable->index()];
// Prune by liveness. This doesn't buy us much other than compile times.
Node* headNode = block->variablesAtHead.operand(variable->operand());
if (!headNode)
return nullptr;
// There is the possibiltiy of "rebirths". The SSA calculator will already prune
// rebirths for the same VariableAccessData. But it will not be able to prune
// rebirths that arose from the same local variable number but a different
// VariableAccessData. We do that pruning here.
//
// Here's an example of a rebirth that this would catch:
//
// var x;
// if (foo) {
// if (bar) {
// x = 42;
// } else {
// x = 43;
// }
// print(x);
// x = 44;
// } else {
// x = 45;
// }
// print(x); // Without this check, we'd have a Phi for x = 42|43 here.
//
// FIXME: Consider feeding local variable numbers, not VariableAccessData*'s, as
// the "variables" for SSACalculator. That would allow us to eliminate this
// special case.
// https://bugs.webkit.org/show_bug.cgi?id=136641
if (headNode->variableAccessData() != variable)
return nullptr;
Node* phiNode = m_graph.addNode(
variable->prediction(), Phi, block->at(0)->origin.withInvalidExit());
FlushFormat format = variable->flushFormat();
NodeFlags result = resultFor(format);
phiNode->mergeFlags(result);
return phiNode;
});
if (verbose) {
dataLog("Computed Phis, about to transform the graph.\n");
dataLog("\n");
dataLog("Graph:\n");
m_graph.dump();
dataLog("\n");
dataLog("Mappings:\n");
for (unsigned i = 0; i < m_variableForSSAIndex.size(); ++i)
dataLog(" ", i, ": ", VariableAccessDataDump(m_graph, m_variableForSSAIndex[i]), "\n");
dataLog("\n");
dataLog("SSA calculator: ", calculator, "\n");
}
// Do the bulk of the SSA conversion. For each block, this tracks the operand->Node
// mapping based on a combination of what the SSACalculator tells us, and us walking over
// the block in forward order. We use our own data structure, valueForOperand, for
// determining the local mapping, but we rely on SSACalculator for the non-local mapping.
//
// This does three things at once:
//
// - Inserts the Phis in all of the places where they need to go. We've already created
// them and they are accounted for in the SSACalculator's data structures, but we
// haven't inserted them yet, mostly because we want to insert all of a block's Phis in
// one go to amortize the cost of node insertion.
//
// - Create and insert Upsilons.
//
// - Convert all of the preexisting SSA nodes (other than the old CPS Phi nodes) into SSA
// form by replacing as follows:
//
// - MovHint has KillLocal prepended to it.
//
// - GetLocal die and get replaced with references to the node specified by
// valueForOperand.
//
// - SetLocal turns into PutStack if it's flushed, or turns into a Check otherwise.
//
// - Flush is removed.
//
// - PhantomLocal becomes Phantom, and its child is whatever is specified by
// valueForOperand.
//
// - SetArgumentDefinitely is removed. Note that GetStack nodes have already been inserted.
//
// - SetArgumentMaybe is removed. It should not have any data flow uses.
Operands<Node*> valueForOperand(OperandsLike, m_graph.block(0)->variablesAtHead);
for (BasicBlock* block : m_graph.blocksInPreOrder()) {
valueForOperand.clear();
// CPS will claim that the root block has all arguments live. But we have already done
// the first step of SSA conversion: argument locals are no longer live at head;
// instead we have GetStack nodes for extracting the values of arguments. So, we
// skip the at-head available value calculation for the root block.
if (block != m_graph.block(0)) {
for (size_t i = valueForOperand.size(); i--;) {
Node* nodeAtHead = block->variablesAtHead[i];
if (!nodeAtHead)
continue;
VariableAccessData* variable = nodeAtHead->variableAccessData();
if (verbose)
dataLog("Considering live variable ", VariableAccessDataDump(m_graph, variable), " at head of block ", *block, "\n");
SSACalculator::Variable* ssaVariable = m_ssaVariableForVariable.get(variable);
SSACalculator::Def* def = calculator.reachingDefAtHead(block, ssaVariable);
if (!def) {
// If we are required to insert a Phi, then we won't have a reaching def
// at head.
continue;
}
Node* node = def->value();
if (node->replacement()) {
// This will occur when a SetLocal had a GetLocal as its source. The
// GetLocal would get replaced with an actual SSA value by the time we get
// here. Note that the SSA value with which the GetLocal got replaced
// would not in turn have a replacement.
node = node->replacement();
ASSERT(!node->replacement());
}
if (verbose)
dataLog("Mapping: ", valueForOperand.operandForIndex(i), " -> ", node, "\n");
valueForOperand[i] = node;
}
}
// Insert Phis by asking the calculator what phis there are in this block. Also update
// valueForOperand with those Phis. For Phis associated with variables that are not
// flushed, we also insert a MovHint.
size_t phiInsertionPoint = 0;
for (SSACalculator::Def* phiDef : calculator.phisForBlock(block)) {
VariableAccessData* variable = m_variableForSSAIndex[phiDef->variable()->index()];
m_insertionSet.insert(phiInsertionPoint, phiDef->value());
valueForOperand.operand(variable->operand()) = phiDef->value();
m_insertionSet.insertNode(
phiInsertionPoint, SpecNone, MovHint, block->at(0)->origin.withInvalidExit(),
OpInfo(variable->operand()), phiDef->value()->defaultEdge());
}
if (block->at(0)->origin.exitOK)
m_insertionSet.insertNode(phiInsertionPoint, SpecNone, ExitOK, block->at(0)->origin);
for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
Node* node = block->at(nodeIndex);
if (verbose) {
dataLog("Processing node ", node, ":\n");
m_graph.dump(WTF::dataFile(), " ", node);
}
m_graph.performSubstitution(node);
switch (node->op()) {
case MovHint: {
m_insertionSet.insertNode(
nodeIndex, SpecNone, KillStack, node->origin,
OpInfo(node->unlinkedOperand()));
node->origin.exitOK = false; // KillStack clobbers exit.
break;
}
case SetLocal: {
VariableAccessData* variable = node->variableAccessData();
Node* child = node->child1().node();
if (!!(node->flags() & NodeIsFlushed)) {
node->convertToPutStack(
m_graph.m_stackAccessData.add(
variable->operand(), variable->flushFormat()));
} else
node->remove(m_graph);
if (verbose)
dataLog("Mapping: ", variable->operand(), " -> ", child, "\n");
valueForOperand.operand(variable->operand()) = child;
break;
}
case GetStack: {
ASSERT(m_argumentGetters.contains(node));
valueForOperand.operand(node->stackAccessData()->operand) = node;
break;
}
case GetLocal: {
VariableAccessData* variable = node->variableAccessData();
node->children.reset();
node->remove(m_graph);
if (verbose)
dataLog("Replacing node ", node, " with ", valueForOperand.operand(variable->operand()), "\n");
node->setReplacement(valueForOperand.operand(variable->operand()));
break;
}
case Flush: {
node->children.reset();
node->remove(m_graph);
break;
}
case PhantomLocal: {
ASSERT(node->child1().useKind() == UntypedUse);
VariableAccessData* variable = node->variableAccessData();
node->child1() = valueForOperand.operand(variable->operand())->defaultEdge();
node->remove(m_graph);
break;
}
case SetArgumentDefinitely: {
node->remove(m_graph);
break;
}
case SetArgumentMaybe: {
node->remove(m_graph);
break;
}
default:
break;
}
}
// We want to insert Upsilons just before the end of the block. On the surface this
// seems dangerous because the Upsilon will have a checking UseKind. But, we will not
// actually be performing the check at the point of the Upsilon; the check will
// already have been performed at the point where the original SetLocal was.
NodeAndIndex terminal = block->findTerminal();
size_t upsilonInsertionPoint = terminal.index;
NodeOrigin upsilonOrigin = terminal.node->origin;
for (unsigned successorIndex = block->numSuccessors(); successorIndex--;) {
BasicBlock* successorBlock = block->successor(successorIndex);
for (SSACalculator::Def* phiDef : calculator.phisForBlock(successorBlock)) {
Node* phiNode = phiDef->value();
SSACalculator::Variable* ssaVariable = phiDef->variable();
VariableAccessData* variable = m_variableForSSAIndex[ssaVariable->index()];
FlushFormat format = variable->flushFormat();
// We can use an unchecked use kind because the SetLocal was turned into a Check.
// We have to use an unchecked use because at least sometimes, the end of the block
// is not exitOK.
UseKind useKind = uncheckedUseKindFor(format);
dataLogLnIf(verbose, "Inserting Upsilon for ", variable->operand(), " propagating ", valueForOperand.operand(variable->operand()), " to ", phiNode);
m_insertionSet.insertNode(
upsilonInsertionPoint, SpecNone, Upsilon, upsilonOrigin,
OpInfo(phiNode), Edge(
valueForOperand.operand(variable->operand()),
useKind));
}
}
m_insertionSet.execute(block);
}
// 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.deleteNode(block->phis[phiIndex]);
block->phis.clear();
block->variablesAtHead.clear();
block->variablesAtTail.clear();
block->valuesAtHead.clear();
block->valuesAtHead.clear();
block->ssa = makeUnique<BasicBlock::SSAData>(block);
}
for (auto& pair : entrypointIndexToArgumentsBlock) {
unsigned entrypointIndex = pair.key;
BasicBlock* oldRoot = pair.value;
ArgumentsVector& arguments = m_graph.m_rootToArguments.find(oldRoot)->value;
Vector<FlushFormat> argumentFormats;
argumentFormats.reserveInitialCapacity(arguments.size());
for (unsigned i = 0; i < arguments.size(); ++i) {
Node* node = m_argumentMapping.get(arguments[i]);
RELEASE_ASSERT(node);
argumentFormats.uncheckedAppend(node->stackAccessData()->format);
}
m_graph.m_argumentFormats[entrypointIndex] = WTFMove(argumentFormats);
}
m_graph.m_rootToArguments.clear();
RELEASE_ASSERT(m_graph.m_isInSSAConversion);
m_graph.m_isInSSAConversion = false;
m_graph.m_form = SSA;
if (verbose) {
dataLog("Graph after SSA transformation:\n");
m_graph.dump();
}
return true;
}
private:
InsertionSet m_insertionSet;
HashMap<VariableAccessData*, SSACalculator::Variable*> m_ssaVariableForVariable;
HashMap<Node*, Node*> m_argumentMapping;
HashSet<Node*> m_argumentGetters;
Vector<VariableAccessData*> m_variableForSSAIndex;
};
bool performSSAConversion(Graph& graph)
{
bool result = runPhase<SSAConversionPhase>(graph);
return result;
}
} } // namespace JSC::DFG
#endif // ENABLE(DFG_JIT)