blob: 44825d191be3522e922c7f239b1139b3f2127aa5 [file]
/*
* Copyright (C) 2018 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 "control_flow_simplifier.h"
#include "base/arena_allocator.h"
#include "base/macros.h"
#include "builder.h"
#include "com_android_art_rw_flags.h"
#include "nodes.h"
#include "optimizing_unit_test.h"
#include "side_effects_analysis.h"
namespace art HIDDEN {
class ControlFlowSimplifierTest : public OptimizingUnitTest {
protected:
HPhi* ConstructBasicGraphForSelect(HBasicBlock* return_block, HInstruction* instr) {
HParameterValue* bool_param = MakeParam(DataType::Type::kBool);
HIntConstant* const1 = graph_->GetIntConstant(1);
auto [if_block, then_block, else_block] = CreateDiamondPattern(return_block, bool_param);
AddOrInsertInstruction(then_block, instr);
HPhi* phi = MakePhi(return_block, {instr, const1});
return phi;
}
bool CheckGraphAndTryControlFlowSimplifier() {
graph_->BuildDominatorTree();
EXPECT_TRUE(CheckGraph());
return HControlFlowSimplifier(graph_, /*handles*/ nullptr, /*stats*/ nullptr).Run();
}
template <typename T>
bool EntriesMatch(HLoadConstantTableEntry* lcte, ArrayRef<const T> entries) {
if (entries.size() != lcte->GetNumEntries()) {
return false;
}
for (size_t i : Range(entries.size())) {
int64_t expected;
if constexpr (std::is_same_v<T, float>) {
expected = bit_cast<uint32_t, float>(entries[i]);
} else if constexpr (std::is_same_v<T, double>) {
expected = bit_cast<uint64_t, double>(entries[i]);
} else {
expected = entries[i];
}
if (lcte->GetEntry(i) != expected) {
return false;
}
}
return true;
}
void UpdateSwitchWithReturns(HBasicBlock* switch_block,
HBasicBlock* return_block,
ArrayRef<const size_t> return_block_indexes);
template <typename T>
void TestSwitchToTable(DataType::Type type,
ArrayRef<const T> entries,
int32_t start_value,
ArrayRef<const size_t> return_block_indexes = ArrayRef<const size_t>());
template <typename T, size_t num_entries>
void TestSwitchToTable(DataType::Type type,
const T (&entries)[num_entries],
int32_t start_value) {
TestSwitchToTable(type, ArrayRef<const T>(entries, num_entries), start_value);
}
template <typename T, size_t num_entries>
void TestSwitchToTable(DataType::Type type,
const T (&entries)[num_entries],
int32_t start_value,
const size_t (&return_block_indexes)[num_entries]) {
TestSwitchToTable(type,
ArrayRef<const T>(entries, num_entries),
start_value,
ArrayRef<const size_t>(return_block_indexes, num_entries));
}
template <typename T>
void TestSwitchToTableWithDefault(
DataType::Type type,
ArrayRef<const T> entries,
int32_t start_value,
T dflt,
ArrayRef<const size_t> return_block_indexes = ArrayRef<const size_t>());
template <typename T, size_t num_entries>
void TestSwitchToTableWithDefault(DataType::Type type,
const T (&entries)[num_entries],
int32_t start_value,
T dflt) {
TestSwitchToTableWithDefault(type, ArrayRef<const T>(entries, num_entries), start_value, dflt);
}
template <typename T, size_t num_entries>
void TestSwitchToTableWithDefault(DataType::Type type,
const T (&entries)[num_entries],
int32_t start_value,
T dflt,
const size_t (&return_block_indexes)[num_entries]) {
TestSwitchToTableWithDefault(type,
ArrayRef<const T>(entries, num_entries),
start_value,
dflt,
ArrayRef<const size_t>(return_block_indexes, num_entries));
}
void TestReturnMerging(ArrayRef<const size_t> return_block_indexes);
};
// HDivZeroCheck might throw and should not be hoisted from the conditional to an unconditional.
TEST_F(ControlFlowSimplifierTest, testZeroCheckPreventsSelect) {
HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
HParameterValue* param = MakeParam(DataType::Type::kInt32);
HDivZeroCheck* instr = new (GetAllocator()) HDivZeroCheck(param, 0);
HPhi* phi = ConstructBasicGraphForSelect(return_block, instr);
ManuallyBuildEnvFor(instr, {param, graph_->GetIntConstant(1)});
EXPECT_FALSE(CheckGraphAndTryControlFlowSimplifier());
EXPECT_INS_RETAINED(phi);
}
// Test that ControlFlowSimplifier succeeds with HAdd.
TEST_F(ControlFlowSimplifierTest, testSelectWithAdd) {
HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
HParameterValue* param = MakeParam(DataType::Type::kInt32);
HAdd* instr = new (GetAllocator()) HAdd(DataType::Type::kInt32, param, param, /*dex_pc=*/ 0);
HPhi* phi = ConstructBasicGraphForSelect(return_block, instr);
EXPECT_TRUE(CheckGraphAndTryControlFlowSimplifier());
EXPECT_INS_REMOVED(phi);
}
// Test that ControlFlowSimplifier succeeds if there is an additional `HPhi` with identical inputs.
TEST_F(ControlFlowSimplifierTest, testSelectWithAddAndExtraPhi) {
// Create a graph with three blocks merging to the `return_block`.
HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
HParameterValue* bool_param1 = MakeParam(DataType::Type::kBool);
HParameterValue* bool_param2 = MakeParam(DataType::Type::kBool);
HParameterValue* param = MakeParam(DataType::Type::kInt32);
HInstruction* const0 = graph_->GetIntConstant(0);
auto [if_block1, left, mid] = CreateDiamondPattern(return_block, bool_param1);
HBasicBlock* if_block2 = AddNewBlock();
if_block1->ReplaceSuccessor(mid, if_block2);
HBasicBlock* right = AddNewBlock();
if_block2->AddSuccessor(mid);
if_block2->AddSuccessor(right);
HIf* if2 = MakeIf(if_block2, bool_param2);
right->AddSuccessor(return_block);
MakeGoto(right);
ASSERT_TRUE(PredecessorsEqual(return_block, {left, mid, right}));
HAdd* add = MakeBinOp<HAdd>(right, DataType::Type::kInt32, param, param);
HPhi* phi1 = MakePhi(return_block, {param, param, add});
HPhi* phi2 = MakePhi(return_block, {param, const0, const0});
// Prevent second `HSelect` match. Do not rely on the "instructions per branch" limit.
MakeInvokeStatic(left, DataType::Type::kVoid, {}, {});
EXPECT_TRUE(CheckGraphAndTryControlFlowSimplifier());
ASSERT_BLOCK_RETAINED(left);
ASSERT_BLOCK_REMOVED(mid);
ASSERT_BLOCK_REMOVED(right);
HInstruction* select = if2->GetPrevious(); // `HSelect` is inserted before `HIf`.
ASSERT_TRUE(select->IsSelect());
ASSERT_INS_RETAINED(phi1);
ASSERT_TRUE(InputsEqual(phi1, {param, select}));
ASSERT_INS_RETAINED(phi2);
ASSERT_TRUE(InputsEqual(phi2, {param, const0}));
}
// Test `HSelect` optimization in an irreducible loop.
TEST_F(ControlFlowSimplifierTest, testSelectInIrreducibleLoop) {
HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
auto [split, left_header, right_header, body] = CreateIrreducibleLoop(return_block);
HParameterValue* split_param = MakeParam(DataType::Type::kBool);
HParameterValue* bool_param = MakeParam(DataType::Type::kBool);
HParameterValue* n_param = MakeParam(DataType::Type::kInt32);
MakeIf(split, split_param);
HInstruction* const0 = graph_->GetIntConstant(0);
HInstruction* const1 = graph_->GetIntConstant(1);
auto [left_phi, right_phi, add] =
MakeLinearIrreducibleLoopVar(left_header, right_header, body, const1, const0, const1);
HCondition* condition = MakeCondition(left_header, kCondGE, left_phi, n_param);
MakeIf(left_header, condition);
auto [if_block, then_block, else_block] = CreateDiamondPattern(body, bool_param);
HPhi* phi = MakePhi(body, {const1, const0});
EXPECT_TRUE(CheckGraphAndTryControlFlowSimplifier());
HLoopInformation* loop_info = left_header->GetLoopInformation();
ASSERT_TRUE(loop_info != nullptr);
ASSERT_TRUE(loop_info->IsIrreducible());
EXPECT_INS_REMOVED(phi);
ASSERT_TRUE(if_block->GetFirstInstruction()->IsSelect());
ASSERT_EQ(if_block, add->GetBlock()); // Moved when merging blocks.
for (HBasicBlock* removed_block : {then_block, else_block, body}) {
ASSERT_BLOCK_REMOVED(removed_block);
uint32_t removed_block_id = removed_block->GetBlockId();
ASSERT_FALSE(loop_info->GetBlockMask().IsBitSet(removed_block_id)) << removed_block_id;
}
}
// Update switch case blocks for return pattern based on `return_block_indexes` for switch cases.
//
// Index 0 specifies the `return_block` and higher indexes specify new blocks that shall be added.
// If an index is specified only once, it shall get a pure return block, otherwise it shall get
// a single-goto block that shall merge into a return block, except for index 0 (`return_block`),
// supporting tests with and without merges with the default case.
void ControlFlowSimplifierTest::UpdateSwitchWithReturns(
HBasicBlock* switch_block,
HBasicBlock* return_block,
ArrayRef<const size_t> return_block_indexes) {
CHECK(switch_block->GetLastInstruction()->IsPackedSwitch());
HPackedSwitch* packed_switch = switch_block->GetLastInstruction()->AsPackedSwitch();
CHECK_EQ(packed_switch->GetNumEntries(), return_block_indexes.size());
CHECK(return_block->HasSinglePhi());
HPhi* phi = return_block->GetFirstPhi()->AsPhi();
std::vector<HBasicBlock*> ret_blocks;
ret_blocks.push_back(return_block);
size_t redirected = 0;
for (size_t i : Range(return_block_indexes.size())) {
if (return_block_indexes[i] != 0) {
HInstruction* ret_input = phi->InputAt(i - redirected);
phi->RemoveInputAt(i - redirected);
++redirected;
HBasicBlock* successor = switch_block->GetSuccessors()[i];
if (return_block_indexes[i] == ret_blocks.size()) {
if (ContainsElement(return_block_indexes.SubArray(i + 1u), return_block_indexes[i])) {
// Create a new block for merging two or more switch cases.
HBasicBlock* new_block = AddNewBlock();
successor->ReplaceSuccessor(return_block, new_block);
new_block->AddSuccessor(exit_block_);
MakeReturn(new_block, ret_input);
ret_blocks.push_back(new_block);
} else {
// Update the successor to a return block.
HReturn* ret = new (GetAllocator()) HReturn(ret_input);
successor->ReplaceAndRemoveInstructionWith(successor->GetLastInstruction(), ret);
successor->ReplaceSuccessor(return_block, exit_block_);
ret_blocks.push_back(successor);
}
} else {
CHECK_LT(return_block_indexes[i], return_block_indexes.size());
HBasicBlock* ret_block = ret_blocks[return_block_indexes[i]];
successor->ReplaceSuccessor(return_block, ret_block);
if (ret_block->GetPhis().IsEmpty()) {
HReturn* ret = ret_block->GetLastInstruction()->AsReturn();
if (ret->InputAt(0) != ret_input) {
size_t num_predecessors = ret_block->GetPredecessors().size();
CHECK_GE(num_predecessors, 2u);
std::vector<HInstruction*> phi_inputs(num_predecessors - 1u, ret->InputAt(0));
phi_inputs.push_back(ret_input);
HPhi* new_phi = MakePhi(ret_block, phi_inputs);
ret->ReplaceInput(new_phi, 0);
}
} else {
CHECK(ret_block->HasSinglePhi());
ret_block->GetFirstPhi()->AsPhi()->AddInput(ret_input);
}
}
}
}
if (phi->InputCount() == 1u) {
phi->ReplaceWith(phi->InputAt(0));
return_block->RemovePhi(phi);
CHECK_EQ(return_block->GetPredecessors().size(), 1u);
HBasicBlock* predecessor = return_block->GetSinglePredecessor();
CHECK(predecessor->IsSingleGoto());
switch_block->ReplaceSuccessor(predecessor, return_block);
// Remove the `predecessor` manually. Helper functions would try to update
// the reverse post order but we have not built it yet.
predecessor->RemoveInstruction(predecessor->GetLastInstruction());
predecessor->DisconnectFromSuccessors();
const_cast<HBasicBlock*&>(graph_->GetBlocks()[predecessor->GetBlockId()]) = nullptr;
predecessor->SetGraph(nullptr);
}
}
template <typename T>
void ControlFlowSimplifierTest::TestSwitchToTable(DataType::Type type,
ArrayRef<const T> entries,
int32_t start_value,
ArrayRef<const size_t> return_block_indexes) {
HBasicBlock* return_block = InitEntryMainExitGraph();
HInstruction* switch_input = MakeParam(DataType::Type::kInt32);
HInstruction* default_value = MakeParam(type);
HBasicBlock* switch_block =
CreateSwitchPattern(return_block, entries.size(), switch_input, start_value);
HPackedSwitch* packed_switch = switch_block->GetLastInstruction()->AsPackedSwitch();
std::vector<HInstruction*> phi_inputs;
for (T entry : entries) {
phi_inputs.push_back(GetConstant(type, entry));
}
phi_inputs.push_back(default_value);
HPhi* phi = MakePhi(return_block, phi_inputs);
if (return_block_indexes.empty()) {
MakeInvokeStatic(return_block, DataType::Type::kVoid, {}, {}); // Avoid the return pattern.
}
HReturn* ret = MakeReturn(return_block, phi);
if (!return_block_indexes.empty()) {
CHECK_EQ(return_block_indexes.size(), entries.size());
UpdateSwitchWithReturns(switch_block, return_block, return_block_indexes);
}
bool success = CheckGraphAndTryControlFlowSimplifier();
ASSERT_TRUE(success);
// Check `HPackedSwitch` replacement.
ASSERT_INS_REMOVED(packed_switch);
ASSERT_TRUE(switch_block->GetLastInstruction()->IsIf());
HInstruction* cond = switch_block->GetLastInstruction()->AsIf()->InputAt(0);
ASSERT_TRUE(cond->IsBelow());
HInstruction* lhs = cond->AsBelow()->GetLeft();
HInstruction* rhs = cond->AsBelow()->GetRight();
if (start_value != 0) {
ASSERT_TRUE(lhs->IsAdd());
ASSERT_INS_EQ(switch_input, lhs->AsAdd()->GetLeft());
ASSERT_TRUE(lhs->AsAdd()->GetRight()->IsIntConstant());
ASSERT_EQ(-start_value, lhs->AsAdd()->GetRight()->AsIntConstant()->GetValue());
} else {
ASSERT_INS_EQ(switch_input, lhs);
}
ASSERT_TRUE(rhs->IsIntConstant());
EXPECT_EQ(dchecked_integral_cast<int32_t>(entries.size()), rhs->AsIntConstant()->GetValue());
// Check `HLoadConstantTableEntry`.
HBasicBlock* then_block = switch_block->GetSuccessors()[0];
ASSERT_TRUE(then_block == switch_block->GetLastInstruction()->AsIf()->IfTrueSuccessor());
ASSERT_TRUE(then_block->GetFirstInstruction()->IsLoadConstantTableEntry());
HLoadConstantTableEntry* lcte = then_block->GetFirstInstruction()->AsLoadConstantTableEntry();
EXPECT_EQ(type, lcte->GetType());
ASSERT_TRUE(EntriesMatch(lcte, entries));
if (!return_block_indexes.empty()) {
// Due to the splitting and merging of blocks, the return block may or may not be the
// original `return_block`. Similar for the phi and return instructions.
return_block = then_block->GetSingleSuccessor();
ASSERT_TRUE(return_block->HasSinglePhi());
phi = return_block->GetFirstPhi()->AsPhi();
ASSERT_TRUE(return_block->GetLastInstruction()->IsReturn());
ret = return_block->GetLastInstruction()->AsReturn();
}
// Check the phi.
ASSERT_TRUE(then_block->GetLastInstruction()->IsGoto());
ASSERT_INS_EQ(phi, ret->InputAt(0));
ASSERT_EQ(2u, phi->InputCount());
ASSERT_INS_EQ(lcte, phi->InputAt(0));
ASSERT_INS_EQ(default_value, phi->InputAt(1));
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableBoolean) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {1, 0, 1, 1, 0, 0, 0, 1, 0, 1};
TestSwitchToTable(DataType::Type::kBool, kEntries, /*start_value=*/ 0);
TestSwitchToTable(DataType::Type::kBool, kEntries, /*start_value=*/ 1);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableByte) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {42, 88, -7, 11, 123};
TestSwitchToTable(DataType::Type::kInt8, kEntries, /*start_value=*/ 0);
TestSwitchToTable(DataType::Type::kInt8, kEntries, /*start_value=*/ 1);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableUint8) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {42, 88, 7, 11, 255};
TestSwitchToTable(DataType::Type::kUint8, kEntries, /*start_value=*/ 0);
TestSwitchToTable(DataType::Type::kUint8, kEntries, /*start_value=*/ 1);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableShort) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {42, 88, -7, 11, 12345};
TestSwitchToTable(DataType::Type::kInt16, kEntries, /*start_value=*/ 0);
TestSwitchToTable(DataType::Type::kInt16, kEntries, /*start_value=*/ 1);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableChar) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {42, 88, 7, 11, 54321};
TestSwitchToTable(DataType::Type::kUint16, kEntries, /*start_value=*/ 0);
TestSwitchToTable(DataType::Type::kUint16, kEntries, /*start_value=*/ 1);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableInt) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {42, 88, -7, 11, 123456789};
TestSwitchToTable(DataType::Type::kInt32, kEntries, /*start_value=*/ 0);
TestSwitchToTable(DataType::Type::kInt32, kEntries, /*start_value=*/ 1);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableLong) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int64_t kEntries[] = {42, 88, -7, 11, INT64_C(123456789987654321)};
TestSwitchToTable(DataType::Type::kInt64, kEntries, /*start_value=*/ 0);
TestSwitchToTable(DataType::Type::kInt64, kEntries, /*start_value=*/ 1);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableFloat) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static constexpr float nan = std::numeric_limits<float>::quiet_NaN();
static const float kEntries[] = {42.0f, 88.0f, -7.0f, 11.0f, 123456789.0f, nan};
TestSwitchToTable(DataType::Type::kFloat32, kEntries, /*start_value=*/ 0);
TestSwitchToTable(DataType::Type::kFloat32, kEntries, /*start_value=*/ 1);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableDouble) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static constexpr double nan = std::numeric_limits<double>::quiet_NaN();
static const double kEntries[] = {42.0, 88.0, -7.0, 11.0, 123456789.0, nan};
TestSwitchToTable(DataType::Type::kFloat64, kEntries, /*start_value=*/ 0);
TestSwitchToTable(DataType::Type::kFloat64, kEntries, /*start_value=*/ 1);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableBoolean) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {1, 0, 1, 1, 0, 0, 0, 1, 0, 1};
static const size_t kReturnBlockIndexes[] = {0, 0, 1, 1, 0, 2, 2, 2, 3, 2};
TestSwitchToTable(DataType::Type::kBool, kEntries, /*start_value=*/ 0, kReturnBlockIndexes);
TestSwitchToTable(DataType::Type::kBool, kEntries, /*start_value=*/ 1, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableByte) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {42, 88, -7, 11, 123};
static const size_t kReturnBlockIndexes[] = {0, 0, 0, 0, 0};
TestSwitchToTable(DataType::Type::kInt8, kEntries, /*start_value=*/ 0, kReturnBlockIndexes);
TestSwitchToTable(DataType::Type::kInt8, kEntries, /*start_value=*/ 1, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableUint8) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {42, 88, 7, 11, 255};
static const size_t kReturnBlockIndexes[] = {0, 1, 2, 3, 4};
TestSwitchToTable(DataType::Type::kUint8, kEntries, /*start_value=*/ 0, kReturnBlockIndexes);
TestSwitchToTable(DataType::Type::kUint8, kEntries, /*start_value=*/ 1, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableShort) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {42, 88, -7, 11, 12345};
static const size_t kReturnBlockIndexes[] = {1, 2, 3, 4, 5};
TestSwitchToTable(DataType::Type::kInt16, kEntries, /*start_value=*/ 0, kReturnBlockIndexes);
TestSwitchToTable(DataType::Type::kInt16, kEntries, /*start_value=*/ 1, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableChar) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {42, 88, 7, 11, 54321};
static const size_t kReturnBlockIndexes[] = {1, 2, 3, 3, 3};
TestSwitchToTable(DataType::Type::kUint16, kEntries, /*start_value=*/ 0, kReturnBlockIndexes);
TestSwitchToTable(DataType::Type::kUint16, kEntries, /*start_value=*/ 1, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableInt) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kEntries[] = {42, 88, -7, 11, 123456789};
static const size_t kReturnBlockIndexes[] = {0, 0, 1, 0, 0};
TestSwitchToTable(DataType::Type::kInt32, kEntries, /*start_value=*/ 0, kReturnBlockIndexes);
TestSwitchToTable(DataType::Type::kInt32, kEntries, /*start_value=*/ 1, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableLong) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int64_t kEntries[] = {42, 88, -7, 11, INT64_C(123456789987654321)};
static const size_t kReturnBlockIndexes[] = {1, 0, 1, 0, 1};
TestSwitchToTable(DataType::Type::kInt64, kEntries, /*start_value=*/ 0, kReturnBlockIndexes);
TestSwitchToTable(DataType::Type::kInt64, kEntries, /*start_value=*/ 1, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableFloat) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static constexpr float nan = std::numeric_limits<float>::quiet_NaN();
static const float kEntries[] = {42.0f, 88.0f, -7.0f, 11.0f, 123456789.0f, nan};
static const size_t kReturnBlockIndexes[] = {1, 0, 0, 0, 0, 0};
TestSwitchToTable(DataType::Type::kFloat32, kEntries, /*start_value=*/ 0, kReturnBlockIndexes);
TestSwitchToTable(DataType::Type::kFloat32, kEntries, /*start_value=*/ 1, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableDouble) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static constexpr double nan = std::numeric_limits<double>::quiet_NaN();
static const double kEntries[] = {42.0, 88.0, -7.0, 11.0, 123456789.0, nan};
static const size_t kReturnBlockIndexes[] = {1, 1, 1, 1, 1, 1};
TestSwitchToTable(DataType::Type::kFloat64, kEntries, /*start_value=*/ 0, kReturnBlockIndexes);
TestSwitchToTable(DataType::Type::kFloat64, kEntries, /*start_value=*/ 1, kReturnBlockIndexes);
}
template <typename T>
void ControlFlowSimplifierTest::TestSwitchToTableWithDefault(
DataType::Type type,
ArrayRef<const T> entries,
int32_t start_value,
T dflt,
ArrayRef<const size_t> return_block_indexes) {
HBasicBlock* return_block = InitEntryMainExitGraph();
HInstruction* switch_input = MakeParam(DataType::Type::kInt32);
HInstruction* default_value = GetConstant(type, dflt);
HBasicBlock* switch_block =
CreateSwitchPattern(return_block, entries.size(), switch_input, start_value);
HPackedSwitch* packed_switch = switch_block->GetLastInstruction()->AsPackedSwitch();
std::vector<HInstruction*> phi_inputs;
for (T entry : entries) {
phi_inputs.push_back(GetConstant(type, entry));
}
phi_inputs.push_back(default_value);
HPhi* phi = MakePhi(return_block, phi_inputs);
if (return_block_indexes.empty()) {
MakeInvokeStatic(return_block, DataType::Type::kVoid, {}, {}); // Avoid the return pattern.
}
HReturn* ret = MakeReturn(return_block, phi);
if (!return_block_indexes.empty()) {
CHECK_EQ(return_block_indexes.size(), entries.size());
UpdateSwitchWithReturns(switch_block, return_block, return_block_indexes);
}
bool success = CheckGraphAndTryControlFlowSimplifier();
ASSERT_TRUE(success);
// The packed switch and phi have been replaced by the load from constant table.
ASSERT_INS_REMOVED(packed_switch);
ASSERT_INS_REMOVED(phi);
if (return_block_indexes.empty()) {
ASSERT_INS_RETAINED(ret);
} else {
// Due to the splitting and merging of blocks, the return block may or may not be the
// original `return_block`. Similar for the return instruction.
ASSERT_TRUE(switch_block->GetLastInstruction()->IsReturn());
ret = switch_block->GetLastInstruction()->AsReturn();
}
ASSERT_TRUE(ret->InputAt(0)->IsLoadConstantTableEntry());
HLoadConstantTableEntry* lcte = ret->InputAt(0)->AsLoadConstantTableEntry();
EXPECT_EQ(type, lcte->GetType());
std::vector<T> expected_entries(entries.begin(), entries.end());
expected_entries.insert(
start_value == 1 ? expected_entries.begin() : expected_entries.end(), dflt);
ASSERT_TRUE(EntriesMatch(lcte, ArrayRef<const T>(expected_entries)));
// The `lcte->GetIndex()` must be a `HSelect` between a raw index and default index.
ASSERT_TRUE(lcte->GetIndex()->IsSelect());
HSelect* select = lcte->GetIndex()->AsSelect();
HInstruction* raw_index = select->GetTrueValue();
HInstruction* default_index = select->GetFalseValue();
int32_t expected_default_index = start_value == 1 ? 0 : static_cast<int32_t>(entries.size());
ASSERT_TRUE(default_index->IsIntConstant());
ASSERT_EQ(expected_default_index, default_index->AsIntConstant()->GetValue());
// The select condition must be `Below (raw_index, entries.size() + <0 or 1>)`.
ASSERT_TRUE(select->GetCondition()->IsBelow());
HBelow* below = select->GetCondition()->AsBelow();
ASSERT_TRUE(below->GetRight()->IsIntConstant());
ASSERT_EQ(static_cast<int32_t>(entries.size()) + (start_value == 1 ? 1 : 0),
below->GetRight()->AsIntConstant()->GetValue());
ASSERT_INS_EQ(raw_index, below->GetLeft());
// Check raw index.
if (start_value != 0 && start_value != 1) {
ASSERT_TRUE(raw_index->IsAdd());
ASSERT_INS_EQ(switch_input, raw_index->AsAdd()->GetLeft());
ASSERT_TRUE(raw_index->AsAdd()->GetRight()->IsIntConstant());
ASSERT_EQ(-start_value, raw_index->AsAdd()->GetRight()->AsIntConstant()->GetValue());
} else {
ASSERT_INS_EQ(switch_input, raw_index);
}
// The `return_block` has been merged into `switch_block`.
ASSERT_BLOCK_REMOVED(return_block);
ASSERT_TRUE(switch_block->EndsWithReturn());
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableBooleanWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = 0;
static const int32_t kEntries[] = {1, 0, 1, 1, 0, 0, 0, 1, 0, 1};
TestSwitchToTableWithDefault(DataType::Type::kBool, kEntries, /*start_value=*/ 0, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kBool, kEntries, /*start_value=*/ 1, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kBool, kEntries, /*start_value=*/ 3, kDefault);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableByteWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = -42;
static const int32_t kEntries[] = {42, 88, -7, 11, 123};
TestSwitchToTableWithDefault(DataType::Type::kInt8, kEntries, /*start_value=*/ 0, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kInt8, kEntries, /*start_value=*/ 1, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kInt8, kEntries, /*start_value=*/ 3, kDefault);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableUint8WithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = 43;
static const int32_t kEntries[] = {42, 88, 7, 11, 255};
TestSwitchToTableWithDefault(DataType::Type::kUint8, kEntries, /*start_value=*/ 0, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kUint8, kEntries, /*start_value=*/ 1, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kUint8, kEntries, /*start_value=*/ 3, kDefault);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableShortWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = -42;
static const int32_t kEntries[] = {42, 88, -7, 11, 12345};
TestSwitchToTableWithDefault(DataType::Type::kInt16, kEntries, /*start_value=*/ 0, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kInt16, kEntries, /*start_value=*/ 1, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kInt16, kEntries, /*start_value=*/ 3, kDefault);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableCharWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = 43;
static const int32_t kEntries[] = {42, 88, 7, 11, 54321};
TestSwitchToTableWithDefault(DataType::Type::kUint16, kEntries, /*start_value=*/ 0, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kUint16, kEntries, /*start_value=*/ 1, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kUint16, kEntries, /*start_value=*/ 3, kDefault);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableIntWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = -42;
static const int32_t kEntries[] = {42, 88, -7, 11, 123456789};
TestSwitchToTableWithDefault(DataType::Type::kInt32, kEntries, /*start_value=*/ 0, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kInt32, kEntries, /*start_value=*/ 1, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kInt32, kEntries, /*start_value=*/ 3, kDefault);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableLongWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int64_t kDefault = -42;
static const int64_t kEntries[] = {42, 88, -7, 11, INT64_C(123456789987654321)};
TestSwitchToTableWithDefault(DataType::Type::kInt64, kEntries, /*start_value=*/ 0, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kInt64, kEntries, /*start_value=*/ 1, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kInt64, kEntries, /*start_value=*/ 2, kDefault);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableFloatWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const float kDefault = -42.0f;
static constexpr float nan = std::numeric_limits<float>::quiet_NaN();
static const float kEntries[] = {42.0f, 88.0f, -7.0f, 11.0f, 123456789.0f, nan};
TestSwitchToTableWithDefault(DataType::Type::kFloat32, kEntries, /*start_value=*/ 0, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kFloat32, kEntries, /*start_value=*/ 1, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kFloat32, kEntries, /*start_value=*/ 2, kDefault);
}
TEST_F(ControlFlowSimplifierTest, SwitchToTableDoubleWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const double kDefault = -42.0;
static constexpr double nan = std::numeric_limits<double>::quiet_NaN();
static const double kEntries[] = {42.0, 88.0, -7.0, 11.0, 123456789.0, nan};
TestSwitchToTableWithDefault(DataType::Type::kFloat64, kEntries, /*start_value=*/ 0, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kFloat64, kEntries, /*start_value=*/ 1, kDefault);
TestSwitchToTableWithDefault(DataType::Type::kFloat64, kEntries, /*start_value=*/ 2, kDefault);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableBooleanWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = 0;
static const int32_t kEntries[] = {1, 0, 1, 1, 0, 0, 0, 1, 0, 1};
static const size_t kReturnBlockIndexes[] = {0, 0, 1, 1, 0, 2, 2, 2, 3, 2};
TestSwitchToTableWithDefault(
DataType::Type::kBool, kEntries, /*start_value=*/ 0, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kBool, kEntries, /*start_value=*/ 1, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kBool, kEntries, /*start_value=*/ 3, kDefault, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableByteWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = -42;
static const int32_t kEntries[] = {42, 88, -7, 11, 123};
static const size_t kReturnBlockIndexes[] = {0, 0, 0, 0, 0};
TestSwitchToTableWithDefault(
DataType::Type::kInt8, kEntries, /*start_value=*/ 0, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kInt8, kEntries, /*start_value=*/ 1, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kInt8, kEntries, /*start_value=*/ 3, kDefault, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableUint8WithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = 43;
static const int32_t kEntries[] = {42, 88, 7, 11, 255};
static const size_t kReturnBlockIndexes[] = {0, 1, 2, 3, 4};
TestSwitchToTableWithDefault(
DataType::Type::kUint8, kEntries, /*start_value=*/ 0, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kUint8, kEntries, /*start_value=*/ 1, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kUint8, kEntries, /*start_value=*/ 3, kDefault, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableShortWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = -42;
static const int32_t kEntries[] = {42, 88, -7, 11, 12345};
static const size_t kReturnBlockIndexes[] = {1, 2, 3, 4, 5};
TestSwitchToTableWithDefault(
DataType::Type::kInt16, kEntries, /*start_value=*/ 0, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kInt16, kEntries, /*start_value=*/ 1, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kInt16, kEntries, /*start_value=*/ 3, kDefault, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableCharWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = 43;
static const int32_t kEntries[] = {42, 88, 7, 11, 54321};
static const size_t kReturnBlockIndexes[] = {1, 2, 3, 3, 3};
TestSwitchToTableWithDefault(
DataType::Type::kUint16, kEntries, /*start_value=*/ 0, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kUint16, kEntries, /*start_value=*/ 1, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kUint16, kEntries, /*start_value=*/ 3, kDefault, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableIntWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int32_t kDefault = -42;
static const int32_t kEntries[] = {42, 88, -7, 11, 123456789};
static const size_t kReturnBlockIndexes[] = {0, 0, 1, 0, 0};
TestSwitchToTableWithDefault(
DataType::Type::kInt32, kEntries, /*start_value=*/ 0, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault
(DataType::Type::kInt32, kEntries, /*start_value=*/ 1, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kInt32, kEntries, /*start_value=*/ 3, kDefault, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableLongWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const int64_t kDefault = -42;
static const int64_t kEntries[] = {42, 88, -7, 11, INT64_C(123456789987654321)};
static const size_t kReturnBlockIndexes[] = {1, 0, 1, 0, 1};
TestSwitchToTableWithDefault(
DataType::Type::kInt64, kEntries, /*start_value=*/ 0, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kInt64, kEntries, /*start_value=*/ 1, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kInt64, kEntries, /*start_value=*/ 2, kDefault, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableFloatWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const float kDefault = -42.0f;
static constexpr float nan = std::numeric_limits<float>::quiet_NaN();
static const float kEntries[] = {42.0f, 88.0f, -7.0f, 11.0f, 123456789.0f, nan};
static const size_t kReturnBlockIndexes[] = {1, 0, 0, 0, 0, 0};
TestSwitchToTableWithDefault(
DataType::Type::kFloat32, kEntries, /*start_value=*/ 0, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kFloat32, kEntries, /*start_value=*/ 1, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kFloat32, kEntries, /*start_value=*/ 2, kDefault, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReturnToTableDoubleWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const double kDefault = -42.0;
static constexpr double nan = std::numeric_limits<double>::quiet_NaN();
static const double kEntries[] = {42.0, 88.0, -7.0, 11.0, 123456789.0, nan};
static const size_t kReturnBlockIndexes[] = {1, 1, 1, 1, 1, 1};
TestSwitchToTableWithDefault(
DataType::Type::kFloat64, kEntries, /*start_value=*/ 0, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kFloat64, kEntries, /*start_value=*/ 1, kDefault, kReturnBlockIndexes);
TestSwitchToTableWithDefault(
DataType::Type::kFloat64, kEntries, /*start_value=*/ 2, kDefault, kReturnBlockIndexes);
}
TEST_F(ControlFlowSimplifierTest, SwitchReplacedByIf) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static constexpr size_t kNumEntries = 5;
static constexpr int32_t kStartValue = 1;
HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
HInstruction* switch_input = MakeParam(DataType::Type::kInt32);
HBasicBlock* switch_block =
CreateSwitchPattern(return_block, kNumEntries, switch_input, kStartValue);
HPackedSwitch* packed_switch = switch_block->GetLastInstruction()->AsPackedSwitch();
MakeInvokeStatic(packed_switch->GetDefaultBlock(), DataType::Type::kVoid, {}, {});
bool success = CheckGraphAndTryControlFlowSimplifier();
ASSERT_TRUE(success);
ASSERT_INS_REMOVED(packed_switch);
ASSERT_TRUE(switch_block->GetLastInstruction()->IsIf());
ASSERT_TRUE(switch_block->GetLastInstruction()->AsIf()->InputAt(0)->IsBelow());
HBelow* below = switch_block->GetLastInstruction()->AsIf()->InputAt(0)->AsBelow();
ASSERT_TRUE(below->GetRight()->IsIntConstant());
ASSERT_EQ(static_cast<int32_t>(kNumEntries), below->GetRight()->AsIntConstant()->GetValue());
ASSERT_TRUE(below->GetLeft()->IsAdd());
HAdd* add = below->GetLeft()->AsAdd();
ASSERT_TRUE(add->GetRight()->IsIntConstant());
ASSERT_EQ(-kStartValue, add->GetRight()->AsIntConstant()->GetValue());
ASSERT_INS_EQ(switch_input, add->GetLeft());
ASSERT_BLOCK_RETAINED(return_block);
}
TEST_F(ControlFlowSimplifierTest, SwitchEliminatedWithDefault) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static constexpr size_t kNumEntries = 5;
static constexpr int32_t kStartValue = 1;
HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
HInstruction* switch_input = MakeParam(DataType::Type::kInt32);
HBasicBlock* switch_block =
CreateSwitchPattern(return_block, kNumEntries, switch_input, kStartValue);
HPackedSwitch* packed_switch = switch_block->GetLastInstruction()->AsPackedSwitch();
bool success = CheckGraphAndTryControlFlowSimplifier();
ASSERT_TRUE(success);
ASSERT_INS_REMOVED(packed_switch);
ASSERT_TRUE(switch_block->GetLastInstruction()->IsReturnVoid());
ASSERT_BLOCK_REMOVED(return_block);
}
void ControlFlowSimplifierTest::TestReturnMerging(ArrayRef<const size_t> return_block_indexes) {
HBasicBlock* return_block = InitEntryMainExitGraph();
HInstruction* switch_input = MakeParam(DataType::Type::kInt32);
static constexpr int32_t kStartValue = 0;
std::vector<HInstruction*> phi_inputs;
for ([[maybe_unused]] size_t i : Range(return_block_indexes.size() + 1u)) {
phi_inputs.push_back(MakeParam(DataType::Type::kInt32));
}
HBasicBlock* switch_block =
CreateSwitchPattern(return_block, return_block_indexes.size(), switch_input, kStartValue);
HPhi* phi = MakePhi(return_block, phi_inputs);
HReturn* ret = MakeReturn(return_block, phi);
UpdateSwitchWithReturns(switch_block, return_block, return_block_indexes);
CHECK_NE(phi->InputCount(), phi_inputs.size());
// Prevent flattening by inserting invokes.
for (HBasicBlock* block : switch_block->GetSuccessors()) {
if (block->IsSingleGoto()) {
HBasicBlock* merge = block->GetSingleSuccessor();
CHECK(merge->GetLastInstruction()->IsReturn());
if (merge->GetFirstInstruction() == merge->GetLastInstruction()) {
MakeInvokeStatic(merge, DataType::Type::kVoid, {}, {});
} else {
CHECK(merge->GetFirstInstruction()->IsInvokeStaticOrDirect());
}
} else {
CHECK(block->GetLastInstruction()->IsReturn());
CHECK(block->GetFirstInstruction() == block->GetLastInstruction());
MakeInvokeStatic(block, DataType::Type::kVoid, {}, {});
}
}
bool success = CheckGraphAndTryControlFlowSimplifier();
ASSERT_TRUE(success);
ASSERT_EQ(exit_block_->GetPredecessors().size(), 1u);
HBasicBlock* new_return_block = exit_block_->GetPredecessors()[0];
ASSERT_TRUE(return_block != new_return_block);
ASSERT_EQ(return_block->GetSuccessors().size(), 1u);
ASSERT_TRUE(return_block->GetSingleSuccessor() == new_return_block);
ASSERT_TRUE(new_return_block->GetLastInstruction()->IsReturn());
ASSERT_TRUE(new_return_block->GetLastInstruction()->AsReturn()->InputAt(0)->IsPhi());
HPhi* new_phi = new_return_block->GetLastInstruction()->AsReturn()->InputAt(0)->AsPhi();
size_t max_return_block_index =
*std::max_element(return_block_indexes.begin(), return_block_indexes.end());
ASSERT_EQ(new_phi->InputCount(), max_return_block_index + 1u);
}
TEST_F(ControlFlowSimplifierTest, ReturnMerging) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
static const size_t kReturnBlockIndexes1[] = {0, 0, 1, 1, 0, 2, 2, 2, 3, 2};
TestReturnMerging(ArrayRef<const size_t>(kReturnBlockIndexes1));
static const size_t kReturnBlockIndexes2[] = {0, 1, 2, 3, 4};
TestReturnMerging(ArrayRef<const size_t>(kReturnBlockIndexes2));
static const size_t kReturnBlockIndexes3[] = {1, 2, 3, 4, 5};
TestReturnMerging(ArrayRef<const size_t>(kReturnBlockIndexes3));
static const size_t kReturnBlockIndexes4[] = {1, 2, 3, 3, 3};
TestReturnMerging(ArrayRef<const size_t>(kReturnBlockIndexes4));
static const size_t kReturnBlockIndexes5[] = {0, 0, 1, 0, 0};
TestReturnMerging(ArrayRef<const size_t>(kReturnBlockIndexes5));
static const size_t kReturnBlockIndexes6[] = {1, 0, 1, 0, 1};
TestReturnMerging(ArrayRef<const size_t>(kReturnBlockIndexes6));
static const size_t kReturnBlockIndexes7[] = {1, 0, 0, 0, 0, 0};
TestReturnMerging(ArrayRef<const size_t>(kReturnBlockIndexes7));
static const size_t kReturnBlockIndexes8[] = {1, 1, 1, 1, 1, 1};
TestReturnMerging(ArrayRef<const size_t>(kReturnBlockIndexes8));
}
TEST_F(ControlFlowSimplifierTest, FlattenGotoRight) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
HParameterValue* bool_param1 = MakeParam(DataType::Type::kBool);
HParameterValue* bool_param2 = MakeParam(DataType::Type::kBool);
HInstruction* const0 = graph_->GetIntConstant(0);
HInstruction* const1 = graph_->GetIntConstant(1);
HInstruction* const2 = graph_->GetIntConstant(2);
auto [start, left, right_end] = CreateDiamondPattern(return_block, bool_param1);
auto [right_start, right_left, right_right] = CreateDiamondPattern(right_end, bool_param2);
HPhi* phi1 = MakePhi(right_end, {const1, const2});
HPhi* phi2 = MakePhi(return_block, {const0, phi1}); // `phi1` shall be replaced by its inputs.
HPhi* phi3 = MakePhi(return_block, {const0, const1}); // `const1` shall be duplicated.
MakeInvokeStatic(right_right, DataType::Type::kVoid, {}, {}); // Prevent `HSelect` generation.
bool success = CheckGraphAndTryControlFlowSimplifier();
ASSERT_TRUE(success);
ASSERT_BLOCK_REMOVED(return_block);
ASSERT_BLOCK_RETAINED(right_end);
ASSERT_TRUE(PredecessorsEqual(right_end, {left, right_left, right_right}));
ASSERT_INS_REMOVED(phi1);
ASSERT_INS_RETAINED(phi2);
ASSERT_TRUE(InputsEqual(phi2, {const0, const1, const2}));
ASSERT_INS_RETAINED(phi3);
ASSERT_TRUE(InputsEqual(phi3, {const0, const1, const1}));
}
TEST_F(ControlFlowSimplifierTest, FlattenGotoLeft) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
HParameterValue* bool_param1 = MakeParam(DataType::Type::kBool);
HParameterValue* bool_param2 = MakeParam(DataType::Type::kBool);
HInstruction* const0 = graph_->GetIntConstant(0);
HInstruction* const1 = graph_->GetIntConstant(1);
HInstruction* const2 = graph_->GetIntConstant(2);
auto [start, left_end, right] = CreateDiamondPattern(return_block, bool_param1);
auto [left_start, left_left, left_right] = CreateDiamondPattern(left_end, bool_param2);
HPhi* phi1 = MakePhi(left_end, {const0, const1});
HPhi* phi2 = MakePhi(return_block, {phi1, const2}); // `phi1` shall be replaced by its inputs.
HPhi* phi3 = MakePhi(return_block, {const0, const2}); // `const0` shall be duplicated.
MakeInvokeStatic(left_right, DataType::Type::kVoid, {}, {}); // Prevent `HSelect` generation.
bool success = CheckGraphAndTryControlFlowSimplifier();
ASSERT_TRUE(success);
ASSERT_BLOCK_REMOVED(return_block);
ASSERT_BLOCK_RETAINED(left_end);
ASSERT_TRUE(PredecessorsEqual(left_end, {left_left, left_right, right}));
ASSERT_INS_REMOVED(phi1);
ASSERT_INS_RETAINED(phi2);
ASSERT_TRUE(InputsEqual(phi2, {const0, const1, const2}));
ASSERT_INS_RETAINED(phi3);
ASSERT_TRUE(InputsEqual(phi3, {const0, const0, const2}));
// The initial reverse post order has `right` after `left_end` and these
// need to be reordered when we flatten the block merging.
auto&& rpo = graph_->GetReversePostOrder();
ASSERT_LT(IndexOfElement(rpo, right), IndexOfElement(rpo, left_end));
}
TEST_F(ControlFlowSimplifierTest, IrreducibleLoopFlattenGotoRight) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
HParameterValue* bool_param1 = MakeParam(DataType::Type::kBool);
HParameterValue* bool_param2 = MakeParam(DataType::Type::kBool);
HParameterValue* n_param = MakeParam(DataType::Type::kInt32);
HInstruction* const0 = graph_->GetIntConstant(0);
HInstruction* const1 = graph_->GetIntConstant(1);
HInstruction* const2 = graph_->GetIntConstant(2);
auto [split, left_header, right_header, body] = CreateIrreducibleLoop(return_block);
auto [start, left, right_end] = CreateDiamondPattern(body, bool_param1);
auto [right_start, right_left, right_right] = CreateDiamondPattern(right_end, bool_param2);
HPhi* phi1 = MakePhi(right_end, {const1, const2});
HPhi* phi2 = MakePhi(body, {const0, phi1}); // `phi1` shall be replaced by its inputs.
HPhi* phi3 = MakePhi(body, {const0, const1}); // `const1` shall be duplicated.
MakeInvokeStatic(right_right, DataType::Type::kVoid, {}, {}); // Prevent `HSelect` generation.
MakeIf(split, bool_param1);
auto [left_phi, right_phi, add] =
MakeLinearIrreducibleLoopVar(left_header, right_header, body, const0, const1, const1);
HCondition* condition = MakeCondition(left_header, kCondGE, left_phi, n_param);
MakeIf(left_header, condition);
bool success = CheckGraphAndTryControlFlowSimplifier();
ASSERT_TRUE(success);
ASSERT_BLOCK_REMOVED(body);
ASSERT_BLOCK_RETAINED(right_end);
ASSERT_TRUE(PredecessorsEqual(right_end, {left, right_left, right_right}));
ASSERT_INS_REMOVED(phi1);
ASSERT_INS_RETAINED(phi2);
ASSERT_TRUE(InputsEqual(phi2, {const0, const1, const2}));
ASSERT_INS_RETAINED(phi3);
ASSERT_TRUE(InputsEqual(phi3, {const0, const1, const1}));
}
TEST_F(ControlFlowSimplifierTest, IrreducibleLoopFlattenGotoNotApplicable) {
if (!com::android::art::rw::flags::packed_switch_simplification()) {
GTEST_SKIP() << "packed switch simplification disabled.";
}
HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
auto [split, left_header, right_header, body] = CreateIrreducibleLoop(return_block);
HBasicBlock* left_preheader = split->GetSuccessors()[0];
ASSERT_EQ(left_header, left_preheader->GetSingleSuccessor());
HBasicBlock* right_preheader = split->GetSuccessors()[1];
ASSERT_EQ(right_header, right_preheader->GetSingleSuccessor());
HParameterValue* bool_param1 = MakeParam(DataType::Type::kBool);
HParameterValue* bool_param2 = MakeParam(DataType::Type::kBool);
HParameterValue* n_param = MakeParam(DataType::Type::kInt32);
MakeIf(split, bool_param1);
// Change each preheader and the body to have two predecessors to test code paths
// that prevent header merging even if all other conditions are satisfied.
auto [left_split, left_left, left_right] = CreateDiamondPattern(left_preheader, bool_param2);
auto [right_split, right_left, right_right] = CreateDiamondPattern(right_preheader, bool_param2);
auto [body_split, body_left, body_right] = CreateDiamondPattern(body, bool_param2);
MakeInvokeStatic(left_right, DataType::Type::kVoid, {}, {}); // Prevent `HSelect` generation.
MakeInvokeStatic(right_right, DataType::Type::kVoid, {}, {}); // Prevent `HSelect` generation.
MakeInvokeStatic(body_right, DataType::Type::kVoid, {}, {}); // Prevent `HSelect` generation.
HInstruction* const0 = graph_->GetIntConstant(0);
HInstruction* const1 = graph_->GetIntConstant(1);
auto [left_phi, right_phi, add] =
MakeLinearIrreducibleLoopVar(left_header, right_header, body, const0, const1, const1);
HCondition* condition = MakeCondition(left_header, kCondGE, left_phi, n_param);
MakeIf(left_header, condition);
bool success = CheckGraphAndTryControlFlowSimplifier();
ASSERT_FALSE(success);
ASSERT_BLOCK_RETAINED(left_preheader);
ASSERT_BLOCK_RETAINED(right_preheader);
ASSERT_BLOCK_RETAINED(left_header);
ASSERT_BLOCK_RETAINED(right_header);
ASSERT_BLOCK_RETAINED(body);
}
} // namespace art