| /* |
| * 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 "base/macros.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 HIDDEN { |
| |
| class LivenessTest : public CommonCompilerTest, public OptimizingUnitTestHelper { |
| 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. |
| std::unique_ptr<CompilerOptions> compiler_options = |
| CommonCompilerTest::CreateCompilerOptions(kRuntimeISA, "default"); |
| 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| // 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| // 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| // 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_DISABLED_FOR_RISCV64(); |
| // 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| // 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| // 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| // 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| // 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) { |
| TEST_DISABLED_FOR_RISCV64(); |
| // 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 |