/*
* 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 "android-base/stringprintf.h"
#include "base/arena_allocator.h"
#include "base/macros.h"
#include "builder.h"
#include "dex/dex_file.h"
#include "dex/dex_instruction.h"
#include "nodes.h"
#include "optimizing_unit_test.h"
#include "pretty_printer.h"
#include "ssa_builder.h"
#include "gtest/gtest.h"
namespace art HIDDEN {
class SsaTest : public CommonCompilerTest, public OptimizingUnitTestHelper {
protected :
void TestCode(const std::vector<uint16_t>& data, const char * expected);
};
class SsaPrettyPrinter : public HPrettyPrinter {
public :
explicit SsaPrettyPrinter(HGraph* graph) : HPrettyPrinter(graph), str_("" ) {}
void PrintInt(int value) override {
str_ += android::base::StringPrintf("%d" , value);
}
void PrintString(const char * value) override {
str_ += value;
}
void PrintNewLine() override {
str_ += '\n' ;
}
void Clear() { str_.clear(); }
std::string str() const { return str_; }
void VisitIntConstant(HIntConstant* constant) override {
PrintPreInstruction(constant);
str_ += constant->DebugName();
str_ += " " ;
PrintInt(constant->GetValue());
PrintPostInstruction(constant);
}
private :
std::string str_;
DISALLOW_COPY_AND_ASSIGN(SsaPrettyPrinter);
};
static void ReNumberInstructions(HGraph* graph) {
int id = 0 ;
for (HBasicBlock* block : graph->GetBlocks()) {
for (HInstructionIteratorPrefetchNext it(block->GetPhis()); !it.Done(); it.Advance()) {
it.Current()->SetId(id++);
}
for (HInstructionIteratorPrefetchNext it(block->GetInstructions()); !it.Done(); it.Advance()) {
it.Current()->SetId(id++);
}
}
}
void SsaTest::TestCode(const std::vector<uint16_t>& data, const char * expected) {
HGraph* graph = CreateCFG(data);
// Suspend checks implementation may change in the future, and this test relies
// on how instructions are ordered.
RemoveSuspendChecks(graph);
ReNumberInstructions(graph);
// Test that phis had their type set.
for (HBasicBlock* block : graph->GetBlocks()) {
for (HInstructionIteratorPrefetchNext it(block->GetPhis()); !it.Done(); it.Advance()) {
ASSERT_NE(it.Current()->GetType(), DataType::Type::kVoid);
}
}
SsaPrettyPrinter printer(graph);
printer.VisitInsertionOrder();
ASSERT_STREQ(expected, printer.str().c_str());
}
TEST_F(SsaTest, CFG1) {
// Test that we get rid of loads and stores.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [2, 2]\n"
" 1: Goto\n"
"BasicBlock 1, pred: 0, succ: 5, 2\n"
" 2: Equal(0, 0) [3]\n"
" 3: If(2)\n"
"BasicBlock 2, pred: 1, succ: 3\n"
" 4: Goto\n"
"BasicBlock 3, pred: 5, 2, succ: 4\n"
" 5: ReturnVoid\n"
"BasicBlock 4, pred: 3\n"
" 6: Exit\n"
// Synthesized block to avoid critical edge.
"BasicBlock 5, pred: 1, succ: 3\n"
" 7: Goto\n" ;
const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::IF_EQ, 3 ,
Instruction::GOTO | 0 x100,
Instruction::RETURN_VOID);
TestCode(data, expected);
}
TEST_F(SsaTest, CFG2) {
// Test that we create a phi for the join block of an if control flow instruction
// when there is only code in the else branch.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [6, 3, 3]\n"
" 1: IntConstant 4 [6]\n"
" 2: Goto\n"
"BasicBlock 1, pred: 0, succ: 5, 2\n"
" 3: Equal(0, 0) [4]\n"
" 4: If(3)\n"
"BasicBlock 2, pred: 1, succ: 3\n"
" 5: Goto\n"
"BasicBlock 3, pred: 5, 2, succ: 4\n"
" 6: Phi(0, 1) [7]\n"
" 7: Return(6)\n"
"BasicBlock 4, pred: 3\n"
" 8: Exit\n"
// Synthesized block to avoid critical edge.
"BasicBlock 5, pred: 1, succ: 3\n"
" 9: Goto\n" ;
const std::vector<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 );
TestCode(data, expected);
}
TEST_F(SsaTest, CFG3) {
// Test that we create a phi for the join block of an if control flow instruction
// when both branches update a local.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [4, 4]\n"
" 1: IntConstant 5 [8]\n"
" 2: IntConstant 4 [8]\n"
" 3: Goto\n"
"BasicBlock 1, pred: 0, succ: 3, 2\n"
" 4: Equal(0, 0) [5]\n"
" 5: If(4)\n"
"BasicBlock 2, pred: 1, succ: 4\n"
" 6: Goto\n"
"BasicBlock 3, pred: 1, succ: 4\n"
" 7: Goto\n"
"BasicBlock 4, pred: 2, 3, succ: 5\n"
" 8: Phi(2, 1) [9]\n"
" 9: Return(8)\n"
"BasicBlock 5, pred: 4\n"
" 10: Exit\n" ;
const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::IF_EQ, 4 ,
Instruction::CONST_4 | 4 << 12 | 0 ,
Instruction::GOTO | 0 x200,
Instruction::CONST_4 | 5 << 12 | 0 ,
Instruction::RETURN | 0 << 8 );
TestCode(data, expected);
}
TEST_F(SsaTest, Loop1) {
// Test that we create a phi for an initialized local at entry of a loop.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [6, 3, 3]\n"
" 1: IntConstant 4 [6]\n"
" 2: Goto\n"
"BasicBlock 1, pred: 0, succ: 4, 2\n"
" 3: Equal(0, 0) [4]\n"
" 4: If(3)\n"
"BasicBlock 2, pred: 1, succ: 3\n"
" 5: Goto\n"
"BasicBlock 3, pred: 2, 4, succ: 5\n"
" 6: Phi(1, 0) [9]\n"
" 7: Goto\n"
"BasicBlock 4, pred: 1, succ: 3\n"
" 8: Goto\n"
"BasicBlock 5, pred: 3, succ: 6\n"
" 9: Return(6)\n"
"BasicBlock 6, pred: 5\n"
" 10: Exit\n" ;
const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::IF_EQ, 4 ,
Instruction::CONST_4 | 4 << 12 | 0 ,
Instruction::GOTO | 0 x200,
Instruction::GOTO | 0 xFF00,
Instruction::RETURN | 0 << 8 );
TestCode(data, expected);
}
TEST_F(SsaTest, Loop2) {
// Simple loop with one preheader and one back edge.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [4]\n"
" 1: IntConstant 4 [4]\n"
" 2: Goto\n"
"BasicBlock 1, pred: 0, succ: 2\n"
" 3: Goto\n"
"BasicBlock 2, pred: 1, 3, succ: 4, 3\n"
" 4: Phi(0, 1) [5, 5]\n"
" 5: Equal(4, 4) [6]\n"
" 6: If(5)\n"
"BasicBlock 3, pred: 2, succ: 2\n"
" 7: Goto\n"
"BasicBlock 4, pred: 2, succ: 5\n"
" 8: ReturnVoid\n"
"BasicBlock 5, pred: 4\n"
" 9: Exit\n" ;
const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::IF_EQ, 4 ,
Instruction::CONST_4 | 4 << 12 | 0 ,
Instruction::GOTO | 0 xFD00,
Instruction::RETURN_VOID);
TestCode(data, expected);
}
TEST_F(SsaTest, Loop3) {
// Test that a local not yet defined at the entry of a loop is handled properly.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [5]\n"
" 1: IntConstant 5 [9]\n"
" 2: IntConstant 4 [5]\n"
" 3: Goto\n"
"BasicBlock 1, pred: 0, succ: 2\n"
" 4: Goto\n"
"BasicBlock 2, pred: 1, 3, succ: 4, 3\n"
" 5: Phi(0, 2) [6, 6]\n"
" 6: Equal(5, 5) [7]\n"
" 7: If(6)\n"
"BasicBlock 3, pred: 2, succ: 2\n"
" 8: Goto\n"
"BasicBlock 4, pred: 2, succ: 5\n"
" 9: Return(1)\n"
"BasicBlock 5, pred: 4\n"
" 10: Exit\n" ;
const std::vector<uint16_t> data = TWO_REGISTERS_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::IF_EQ, 4 ,
Instruction::CONST_4 | 4 << 12 | 0 ,
Instruction::GOTO | 0 xFD00,
Instruction::CONST_4 | 5 << 12 | 1 << 8 ,
Instruction::RETURN | 1 << 8 );
TestCode(data, expected);
}
TEST_F(SsaTest, Loop4) {
// Make sure we support a preheader of a loop not being the first predecessor
// in the predecessor list of the header.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [4]\n"
" 1: IntConstant 4 [4]\n"
" 2: Goto\n"
"BasicBlock 1, pred: 0, succ: 4\n"
" 3: Goto\n"
"BasicBlock 2, pred: 4, 3, succ: 5, 3\n"
" 4: Phi(0, 1) [9, 5, 5]\n"
" 5: Equal(4, 4) [6]\n"
" 6: If(5)\n"
"BasicBlock 3, pred: 2, succ: 2\n"
" 7: Goto\n"
"BasicBlock 4, pred: 1, succ: 2\n"
" 8: Goto\n"
"BasicBlock 5, pred: 2, succ: 6\n"
" 9: Return(4)\n"
"BasicBlock 6, pred: 5\n"
" 10: Exit\n" ;
const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::GOTO | 0 x500,
Instruction::IF_EQ, 5 ,
Instruction::CONST_4 | 4 << 12 | 0 ,
Instruction::GOTO | 0 xFD00,
Instruction::GOTO | 0 xFC00,
Instruction::RETURN | 0 << 8 );
TestCode(data, expected);
}
TEST_F(SsaTest, Loop5) {
// Make sure we create a preheader of a loop when a header originally has two
// incoming blocks and one back edge.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [4, 4]\n"
" 1: IntConstant 5 [13]\n"
" 2: IntConstant 4 [13]\n"
" 3: Goto\n"
"BasicBlock 1, pred: 0, succ: 3, 2\n"
" 4: Equal(0, 0) [5]\n"
" 5: If(4)\n"
"BasicBlock 2, pred: 1, succ: 8\n"
" 6: Goto\n"
"BasicBlock 3, pred: 1, succ: 8\n"
" 7: Goto\n"
"BasicBlock 4, pred: 8, 5, succ: 6, 5\n"
" 8: Equal(13, 13) [9]\n"
" 9: If(8)\n"
"BasicBlock 5, pred: 4, succ: 4\n"
" 10: Goto\n"
"BasicBlock 6, pred: 4, succ: 7\n"
" 11: Return(13)\n"
"BasicBlock 7, pred: 6\n"
" 12: Exit\n"
"BasicBlock 8, pred: 2, 3, succ: 4\n"
" 13: Phi(2, 1) [11, 8, 8]\n"
" 14: Goto\n" ;
const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::IF_EQ, 4 ,
Instruction::CONST_4 | 4 << 12 | 0 ,
Instruction::GOTO | 0 x200,
Instruction::CONST_4 | 5 << 12 | 0 ,
Instruction::IF_EQ, 3 ,
Instruction::GOTO | 0 xFE00,
Instruction::RETURN | 0 << 8 );
TestCode(data, expected);
}
TEST_F(SsaTest, Loop6) {
// Test a loop with one preheader and two back edges (e.g. continue).
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [5]\n"
" 1: IntConstant 4 [5, 8, 8]\n"
" 2: IntConstant 5 [5]\n"
" 3: Goto\n"
"BasicBlock 1, pred: 0, succ: 2\n"
" 4: Goto\n"
"BasicBlock 2, pred: 1, 4, 5, succ: 6, 3\n"
" 5: Phi(0, 2, 1) [12, 6, 6]\n"
" 6: Equal(5, 5) [7]\n"
" 7: If(6)\n"
"BasicBlock 3, pred: 2, succ: 5, 4\n"
" 8: Equal(1, 1) [9]\n"
" 9: If(8)\n"
"BasicBlock 4, pred: 3, succ: 2\n"
" 10: Goto\n"
"BasicBlock 5, pred: 3, succ: 2\n"
" 11: Goto\n"
"BasicBlock 6, pred: 2, succ: 7\n"
" 12: Return(5)\n"
"BasicBlock 7, pred: 6\n"
" 13: Exit\n" ;
const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::IF_EQ, 8 ,
Instruction::CONST_4 | 4 << 12 | 0 ,
Instruction::IF_EQ, 4 ,
Instruction::CONST_4 | 5 << 12 | 0 ,
Instruction::GOTO | 0 xFA00,
Instruction::GOTO | 0 xF900,
Instruction::RETURN | 0 << 8 );
TestCode(data, expected);
}
TEST_F(SsaTest, Loop7) {
// Test a loop with one preheader, one back edge, and two exit edges (e.g. break).
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [5]\n"
" 1: IntConstant 4 [5, 8, 8]\n"
" 2: IntConstant 5 [12]\n"
" 3: Goto\n"
"BasicBlock 1, pred: 0, succ: 2\n"
" 4: Goto\n"
"BasicBlock 2, pred: 1, 5, succ: 8, 3\n"
" 5: Phi(0, 1) [12, 6, 6]\n"
" 6: Equal(5, 5) [7]\n"
" 7: If(6)\n"
"BasicBlock 3, pred: 2, succ: 5, 4\n"
" 8: Equal(1, 1) [9]\n"
" 9: If(8)\n"
"BasicBlock 4, pred: 3, succ: 6\n"
" 10: Goto\n"
"BasicBlock 5, pred: 3, succ: 2\n"
" 11: Goto\n"
"BasicBlock 6, pred: 8, 4, succ: 7\n"
" 12: Phi(5, 2) [13]\n"
" 13: Return(12)\n"
"BasicBlock 7, pred: 6\n"
" 14: Exit\n"
"BasicBlock 8, pred: 2, succ: 6\n"
" 15: Goto\n" ;
const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::IF_EQ, 8 ,
Instruction::CONST_4 | 4 << 12 | 0 ,
Instruction::IF_EQ, 4 ,
Instruction::CONST_4 | 5 << 12 | 0 ,
Instruction::GOTO | 0 x0200,
Instruction::GOTO | 0 xF900,
Instruction::RETURN | 0 << 8 );
TestCode(data, expected);
}
TEST_F(SsaTest, DeadLocal) {
// Test that we correctly handle a local not being used.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0\n"
" 1: Goto\n"
"BasicBlock 1, pred: 0, succ: 2\n"
" 2: ReturnVoid\n"
"BasicBlock 2, pred: 1\n"
" 3: Exit\n" ;
const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::RETURN_VOID);
TestCode(data, expected);
}
TEST_F(SsaTest, LocalInIf) {
// Test that we do not create a phi in the join block when one predecessor
// does not update the local.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [3, 3]\n"
" 1: IntConstant 4\n"
" 2: Goto\n"
"BasicBlock 1, pred: 0, succ: 5, 2\n"
" 3: Equal(0, 0) [4]\n"
" 4: If(3)\n"
"BasicBlock 2, pred: 1, succ: 3\n"
" 5: Goto\n"
"BasicBlock 3, pred: 5, 2, succ: 4\n"
" 6: ReturnVoid\n"
"BasicBlock 4, pred: 3\n"
" 7: Exit\n"
// Synthesized block to avoid critical edge.
"BasicBlock 5, pred: 1, succ: 3\n"
" 8: Goto\n" ;
const std::vector<uint16_t> data = TWO_REGISTERS_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::IF_EQ, 3 ,
Instruction::CONST_4 | 4 << 12 | 1 << 8 ,
Instruction::RETURN_VOID);
TestCode(data, expected);
}
TEST_F(SsaTest, MultiplePredecessors) {
// Test that we do not create a phi when one predecessor
// does not update the local.
const char * expected =
"BasicBlock 0, succ: 1\n"
" 0: IntConstant 0 [4, 4, 8, 8, 6, 6, 2, 2]\n"
" 1: Goto\n"
"BasicBlock 1, pred: 0, succ: 3, 2\n"
" 2: Equal(0, 0) [3]\n"
" 3: If(2)\n"
"BasicBlock 2, pred: 1, succ: 5\n"
" 4: Add(0, 0)\n"
" 5: Goto\n"
"BasicBlock 3, pred: 1, succ: 7, 4\n"
" 6: Equal(0, 0) [7]\n"
" 7: If(6)\n"
"BasicBlock 4, pred: 3, succ: 5\n"
" 8: Add(0, 0)\n"
" 9: Goto\n"
// This block should not get a phi for local 1.
"BasicBlock 5, pred: 2, 7, 4, succ: 6\n"
" 10: ReturnVoid\n"
"BasicBlock 6, pred: 5\n"
" 11: Exit\n"
"BasicBlock 7, pred: 3, succ: 5\n"
" 12: Goto\n" ;
const std::vector<uint16_t> data = TWO_REGISTERS_CODE_ITEM(
Instruction::CONST_4 | 0 | 0 ,
Instruction::IF_EQ, 5 ,
Instruction::ADD_INT_LIT8 | 1 << 8 , 0 << 8 ,
Instruction::GOTO | 0 x0500,
Instruction::IF_EQ, 4 ,
Instruction::ADD_INT_LIT8 | 1 << 8 , 0 << 8 ,
Instruction::RETURN_VOID);
TestCode(data, expected);
}
} // namespace art
Messung V0.5 in Prozent C=89 H=94 G=91
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-06-29)
¤
*© Formatika GbR, Deutschland