blob: f53f94f019d90599e0bfa4f2e863e9d2cf30fb6f [file] [log] [blame]
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'nodes.dart';
class HValidator extends HInstructionVisitor {
bool isValid = true;
HGraph graph;
void visitGraph(HGraph visitee) {
graph = visitee;
visitDominatorTree(visitee);
}
void markInvalid(String reason) {
print(reason);
isValid = false;
}
// Note that during construction of the Ssa graph the basic blocks are
// not required to be valid yet.
@override
void visitBasicBlock(HBasicBlock block) {
currentBlock = block;
if (!isValid) return; // Don't need to continue if we are already invalid.
// Test that the last instruction is a branching instruction and that the
// basic block contains the branch-target.
if (block.first == null || block.last == null) {
markInvalid("empty block");
}
if (block.last is! HControlFlow) {
markInvalid("block ends with non-tail node.");
}
if (block.last is HIf && block.successors.length != 2) {
markInvalid("If node without two successors");
}
if (block.last is HConditionalBranch && block.successors.length != 2) {
markInvalid("Conditional node without two successors");
}
if (block.last is HLoopBranch) {
// Assert that the block we inserted to avoid critical edges satisfies
// our assumptions. That is, it must not contain any instructions
// (although it may contain phi-updates).
HBasicBlock avoidCriticalEdgeBlock = block.successors.last;
if (avoidCriticalEdgeBlock.first is! HGoto) {
markInvalid("Critical edge block contains instructions");
}
}
if (block.last is HGoto && block.successors.length != 1) {
markInvalid("Goto node with not exactly one successor");
}
if (block.last is HJump && block.successors.length != 1) {
markInvalid("Break or continue node without one successor");
}
if ((block.last is HReturn || block.last is HThrow) &&
(block.successors.length != 1 || !block.successors[0].isExitBlock())) {
markInvalid("Return or throw node with > 1 successor "
"or not going to exit-block");
}
if (block.last is HExit && !block.successors.isEmpty) {
markInvalid("Exit block with successor");
}
if (block.successors.isEmpty && !block.isExitBlock()) {
markInvalid("Non-exit block without successor");
}
// Check that successors ids are always higher than the current one.
// TODO(floitsch): this is, of course, not true for back-branches.
if (block.id == null) markInvalid("block without id");
for (HBasicBlock successor in block.successors) {
if (!isValid) break;
if (successor.id == null) markInvalid("successor without id");
if (successor.id <= block.id && !successor.isLoopHeader()) {
markInvalid("successor with lower id, but not a loop-header");
}
}
// Make sure we don't have a critical edge.
if (isValid &&
block.successors.length > 1 &&
block.last is! HTry &&
block.last is! HExitTry &&
block.last is! HSwitch) {
for (HBasicBlock successor in block.successors) {
if (!isValid) break;
if (successor.predecessors.length >= 2) {
markInvalid("SSA graph contains critical edge.");
}
}
}
// Check that the entries in the dominated-list are sorted.
int lastId = 0;
for (HBasicBlock dominated in block.dominatedBlocks) {
if (!isValid) break;
if (!identical(dominated.dominator, block)) {
markInvalid("dominated block not pointing back");
}
if (dominated.id == null || dominated.id <= lastId) {
markInvalid("dominated.id == null or dominated has <= id");
}
lastId = dominated.id;
}
if (!isValid) return;
block.forEachPhi(visitInstruction);
// Check that the blocks of the parameters of a phi are dominating the
// corresponding predecessor block. Note that a block dominates
// itself.
block.forEachPhi((HPhi phi) {
assert(phi.inputs.length <= block.predecessors.length);
for (int i = 0; i < phi.inputs.length; i++) {
HInstruction input = phi.inputs[i];
if (!input.block.dominates(block.predecessors[i])) {
markInvalid("Definition does not dominate use");
}
}
});
// Check that the blocks of the inputs of an instruction dominate the
// instruction's block.
block.forEachInstruction((HInstruction instruction) {
for (HInstruction input in instruction.inputs) {
if (!input.block.dominates(block)) {
markInvalid("Definition does not dominate use");
}
}
});
super.visitBasicBlock(block);
}
// Limit for the size of `inputs` and `usedBy` lists. We assume lists longer
// than this are OK in order to avoid the O(N^2) validation getting out of
// hand.
//
// Poster child: corelib_2/regexp/pcre_test.dart, which has a 7KLOC main().
static const int kMaxValidatedInstructionListLength = 1000;
/// Verifies [instruction] is contained in [instructions] [count] times.
static bool checkInstructionCount(
List<HInstruction> instructions, HInstruction instruction, int count) {
if (instructions.length > kMaxValidatedInstructionListLength) return true;
int result = 0;
for (int i = 0; i < instructions.length; i++) {
if (identical(instructions[i], instruction)) result++;
}
return result == count;
}
/// Returns true if the predicate returns true for every instruction in the
/// list. The argument to [f] is an instruction with the count of how often
/// it appeared in the list [instructions].
static bool everyInstruction(
List<HInstruction> instructions, bool Function(HInstruction, int) f) {
if (instructions.length > kMaxValidatedInstructionListLength) return true;
var copy = new List<HInstruction>.from(instructions);
// TODO(floitsch): there is currently no way to sort HInstructions before
// we have assigned an ID. The loop is therefore O(n^2) for now.
for (int i = 0; i < copy.length; i++) {
var current = copy[i];
if (current == null) continue;
int count = 1;
for (int j = i + 1; j < copy.length; j++) {
if (identical(copy[j], current)) {
copy[j] = null;
count++;
}
}
if (!f(current, count)) return false;
}
return true;
}
@override
void visitInstruction(HInstruction instruction) {
// Verifies that we are in the use list of our inputs.
bool hasCorrectInputs() {
bool inBasicBlock = instruction.isInBasicBlock();
return everyInstruction(instruction.inputs, (input, count) {
if (inBasicBlock) {
return input.isInBasicBlock() &&
checkInstructionCount(input.usedBy, instruction, count);
} else {
return checkInstructionCount(input.usedBy, instruction, 0);
}
});
}
// Verifies that all our uses have us in their inputs.
bool hasCorrectUses() {
if (!instruction.isInBasicBlock()) return true;
return everyInstruction(instruction.usedBy, (use, count) {
return use.isInBasicBlock() &&
checkInstructionCount(use.inputs, instruction, count);
});
}
if (!identical(instruction.block, currentBlock)) {
markInvalid("Instruction in wrong block");
}
if (!hasCorrectInputs()) {
markInvalid("Incorrect inputs");
}
if (!hasCorrectUses()) {
markInvalid("Incorrect uses");
}
}
}