/* * 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 "base/arena_allocator.h" #include "builder.h" #include "code_generator.h" #include "dex/dex_file.h" #include "dex/dex_instruction.h" #include "driver/compiler_options.h" #include "nodes.h" #include "optimizing_unit_test.h" #include "prepare_for_register_allocation.h" #include "ssa_liveness_analysis.h" namespace art { class LivenessTest : public OptimizingUnitTest { protected: void TestCode(const std::vector<uint16_t>& data, const char* expected); }; static void DumpBitVector(BitVector* vector, std::ostream& buffer, size_t count, const char* prefix) { buffer << prefix; buffer << '('; for (size_t i = 0; i < count; ++i) { buffer << vector->IsBitSet(i); } buffer << ")\n"; } void LivenessTest::TestCode(const std::vector<uint16_t>& data, const char* expected) { HGraph* graph = CreateCFG(data); // `Inline` conditions into ifs. PrepareForRegisterAllocation(graph, *compiler_options_).Run(); std::unique_ptr<CodeGenerator> codegen = CodeGenerator::Create(graph, *compiler_options_); SsaLivenessAnalysis liveness(graph, codegen.get(), GetScopedAllocator()); liveness.Analyze(); std::ostringstream buffer; for (HBasicBlock* block : graph->GetBlocks()) { buffer << "Block " << block->GetBlockId() << std::endl; size_t ssa_values = liveness.GetNumberOfSsaValues(); BitVector* live_in = liveness.GetLiveInSet(*block); DumpBitVector(live_in, buffer, ssa_values, " live in: "); BitVector* live_out = liveness.GetLiveOutSet(*block); DumpBitVector(live_out, buffer, ssa_values, " live out: "); BitVector* kill = liveness.GetKillSet(*block); DumpBitVector(kill, buffer, ssa_values, " kill: "); } ASSERT_STREQ(expected, buffer.str().c_str()); } TEST_F(LivenessTest, CFG1) { const char* expected = "Block 0\n" " live in: (0)\n" " live out: (0)\n" " kill: (1)\n" "Block 1\n" " live in: (0)\n" " live out: (0)\n" " kill: (0)\n" "Block 2\n" " live in: (0)\n" " live out: (0)\n" " kill: (0)\n"; // Constant is not used. const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::RETURN_VOID); TestCode(data, expected); } TEST_F(LivenessTest, CFG2) { const char* expected = "Block 0\n" " live in: (0)\n" " live out: (1)\n" " kill: (1)\n" "Block 1\n" " live in: (1)\n" " live out: (0)\n" " kill: (0)\n" "Block 2\n" " live in: (0)\n" " live out: (0)\n" " kill: (0)\n"; const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::RETURN); TestCode(data, expected); } TEST_F(LivenessTest, CFG3) { const char* expected = "Block 0\n" // entry block " live in: (000)\n" " live out: (110)\n" " kill: (110)\n" "Block 1\n" // block with add " live in: (110)\n" " live out: (001)\n" " kill: (001)\n" "Block 2\n" // block with return " live in: (001)\n" " live out: (000)\n" " kill: (000)\n" "Block 3\n" // exit block " live in: (000)\n" " live out: (000)\n" " kill: (000)\n"; const std::vector<uint16_t> data = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 3 << 12 | 0, Instruction::CONST_4 | 4 << 12 | 1 << 8, Instruction::ADD_INT_2ADDR | 1 << 12, Instruction::GOTO | 0x100, Instruction::RETURN); TestCode(data, expected); } TEST_F(LivenessTest, CFG4) { // var a; // if (0 == 0) { // a = 5; // } else { // a = 4; // } // return a; // // Bitsets are made of: // (constant0, constant5, constant4, phi) const char* expected = "Block 0\n" // entry block " live in: (0000)\n" " live out: (1110)\n" " kill: (1110)\n" "Block 1\n" // block with if " live in: (1110)\n" " live out: (0110)\n" " kill: (0000)\n" "Block 2\n" // else block " live in: (0010)\n" " live out: (0000)\n" " kill: (0000)\n" "Block 3\n" // then block " live in: (0100)\n" " live out: (0000)\n" " kill: (0000)\n" "Block 4\n" // return block " live in: (0000)\n" " live out: (0000)\n" " kill: (0001)\n" "Block 5\n" // exit block " live in: (0000)\n" " live out: (0000)\n" " kill: (0000)\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 | 0x200, Instruction::CONST_4 | 5 << 12 | 0, Instruction::RETURN | 0 << 8); TestCode(data, expected); } TEST_F(LivenessTest, CFG5) { // var a = 0; // if (0 == 0) { // } else { // a = 4; // } // return a; // // Bitsets are made of: // (constant0, constant4, phi) const char* expected = "Block 0\n" // entry block " live in: (000)\n" " live out: (110)\n" " kill: (110)\n" "Block 1\n" // block with if " live in: (110)\n" " live out: (110)\n" " kill: (000)\n" "Block 2\n" // else block " live in: (010)\n" " live out: (000)\n" " kill: (000)\n" "Block 3\n" // return block " live in: (000)\n" " live out: (000)\n" " kill: (001)\n" "Block 4\n" // exit block " live in: (000)\n" " live out: (000)\n" " kill: (000)\n" "Block 5\n" // block to avoid critical edge. Predecessor is 1, successor is 3. " live in: (100)\n" " live out: (000)\n" " kill: (000)\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(LivenessTest, Loop1) { // Simple loop with one preheader and one back edge. // var a = 0; // while (a == a) { // a = 4; // } // return; // Bitsets are made of: // (constant0, constant4, phi) const char* expected = "Block 0\n" // entry block " live in: (000)\n" " live out: (110)\n" " kill: (110)\n" "Block 1\n" // pre header " live in: (110)\n" " live out: (010)\n" " kill: (000)\n" "Block 2\n" // loop header " live in: (010)\n" " live out: (010)\n" " kill: (001)\n" "Block 3\n" // back edge " live in: (010)\n" " live out: (010)\n" " kill: (000)\n" "Block 4\n" // return block " live in: (000)\n" " live out: (000)\n" " kill: (000)\n" "Block 5\n" // exit block " live in: (000)\n" " live out: (000)\n" " kill: (000)\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 | 0xFD00, Instruction::RETURN_VOID); TestCode(data, expected); } TEST_F(LivenessTest, Loop3) { // Test that the returned value stays live in a preceding loop. // var a = 0; // while (a == a) { // a = 4; // } // return 5; // Bitsets are made of: // (constant0, constant5, constant4, phi) const char* expected = "Block 0\n" " live in: (0000)\n" " live out: (1110)\n" " kill: (1110)\n" "Block 1\n" " live in: (1110)\n" " live out: (0110)\n" " kill: (0000)\n" "Block 2\n" // loop header " live in: (0110)\n" " live out: (0110)\n" " kill: (0001)\n" "Block 3\n" // back edge " live in: (0110)\n" " live out: (0110)\n" " kill: (0000)\n" "Block 4\n" // return block " live in: (0100)\n" " live out: (0000)\n" " kill: (0000)\n" "Block 5\n" // exit block " live in: (0000)\n" " live out: (0000)\n" " kill: (0000)\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 | 0xFD00, Instruction::CONST_4 | 5 << 12 | 1 << 8, Instruction::RETURN | 1 << 8); TestCode(data, expected); } TEST_F(LivenessTest, Loop4) { // Make sure we support a preheader of a loop not being the first predecessor // in the predecessor list of the header. // var a = 0; // while (a == a) { // a = 4; // } // return a; // Bitsets are made of: // (constant0, constant4, phi) const char* expected = "Block 0\n" " live in: (000)\n" " live out: (110)\n" " kill: (110)\n" "Block 1\n" " live in: (110)\n" " live out: (110)\n" " kill: (000)\n" "Block 2\n" // loop header " live in: (010)\n" " live out: (011)\n" " kill: (001)\n" "Block 3\n" // back edge " live in: (010)\n" " live out: (010)\n" " kill: (000)\n" "Block 4\n" // pre loop header " live in: (110)\n" " live out: (010)\n" " kill: (000)\n" "Block 5\n" // return block " live in: (001)\n" " live out: (000)\n" " kill: (000)\n" "Block 6\n" // exit block " live in: (000)\n" " live out: (000)\n" " kill: (000)\n"; const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::GOTO | 0x500, Instruction::IF_EQ, 5, Instruction::CONST_4 | 4 << 12 | 0, Instruction::GOTO | 0xFD00, Instruction::GOTO | 0xFC00, Instruction::RETURN | 0 << 8); TestCode(data, expected); } TEST_F(LivenessTest, Loop5) { // Make sure we create a preheader of a loop when a header originally has two // incoming blocks and one back edge. // Bitsets are made of: // (constant0, constant5, constant4, phi in block 8) const char* expected = "Block 0\n" " live in: (0000)\n" " live out: (1110)\n" " kill: (1110)\n" "Block 1\n" " live in: (1110)\n" " live out: (0110)\n" " kill: (0000)\n" "Block 2\n" " live in: (0010)\n" " live out: (0000)\n" " kill: (0000)\n" "Block 3\n" " live in: (0100)\n" " live out: (0000)\n" " kill: (0000)\n" "Block 4\n" // loop header " live in: (0001)\n" " live out: (0001)\n" " kill: (0000)\n" "Block 5\n" // back edge " live in: (0001)\n" " live out: (0001)\n" " kill: (0000)\n" "Block 6\n" // return block " live in: (0001)\n" " live out: (0000)\n" " kill: (0000)\n" "Block 7\n" // exit block " live in: (0000)\n" " live out: (0000)\n" " kill: (0000)\n" "Block 8\n" // synthesized pre header " live in: (0000)\n" " live out: (0001)\n" " kill: (0001)\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 | 0x200, Instruction::CONST_4 | 5 << 12 | 0, Instruction::IF_EQ, 3, Instruction::GOTO | 0xFE00, Instruction::RETURN | 0 << 8); TestCode(data, expected); } TEST_F(LivenessTest, Loop6) { // Bitsets are made of: // (constant0, constant4, constant5, phi in block 2) const char* expected = "Block 0\n" " live in: (0000)\n" " live out: (1110)\n" " kill: (1110)\n" "Block 1\n" " live in: (1110)\n" " live out: (0110)\n" " kill: (0000)\n" "Block 2\n" // loop header " live in: (0110)\n" " live out: (0111)\n" " kill: (0001)\n" "Block 3\n" " live in: (0110)\n" " live out: (0110)\n" " kill: (0000)\n" "Block 4\n" // back edge " live in: (0110)\n" " live out: (0110)\n" " kill: (0000)\n" "Block 5\n" // back edge " live in: (0110)\n" " live out: (0110)\n" " kill: (0000)\n" "Block 6\n" // return block " live in: (0001)\n" " live out: (0000)\n" " kill: (0000)\n" "Block 7\n" // exit block " live in: (0000)\n" " live out: (0000)\n" " kill: (0000)\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 | 0xFA00, Instruction::GOTO | 0xF900, Instruction::RETURN | 0 << 8); TestCode(data, expected); } TEST_F(LivenessTest, Loop7) { // Bitsets are made of: // (constant0, constant4, constant5, phi in block 2, phi in block 6) const char* expected = "Block 0\n" " live in: (00000)\n" " live out: (11100)\n" " kill: (11100)\n" "Block 1\n" " live in: (11100)\n" " live out: (01100)\n" " kill: (00000)\n" "Block 2\n" // loop header " live in: (01100)\n" " live out: (01110)\n" " kill: (00010)\n" "Block 3\n" " live in: (01100)\n" " live out: (01100)\n" " kill: (00000)\n" "Block 4\n" // loop exit " live in: (00100)\n" " live out: (00000)\n" " kill: (00000)\n" "Block 5\n" // back edge " live in: (01100)\n" " live out: (01100)\n" " kill: (00000)\n" "Block 6\n" // return block " live in: (00000)\n" " live out: (00000)\n" " kill: (00001)\n" "Block 7\n" // exit block " live in: (00000)\n" " live out: (00000)\n" " kill: (00000)\n" "Block 8\n" // synthesized block to avoid critical edge. " live in: (00010)\n" " live out: (00000)\n" " kill: (00000)\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 | 0x0200, Instruction::GOTO | 0xF900, Instruction::RETURN | 0 << 8); TestCode(data, expected); } TEST_F(LivenessTest, Loop8) { // var a = 0; // while (a == a) { // a = a + a; // } // return a; // // We want to test that the ins of the loop exit // does contain the phi. // Bitsets are made of: // (constant0, phi, add) const char* expected = "Block 0\n" " live in: (000)\n" " live out: (100)\n" " kill: (100)\n" "Block 1\n" // pre loop header " live in: (100)\n" " live out: (000)\n" " kill: (000)\n" "Block 2\n" // loop header " live in: (000)\n" " live out: (010)\n" " kill: (010)\n" "Block 3\n" // back edge " live in: (010)\n" " live out: (000)\n" " kill: (001)\n" "Block 4\n" // return block " live in: (010)\n" " live out: (000)\n" " kill: (000)\n" "Block 5\n" // exit block " live in: (000)\n" " live out: (000)\n" " kill: (000)\n"; const std::vector<uint16_t> data = ONE_REGISTER_CODE_ITEM( Instruction::CONST_4 | 0 | 0, Instruction::IF_EQ, 6, Instruction::ADD_INT, 0, 0, Instruction::GOTO | 0xFB00, Instruction::RETURN | 0 << 8); TestCode(data, expected); } } // namespace art