blob: 6a10440d1181573756ddfa19c06308a2bdbba139 [file] [log] [blame]
/*
* 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 "select_generator.h"
#include "optimizing/nodes.h"
#include "reference_type_propagation.h"
namespace art HIDDEN {
static constexpr size_t kMaxInstructionsInBranch = 1u;
HSelectGenerator::HSelectGenerator(HGraph* graph,
OptimizingCompilerStats* stats,
const char* name)
: HOptimization(graph, name, stats) {
}
// 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 (HInstructionIterator 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();
}
// Returns nullptr if `block` has either no phis or there is more than one phi. Otherwise returns
// that phi.
static HPhi* GetSinglePhi(HBasicBlock* block, size_t index1, size_t index2) {
DCHECK_NE(index1, index2);
HPhi* select_phi = nullptr;
for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
HPhi* phi = it.Current()->AsPhi();
if (select_phi == nullptr) {
// First phi found.
select_phi = phi;
} else {
// More than one phi found, return null.
return nullptr;
}
}
return select_phi;
}
bool HSelectGenerator::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
HPhi* phi = GetSinglePhi(merge_block, predecessor_index_true, predecessor_index_false);
HInstruction* true_value = nullptr;
HInstruction* false_value = nullptr;
if (both_successors_return) {
true_value = true_block->GetFirstInstruction()->InputAt(0);
false_value = false_block->GetFirstInstruction()->InputAt(0);
} else if (phi != nullptr) {
true_value = phi->InputAt(predecessor_index_true);
false_value = phi->InputAt(predecessor_index_false);
} else {
return false;
}
DCHECK(both_successors_return || phi != nullptr);
// Create the Select instruction and insert it in front of the If.
HInstruction* condition = if_instruction->InputAt(0);
HSelect* select = new (graph_->GetAllocator()) HSelect(condition,
true_value,
false_value,
if_instruction->GetDexPc());
if (both_successors_return) {
if (true_value->GetType() == DataType::Type::kReference) {
DCHECK(false_value->GetType() == DataType::Type::kReference);
ReferenceTypePropagation::FixUpInstructionType(select, graph_->GetHandleCache());
}
} else if (phi->GetType() == DataType::Type::kReference) {
select->SetReferenceTypeInfoIfValid(phi->GetReferenceTypeInfo());
}
block->InsertInstructionBefore(select, if_instruction);
// Remove the true branch which removes the corresponding Phi
// input if needed. If left only with the false branch, the Phi is
// automatically removed.
if (both_successors_return) {
false_block->GetFirstInstruction()->ReplaceInput(select, 0);
} else {
phi->ReplaceInput(select, predecessor_index_false);
}
bool only_two_predecessors = (merge_block->GetPredecessors().size() == 2u);
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 && only_two_predecessors) {
DCHECK_EQ(only_two_predecessors, phi->GetBlock() == nullptr);
DCHECK_EQ(block->GetSingleSuccessor(), merge_block);
block->MergeWith(merge_block);
}
MaybeRecordStat(stats_, MethodCompilationStat::kSelectGenerated);
// 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.
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* HSelectGenerator::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;
}
size_t index = second_merge->GetPredecessorIndexOf(merges_into_second_merge);
HPhi* second_phi = second_merge->GetFirstPhi()->AsPhi();
// Merge the phis.
first_phi->AddInput(second_phi->InputAt(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 HSelectGenerator::Run() {
bool did_select = false;
// Select cache with local allocator.
ScopedArenaAllocator allocator(graph_->GetArenaStack());
ScopedArenaSafeMap<HInstruction*, HSelect*> cache(std::less<HInstruction*>(),
allocator.Adapter(kArenaAllocSelectGenerator));
// Iterate in post order in the unlikely case that removing one occurrence of
// the selection pattern empties a branch block of another occurrence.
for (HBasicBlock* block : graph_->GetPostOrder()) {
if (!block->EndsWithIf()) {
continue;
}
if (TryGenerateSelectSimpleDiamondPattern(block, &cache)) {
did_select = true;
} else {
// 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, &cache);
DCHECK(result);
result = TryGenerateSelectSimpleDiamondPattern(block, &cache);
DCHECK(result);
did_select = true;
}
}
}
return did_select;
}
} // namespace art