blob: 442e75fcb5c059733408884e36120f9e5b9b305e [file] [log] [blame]
/*
* Copyright (C) 2012, 2013 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 "DFGValidate.h"
#if ENABLE(DFG_JIT)
#include "CodeBlockWithJITType.h"
#include <wtf/Assertions.h>
#include <wtf/BitVector.h>
namespace JSC { namespace DFG {
class Validate {
public:
Validate(Graph& graph, GraphDumpMode graphDumpMode)
: m_graph(graph)
, m_graphDumpMode(graphDumpMode)
{
}
#define VALIDATE(context, assertion) do { \
if (!(assertion)) { \
dataLogF("\n\n\nAt "); \
reportValidationContext context; \
dataLogF(": validation %s (%s:%d) failed.\n", #assertion, __FILE__, __LINE__); \
dumpGraphIfAppropriate(); \
WTFReportAssertionFailure(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, #assertion); \
CRASH(); \
} \
} while (0)
#define V_EQUAL(context, left, right) do { \
if (left != right) { \
dataLogF("\n\n\nAt "); \
reportValidationContext context; \
dataLogF(": validation (%s = ", #left); \
dataLog(left); \
dataLogF(") == (%s = ", #right); \
dataLog(right); \
dataLogF(") (%s:%d) failed.\n", __FILE__, __LINE__); \
dumpGraphIfAppropriate(); \
WTFReportAssertionFailure(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, #left " == " #right); \
CRASH(); \
} \
} while (0)
#define notSet (static_cast<size_t>(-1))
void validate()
{
// NB. This code is not written for performance, since it is not intended to run
// in release builds.
// Validate that all local variables at the head of the root block are dead.
BasicBlock* root = m_graph.block(0);
for (unsigned i = 0; i < root->variablesAtHead.numberOfLocals(); ++i)
V_EQUAL((virtualRegisterForLocal(i), root), static_cast<Node*>(0), root->variablesAtHead.local(i));
// Validate ref counts and uses.
for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex) {
BasicBlock* block = m_graph.block(blockIndex);
if (!block)
continue;
VALIDATE((block), block->isReachable);
for (size_t i = 0; i < block->numNodes(); ++i)
m_myRefCounts.add(block->node(i), 0);
}
for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex) {
BasicBlock* block = m_graph.block(blockIndex);
if (!block)
continue;
for (size_t i = 0; i < block->numNodes(); ++i) {
Node* node = block->node(i);
m_acceptableNodes.add(node);
if (!node->shouldGenerate())
continue;
if (node->op() == Upsilon) {
VALIDATE((node), m_graph.m_form == SSA);
if (node->phi()->shouldGenerate())
m_myRefCounts.find(node)->value++;
}
for (unsigned j = 0; j < m_graph.numChildren(node); ++j) {
// Phi children in LoadStore form are invalid.
if (m_graph.m_form == LoadStore && block->isPhiIndex(i))
continue;
Edge edge = m_graph.child(node, j);
if (!edge)
continue;
m_myRefCounts.find(edge.node())->value++;
if (m_graph.m_form == SSA) {
// In SSA, all edges must hasResult().
VALIDATE((node, edge), edge->hasResult());
continue;
}
// Unless I'm a Flush, Phantom, GetLocal, or Phi, my children should hasResult().
switch (node->op()) {
case Flush:
case GetLocal:
VALIDATE((node, edge), edge->hasVariableAccessData(m_graph));
VALIDATE((node, edge), edge->variableAccessData() == node->variableAccessData());
break;
case PhantomLocal:
VALIDATE((node, edge), edge->hasVariableAccessData(m_graph));
VALIDATE((node, edge), edge->variableAccessData() == node->variableAccessData());
VALIDATE((node, edge), edge->op() != SetLocal);
break;
case Phi:
VALIDATE((node, edge), edge->hasVariableAccessData(m_graph));
if (m_graph.m_unificationState == LocallyUnified)
break;
VALIDATE((node, edge), edge->variableAccessData() == node->variableAccessData());
break;
case Phantom:
switch (m_graph.m_form) {
case LoadStore:
if (j) {
VALIDATE((node, edge), edge->hasResult());
break;
}
switch (edge->op()) {
case Phi:
case SetArgument:
case SetLocal:
break;
default:
VALIDATE((node, edge), edge->hasResult());
break;
}
break;
case ThreadedCPS:
VALIDATE((node, edge), edge->hasResult());
break;
case SSA:
RELEASE_ASSERT_NOT_REACHED();
break;
}
break;
default:
VALIDATE((node, edge), edge->hasResult());
break;
}
}
}
}
for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex) {
BasicBlock* block = m_graph.block(blockIndex);
if (!block)
continue;
for (size_t i = 0; i < block->numNodes(); ++i) {
Node* node = block->node(i);
if (m_graph.m_refCountState == ExactRefCount)
V_EQUAL((node), m_myRefCounts.get(node), node->adjustedRefCount());
else
V_EQUAL((node), node->refCount(), 1);
}
}
switch (m_graph.m_form) {
case LoadStore:
case ThreadedCPS:
validateCPS();
break;
case SSA:
// FIXME: Implement SSA verification.
break;
}
}
private:
Graph& m_graph;
GraphDumpMode m_graphDumpMode;
HashMap<Node*, unsigned> m_myRefCounts;
HashSet<Node*> m_acceptableNodes;
void validateCPS()
{
for (BlockIndex blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex) {
BasicBlock* block = m_graph.block(blockIndex);
if (!block)
continue;
HashSet<Node*> phisInThisBlock;
HashSet<Node*> nodesInThisBlock;
for (size_t i = 0; i < block->numNodes(); ++i) {
Node* node = block->node(i);
nodesInThisBlock.add(node);
if (block->isPhiIndex(i))
phisInThisBlock.add(node);
for (unsigned j = 0; j < m_graph.numChildren(node); ++j) {
Edge edge = m_graph.child(node, j);
if (!edge)
continue;
VALIDATE((node, edge), m_acceptableNodes.contains(edge.node()));
}
}
for (size_t i = 0; i < block->phis.size(); ++i) {
Node* node = block->phis[i];
ASSERT(phisInThisBlock.contains(node));
VALIDATE((node), node->op() == Phi);
VirtualRegister local = node->local();
for (unsigned j = 0; j < m_graph.numChildren(node); ++j) {
// Phi children in LoadStore form are invalid.
if (m_graph.m_form == LoadStore && block->isPhiIndex(i))
continue;
Edge edge = m_graph.child(node, j);
if (!edge)
continue;
VALIDATE(
(node, edge),
edge->op() == SetLocal
|| edge->op() == SetArgument
|| edge->op() == Flush
|| edge->op() == Phi
|| edge->op() == ZombieHint
|| edge->op() == MovHint
|| edge->op() == MovHintAndCheck);
if (phisInThisBlock.contains(edge.node()))
continue;
if (nodesInThisBlock.contains(edge.node())) {
VALIDATE(
(node, edge),
edge->op() == SetLocal
|| edge->op() == ZombieHint
|| edge->op() == MovHint
|| edge->op() == MovHintAndCheck
|| edge->op() == SetArgument
|| edge->op() == Flush);
continue;
}
// There must exist a predecessor block that has this node index in
// its tail variables.
bool found = false;
for (unsigned k = 0; k < block->predecessors.size(); ++k) {
BasicBlock* prevBlock = block->predecessors[k];
VALIDATE((block->predecessors[k]), prevBlock);
Node* prevNode = prevBlock->variablesAtTail.operand(local);
// If we have a Phi that is not referring to *this* block then all predecessors
// must have that local available.
VALIDATE((local, block, block->predecessors[k]), prevNode);
switch (prevNode->op()) {
case GetLocal:
case Flush:
case PhantomLocal:
prevNode = prevNode->child1().node();
break;
default:
break;
}
if (node->shouldGenerate()) {
VALIDATE((local, block->predecessors[k], prevNode),
prevNode->shouldGenerate());
}
VALIDATE(
(local, block->predecessors[k], prevNode),
prevNode->op() == SetLocal
|| prevNode->op() == MovHint
|| prevNode->op() == MovHintAndCheck
|| prevNode->op() == ZombieHint
|| prevNode->op() == SetArgument
|| prevNode->op() == Phi);
if (prevNode == edge.node()) {
found = true;
break;
}
// At this point it cannot refer into this block.
VALIDATE((local, block->predecessors[k], prevNode), !prevBlock->isInBlock(edge.node()));
}
VALIDATE((node, edge), found);
}
}
Operands<size_t> getLocalPositions(
block->variablesAtHead.numberOfArguments(),
block->variablesAtHead.numberOfLocals());
Operands<size_t> setLocalPositions(
block->variablesAtHead.numberOfArguments(),
block->variablesAtHead.numberOfLocals());
for (size_t i = 0; i < block->variablesAtHead.numberOfArguments(); ++i) {
VALIDATE((virtualRegisterForArgument(i), block), !block->variablesAtHead.argument(i) || block->variablesAtHead.argument(i)->hasVariableAccessData(m_graph));
if (m_graph.m_form == ThreadedCPS)
VALIDATE((virtualRegisterForArgument(i), block), !block->variablesAtTail.argument(i) || block->variablesAtTail.argument(i)->hasVariableAccessData(m_graph));
getLocalPositions.argument(i) = notSet;
setLocalPositions.argument(i) = notSet;
}
for (size_t i = 0; i < block->variablesAtHead.numberOfLocals(); ++i) {
VALIDATE((virtualRegisterForLocal(i), block), !block->variablesAtHead.local(i) || block->variablesAtHead.local(i)->hasVariableAccessData(m_graph));
if (m_graph.m_form == ThreadedCPS)
VALIDATE((virtualRegisterForLocal(i), block), !block->variablesAtTail.local(i) || block->variablesAtTail.local(i)->hasVariableAccessData(m_graph));
getLocalPositions.local(i) = notSet;
setLocalPositions.local(i) = notSet;
}
for (size_t i = 0; i < block->size(); ++i) {
Node* node = block->at(i);
ASSERT(nodesInThisBlock.contains(node));
VALIDATE((node), node->op() != Phi);
for (unsigned j = 0; j < m_graph.numChildren(node); ++j) {
Edge edge = m_graph.child(node, j);
if (!edge)
continue;
VALIDATE((node, edge), nodesInThisBlock.contains(edge.node()));
switch (node->op()) {
case PhantomLocal:
case GetLocal:
case Flush:
break;
case Phantom:
if (m_graph.m_form == LoadStore && !j)
break;
default:
VALIDATE((node, edge), !phisInThisBlock.contains(edge.node()));
break;
}
}
if (!node->shouldGenerate())
continue;
switch (node->op()) {
case GetLocal:
if (node->variableAccessData()->isCaptured())
break;
// Ignore GetLocal's that we know to be dead, but that the graph
// doesn't yet know to be dead.
if (!m_myRefCounts.get(node))
break;
if (m_graph.m_form == ThreadedCPS)
VALIDATE((node, block), getLocalPositions.operand(node->local()) == notSet);
getLocalPositions.operand(node->local()) = i;
break;
case SetLocal:
if (node->variableAccessData()->isCaptured())
break;
// Only record the first SetLocal. There may be multiple SetLocals
// because of flushing.
if (setLocalPositions.operand(node->local()) != notSet)
break;
setLocalPositions.operand(node->local()) = i;
break;
default:
break;
}
}
if (m_graph.m_form == LoadStore)
continue;
for (size_t i = 0; i < block->variablesAtHead.numberOfArguments(); ++i) {
checkOperand(
block, getLocalPositions, setLocalPositions, virtualRegisterForArgument(i));
}
for (size_t i = 0; i < block->variablesAtHead.numberOfLocals(); ++i) {
checkOperand(
block, getLocalPositions, setLocalPositions, virtualRegisterForLocal(i));
}
}
}
void checkOperand(
BasicBlock* block, Operands<size_t>& getLocalPositions,
Operands<size_t>& setLocalPositions, VirtualRegister operand)
{
if (getLocalPositions.operand(operand) == notSet)
return;
if (setLocalPositions.operand(operand) == notSet)
return;
VALIDATE(
(block->at(getLocalPositions.operand(operand)),
block->at(setLocalPositions.operand(operand)),
block),
getLocalPositions.operand(operand) < setLocalPositions.operand(operand));
}
void reportValidationContext(Node* node)
{
dataLogF("@%u", node->index());
}
void reportValidationContext(BasicBlock* block)
{
dataLog("Block ", *block);
}
void reportValidationContext(Node* node, Edge edge)
{
dataLog(node, " -> ", edge);
}
void reportValidationContext(VirtualRegister local, BasicBlock* block)
{
if (!block) {
dataLog("r", local, " in null Block ");
return;
}
dataLog("r", local, " in Block ", *block);
}
void reportValidationContext(
VirtualRegister local, BasicBlock* sourceBlock, BasicBlock* destinationBlock)
{
dataLog("r", local, " in Block ", *sourceBlock, " -> ", *destinationBlock);
}
void reportValidationContext(
VirtualRegister local, BasicBlock* sourceBlock, Node* prevNode)
{
dataLog(prevNode, " for r", local, " in Block ", *sourceBlock);
}
void reportValidationContext(Node* node, BasicBlock* block)
{
dataLog(node, " in Block ", *block);
}
void reportValidationContext(Node* node, Node* node2, BasicBlock* block)
{
dataLog(node, " and ", node2, " in Block ", *block);
}
void reportValidationContext(
Node* node, BasicBlock* block, Node* expectedNode, Edge incomingEdge)
{
dataLog(node, " in Block ", *block, ", searching for ", expectedNode, " from ", incomingEdge);
}
void dumpGraphIfAppropriate()
{
if (m_graphDumpMode == DontDumpGraph)
return;
dataLog("At time of failure:\n");
m_graph.dump();
}
};
void validate(Graph& graph, GraphDumpMode graphDumpMode)
{
Validate validationObject(graph, graphDumpMode);
validationObject.validate();
}
} } // namespace JSC::DFG
#endif // ENABLE(DFG_JIT)