Add CFG and SSA form checkers in the optimizing compiler.
Checks performed on control-flow graphs:
- Ensure that the predecessors and successors of a basic block are
consistent within a control-flow graph.
- Ensure basic blocks end with a branch instruction.
- Detect phi functions listed in non-phi instruction lists and vice
versa.
- Ensure a block's instructions (and phi functions) are associated
with this very block.
Checks performed on SSA form graphs:
- Ensure an instruction dominates all its uses.
- Ensure there are no critical edges.
Change-Id: I1c12b4a61ecf608682152c897980ababa7eca847
diff --git a/compiler/optimizing/graph_checker_test.cc b/compiler/optimizing/graph_checker_test.cc
new file mode 100644
index 0000000..ea06920
--- /dev/null
+++ b/compiler/optimizing/graph_checker_test.cc
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "graph_checker.h"
+#include "optimizing_unit_test.h"
+
+#include "gtest/gtest.h"
+
+namespace art {
+
+/**
+ * Create a simple control-flow graph composed of two blocks:
+ *
+ * BasicBlock 0, succ: 1
+ * 0: Goto 1
+ * BasicBlock 1, pred: 0
+ * 1: Exit
+ */
+HGraph* CreateSimpleCFG(ArenaAllocator* allocator) {
+ HGraph* graph = new (allocator) HGraph(allocator);
+ HBasicBlock* entry_block = new (allocator) HBasicBlock(graph);
+ entry_block->AddInstruction(new (allocator) HGoto());
+ graph->AddBlock(entry_block);
+ graph->SetEntryBlock(entry_block);
+ HBasicBlock* exit_block = new (allocator) HBasicBlock(graph);
+ exit_block->AddInstruction(new (allocator) HExit());
+ graph->AddBlock(exit_block);
+ graph->SetExitBlock(exit_block);
+ entry_block->AddSuccessor(exit_block);
+ return graph;
+}
+
+
+static void TestCode(const uint16_t* data) {
+ ArenaPool pool;
+ ArenaAllocator allocator(&pool);
+ HGraph* graph = CreateCFG(&allocator, data);
+ ASSERT_NE(graph, nullptr);
+
+ GraphChecker graph_checker(&allocator, graph);
+ graph_checker.VisitInsertionOrder();
+ ASSERT_TRUE(graph_checker.IsValid());
+}
+
+static void TestCodeSSA(const uint16_t* data) {
+ ArenaPool pool;
+ ArenaAllocator allocator(&pool);
+ HGraph* graph = CreateCFG(&allocator, data);
+ ASSERT_NE(graph, nullptr);
+
+ graph->BuildDominatorTree();
+ graph->TransformToSSA();
+
+ SSAChecker ssa_checker(&allocator, graph);
+ ssa_checker.VisitInsertionOrder();
+ ASSERT_TRUE(ssa_checker.IsValid());
+}
+
+
+TEST(GraphChecker, ReturnVoid) {
+ const uint16_t data[] = ZERO_REGISTER_CODE_ITEM(
+ Instruction::RETURN_VOID);
+
+ TestCode(data);
+}
+
+TEST(GraphChecker, CFG1) {
+ const uint16_t data[] = ZERO_REGISTER_CODE_ITEM(
+ Instruction::GOTO | 0x100,
+ Instruction::RETURN_VOID);
+
+ TestCode(data);
+}
+
+TEST(GraphChecker, CFG2) {
+ const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
+ Instruction::CONST_4 | 0 | 0,
+ Instruction::IF_EQ, 3,
+ Instruction::GOTO | 0x100,
+ Instruction::RETURN_VOID);
+
+ TestCode(data);
+}
+
+TEST(GraphChecker, CFG3) {
+ const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
+ Instruction::CONST_4 | 0 | 0,
+ Instruction::IF_EQ, 3,
+ Instruction::GOTO | 0x100,
+ Instruction::GOTO | 0xFF00);
+
+ TestCode(data);
+}
+
+// Test case with an invalid graph containing inconsistent
+// predecessor/successor arcs in CFG.
+TEST(GraphChecker, InconsistentPredecessorsAndSuccessors) {
+ ArenaPool pool;
+ ArenaAllocator allocator(&pool);
+
+ HGraph* graph = CreateSimpleCFG(&allocator);
+ GraphChecker graph_checker(&allocator, graph);
+ graph_checker.VisitInsertionOrder();
+ ASSERT_TRUE(graph_checker.IsValid());
+
+ // Remove the entry block from the exit block's predecessors, to create an
+ // inconsistent successor/predecessor relation.
+ graph->GetExitBlock()->RemovePredecessor(graph->GetEntryBlock());
+ graph_checker.VisitInsertionOrder();
+ ASSERT_FALSE(graph_checker.IsValid());
+}
+
+// Test case with an invalid graph containing a non-branch last
+// instruction in a block.
+TEST(GraphChecker, BlockEndingWithNonBranchInstruction) {
+ ArenaPool pool;
+ ArenaAllocator allocator(&pool);
+
+ HGraph* graph = CreateSimpleCFG(&allocator);
+ GraphChecker graph_checker(&allocator, graph);
+ graph_checker.VisitInsertionOrder();
+ ASSERT_TRUE(graph_checker.IsValid());
+
+ // Remove the sole instruction of the exit block (composed of a
+ // single Exit instruction) to make it invalid (i.e. not ending by a
+ // branch instruction).
+ HBasicBlock* exit_block = graph->GetExitBlock();
+ HInstruction* last_inst = exit_block->GetLastInstruction();
+ exit_block->RemoveInstruction(last_inst);
+
+ graph_checker.VisitInsertionOrder();
+ ASSERT_FALSE(graph_checker.IsValid());
+}
+
+TEST(SSAChecker, SSAPhi) {
+ // This code creates one Phi function during the conversion to SSA form.
+ const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
+ Instruction::CONST_4 | 0 | 0,
+ Instruction::IF_EQ, 3,
+ Instruction::CONST_4 | 4 << 12 | 0,
+ Instruction::RETURN | 0 << 8);
+
+ TestCodeSSA(data);
+}
+
+} // namespace art