blob: 273e3571a7389ad118dea01bec13e3d8b99381d8 [file] [log] [blame]
/*
* Copyright (C) 2011 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 "DFGGraph.h"
#include "CodeBlock.h"
#include "DFGVariableAccessDataDump.h"
#if ENABLE(DFG_JIT)
namespace JSC { namespace DFG {
// Creates an array of stringized names.
static const char* dfgOpNames[] = {
#define STRINGIZE_DFG_OP_ENUM(opcode, flags) #opcode ,
FOR_EACH_DFG_OP(STRINGIZE_DFG_OP_ENUM)
#undef STRINGIZE_DFG_OP_ENUM
};
Graph::Graph(JSGlobalData& globalData, CodeBlock* codeBlock, unsigned osrEntryBytecodeIndex, const Operands<JSValue>& mustHandleValues)
: m_globalData(globalData)
, m_codeBlock(codeBlock)
, m_compilation(globalData.m_perBytecodeProfiler ? globalData.m_perBytecodeProfiler->newCompilation(codeBlock, Profiler::DFG) : 0)
, m_profiledBlock(codeBlock->alternative())
, m_hasArguments(false)
, m_osrEntryBytecodeIndex(osrEntryBytecodeIndex)
, m_mustHandleValues(mustHandleValues)
, m_fixpointState(BeforeFixpoint)
{
ASSERT(m_profiledBlock);
}
const char *Graph::opName(NodeType op)
{
return dfgOpNames[op];
}
static void printWhiteSpace(PrintStream& out, unsigned amount)
{
while (amount-- > 0)
out.print(" ");
}
bool Graph::dumpCodeOrigin(PrintStream& out, const char* prefix, NodeIndex prevNodeIndex, NodeIndex nodeIndex)
{
if (prevNodeIndex == NoNode)
return false;
Node& currentNode = at(nodeIndex);
Node& previousNode = at(prevNodeIndex);
if (previousNode.codeOrigin.inlineCallFrame == currentNode.codeOrigin.inlineCallFrame)
return false;
Vector<CodeOrigin> previousInlineStack = previousNode.codeOrigin.inlineStack();
Vector<CodeOrigin> currentInlineStack = currentNode.codeOrigin.inlineStack();
unsigned commonSize = std::min(previousInlineStack.size(), currentInlineStack.size());
unsigned indexOfDivergence = commonSize;
for (unsigned i = 0; i < commonSize; ++i) {
if (previousInlineStack[i].inlineCallFrame != currentInlineStack[i].inlineCallFrame) {
indexOfDivergence = i;
break;
}
}
bool hasPrinted = false;
// Print the pops.
for (unsigned i = previousInlineStack.size(); i-- > indexOfDivergence;) {
out.print(prefix);
printWhiteSpace(out, i * 2);
out.print("<-- ", *previousInlineStack[i].inlineCallFrame, "\n");
hasPrinted = true;
}
// Print the pushes.
for (unsigned i = indexOfDivergence; i < currentInlineStack.size(); ++i) {
out.print(prefix);
printWhiteSpace(out, i * 2);
out.print("--> ", *currentInlineStack[i].inlineCallFrame, "\n");
hasPrinted = true;
}
return hasPrinted;
}
int Graph::amountOfNodeWhiteSpace(Node& node)
{
return (node.codeOrigin.inlineDepth() - 1) * 2;
}
void Graph::printNodeWhiteSpace(PrintStream& out, Node& node)
{
printWhiteSpace(out, amountOfNodeWhiteSpace(node));
}
void Graph::dump(PrintStream& out, Edge edge)
{
out.print(
useKindToString(edge.useKind()),
"@", edge.index(),
AbbreviatedSpeculationDump(at(edge).prediction()));
}
void Graph::dump(PrintStream& out, const char* prefix, NodeIndex nodeIndex)
{
Node& node = at(nodeIndex);
NodeType op = node.op();
unsigned refCount = node.refCount();
bool skipped = !refCount;
bool mustGenerate = node.mustGenerate();
if (mustGenerate)
--refCount;
out.print(prefix);
printNodeWhiteSpace(out, node);
// Example/explanation of dataflow dump output
//
// 14: <!2:7> GetByVal(@3, @13)
// ^1 ^2 ^3 ^4 ^5
//
// (1) The nodeIndex of this operation.
// (2) The reference count. The number printed is the 'real' count,
// not including the 'mustGenerate' ref. If the node is
// 'mustGenerate' then the count it prefixed with '!'.
// (3) The virtual register slot assigned to this node.
// (4) The name of the operation.
// (5) The arguments to the operation. The may be of the form:
// @# - a NodeIndex referencing a prior node in the graph.
// arg# - an argument number.
// $# - the index in the CodeBlock of a constant { for numeric constants the value is displayed | for integers, in both decimal and hex }.
// id# - the index in the CodeBlock of an identifier { if codeBlock is passed to dump(), the string representation is displayed }.
// var# - the index of a var on the global object, used by GetGlobalVar/PutGlobalVar operations.
out.printf("% 4d:%s<%c%u:", (int)nodeIndex, skipped ? " skipped " : " ", mustGenerate ? '!' : ' ', refCount);
if (node.hasResult() && !skipped && node.hasVirtualRegister())
out.print(node.virtualRegister());
else
out.print("-");
out.print(">\t", opName(op), "(");
bool hasPrinted = false;
if (node.flags() & NodeHasVarArgs) {
for (unsigned childIdx = node.firstChild(); childIdx < node.firstChild() + node.numChildren(); childIdx++) {
if (hasPrinted)
out.print(", ");
else
hasPrinted = true;
if (!m_varArgChildren[childIdx])
continue;
dump(out, m_varArgChildren[childIdx]);
}
} else {
if (!!node.child1()) {
dump(out, node.child1());
hasPrinted = true;
}
if (!!node.child2()) {
out.print(", "); // Whether or not there is a first child, we print a comma to ensure that we see a blank entry if there wasn't one.
dump(out, node.child2());
hasPrinted = true;
}
if (!!node.child3()) {
if (!node.child1() && !node.child2())
out.print(", "); // If the third child is the first non-empty one then make sure we have two blanks preceding it.
out.print(", ");
dump(out, node.child3());
hasPrinted = true;
}
}
if (strlen(nodeFlagsAsString(node.flags()))) {
out.print(hasPrinted ? ", " : "", nodeFlagsAsString(node.flags()));
hasPrinted = true;
}
if (node.hasArrayMode()) {
out.print(hasPrinted ? ", " : "", node.arrayMode());
hasPrinted = true;
}
if (node.hasVarNumber()) {
out.print(hasPrinted ? ", " : "", "var", node.varNumber());
hasPrinted = true;
}
if (node.hasRegisterPointer()) {
out.print(hasPrinted ? ", " : "", "global", globalObjectFor(node.codeOrigin)->findRegisterIndex(node.registerPointer()), "(", RawPointer(node.registerPointer()), ")");
hasPrinted = true;
}
if (node.hasIdentifier()) {
out.print(hasPrinted ? ", " : "", "id", node.identifierNumber(), "{", m_codeBlock->identifier(node.identifierNumber()).string(), "}");
hasPrinted = true;
}
if (node.hasStructureSet()) {
for (size_t i = 0; i < node.structureSet().size(); ++i) {
out.print(hasPrinted ? ", " : "", "struct(", RawPointer(node.structureSet()[i]), ": ", indexingTypeToString(node.structureSet()[i]->indexingType()), ")");
hasPrinted = true;
}
}
if (node.hasStructure()) {
out.print(hasPrinted ? ", " : "", "struct(", RawPointer(node.structure()), ": ", indexingTypeToString(node.structure()->indexingType()), ")");
hasPrinted = true;
}
if (node.hasStructureTransitionData()) {
out.print(hasPrinted ? ", " : "", "struct(", RawPointer(node.structureTransitionData().previousStructure), " -> ", RawPointer(node.structureTransitionData().newStructure), ")");
hasPrinted = true;
}
if (node.hasFunction()) {
out.print(hasPrinted ? ", " : "", RawPointer(node.function()));
hasPrinted = true;
}
if (node.hasStorageAccessData()) {
StorageAccessData& storageAccessData = m_storageAccessData[node.storageAccessDataIndex()];
out.print(hasPrinted ? ", " : "", "id", storageAccessData.identifierNumber, "{", m_codeBlock->identifier(storageAccessData.identifierNumber).string(), "}");
out.print(", ", static_cast<ptrdiff_t>(storageAccessData.offset));
hasPrinted = true;
}
ASSERT(node.hasVariableAccessData() == node.hasLocal());
if (node.hasVariableAccessData()) {
VariableAccessData* variableAccessData = node.variableAccessData();
int operand = variableAccessData->operand();
if (operandIsArgument(operand))
out.print(hasPrinted ? ", " : "", "arg", operandToArgument(operand), "(", VariableAccessDataDump(*this, variableAccessData), ")");
else
out.print(hasPrinted ? ", " : "", "r", operand, "(", VariableAccessDataDump(*this, variableAccessData), ")");
hasPrinted = true;
}
if (node.hasConstantBuffer()) {
if (hasPrinted)
out.print(", ");
out.print(node.startConstant(), ":[");
for (unsigned i = 0; i < node.numConstants(); ++i) {
if (i)
out.print(", ");
out.print(m_codeBlock->constantBuffer(node.startConstant())[i]);
}
out.print("]");
hasPrinted = true;
}
if (node.hasIndexingType()) {
if (hasPrinted)
out.print(", ");
out.print(indexingTypeToString(node.indexingType()));
hasPrinted = true;
}
if (node.hasExecutionCounter()) {
if (hasPrinted)
out.print(", ");
out.print(RawPointer(node.executionCounter()));
hasPrinted = true;
}
if (op == JSConstant) {
out.print(hasPrinted ? ", " : "", "$", node.constantNumber());
JSValue value = valueOfJSConstant(nodeIndex);
out.print(" = ", value);
hasPrinted = true;
}
if (op == WeakJSConstant) {
out.print(hasPrinted ? ", " : "", RawPointer(node.weakConstant()));
hasPrinted = true;
}
if (node.isBranch() || node.isJump()) {
out.print(hasPrinted ? ", " : "", "T:#", node.takenBlockIndex());
hasPrinted = true;
}
if (node.isBranch()) {
out.print(hasPrinted ? ", " : "", "F:#", node.notTakenBlockIndex());
hasPrinted = true;
}
out.print(hasPrinted ? ", " : "", "bc#", node.codeOrigin.bytecodeIndex);
hasPrinted = true;
(void)hasPrinted;
out.print(")");
if (!skipped) {
if (node.hasVariableAccessData())
out.print(" predicting ", SpeculationDump(node.variableAccessData()->prediction()), node.variableAccessData()->shouldUseDoubleFormat() ? ", forcing double" : "");
else if (node.hasHeapPrediction())
out.print(" predicting ", SpeculationDump(node.getHeapPrediction()));
}
out.print("\n");
}
void Graph::dumpBlockHeader(PrintStream& out, const char* prefix, BlockIndex blockIndex, PhiNodeDumpMode phiNodeDumpMode)
{
BasicBlock* block = m_blocks[blockIndex].get();
out.print(prefix, "Block #", blockIndex, " (", at(block->at(0)).codeOrigin, "): ", block->isReachable ? "" : "(skipped)", block->isOSRTarget ? " (OSR target)" : "", "\n");
out.print(prefix, " Predecessors:");
for (size_t i = 0; i < block->m_predecessors.size(); ++i)
out.print(" #", block->m_predecessors[i]);
out.print("\n");
if (m_dominators.isValid()) {
out.print(prefix, " Dominated by:");
for (size_t i = 0; i < m_blocks.size(); ++i) {
if (!m_dominators.dominates(i, blockIndex))
continue;
out.print(" #", i);
}
out.print("\n");
out.print(prefix, " Dominates:");
for (size_t i = 0; i < m_blocks.size(); ++i) {
if (!m_dominators.dominates(blockIndex, i))
continue;
out.print(" #", i);
}
out.print("\n");
}
out.print(prefix, " Phi Nodes:");
for (size_t i = 0; i < block->phis.size(); ++i) {
NodeIndex phiNodeIndex = block->phis[i];
Node& phiNode = at(phiNodeIndex);
if (!phiNode.shouldGenerate() && phiNodeDumpMode == DumpLivePhisOnly)
continue;
out.print(" @", phiNodeIndex, "<", phiNode.refCount(), ">->(");
if (phiNode.child1()) {
out.print("@", phiNode.child1().index());
if (phiNode.child2()) {
out.print(", @", phiNode.child2().index());
if (phiNode.child3())
out.print(", @", phiNode.child3().index());
}
}
out.print(")", i + 1 < block->phis.size() ? "," : "");
}
out.print("\n");
}
void Graph::dump(PrintStream& out)
{
NodeIndex lastNodeIndex = NoNode;
for (size_t b = 0; b < m_blocks.size(); ++b) {
BasicBlock* block = m_blocks[b].get();
if (!block)
continue;
dumpBlockHeader(out, "", b, DumpAllPhis);
out.print(" vars before: ");
if (block->cfaHasVisited)
dumpOperands(block->valuesAtHead, out);
else
out.print("<empty>");
out.print("\n");
out.print(" var links: ");
dumpOperands(block->variablesAtHead, out);
out.print("\n");
for (size_t i = 0; i < block->size(); ++i) {
dumpCodeOrigin(out, "", lastNodeIndex, block->at(i));
dump(out, "", block->at(i));
lastNodeIndex = block->at(i);
}
out.print(" vars after: ");
if (block->cfaHasVisited)
dumpOperands(block->valuesAtTail, out);
else
out.print("<empty>");
out.print("\n");
out.print(" var links: ");
dumpOperands(block->variablesAtTail, out);
out.print("\n");
}
}
// FIXME: Convert this to be iterative, not recursive.
#define DO_TO_CHILDREN(node, thingToDo) do { \
Node& _node = (node); \
if (_node.flags() & NodeHasVarArgs) { \
for (unsigned _childIdx = _node.firstChild(); \
_childIdx < _node.firstChild() + _node.numChildren(); \
_childIdx++) { \
if (!!m_varArgChildren[_childIdx]) \
thingToDo(m_varArgChildren[_childIdx]); \
} \
} else { \
if (!_node.child1()) { \
ASSERT(!_node.child2() \
&& !_node.child3()); \
break; \
} \
thingToDo(_node.child1()); \
\
if (!_node.child2()) { \
ASSERT(!_node.child3()); \
break; \
} \
thingToDo(_node.child2()); \
\
if (!_node.child3()) \
break; \
thingToDo(_node.child3()); \
} \
} while (false)
void Graph::refChildren(NodeIndex op)
{
DO_TO_CHILDREN(at(op), ref);
}
void Graph::derefChildren(NodeIndex op)
{
DO_TO_CHILDREN(at(op), deref);
}
void Graph::predictArgumentTypes()
{
ASSERT(m_codeBlock->numParameters() >= 1);
for (size_t arg = 0; arg < static_cast<size_t>(m_codeBlock->numParameters()); ++arg) {
ValueProfile* profile = m_profiledBlock->valueProfileForArgument(arg);
if (!profile)
continue;
at(m_arguments[arg]).variableAccessData()->predict(profile->computeUpdatedPrediction());
#if DFG_ENABLE(DEBUG_VERBOSE)
dataLog(
"Argument [", arg, "] prediction: ",
SpeculationDump(at(m_arguments[arg]).variableAccessData()->prediction()), "\n");
#endif
}
}
void Graph::handleSuccessor(Vector<BlockIndex, 16>& worklist, BlockIndex blockIndex, BlockIndex successorIndex)
{
BasicBlock* successor = m_blocks[successorIndex].get();
if (!successor->isReachable) {
successor->isReachable = true;
worklist.append(successorIndex);
}
successor->m_predecessors.append(blockIndex);
}
void Graph::collectGarbage()
{
// First reset the counts to 0 for all nodes.
for (unsigned i = size(); i--;)
at(i).setRefCount(0);
// Now find the roots: the nodes that are must-generate. Set their ref counts to
// 1 and put them on the worklist.
Vector<NodeIndex, 128> worklist;
for (BlockIndex blockIndex = 0; blockIndex < m_blocks.size(); ++blockIndex) {
BasicBlock* block = m_blocks[blockIndex].get();
if (!block)
continue;
for (unsigned indexInBlock = block->size(); indexInBlock--;) {
NodeIndex nodeIndex = block->at(indexInBlock);
Node& node = at(nodeIndex);
if (!(node.flags() & NodeMustGenerate))
continue;
node.setRefCount(1);
worklist.append(nodeIndex);
}
}
while (!worklist.isEmpty()) {
NodeIndex nodeIndex = worklist.last();
worklist.removeLast();
Node& node = at(nodeIndex);
ASSERT(node.shouldGenerate()); // It should not be on the worklist unless it's ref'ed.
if (node.flags() & NodeHasVarArgs) {
for (unsigned childIdx = node.firstChild();
childIdx < node.firstChild() + node.numChildren();
++childIdx) {
if (!m_varArgChildren[childIdx])
continue;
NodeIndex childNodeIndex = m_varArgChildren[childIdx].index();
if (!at(childNodeIndex).ref())
continue;
worklist.append(childNodeIndex);
}
} else if (node.child1()) {
if (at(node.child1()).ref())
worklist.append(node.child1().index());
if (node.child2()) {
if (at(node.child2()).ref())
worklist.append(node.child2().index());
if (node.child3()) {
if (at(node.child3()).ref())
worklist.append(node.child3().index());
}
}
}
}
}
void Graph::determineReachability()
{
Vector<BlockIndex, 16> worklist;
worklist.append(0);
m_blocks[0]->isReachable = true;
while (!worklist.isEmpty()) {
BlockIndex index = worklist.last();
worklist.removeLast();
BasicBlock* block = m_blocks[index].get();
ASSERT(block->isLinked);
Node& node = at(block->last());
ASSERT(node.isTerminal());
if (node.isJump())
handleSuccessor(worklist, index, node.takenBlockIndex());
else if (node.isBranch()) {
handleSuccessor(worklist, index, node.takenBlockIndex());
handleSuccessor(worklist, index, node.notTakenBlockIndex());
}
}
}
void Graph::resetReachability()
{
for (BlockIndex blockIndex = m_blocks.size(); blockIndex--;) {
BasicBlock* block = m_blocks[blockIndex].get();
if (!block)
continue;
block->isReachable = false;
block->m_predecessors.clear();
}
determineReachability();
}
void Graph::resetExitStates()
{
for (unsigned i = size(); i--;)
at(i).setCanExit(true);
}
} } // namespace JSC::DFG
#endif