bool HControlFlowSimplifier::ReturnSinking() {
HBasicBlock* exit = graph_->GetExitBlock(); if (exit == nullptr) { returnfalse; // 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. returnfalse;
}
// `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);
}
// 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) { returnfalse;
}
// 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()) { returnfalse;
}
HBasicBlock* merge = successors[0]->GetSingleSuccessor(); for (size_t i : Range(1, num_entries)) { if (!successors[i]->IsSingleGoto() || successors[i]->GetSingleSuccessor() != merge) { returnfalse;
}
}
// 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.
} elseif (merge->HasSinglePhi()) {
phi = merge->GetFirstPhi()->AsPhi();
DCHECK(phi != nullptr);
} else { returnfalse; // Do not simplify when there are two or more phis.
}
// 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_);
}
// 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. staticbool IsSimpleBlock(HBasicBlock* block) { if (block->GetPredecessors().size() != 1u) { returnfalse;
}
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();
} elseif (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;
} elseif (++num_instructions > kMaxInstructionsInBranch) { returnfalse; // bail as soon as we exceed number of allowed instructions
}
} else { returnfalse;
}
}
LOG(FATAL) << "Unreachable";
UNREACHABLE();
}
// Returns true if 'block1' and 'block2' are empty and merge into the // same single successor. staticbool 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};
}
// 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());
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) { returnfalse;
} 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);
}
// 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 returntrue;
}
// One branch must be a single goto, and the other one the inner if. if (true_block->IsSingleGoto() == false_block->IsSingleGoto()) { return nullptr;
}
// 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;
}
// 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;
}
// 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;
}
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;
}
// 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) { returnfalse;
} if (block->IsCatchBlock() || successor->IsCatchBlock()) { // Phi inputs do not correspond to catch block predecessors. Do not flatten. returnfalse;
} 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`. returnfalse;
} 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()) { returnfalse;
} 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)) { returnfalse;
}
}
}
// 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);
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;
} elseif (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;
} elseif (last_inst->IsIf()) { if (TryGenerateSelectSimpleDiamondPattern(block, &select_cache)) {
simplified = true;
} elseif (!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
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-06-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.