mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2025-04-08 18:01:37 +00:00
1070 lines
45 KiB
C++
1070 lines
45 KiB
C++
/*
|
|
* Copyright (C) 2012-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 "DFGValidate.h"
|
|
|
|
#if ENABLE(DFG_JIT)
|
|
|
|
#include "ButterflyInlines.h"
|
|
#include "DFGClobberize.h"
|
|
#include "DFGClobbersExitState.h"
|
|
#include "DFGDominators.h"
|
|
#include "DFGMayExit.h"
|
|
#include "DFGOSRAvailabilityAnalysisPhase.h"
|
|
#include <wtf/Assertions.h>
|
|
|
|
namespace JSC { namespace DFG {
|
|
|
|
namespace {
|
|
|
|
class Validate {
|
|
public:
|
|
Validate(Graph& graph, GraphDumpMode graphDumpMode, CString graphDumpBeforePhase)
|
|
: m_graph(graph)
|
|
, m_graphDumpMode(graphDumpMode)
|
|
, m_graphDumpBeforePhase(graphDumpBeforePhase)
|
|
{
|
|
}
|
|
|
|
#define VALIDATE(context, assertion) do { \
|
|
if (!(assertion)) { \
|
|
startCrashing(); \
|
|
dataLogF("\n\n\nAt "); \
|
|
reportValidationContext context; \
|
|
dataLogF(": validation failed: %s (%s:%d).\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) { \
|
|
startCrashing(); \
|
|
dataLogF("\n\n\nAt "); \
|
|
reportValidationContext context; \
|
|
dataLogF(": validation failed: (%s = ", #left); \
|
|
dataLog(left); \
|
|
dataLogF(") == (%s = ", #right); \
|
|
dataLog(right); \
|
|
dataLogF(") (%s:%d).\n", __FILE__, __LINE__); \
|
|
dataLog("\n\n\n"); \
|
|
m_graph.baselineCodeBlockFor(nullptr)->dumpBytecode(); \
|
|
dumpGraphIfAppropriate(); \
|
|
WTFReportAssertionFailure(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, #left " == " #right); \
|
|
CRASH(); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define notSet (static_cast<size_t>(-1))
|
|
|
|
void validate()
|
|
{
|
|
if (m_graph.m_isValidating)
|
|
return;
|
|
|
|
auto isValidating = SetForScope(m_graph.m_isValidating, true);
|
|
|
|
// NB. This code is not written for performance, since it is not intended to run
|
|
// in release builds.
|
|
|
|
VALIDATE((m_graph.block(0)), m_graph.isRoot(m_graph.block(0)));
|
|
VALIDATE((m_graph.block(0)), m_graph.block(0) == m_graph.m_roots[0]);
|
|
|
|
for (BasicBlock* block : m_graph.m_roots)
|
|
VALIDATE((block), block->predecessors.isEmpty());
|
|
|
|
// Validate that all local variables at the head of all entrypoints are dead.
|
|
for (BasicBlock* entrypoint : m_graph.m_roots) {
|
|
for (unsigned i = 0; i < entrypoint->variablesAtHead.numberOfLocals(); ++i)
|
|
V_EQUAL((virtualRegisterForLocal(i), entrypoint), static_cast<Node*>(nullptr), entrypoint->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++;
|
|
|
|
validateEdgeWithDoubleResultIfNecessary(node, edge);
|
|
VALIDATE((node, edge), edge->hasInt52Result() == (edge.useKind() == Int52RepUse));
|
|
|
|
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;
|
|
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());
|
|
}
|
|
|
|
bool foundTerminal = false;
|
|
for (size_t i = 0 ; i < block->size(); ++i) {
|
|
Node* node = block->at(i);
|
|
if (node->isTerminal()) {
|
|
foundTerminal = true;
|
|
for (size_t j = i + 1; j < block->size(); ++j) {
|
|
node = block->at(j);
|
|
VALIDATE((node), node->op() == Phantom || node->op() == PhantomLocal || node->op() == Flush || node->op() == Check);
|
|
m_graph.doToChildren(
|
|
node,
|
|
[&] (Edge edge) {
|
|
VALIDATE((node, edge), shouldNotHaveTypeCheck(edge.useKind()));
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
VALIDATE((block), foundTerminal);
|
|
|
|
for (size_t i = 0; i < block->size(); ++i) {
|
|
Node* node = block->at(i);
|
|
|
|
VALIDATE((node), node->origin.isSet());
|
|
VALIDATE((node), node->origin.semantic.isSet() == node->origin.forExit.isSet());
|
|
VALIDATE((node), !(!node->origin.forExit.isSet() && node->origin.exitOK));
|
|
VALIDATE((node), !(mayExit(m_graph, node) == Exits && !node->origin.exitOK));
|
|
|
|
if (i) {
|
|
Node* previousNode = block->at(i - 1);
|
|
VALIDATE(
|
|
(node),
|
|
!clobbersExitState(m_graph, previousNode)
|
|
|| !node->origin.exitOK
|
|
|| node->op() == ExitOK
|
|
|| node->origin.forExit != previousNode->origin.forExit);
|
|
VALIDATE(
|
|
(node),
|
|
!(!previousNode->origin.exitOK && node->origin.exitOK)
|
|
|| node->op() == ExitOK
|
|
|| node->origin.forExit != previousNode->origin.forExit);
|
|
}
|
|
|
|
VALIDATE((node), !node->hasStructure() || !!node->structure().get());
|
|
VALIDATE((node), !node->hasCellOperand() || node->cellOperand()->value().isCell());
|
|
VALIDATE((node), !node->hasCellOperand() || !!node->cellOperand()->value());
|
|
|
|
if (!(node->flags() & NodeHasVarArgs)) {
|
|
if (!node->child2())
|
|
VALIDATE((node), !node->child3());
|
|
if (!node->child1())
|
|
VALIDATE((node), !node->child2());
|
|
}
|
|
|
|
switch (node->op()) {
|
|
case Identity:
|
|
case IdentityWithProfile:
|
|
VALIDATE((node), canonicalResultRepresentation(node->result()) == canonicalResultRepresentation(node->child1()->result()));
|
|
break;
|
|
case SetLocal:
|
|
case PutStack:
|
|
case Upsilon:
|
|
VALIDATE((node), !!node->child1());
|
|
switch (node->child1().useKind()) {
|
|
case UntypedUse:
|
|
case CellUse:
|
|
case KnownCellUse:
|
|
case Int32Use:
|
|
case KnownInt32Use:
|
|
case Int52RepUse:
|
|
case DoubleRepUse:
|
|
case BooleanUse:
|
|
case KnownBooleanUse:
|
|
break;
|
|
default:
|
|
VALIDATE((node), !"Bad use kind");
|
|
break;
|
|
}
|
|
break;
|
|
case MakeRope:
|
|
case ValueAdd:
|
|
case ValueSub:
|
|
case ValueMul:
|
|
case ValueDiv:
|
|
case ValueMod:
|
|
case ValuePow:
|
|
case ArithAdd:
|
|
case ArithSub:
|
|
case ArithMul:
|
|
case ArithIMul:
|
|
case ArithDiv:
|
|
case ArithMod:
|
|
case ArithMin:
|
|
case ArithMax:
|
|
case ArithPow:
|
|
case CompareLess:
|
|
case CompareLessEq:
|
|
case CompareGreater:
|
|
case CompareGreaterEq:
|
|
case CompareBelow:
|
|
case CompareBelowEq:
|
|
case CompareEq:
|
|
case CompareStrictEq:
|
|
case SameValue:
|
|
case StrCat:
|
|
VALIDATE((node), !!node->child1());
|
|
VALIDATE((node), !!node->child2());
|
|
break;
|
|
case CompareEqPtr:
|
|
VALIDATE((node), !!node->child1());
|
|
VALIDATE((node), !!node->cellOperand()->value() && node->cellOperand()->value().isCell());
|
|
break;
|
|
case CheckArrayOrEmpty:
|
|
VALIDATE((node), is64Bit());
|
|
VALIDATE((node), !!node->child1());
|
|
VALIDATE((node), node->child1().useKind() == CellUse);
|
|
break;
|
|
case CheckStructureOrEmpty:
|
|
VALIDATE((node), is64Bit());
|
|
VALIDATE((node), !!node->child1());
|
|
VALIDATE((node), node->child1().useKind() == CellUse);
|
|
break;
|
|
case CheckStructure:
|
|
case StringFromCharCode:
|
|
VALIDATE((node), !!node->child1());
|
|
break;
|
|
case PutStructure:
|
|
VALIDATE((node), !node->transition()->previous->dfgShouldWatch());
|
|
break;
|
|
case MultiPutByOffset:
|
|
for (unsigned i = node->multiPutByOffsetData().variants.size(); i--;) {
|
|
const PutByIdVariant& variant = node->multiPutByOffsetData().variants[i];
|
|
if (variant.kind() != PutByIdVariant::Transition)
|
|
continue;
|
|
VALIDATE((node), !variant.oldStructureForTransition()->dfgShouldWatch());
|
|
}
|
|
break;
|
|
case MultiDeleteByOffset:
|
|
for (unsigned i = node->multiDeleteByOffsetData().variants.size(); i--;) {
|
|
const DeleteByIdVariant& variant = node->multiDeleteByOffsetData().variants[i];
|
|
VALIDATE((node), !variant.newStructure() || !variant.oldStructure()->dfgShouldWatch());
|
|
}
|
|
break;
|
|
case MaterializeNewObject:
|
|
for (RegisteredStructure structure : node->structureSet()) {
|
|
// This only supports structures that are JSFinalObject or JSArray.
|
|
VALIDATE(
|
|
(node),
|
|
structure->classInfo() == JSFinalObject::info()
|
|
|| structure->classInfo() == JSArray::info());
|
|
|
|
// We only support certain indexing shapes.
|
|
VALIDATE((node), !hasAnyArrayStorage(structure->indexingType()));
|
|
}
|
|
break;
|
|
case DoubleConstant:
|
|
case Int52Constant:
|
|
VALIDATE((node), node->isNumberConstant());
|
|
break;
|
|
case GetByOffset:
|
|
case PutByOffset:
|
|
// FIXME: We should be able to validate that GetByOffset and PutByOffset are
|
|
// using the same object for storage and base. I think this means finally
|
|
// splitting these nodes into two node types, one for inline and one for
|
|
// out-of-line. The out-of-line one will require that the first node is storage,
|
|
// while the inline one will not take a storage child at all.
|
|
// https://bugs.webkit.org/show_bug.cgi?id=159602
|
|
break;
|
|
case HasOwnProperty: {
|
|
VALIDATE((node), !!m_graph.m_vm.hasOwnPropertyCache());
|
|
break;
|
|
}
|
|
case GetVectorLength: {
|
|
Array::Type type = node->arrayMode().type();
|
|
VALIDATE((node), type == Array::ArrayStorage || type == Array::SlowPutArrayStorage);
|
|
break;
|
|
}
|
|
case CPUIntrinsic: {
|
|
switch (node->intrinsic()) {
|
|
case CPUMfenceIntrinsic:
|
|
case CPURdtscIntrinsic:
|
|
case CPUCpuidIntrinsic:
|
|
case CPUPauseIntrinsic:
|
|
break;
|
|
default:
|
|
VALIDATE((node), false);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case GetArgumentCountIncludingThis: {
|
|
if (InlineCallFrame* inlineCallFrame = node->argumentsInlineCallFrame())
|
|
VALIDATE((node), inlineCallFrame->isVarargs());
|
|
break;
|
|
}
|
|
case NewArray:
|
|
VALIDATE((node), node->vectorLengthHint() >= node->numChildren());
|
|
break;
|
|
case NewArrayBuffer:
|
|
VALIDATE((node), node->vectorLengthHint() >= node->castOperand<JSImmutableButterfly*>()->length());
|
|
break;
|
|
case GetByVal:
|
|
switch (node->arrayMode().type()) {
|
|
case Array::Int32:
|
|
case Array::Double:
|
|
case Array::Contiguous:
|
|
// We rely on being an original array structure because we are SaneChain, and we need
|
|
// Array.prototype to be our prototype, so we can return undefined when we go OOB.
|
|
if (node->arrayMode().isOutOfBoundsSaneChain())
|
|
VALIDATE((node), node->arrayMode().isJSArrayWithOriginalStructure());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (m_graph.m_form) {
|
|
case LoadStore:
|
|
case ThreadedCPS:
|
|
validateCPS();
|
|
break;
|
|
|
|
case SSA:
|
|
validateSSA();
|
|
break;
|
|
}
|
|
|
|
// Validate clobbered states.
|
|
struct DefLambdaAdaptor {
|
|
Function<void(PureValue)> pureValue;
|
|
Function<void(HeapLocation, LazyNode)> locationAndNode;
|
|
|
|
void operator()(PureValue value) const
|
|
{
|
|
pureValue(value);
|
|
}
|
|
|
|
void operator()(HeapLocation location, LazyNode node) const
|
|
{
|
|
locationAndNode(location, node);
|
|
}
|
|
};
|
|
for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
|
|
for (Node* node : *block) {
|
|
clobberize(m_graph, node,
|
|
[&] (AbstractHeap) { },
|
|
[&] (AbstractHeap heap)
|
|
{
|
|
// CSE assumes that HEAP TOP is never written.
|
|
// If this assumption is weakened, you need to update clobbering
|
|
// in CSE accordingly.
|
|
if (heap.kind() == Stack)
|
|
VALIDATE((node), !heap.payload().isTop());
|
|
},
|
|
DefLambdaAdaptor {
|
|
[&] (PureValue) { },
|
|
[&] (HeapLocation location, LazyNode)
|
|
{
|
|
VALIDATE((node), location.heap().kind() != SideState);
|
|
|
|
// More specific kinds should be used instead.
|
|
VALIDATE((node), location.heap().kind() != World);
|
|
VALIDATE((node), location.heap().kind() != Heap);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
|
|
// We expect the predecessor list to be de-duplicated.
|
|
HashSet<BasicBlock*> predecessors;
|
|
for (BasicBlock* predecessor : block->predecessors)
|
|
predecessors.add(predecessor);
|
|
VALIDATE((block), predecessors.size() == block->predecessors.size());
|
|
}
|
|
}
|
|
|
|
private:
|
|
Graph& m_graph;
|
|
GraphDumpMode m_graphDumpMode;
|
|
CString m_graphDumpBeforePhase;
|
|
|
|
HashMap<Node*, unsigned> m_myRefCounts;
|
|
HashSet<Node*> m_acceptableNodes;
|
|
|
|
void validateCPS()
|
|
{
|
|
VALIDATE((), !m_graph.m_rootToArguments.isEmpty()); // We should have at least one root.
|
|
VALIDATE((), m_graph.m_rootToArguments.size() == m_graph.m_roots.size());
|
|
for (BasicBlock* root : m_graph.m_rootToArguments.keys())
|
|
VALIDATE((), m_graph.m_roots.contains(root));
|
|
|
|
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()));
|
|
}
|
|
}
|
|
|
|
{
|
|
HashSet<Node*> seenNodes;
|
|
for (size_t i = 0; i < block->size(); ++i) {
|
|
Node* node = block->at(i);
|
|
m_graph.doToChildren(node, [&] (const Edge& edge) {
|
|
Node* child = edge.node();
|
|
VALIDATE((node, edge), block->isInPhis(child) || seenNodes.contains(child));
|
|
});
|
|
seenNodes.add(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);
|
|
Operand operand = node->operand();
|
|
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() == SetArgumentDefinitely
|
|
|| edge->op() == SetArgumentMaybe
|
|
|| edge->op() == Phi);
|
|
|
|
if (phisInThisBlock.contains(edge.node()))
|
|
continue;
|
|
|
|
if (nodesInThisBlock.contains(edge.node())) {
|
|
VALIDATE(
|
|
(node, edge),
|
|
edge->op() == SetLocal
|
|
|| edge->op() == SetArgumentDefinitely
|
|
|| edge->op() == SetArgumentMaybe);
|
|
|
|
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(operand);
|
|
// If we have a Phi that is not referring to *this* block then all predecessors
|
|
// must have that local available.
|
|
VALIDATE((operand, 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((operand, block->predecessors[k], prevNode),
|
|
prevNode->shouldGenerate());
|
|
}
|
|
VALIDATE(
|
|
(operand, block->predecessors[k], prevNode),
|
|
prevNode->op() == SetLocal
|
|
|| prevNode->op() == SetArgumentDefinitely
|
|
|| prevNode->op() == SetArgumentMaybe
|
|
|| prevNode->op() == Phi);
|
|
if (prevNode == edge.node()) {
|
|
found = true;
|
|
break;
|
|
}
|
|
// At this point it cannot refer into this block.
|
|
VALIDATE((operand, block->predecessors[k], prevNode), !prevBlock->isInBlock(edge.node()));
|
|
}
|
|
|
|
VALIDATE((node, edge), found);
|
|
}
|
|
}
|
|
|
|
Operands<size_t> getLocalPositions(OperandsLike, block->variablesAtHead);
|
|
Operands<size_t> setLocalPositions(OperandsLike, block->variablesAtHead);
|
|
|
|
for (size_t i = 0; i < block->variablesAtHead.numberOfTmps(); ++i) {
|
|
VALIDATE((Operand::tmp(i), block), !block->variablesAtHead.tmp(i) || block->variablesAtHead.tmp(i)->accessesStack(m_graph));
|
|
if (m_graph.m_form == ThreadedCPS)
|
|
VALIDATE((Operand::tmp(i), block), !block->variablesAtTail.tmp(i) || block->variablesAtTail.tmp(i)->accessesStack(m_graph));
|
|
|
|
getLocalPositions.tmp(i) = notSet;
|
|
setLocalPositions.tmp(i) = notSet;
|
|
}
|
|
for (size_t i = 0; i < block->variablesAtHead.numberOfArguments(); ++i) {
|
|
VALIDATE((virtualRegisterForArgumentIncludingThis(i), block), !block->variablesAtHead.argument(i) || block->variablesAtHead.argument(i)->accessesStack(m_graph));
|
|
if (m_graph.m_form == ThreadedCPS)
|
|
VALIDATE((virtualRegisterForArgumentIncludingThis(i), block), !block->variablesAtTail.argument(i) || block->variablesAtTail.argument(i)->accessesStack(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)->accessesStack(m_graph));
|
|
if (m_graph.m_form == ThreadedCPS)
|
|
VALIDATE((virtualRegisterForLocal(i), block), !block->variablesAtTail.local(i) || block->variablesAtTail.local(i)->accessesStack(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);
|
|
VALIDATE((node), node->origin.forExit.isSet());
|
|
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;
|
|
default:
|
|
VALIDATE((node, edge), !phisInThisBlock.contains(edge.node()));
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (node->op()) {
|
|
case Phi:
|
|
case Upsilon:
|
|
case AssertInBounds:
|
|
case CheckInBounds:
|
|
case PhantomNewObject:
|
|
case PhantomNewFunction:
|
|
case PhantomNewGeneratorFunction:
|
|
case PhantomNewAsyncFunction:
|
|
case PhantomNewAsyncGeneratorFunction:
|
|
case PhantomCreateActivation:
|
|
case PhantomNewRegexp:
|
|
case GetMyArgumentByVal:
|
|
case GetMyArgumentByValOutOfBounds:
|
|
case PutHint:
|
|
case CheckStructureImmediate:
|
|
case MaterializeCreateActivation:
|
|
case PutStack:
|
|
case KillStack:
|
|
case GetStack:
|
|
case EntrySwitch:
|
|
case InitializeEntrypointArguments:
|
|
VALIDATE((node), !"unexpected node type in CPS");
|
|
break;
|
|
case MaterializeNewObject: {
|
|
// CPS only allows array lengths to be constant. This constraint only exists
|
|
// because we don't have DFG support for anything more and we don't need any
|
|
// other kind of support for now.
|
|
ObjectMaterializationData& data = node->objectMaterializationData();
|
|
for (unsigned i = data.m_properties.size(); i--;) {
|
|
PromotedLocationDescriptor descriptor = data.m_properties[i];
|
|
Edge edge = m_graph.varArgChild(node, 1 + i);
|
|
switch (descriptor.kind()) {
|
|
case PublicLengthPLoc:
|
|
case VectorLengthPLoc:
|
|
VALIDATE((node, edge), edge->isInt32Constant());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// CPS only allows one structure.
|
|
VALIDATE((node), node->structureSet().size() == 1);
|
|
|
|
// CPS disallows int32 and double arrays. Those require weird type checks and
|
|
// conversions. They are not needed in the DFG right now. We should add support
|
|
// for these if the DFG ever needs it.
|
|
for (RegisteredStructure structure : node->structureSet()) {
|
|
VALIDATE((node), !hasInt32(structure->indexingType()));
|
|
VALIDATE((node), !hasDouble(structure->indexingType()));
|
|
}
|
|
break;
|
|
}
|
|
case Phantom:
|
|
VALIDATE((node), m_graph.m_fixpointState != FixpointNotConverged);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!node->shouldGenerate())
|
|
continue;
|
|
switch (node->op()) {
|
|
case GetLocal:
|
|
// 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->operand()) == notSet);
|
|
VALIDATE((node, block), !!node->child1());
|
|
VALIDATE((node, block), node->child1()->op() == SetArgumentDefinitely || node->child1()->op() == Phi);
|
|
}
|
|
getLocalPositions.operand(node->operand()) = i;
|
|
break;
|
|
case SetLocal:
|
|
// Only record the first SetLocal. There may be multiple SetLocals
|
|
// because of flushing.
|
|
if (setLocalPositions.operand(node->operand()) != notSet)
|
|
break;
|
|
setLocalPositions.operand(node->operand()) = i;
|
|
break;
|
|
case SetArgumentDefinitely:
|
|
// This acts like a reset. It's ok to have a second GetLocal for a local in the same
|
|
// block if we had a SetArgumentDefinitely for that local.
|
|
getLocalPositions.operand(node->operand()) = notSet;
|
|
setLocalPositions.operand(node->operand()) = notSet;
|
|
break;
|
|
case SetArgumentMaybe:
|
|
break;
|
|
case Flush:
|
|
case PhantomLocal:
|
|
if (m_graph.m_form == ThreadedCPS) {
|
|
VALIDATE((node, block),
|
|
node->child1()->op() == Phi
|
|
|| node->child1()->op() == SetLocal
|
|
|| node->child1()->op() == SetArgumentDefinitely
|
|
|| node->child1()->op() == SetArgumentMaybe);
|
|
if (node->op() == PhantomLocal)
|
|
VALIDATE((node, block), node->child1()->op() != SetArgumentMaybe);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (m_graph.m_form == LoadStore)
|
|
continue;
|
|
|
|
for (size_t i = 0; i < block->variablesAtHead.numberOfTmps(); ++i) {
|
|
checkOperand(
|
|
block, getLocalPositions, setLocalPositions, Operand::tmp(i));
|
|
}
|
|
|
|
for (size_t i = 0; i < block->variablesAtHead.numberOfArguments(); ++i) {
|
|
checkOperand(
|
|
block, getLocalPositions, setLocalPositions, virtualRegisterForArgumentIncludingThis(i));
|
|
}
|
|
for (size_t i = 0; i < block->variablesAtHead.numberOfLocals(); ++i) {
|
|
checkOperand(
|
|
block, getLocalPositions, setLocalPositions, virtualRegisterForLocal(i));
|
|
}
|
|
}
|
|
|
|
if (m_graph.m_form == ThreadedCPS) {
|
|
Vector<Node*> worklist;
|
|
HashSet<Node*> seen;
|
|
for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
|
|
for (Node* node : *block) {
|
|
if (node->op() == GetLocal || node->op() == PhantomLocal) {
|
|
worklist.append(node);
|
|
auto addResult = seen.add(node);
|
|
VALIDATE((node, block), addResult.isNewEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (worklist.size()) {
|
|
Node* node = worklist.takeLast();
|
|
switch (node->op()) {
|
|
case PhantomLocal:
|
|
case GetLocal: {
|
|
Node* child = node->child1().node();
|
|
if (seen.add(child).isNewEntry)
|
|
worklist.append(child);
|
|
break;
|
|
}
|
|
case Phi: {
|
|
for (unsigned i = 0; i < m_graph.numChildren(node); ++i) {
|
|
Edge edge = m_graph.child(node, i);
|
|
if (!edge)
|
|
continue;
|
|
if (seen.add(edge.node()).isNewEntry)
|
|
worklist.append(edge.node());
|
|
}
|
|
break;
|
|
}
|
|
case SetLocal:
|
|
case SetArgumentDefinitely:
|
|
break;
|
|
case SetArgumentMaybe:
|
|
VALIDATE((node), !"Should not reach SetArgumentMaybe. GetLocal that has data flow that reaches a SetArgumentMaybe is invalid IR.");
|
|
break;
|
|
default:
|
|
VALIDATE((node), !"Unexpected node type.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void validateSSA()
|
|
{
|
|
// FIXME: Add more things here.
|
|
// https://bugs.webkit.org/show_bug.cgi?id=123471
|
|
|
|
VALIDATE((), m_graph.m_roots.size() == 1);
|
|
VALIDATE((), m_graph.m_roots[0] == m_graph.block(0));
|
|
VALIDATE((), !m_graph.m_argumentFormats.isEmpty()); // We always have at least one entrypoint.
|
|
VALIDATE((), m_graph.m_rootToArguments.isEmpty()); // This is only used in CPS.
|
|
|
|
m_graph.initializeNodeOwners();
|
|
|
|
auto& dominators = m_graph.ensureSSADominators();
|
|
|
|
if (Options::validateFTLOSRExitLiveness())
|
|
validateOSRExitAvailability(m_graph);
|
|
|
|
for (unsigned entrypointIndex : m_graph.m_entrypointIndexToCatchBytecodeIndex.keys())
|
|
VALIDATE((), entrypointIndex > 0); // By convention, 0 is the entrypoint index for the op_enter entrypoint, which can not be in a catch.
|
|
|
|
for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
|
|
VALIDATE((block), block->phis.isEmpty());
|
|
|
|
bool isOSRExited = false;
|
|
|
|
HashSet<Node*> nodesInThisBlock;
|
|
|
|
for (auto* node : *block) {
|
|
switch (node->op()) {
|
|
case Phi:
|
|
// Phi cannot exit, and it would be wrong to hoist anything to the Phi that could
|
|
// exit.
|
|
VALIDATE((node), !node->origin.exitOK);
|
|
break;
|
|
|
|
case GetLocal:
|
|
case SetLocal:
|
|
case SetArgumentDefinitely:
|
|
case SetArgumentMaybe:
|
|
case Phantom:
|
|
VALIDATE((node), !"bad node type for SSA");
|
|
break;
|
|
|
|
default:
|
|
// FIXME: Add more things here.
|
|
// https://bugs.webkit.org/show_bug.cgi?id=123471
|
|
break;
|
|
}
|
|
|
|
if (isOSRExited)
|
|
continue;
|
|
switch (node->op()) {
|
|
case PhantomNewObject:
|
|
case PhantomNewFunction:
|
|
case PhantomNewGeneratorFunction:
|
|
case PhantomNewAsyncFunction:
|
|
case PhantomNewAsyncGeneratorFunction:
|
|
case PhantomCreateActivation:
|
|
case PhantomDirectArguments:
|
|
case PhantomCreateRest:
|
|
case PhantomClonedArguments:
|
|
case PhantomNewRegexp:
|
|
case MovHint:
|
|
case Upsilon:
|
|
case ForwardVarargs:
|
|
case CallForwardVarargs:
|
|
case TailCallForwardVarargs:
|
|
case TailCallForwardVarargsInlinedCaller:
|
|
case ConstructForwardVarargs:
|
|
case GetMyArgumentByVal:
|
|
case GetMyArgumentByValOutOfBounds:
|
|
break;
|
|
|
|
case Check:
|
|
case CheckVarargs:
|
|
// FIXME: This is probably not correct.
|
|
break;
|
|
|
|
case PutHint:
|
|
VALIDATE((node), node->child1()->isPhantomAllocation());
|
|
break;
|
|
|
|
case PhantomSpread:
|
|
VALIDATE((node), m_graph.m_form == SSA);
|
|
// We currently support PhantomSpread over PhantomCreateRest and PhantomNewArrayBuffer.
|
|
VALIDATE((node), node->child1()->op() == PhantomCreateRest || node->child1()->op() == PhantomNewArrayBuffer);
|
|
break;
|
|
|
|
case PhantomNewArrayWithSpread: {
|
|
VALIDATE((node), m_graph.m_form == SSA);
|
|
BitVector* bitVector = node->bitVector();
|
|
for (unsigned i = 0; i < node->numChildren(); i++) {
|
|
Node* child = m_graph.varArgChild(node, i).node();
|
|
if (bitVector->get(i)) {
|
|
// We currently support PhantomSpread over PhantomCreateRest and PhantomNewArrayBuffer.
|
|
VALIDATE((node), child->op() == PhantomSpread);
|
|
} else
|
|
VALIDATE((node), !child->isPhantomAllocation());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PhantomNewArrayBuffer:
|
|
VALIDATE((node), m_graph.m_form == SSA);
|
|
VALIDATE((node), node->vectorLengthHint() >= node->castOperand<JSImmutableButterfly*>()->length());
|
|
break;
|
|
|
|
case NewArrayWithSpread: {
|
|
BitVector* bitVector = node->bitVector();
|
|
for (unsigned i = 0; i < node->numChildren(); i++) {
|
|
Node* child = m_graph.varArgChild(node, i).node();
|
|
if (child->isPhantomAllocation()) {
|
|
VALIDATE((node), bitVector->get(i));
|
|
VALIDATE((node), m_graph.m_form == SSA);
|
|
VALIDATE((node), child->op() == PhantomSpread);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Spread:
|
|
VALIDATE((node), !node->child1()->isPhantomAllocation() || node->child1()->op() == PhantomCreateRest || node->child1()->op() == PhantomNewArrayBuffer);
|
|
break;
|
|
|
|
case EntrySwitch:
|
|
VALIDATE((node), node->entrySwitchData()->cases.size() == m_graph.m_numberOfEntrypoints);
|
|
break;
|
|
|
|
case InitializeEntrypointArguments:
|
|
VALIDATE((node), node->entrypointIndex() < m_graph.m_numberOfEntrypoints);
|
|
break;
|
|
|
|
default:
|
|
m_graph.doToChildren(
|
|
node,
|
|
[&] (const Edge& edge) {
|
|
VALIDATE((node), !edge->isPhantomAllocation());
|
|
});
|
|
break;
|
|
}
|
|
|
|
isOSRExited |= node->isPseudoTerminal();
|
|
|
|
m_graph.doToChildren(node, [&] (Edge child) {
|
|
VALIDATE((node), dominators.strictlyDominates(child->owner, block) || nodesInThisBlock.contains(child.node()));
|
|
});
|
|
nodesInThisBlock.add(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
void validateEdgeWithDoubleResultIfNecessary(Node* node, Edge edge)
|
|
{
|
|
if (!edge->hasDoubleResult())
|
|
return;
|
|
|
|
if (m_graph.m_planStage < PlanStage::AfterFixup)
|
|
return;
|
|
|
|
VALIDATE((node, edge), edge.useKind() == DoubleRepUse || edge.useKind() == DoubleRepRealUse || edge.useKind() == DoubleRepAnyIntUse);
|
|
}
|
|
|
|
void checkOperand(
|
|
BasicBlock* block, Operands<size_t>& getLocalPositions,
|
|
Operands<size_t>& setLocalPositions, Operand 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() { }
|
|
|
|
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(Operand operand, BasicBlock* block)
|
|
{
|
|
if (!block) {
|
|
dataLog(operand, " in null Block ");
|
|
return;
|
|
}
|
|
|
|
dataLog(operand, " in Block ", *block);
|
|
}
|
|
|
|
void reportValidationContext(
|
|
Operand operand, BasicBlock* sourceBlock, BasicBlock* destinationBlock)
|
|
{
|
|
dataLog(operand, " in Block ", *sourceBlock, " -> ", *destinationBlock);
|
|
}
|
|
|
|
void reportValidationContext(
|
|
Operand operand, BasicBlock* sourceBlock, Node* prevNode)
|
|
{
|
|
dataLog(prevNode, " for ", operand, " 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("\n");
|
|
if (!m_graphDumpBeforePhase.isNull()) {
|
|
dataLog("Before phase:\n");
|
|
dataLog(m_graphDumpBeforePhase);
|
|
}
|
|
dataLog("At time of failure:\n");
|
|
m_graph.dump();
|
|
}
|
|
};
|
|
|
|
} // End anonymous namespace.
|
|
|
|
void validate(Graph& graph, GraphDumpMode graphDumpMode, CString graphDumpBeforePhase)
|
|
{
|
|
Validate validationObject(graph, graphDumpMode, graphDumpBeforePhase);
|
|
validationObject.validate();
|
|
}
|
|
|
|
} } // namespace JSC::DFG
|
|
|
|
#endif // ENABLE(DFG_JIT)
|
|
|