blob: a7a39f6cd8b091e5dff88c1fc27a39cc3c0acd28 [file]
/*
* Copyright (C) 2016 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 "com_android_art_rw_flags.h"
#include "common_dominator.h"
#include "optimizing/nodes.h"
#include "reference_type_propagation.h"
namespace art HIDDEN {
static constexpr size_t kMaxInstructionsInBranch = 1u;
HControlFlowSimplifier::HControlFlowSimplifier(HGraph* graph,
OptimizingCompilerStats* stats,
const char* name)
: HOptimization(graph, name, stats) {
}
bool HControlFlowSimplifier::ReturnSinking() {
HBasicBlock* exit = graph_->GetExitBlock();
if (exit == nullptr) {
return false; // No exit block, only infinite loop(s).
}
size_t number_of_returns = 0u;
bool saw_return = false;
for (HBasicBlock* pred : exit->GetPredecessors()) {
// TODO(solanes): We might have Return/ReturnVoid->TryBoundary->Exit. We can theoretically
// handle them and move them out of the TryBoundary. However, it is a border case and it adds
// codebase complexity.
if (pred->GetLastInstruction()->IsReturn() || pred->GetLastInstruction()->IsReturnVoid()) {
DCHECK(!pred->IsInLoop()); // Return can be in a loop only if it's in a try-block.
saw_return |= pred->GetLastInstruction()->IsReturn();
++number_of_returns;
}
}
if (number_of_returns < 2) {
// Nothing to do.
return false;
}
// `new_block` will coalesce the Return instructions into Phi+Return,
// or the ReturnVoid instructions into a ReturnVoid.
HBasicBlock* new_block = nullptr;
HInstruction::InstructionKind return_kind =
saw_return ? HInstruction::kReturn : HInstruction::kReturnVoid;
HReturn* first_return = nullptr; // The first `HReturn` shall be reused by the new block.
HPhi* new_phi = nullptr;
size_t new_phi_input_index = 0u;
ArrayRef<HBasicBlock* const> rpo(graph_->GetReversePostOrder());
size_t rpo_insert_index = 0u;
for (size_t rpo_index : Range(rpo.size())) {
HBasicBlock* pred = rpo[rpo_index];
HInstruction* last_inst = pred->GetLastInstruction();
DCHECK_IMPLIES(last_inst == nullptr, pred == exit);
if (last_inst == nullptr || // Exit block?
last_inst->GetKind() != return_kind || // Not `HReturn`/`HReturnVoid`?
pred->GetSingleSuccessor() != exit) { // Leading to try boundary instead of `exit`?
continue;
}
if (new_block == nullptr) {
new_block = pred->SplitBefore(last_inst);
if (saw_return) {
first_return = last_inst->AsReturn();
// Create the `new_phi`. We do it here since we need to know the type to assign to it.
new_phi = new (graph_->GetAllocator()) HPhi(
graph_->GetAllocator(),
kNoRegNumber,
/*number_of_inputs=*/ number_of_returns,
DataType::Kind(first_return->InputAt(0)->GetType()));
new_phi->SetRawInputAt(new_phi_input_index, first_return->InputAt(0));
++new_phi_input_index;
}
} else {
pred->ReplaceSuccessor(exit, new_block);
if (saw_return) {
DCHECK(new_phi != nullptr);
new_phi->SetRawInputAt(new_phi_input_index, last_inst->AsReturn()->InputAt(0));
++new_phi_input_index;
}
pred->ReplaceAndRemoveInstructionWith(
last_inst, new (graph_->GetAllocator()) HGoto(last_inst->GetDexPc()));
}
rpo_insert_index = rpo_index + 1u;
}
if (saw_return) {
DCHECK_EQ(number_of_returns, new_phi_input_index);
DCHECK(new_phi != nullptr);
new_block->AddPhi(new_phi);
DCHECK(first_return != nullptr);
first_return->ReplaceInput(new_phi, 0);
}
graph_->reverse_post_order_.insert(graph_->reverse_post_order_.begin() + rpo_insert_index,
new_block);
if (exit->GetPredecessors().size() == 1u) {
HBasicBlock* dominator = exit->GetDominator();
dominator->RemoveDominatedBlock(exit);
new_block->SetDominator(dominator);
dominator->AddDominatedBlock(new_block);
exit->SetDominator(new_block);
new_block->AddDominatedBlock(exit);
} else {
ArrayRef<HBasicBlock* const> predecessors(new_block->GetPredecessors());
CommonDominator dominator(predecessors[0]);
for (HBasicBlock* pred : predecessors.SubArray(/*pos=*/ 1u)) {
dominator.Update(pred);
}
new_block->SetDominator(dominator.Get());
dominator.Get()->AddDominatedBlock(new_block);
}
return true;
}
bool HControlFlowSimplifier::TrySimplifyPackedSwitch(HBasicBlock* block,
ScopedArenaAllocator* allocator) {
DCHECK(!block->GetInstructions().IsEmpty());
DCHECK(block->GetLastInstruction()->IsPackedSwitch());
HPackedSwitch* packed_switch = block->GetLastInstruction()->AsPackedSwitch();
// Defensively check if we have at least 4 entries. This should be true as long as we build
// a decision tree for smaller switches, see `DexSwitchTable::ShouldBuildDecisionTree()`.
static constexpr size_t kMinEntries = 4u;
size_t num_entries = packed_switch->GetNumEntries();
if (num_entries < kMinEntries) {
return false;
}
// Check if all non-default successors are single-goto blocks merging at the same block.
ArrayRef<HBasicBlock* const> successors(block->GetSuccessors());
DCHECK_EQ(num_entries + 1u, successors.size());
if (!successors[0]->IsSingleGoto()) {
return false;
}
HBasicBlock* merge = successors[0]->GetSingleSuccessor();
for (size_t i : Range(1, num_entries)) {
if (!successors[i]->IsSingleGoto() || successors[i]->GetSingleSuccessor() != merge) {
return false;
}
}
// Check if the `merge` has at most one Phi.
HPhi* phi = nullptr;
if (merge->GetPhis().IsEmpty()) {
// We won't even need a constant table load to simplify this.
} else if (merge->HasSinglePhi()) {
phi = merge->GetFirstPhi()->AsPhi();
DCHECK(phi != nullptr);
} else {
return false; // Do not simplify when there are two or more phis.
}
auto get_value = [](HInstruction* input, int64_t* value) {
if (input->IsIntConstant()) {
*value = input->AsIntConstant()->GetValue();
} else if (input->IsLongConstant()) {
*value = input->AsLongConstant()->GetValue();
} else if (input->IsFloatConstant()) {
*value = input->AsFloatConstant()->GetValueAsUint64();
} else if (input->IsDoubleConstant()) {
*value = input->AsDoubleConstant()->GetValueAsUint64();
} else {
return false;
}
return true;
};
// Check if the default case can be included in the simplified pattern.
HBasicBlock* default_block = successors[num_entries];
int64_t default_value = 0;
bool with_default =
default_block->IsSingleGoto() &&
default_block->GetSingleSuccessor() == merge &&
(phi == nullptr || get_value(phi->InputAt(merge->GetPredecessorIndexOf(default_block)),
&default_value));
bool with_default_index_0 = with_default && packed_switch->GetStartValue() == 1;
ArrayRef<int64_t> entries;
if (phi != nullptr) {
// Check if all the phi's non-default inputs are constants and collect the values.
size_t size = num_entries + (with_default ? 1u : 0u);
entries = ArrayRef<int64_t>(
allocator->AllocArray<int64_t>(size, kArenaAllocControlFlowSimplifier), size);
ArrayRef<int64_t> non_default_entries =
entries.SubArray(with_default_index_0 ? 1u : 0u, num_entries);
for (size_t i : Range(num_entries)) {
size_t predecessor_index = merge->GetPredecessorIndexOf(successors[i]);
if (!get_value(phi->InputAt(predecessor_index), &non_default_entries[i])) {
return false;
}
}
if (with_default) {
entries[with_default_index_0 ? 0u : num_entries] = default_value;
}
}
// Determine table type.
DataType::Type table_type = (phi != nullptr) ? phi->GetType() : DataType::Type::kVoid;
if (DataType::Kind(table_type) == DataType::Type::kInt32) {
// Try to narrow down the type to save space.
auto [min_it, max_it] = std::minmax_element(entries.begin(), entries.end());
DCHECK_GE(*min_it, std::numeric_limits<int32_t>::min());
DCHECK_LE(*max_it, std::numeric_limits<int32_t>::max());
bool has_negative = (*min_it < 0);
int max_value_to_encode = has_negative
? dchecked_integral_cast<int32_t>(std::max(*max_it, -1 - *min_it))
: dchecked_integral_cast<int32_t>(*max_it);
if (!has_negative && max_value_to_encode <= 1) {
table_type = DataType::Type::kBool;
} else if (max_value_to_encode <= std::numeric_limits<int8_t>::max()) {
table_type = DataType::Type::kInt8;
} else if (!has_negative && max_value_to_encode <= std::numeric_limits<uint8_t>::max()) {
table_type = DataType::Type::kUint8;
} else if (max_value_to_encode <= std::numeric_limits<int16_t>::max()) {
table_type = DataType::Type::kInt16;
} else if (!has_negative && max_value_to_encode <= std::numeric_limits<uint16_t>::max()) {
table_type = DataType::Type::kUint16;
} else {
DCHECK_EQ(table_type, DataType::Type::kInt32); // Keep the `kInt32`.
}
}
// Prepare the index calculation.
uint32_t dex_pc = packed_switch->GetDexPc();
HInstruction* index = packed_switch->InputAt(0);
if (packed_switch->GetStartValue() != 0 && !with_default_index_0) {
HInstruction* addend = graph_->GetIntConstant(-packed_switch->GetStartValue());
index = new (graph_->GetAllocator()) HAdd(DataType::Type::kInt32, index, addend, dex_pc);
block->InsertInstructionBefore(index, packed_switch);
}
// Prepare the comparison with the upper bound.
HInstruction* bound = graph_->GetIntConstant(num_entries + (with_default_index_0 ? 1 : 0));
HBelow* below = new (graph_->GetAllocator()) HBelow(index, bound, dex_pc);
block->InsertInstructionBefore(below, packed_switch);
if (with_default) {
// Insert `HSelect` to clamp the index.
HInstruction* false_value = graph_->GetIntConstant(with_default_index_0 ? 0 : num_entries);
index = new (graph_->GetAllocator()) HSelect(below, index, false_value, dex_pc);
block->InsertInstructionBefore(index, packed_switch);
}
if (phi != nullptr) {
// Insert constant table load and update the phi input we intend to keep.
HLoadConstantTableEntry* load = new (graph_->GetAllocator()) HLoadConstantTableEntry(
table_type, index, ArrayRef<const int64_t>(entries), graph_->GetAllocator(), dex_pc);
if (with_default) {
block->InsertInstructionBefore(load, packed_switch);
} else {
successors[0]->InsertInstructionBefore(load, successors[0]->GetFirstInstruction());
}
phi->ReplaceInput(load, merge->GetPredecessorIndexOf(successors[0]));
}
// Remove the default successor if `with_default` and all non-default successors,
// except the first. This also removes `phi` inputs and replaces the Phi with the
// `load` if it's the only remaining input.
// Note: Stop using `successors` as the underlying vector is being modified.
for (size_t i : Range(with_default ? 0u : 1u, num_entries)) {
block->GetSuccessors()[num_entries - i]->DisconnectAndDelete();
}
// If the merge block has only one predecessor now, update domination info and merge.
if (merge->GetPredecessors().size() == 1u) {
HBasicBlock* remaining_block = block->GetSuccessors()[0];
DCHECK(remaining_block == merge->GetSinglePredecessor());
DCHECK(merge->GetDominator() == block);
block->RemoveDominatedBlock(merge);
merge->SetDominator(remaining_block);
remaining_block->AddDominatedBlock(merge);
remaining_block->MergeWith(merge);
}
if (with_default) {
// Merge `block` with its remaining successor.
DCHECK(block->GetLastInstruction()->IsGoto()); // Updated by last `DisconnectAndDelete()`.
block->MergeWith(block->GetSingleSuccessor());
} else {
// Replace the switch with `HBelow` and `HIf`.
DCHECK_EQ(2u, block->GetSuccessors().size());
HIf* if_ = new (graph_->GetAllocator()) HIf(below, dex_pc);
block->ReplaceAndRemoveInstructionWith(packed_switch, if_);
}
MaybeRecordStat(stats_, MethodCompilationStat::kControlFlowSwitchSimplified);
return true;
}
// Returns true if `block` has only one predecessor, ends with a Goto
// or a Return and contains at most `kMaxInstructionsInBranch` other
// movable instruction with no side-effects.
static bool IsSimpleBlock(HBasicBlock* block) {
if (block->GetPredecessors().size() != 1u) {
return false;
}
DCHECK(block->GetPhis().IsEmpty());
size_t num_instructions = 0u;
for (HInstructionIteratorPrefetchNext it(block->GetInstructions()); !it.Done(); it.Advance()) {
HInstruction* instruction = it.Current();
if (instruction->IsControlFlow()) {
return instruction->IsGoto() || instruction->IsReturn();
} else if (instruction->CanBeMoved() &&
!instruction->HasSideEffects() &&
!instruction->CanThrow()) {
if (instruction->IsSelect() && instruction->AsSelect()->GetCondition()->GetBlock() == block) {
// Count one HCondition and HSelect in the same block as a single instruction.
// This enables finding nested selects.
continue;
} else if (++num_instructions > kMaxInstructionsInBranch) {
return false; // bail as soon as we exceed number of allowed instructions
}
} else {
return false;
}
}
LOG(FATAL) << "Unreachable";
UNREACHABLE();
}
// Returns true if 'block1' and 'block2' are empty and merge into the
// same single successor.
static bool BlocksMergeTogether(HBasicBlock* block1, HBasicBlock* block2) {
return block1->GetSingleSuccessor() == block2->GetSingleSuccessor();
}
// Search `block` for phis that have different inputs at `index1` and `index2`.
// If none is found, returns `{true, nullptr}`.
// If exactly one such `phi` is found, returns `{true, phi}`.
// Otherwise (if more than one such phi is found), returns `{false, nullptr}`.
static std::pair<bool, HPhi*> HasAtMostOnePhiWithDifferentInputs(HBasicBlock* block,
size_t index1,
size_t index2) {
DCHECK_NE(index1, index2);
HPhi* select_phi = nullptr;
for (HInstructionIteratorPrefetchNext it(block->GetPhis()); !it.Done(); it.Advance()) {
HPhi* phi = it.Current()->AsPhi();
auto&& inputs = phi->GetInputs();
if (inputs[index1] == inputs[index2]) {
continue;
}
if (select_phi == nullptr) {
// First phi found.
select_phi = phi;
} else {
// More than one phi found, return null.
return {false, nullptr};
}
}
return {true, select_phi};
}
bool HControlFlowSimplifier::TryGenerateSelectSimpleDiamondPattern(
HBasicBlock* block, ScopedArenaSafeMap<HInstruction*, HSelect*>* cache) {
DCHECK(block->GetLastInstruction()->IsIf());
HIf* if_instruction = block->GetLastInstruction()->AsIf();
HBasicBlock* true_block = if_instruction->IfTrueSuccessor();
HBasicBlock* false_block = if_instruction->IfFalseSuccessor();
DCHECK_NE(true_block, false_block);
if (!IsSimpleBlock(true_block) ||
!IsSimpleBlock(false_block) ||
!BlocksMergeTogether(true_block, false_block)) {
return false;
}
HBasicBlock* merge_block = true_block->GetSingleSuccessor();
// If the branches are not empty, move instructions in front of the If.
// TODO(dbrazdil): This puts an instruction between If and its condition.
// Implement moving of conditions to first users if possible.
while (!true_block->IsSingleGoto() && !true_block->IsSingleReturn()) {
HInstruction* instr = true_block->GetFirstInstruction();
DCHECK(!instr->CanThrow());
instr->MoveBefore(if_instruction);
}
while (!false_block->IsSingleGoto() && !false_block->IsSingleReturn()) {
HInstruction* instr = false_block->GetFirstInstruction();
DCHECK(!instr->CanThrow());
instr->MoveBefore(if_instruction);
}
DCHECK(true_block->IsSingleGoto() || true_block->IsSingleReturn());
DCHECK(false_block->IsSingleGoto() || false_block->IsSingleReturn());
// Find the resulting true/false values.
size_t predecessor_index_true = merge_block->GetPredecessorIndexOf(true_block);
size_t predecessor_index_false = merge_block->GetPredecessorIndexOf(false_block);
DCHECK_NE(predecessor_index_true, predecessor_index_false);
bool both_successors_return = true_block->IsSingleReturn() && false_block->IsSingleReturn();
// TODO(solanes): Extend to support multiple phis? e.g.
// int a, b;
// if (bool) {
// a = 0; b = 1;
// } else {
// a = 1; b = 2;
// }
// // use a and b
bool at_most_one_phi_with_different_inputs = false;
HPhi* phi = nullptr;
HInstruction* true_value = nullptr;
HInstruction* false_value = nullptr;
if (both_successors_return) {
// Note: This can create a select with the same then-value and else-value.
true_value = true_block->GetFirstInstruction()->InputAt(0);
false_value = false_block->GetFirstInstruction()->InputAt(0);
} else {
std::tie(at_most_one_phi_with_different_inputs, phi) = HasAtMostOnePhiWithDifferentInputs(
merge_block, predecessor_index_true, predecessor_index_false);
if (!at_most_one_phi_with_different_inputs) {
return false;
}
if (phi != nullptr) {
true_value = phi->InputAt(predecessor_index_true);
false_value = phi->InputAt(predecessor_index_false);
} // else we don't need to create a `HSelect` at all.
}
DCHECK(both_successors_return || at_most_one_phi_with_different_inputs);
// Create the Select instruction and insert it in front of the If.
HInstruction* condition = if_instruction->InputAt(0);
HSelect* select = nullptr;
if (both_successors_return || phi != nullptr) {
select = new (graph_->GetAllocator()) HSelect(condition,
true_value,
false_value,
if_instruction->GetDexPc());
block->InsertInstructionBefore(select, if_instruction);
if (both_successors_return) {
if (true_value->GetType() == DataType::Type::kReference) {
DCHECK(false_value->GetType() == DataType::Type::kReference);
ReferenceTypePropagation::FixUpSelectType(select, graph_->GetHandleCache());
}
false_block->GetFirstInstruction()->ReplaceInput(select, 0);
} else {
if (phi->GetType() == DataType::Type::kReference) {
select->SetReferenceTypeInfoIfValid(phi->GetReferenceTypeInfo());
}
phi->ReplaceInput(select, predecessor_index_false); // We'll remove the true branch below.
}
}
// Remove the true branch which removes the corresponding Phi input if needed.
// If left only with the false branch, the Phi is automatically removed.
true_block->DisconnectAndDelete();
// Merge remaining blocks which are now connected with Goto.
DCHECK_EQ(block->GetSingleSuccessor(), false_block);
block->MergeWith(false_block);
if (!both_successors_return && merge_block->GetPredecessors().size() == 1u) {
DCHECK_IMPLIES(phi != nullptr, phi->GetBlock() == nullptr);
DCHECK(merge_block->GetPhis().IsEmpty());
DCHECK_EQ(block->GetSingleSuccessor(), merge_block);
block->MergeWith(merge_block);
}
MaybeRecordStat(stats_, select != nullptr ? MethodCompilationStat::kControlFlowSelectGenerated
: MethodCompilationStat::kControlFlowDiamondRemoved);
// Very simple way of finding common subexpressions in the generated HSelect statements
// (since this runs after GVN). Lookup by condition, and reuse latest one if possible
// (due to post order, latest select is most likely replacement). If needed, we could
// improve this by e.g. using the operands in the map as well.
if (select != nullptr) {
auto it = cache->find(condition);
if (it == cache->end()) {
cache->Put(condition, select);
} else {
// Found cached value. See if latest can replace cached in the HIR.
HSelect* cached_select = it->second;
DCHECK_EQ(cached_select->GetCondition(), select->GetCondition());
if (cached_select->GetTrueValue() == select->GetTrueValue() &&
cached_select->GetFalseValue() == select->GetFalseValue() &&
select->StrictlyDominates(cached_select)) {
cached_select->ReplaceWith(select);
cached_select->GetBlock()->RemoveInstruction(cached_select);
}
it->second = select; // always cache latest
}
}
// No need to update dominance information, as we are simplifying
// a simple diamond shape, where the join block is merged with the
// entry block. Any following blocks would have had the join block
// as a dominator, and `MergeWith` handles changing that to the
// entry block
return true;
}
HBasicBlock* HControlFlowSimplifier::TryFixupDoubleDiamondPattern(HBasicBlock* block) {
DCHECK(block->GetLastInstruction()->IsIf());
HIf* if_instruction = block->GetLastInstruction()->AsIf();
HBasicBlock* true_block = if_instruction->IfTrueSuccessor();
HBasicBlock* false_block = if_instruction->IfFalseSuccessor();
DCHECK_NE(true_block, false_block);
// One branch must be a single goto, and the other one the inner if.
if (true_block->IsSingleGoto() == false_block->IsSingleGoto()) {
return nullptr;
}
HBasicBlock* single_goto = true_block->IsSingleGoto() ? true_block : false_block;
HBasicBlock* inner_if_block = true_block->IsSingleGoto() ? false_block : true_block;
// The innner if branch has to be a block with just a comparison and an if.
if (!inner_if_block->EndsWithIf() ||
inner_if_block->GetLastInstruction()->AsIf()->InputAt(0) !=
inner_if_block->GetFirstInstruction() ||
inner_if_block->GetLastInstruction()->GetPrevious() !=
inner_if_block->GetFirstInstruction() ||
!inner_if_block->GetFirstInstruction()->IsCondition()) {
return nullptr;
}
HIf* inner_if_instruction = inner_if_block->GetLastInstruction()->AsIf();
HBasicBlock* inner_if_true_block = inner_if_instruction->IfTrueSuccessor();
HBasicBlock* inner_if_false_block = inner_if_instruction->IfFalseSuccessor();
if (!inner_if_true_block->IsSingleGoto() || !inner_if_false_block->IsSingleGoto()) {
return nullptr;
}
// One must merge into the outer condition and the other must not.
if (BlocksMergeTogether(single_goto, inner_if_true_block) ==
BlocksMergeTogether(single_goto, inner_if_false_block)) {
return nullptr;
}
// First merge merges the outer if with one of the inner if branches. The block must be a Phi and
// a Goto.
HBasicBlock* first_merge = single_goto->GetSingleSuccessor();
if (first_merge->GetNumberOfPredecessors() != 2 ||
first_merge->GetPhis().CountSize() != 1 ||
!first_merge->GetLastInstruction()->IsGoto() ||
first_merge->GetFirstInstruction() != first_merge->GetLastInstruction()) {
return nullptr;
}
HPhi* first_phi = first_merge->GetFirstPhi()->AsPhi();
// Second merge is first_merge and the remainder branch merging. It must be phi + goto, or phi +
// return. Depending on the first merge, we define the second merge.
HBasicBlock* merges_into_second_merge =
BlocksMergeTogether(single_goto, inner_if_true_block)
? inner_if_false_block
: inner_if_true_block;
if (!BlocksMergeTogether(first_merge, merges_into_second_merge)) {
return nullptr;
}
HBasicBlock* second_merge = merges_into_second_merge->GetSingleSuccessor();
if (second_merge->GetNumberOfPredecessors() != 2 ||
second_merge->GetPhis().CountSize() != 1 ||
!(second_merge->GetLastInstruction()->IsGoto() ||
second_merge->GetLastInstruction()->IsReturn()) ||
second_merge->GetFirstInstruction() != second_merge->GetLastInstruction()) {
return nullptr;
}
HPhi* second_phi = second_merge->GetFirstPhi()->AsPhi();
size_t first_merge_index = second_merge->GetPredecessorIndexOf(first_merge);
if (second_phi->InputAt(first_merge_index) != first_phi) {
// The first phi does not merge into the second one. This is an odd case where `first_phi` is
// dead but hasn't been eliminated from the graph yet.
// TODO(solanes): We can refactor this method to do:
// 1) Keep the `second_merge` alive instead of `first_merge`
// 2) For all `first_phi` instances, fetch the appropriate `first_phi` input
// 3) For the rest of the inputs, duplicate the value that was already present in
// `second_phi`.
// When we do this, we can eliminate this `if` case, but it would complicate the
// logic of this method.
return nullptr;
}
size_t merges_into_second_merge_index =
second_merge->GetPredecessorIndexOf(merges_into_second_merge);
// Merge the phis.
first_phi->AddInput(second_phi->InputAt(merges_into_second_merge_index));
merges_into_second_merge->ReplaceSuccessor(second_merge, first_merge);
second_phi->ReplaceWith(first_phi);
second_merge->RemovePhi(second_phi);
// Sort out the new domination before merging the blocks
DCHECK_EQ(second_merge->GetSinglePredecessor(), first_merge);
second_merge->GetDominator()->RemoveDominatedBlock(second_merge);
second_merge->SetDominator(first_merge);
first_merge->AddDominatedBlock(second_merge);
first_merge->MergeWith(second_merge);
// No need to update dominance information. There's a chance that `merges_into_second_merge`
// doesn't come before `first_merge` but we don't need to fix it since `merges_into_second_merge`
// will disappear from the graph altogether when doing the follow-up
// TryGenerateSelectSimpleDiamondPattern.
return inner_if_block;
}
bool HControlFlowSimplifier::TryFlattenMerge(HBasicBlock* block,
size_t reverse_post_order_index,
BitVectorView<size_t> visited_blocks) {
DCHECK(block->GetFirstInstruction()->IsGoto());
DCHECK_EQ(block->GetFirstInstruction(), block->GetLastInstruction());
HBasicBlock* successor = block->GetSingleSuccessor();
DCHECK(!graph_->IsExitBlock(successor)); // `HGoto` does not flow to exit block.
if (block->GetPredecessors().size() < 2u || successor->GetPredecessors().size() < 2u) {
return false;
}
if (block->IsCatchBlock() || successor->IsCatchBlock()) {
// Phi inputs do not correspond to catch block predecessors. Do not flatten.
return false;
}
if (block->GetLoopInformation() != successor->GetLoopInformation()) {
// The `block` is a pre-header, including the case when `successor` is an irreducible
// loop entry that's not actually marked as loop header in the `HLoopInformation`.
return false;
}
if (block->IsInLoop()) {
// Do not merge if the `block` or the `successor` is a loop header, including irreducible
// loop entries that are not actually marked as loop header in the `HLoopInformation`.
// Even for irreducible loops, check for the recorded loop header first.
HLoopInformation* loop_info = block->GetLoopInformation();
if (block == loop_info->GetHeader() || successor == loop_info->GetHeader()) {
return false;
}
if (UNLIKELY(loop_info->IsIrreducible())) {
auto is_loop_header = [loop_info](HBasicBlock* b) {
DCHECK_EQ(loop_info, b->GetLoopInformation());
auto&& predecessors = b->GetPredecessors();
return std::any_of(
predecessors.begin(),
predecessors.end(),
[loop_info](HBasicBlock* p) { return p->GetLoopInformation() != loop_info; });
};
if (is_loop_header(block) || is_loop_header(successor)) {
return false;
}
}
}
block->TakeGotoBlockSuccessorsOtherPredecessorsAndMergePhis();
// Fix up domination information for unmerged blocks before calling `MergeWith()`.
HBasicBlock* dominator = successor->GetDominator();
if (block->GetDominator() == dominator) {
dominator->RemoveDominatedBlock(successor);
} else {
block->GetDominator()->RemoveDominatedBlock(block);
block->SetDominator(dominator);
dominator->ReplaceDominatedBlock(successor, block);
}
successor->SetDominator(block);
block->AddDominatedBlock(successor);
// Move predecessors before `block` in reverse post order if needed.
ScopedArenaAllocator allocator(graph_->GetArenaStack());
BitVectorView<size_t> predecessors_to_move = ArenaBitVector::CreateFixedSize(
&allocator, graph_->GetBlocks().size(), kArenaAllocControlFlowSimplifier);
ScopedArenaVector<HBasicBlock*> work_queue(allocator.Adapter(kArenaAllocControlFlowSimplifier));
auto mark_predecessors = [&](HBasicBlock* current) {
for (HBasicBlock* predecessor : current->GetPredecessors()) {
if (visited_blocks.IsBitSet(predecessor->GetBlockId()) &&
!predecessors_to_move.IsBitSet(predecessor->GetBlockId())) {
predecessors_to_move.SetBit(predecessor->GetBlockId());
work_queue.push_back(predecessor);
}
}
};
mark_predecessors(block);
if (!work_queue.empty()) {
do {
HBasicBlock* current = work_queue.back();
work_queue.pop_back();
mark_predecessors(current);
} while (!work_queue.empty());
// Move blocks marked in `predecessors_to_move` to the correct position in the reverse
// post order while extracting `block` and other unmarked blocks to a temporary vector.
ScopedArenaVector<HBasicBlock*> extracted(allocator.Adapter(kArenaAllocControlFlowSimplifier));
auto moved_end = graph_->reverse_post_order_.begin() + reverse_post_order_index;
DCHECK_EQ(block, *moved_end);
DCHECK(!predecessors_to_move.IsBitSet(block->GetBlockId()));
extracted.push_back(block);
auto move_it = std::next(moved_end);
DCHECK(move_it != graph_->reverse_post_order_.end());
while (*move_it != successor) {
if (predecessors_to_move.IsBitSet((*move_it)->GetBlockId())) {
*moved_end = *move_it;
++moved_end;
} else {
extracted.push_back(*move_it);
}
++move_it;
DCHECK(move_it != graph_->reverse_post_order_.end());
}
// Place extracted blocks in the freed range in reverse post order.
DCHECK_EQ(static_cast<size_t>(std::distance(moved_end, move_it)), extracted.size());
std::copy(extracted.begin(), extracted.end(), moved_end);
}
// Finish the merge using `MergeWith()`.
block->MergeWith(successor);
MaybeRecordStat(stats_, MethodCompilationStat::kControlFlowFlattenedMerge);
return true;
}
bool HControlFlowSimplifier::Run() {
bool simplified = com::android::art::rw::flags::packed_switch_simplification() && ReturnSinking();
ScopedArenaAllocator allocator(graph_->GetArenaStack());
// Select cache with local allocator for `TryGenerateSelectSimpleDiamondPattern()`.
ScopedArenaSafeMap<HInstruction*, HSelect*> select_cache(
std::less<HInstruction*>(), allocator.Adapter(kArenaAllocControlFlowSimplifier));
// Mark visited blocks by block id for reverse post order fixup in `TryFlattenMerge()`.
BitVectorView<size_t> visited_blocks = ArenaBitVector::CreateFixedSize(
&allocator, graph_->GetBlocks().size(), kArenaAllocControlFlowSimplifier);
// Iterate in post order in the case that simplifying a block exposes simplification
// opportunities for earier blocks. Do not process the entry block.
// We may remove blocks from the reverse post order array, so make the iteration very explicit.
HBasicBlock* const * reverse_post_order_data = graph_->GetReversePostOrder().data();
size_t reverse_post_order_index = graph_->GetReversePostOrder().size();
while (reverse_post_order_index != /* Do not process entry block with index 0. */ 1u) {
--reverse_post_order_index;
HBasicBlock* block = reverse_post_order_data[reverse_post_order_index];
DCHECK(block != nullptr);
DCHECK(!block->GetInstructions().IsEmpty());
HInstruction* last_inst = block->GetLastInstruction();
bool block_may_have_moved_in_rpo = false;
if (com::android::art::rw::flags::packed_switch_simplification() &&
last_inst->IsPackedSwitch() &&
TrySimplifyPackedSwitch(block, &allocator)) {
simplified = true;
} else if (com::android::art::rw::flags::packed_switch_simplification() &&
last_inst->IsGoto() &&
last_inst == block->GetFirstInstruction() &&
TryFlattenMerge(block, reverse_post_order_index, visited_blocks)) {
simplified = true;
block_may_have_moved_in_rpo = true;
} else if (last_inst->IsIf()) {
if (TryGenerateSelectSimpleDiamondPattern(block, &select_cache)) {
simplified = true;
} else if (!com::android::art::rw::flags::packed_switch_simplification()) {
// Note: The `TryFlattenMerge()` simplification above replaces the
// `TryFixupDoubleDiamondPattern()` here if the flag is enabled.
// Try to fix up the odd version of the double diamond pattern. If we could do it, it means
// that we can generate two selects.
HBasicBlock* inner_if_block = TryFixupDoubleDiamondPattern(block);
if (inner_if_block != nullptr) {
// Generate the selects now since `inner_if_block` should be after `block` in PostOrder.
bool result = TryGenerateSelectSimpleDiamondPattern(inner_if_block, &select_cache);
DCHECK(result);
result = TryGenerateSelectSimpleDiamondPattern(block, &select_cache);
DCHECK(result);
simplified = true;
}
}
}
DCHECK_LT(block->GetBlockId(), graph_->GetBlocks().size());
visited_blocks.SetBit(block->GetBlockId());
// Blocks with higher indexes may have been removed and the `block` may have been moved
// further back. Removing from a `std::vector<>` does not change the data pointer.
DCHECK_EQ(reverse_post_order_data, graph_->GetReversePostOrder().data());
DCHECK_LT(reverse_post_order_index, graph_->GetReversePostOrder().size());
if (block_may_have_moved_in_rpo) {
DCHECK_GE(IndexOfElement(graph_->GetReversePostOrder(), block), reverse_post_order_index);
} else {
DCHECK_EQ(block, reverse_post_order_data[reverse_post_order_index]);
}
}
DCHECK_EQ(reverse_post_order_index, 1u);
DCHECK(graph_->IsEntryBlock(reverse_post_order_data[0u]));
return simplified;
}
} // namespace art