void SchedulingGraph::AddDependency(SchedulingNode* node,
SchedulingNode* dependency, bool is_data_dependency) { if (node == nullptr || dependency == nullptr) { // A `nullptr` node indicates an instruction out of scheduling range (eg. in // an other block), so we do not need to add a dependency edge to the graph. return;
}
if (is_data_dependency) {
node->AddDataPredecessor(dependency);
} else {
node->AddOtherPredecessor(dependency);
}
}
HInstruction* ref = instr->InputAt(0);
size_t heap_loc = heap_location_collector_->GetFieldHeapLocation(ref, GetFieldInfo(instr)); // This field access should be analyzed and added to HeapLocationCollector before.
DCHECK(heap_loc != HeapLocationCollector::kHeapLocationNotFound);
// Static and instance field accesses should not alias. if ((IsInstanceFieldAccess(instr1) && IsStaticFieldAccess(instr2)) ||
(IsStaticFieldAccess(instr1) && IsInstanceFieldAccess(instr2))) { returnfalse;
}
// If both fields accesses are resolved.
size_t instr1_field_access_heap_loc = FieldAccessHeapLocation(instr1);
size_t instr2_field_access_heap_loc = FieldAccessHeapLocation(instr2);
if (instr1_field_access_heap_loc == instr2_field_access_heap_loc) { returntrue;
}
if (!heap_location_collector_->MayAlias(instr1_field_access_heap_loc,
instr2_field_access_heap_loc)) { returnfalse;
}
if (heap_location_collector_ == nullptr ||
heap_location_collector_->GetNumberOfHeapLocations() == 0) { // Without HeapLocation information from load store analysis, // we cannot do further disambiguation analysis on these two instructions. // Just simply say that those two instructions have memory dependency. returntrue;
}
// Note: Unresolved field access instructions are currently marked as not schedulable. // If we change that, we should still keep in mind that these instructions can throw and // read or write volatile fields and, if static, cause class initialization and write to // arbitrary heap locations, and therefore cannot be reordered with any other field or // array access to preserve the observable behavior. The only exception is access to // singleton members that could actually be reodered across these instructions but we // currently do not analyze singletons here anyway.
if (IsArrayAccess(instr1) && IsArrayAccess(instr2)) { return ArrayAccessMayAlias(instr1, instr2);
} if (IsFieldAccess(instr1) && IsFieldAccess(instr2)) { return FieldAccessMayAlias(instr1, instr2);
}
// TODO(xueliang): LSA to support alias analysis among HVecLoad, HVecStore and ArrayAccess if (instr1->IsVecMemoryOperation() && instr2->IsVecMemoryOperation()) { returntrue;
} if (instr1->IsVecMemoryOperation() && IsArrayAccess(instr2)) { returntrue;
} if (IsArrayAccess(instr1) && instr2->IsVecMemoryOperation()) { returntrue;
}
// Heap accesses of different kinds should not alias. if (IsArrayAccess(instr1) && IsFieldAccess(instr2)) { returnfalse;
} if (IsFieldAccess(instr1) && IsArrayAccess(instr2)) { returnfalse;
} if (instr1->IsVecMemoryOperation() && IsFieldAccess(instr2)) { returnfalse;
} if (IsFieldAccess(instr1) && instr2->IsVecMemoryOperation()) { returnfalse;
}
// We conservatively treat all other cases having dependency, // for example, Invoke and ArrayGet. returntrue;
}
bool SideEffectDependencyAnalysis::HasExceptionDependency(const HInstruction* instr1, const HInstruction* instr2) { if (instr2->CanThrow() && instr1->GetSideEffects().DoesAnyWrite()) { returntrue;
} if (instr2->GetSideEffects().DoesAnyWrite() && instr1->CanThrow()) { returntrue;
} if (instr2->CanThrow() && instr1->CanThrow()) { returntrue;
}
// Above checks should cover all cases where we cannot reorder two // instructions which may throw exception. returnfalse;
}
// Check if the specified instruction is a better candidate which more likely will // have other instructions depending on it. staticbool IsBetterCandidateWithMoreLikelyDependencies(HInstruction* new_candidate,
HInstruction* old_candidate) { if (!new_candidate->GetSideEffects().Includes(old_candidate->GetSideEffects())) { // Weaker side effects. returnfalse;
} if (old_candidate->GetSideEffects().Includes(new_candidate->GetSideEffects())) { // Same side effects, check if `new_candidate` has stronger `CanThrow()`. return new_candidate->CanThrow() && !old_candidate->CanThrow();
} else { // Stronger side effects, check if `new_candidate` has at least as strong `CanThrow()`. return new_candidate->CanThrow() || !old_candidate->CanThrow();
}
}
void SchedulingGraph::AddCrossIterationDependencies(SchedulingNode* node) { for (HInstruction* instruction : node->GetInstruction()->GetInputs()) { // Having a phi-function from a loop header as an input means the current node of the // scheduling graph has a cross-iteration dependency because such phi-functions bring values // from the previous iteration to the current iteration. if (!instruction->IsLoopHeaderPhi()) { continue;
} for (HInstruction* phi_input : instruction->GetInputs()) { // As a scheduling graph of the current basic block is built by // processing instructions bottom-up, nullptr returned by GetNode means // an instruction defining a value for the phi is either before the // instruction represented by node or it is in a different basic block.
SchedulingNode* def_node = GetNode(phi_input);
// We don't create a dependency if there are uses besides the use in phi. // In such cases a register to hold phi_input is usually allocated and // a MOV instruction is generated. In cases with multiple uses and no MOV // instruction, reordering creating a MOV instruction can improve // performance more than an attempt to avoid a MOV instruction. if (def_node != nullptr && def_node != node && phi_input->GetUses().HasExactlyOneElement()) { // We have an implicit data dependency between node and def_node. // AddAddDataDependency cannot be used because it is for explicit data dependencies. // So AddOtherDependency is used.
AddOtherDependency(def_node, node);
}
}
}
}
// Define-use dependencies. for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) {
AddDataDependency(GetNode(use.GetUser()), instruction_node);
}
// Scheduling barrier dependencies.
DCHECK_IMPLIES(is_scheduling_barrier, contains_scheduling_barrier_); if (contains_scheduling_barrier_) { // A barrier depends on instructions after it. And instructions before the // barrier depend on it. for (HInstruction* other = instruction->GetNext(); other != nullptr; other = other->GetNext()) {
SchedulingNode* other_node = GetNode(other);
CHECK(other_node != nullptr)
<< other->DebugName()
<< " is in block " << other->GetBlock()->GetBlockId()
<< ", and expected in block " << instruction->GetBlock()->GetBlockId(); bool other_is_barrier = other_node->IsSchedulingBarrier(); if (is_scheduling_barrier || other_is_barrier) {
AddOtherDependency(other_node, instruction_node);
} if (other_is_barrier) { // This other scheduling barrier guarantees ordering of instructions after // it, so avoid creating additional useless dependencies in the graph. // For example if we have // instr_1 // barrier_2 // instr_3 // barrier_4 // instr_5 // we only create the following non-data dependencies // 1 -> 2 // 2 -> 3 // 2 -> 4 // 3 -> 4 // 4 -> 5 // and do not create // 1 -> 4 // 2 -> 5 // Note that in this example we could also avoid creating the dependency // `2 -> 4`. But if we remove `instr_3` that dependency is required to // order the barriers. So we generate it to avoid a special case. break;
}
}
}
// Side effect dependencies. if (!instruction->GetSideEffects().DoesNothing() || instruction->CanThrow()) {
HInstruction* dep_chain_candidate = nullptr; for (HInstruction* other = instruction->GetNext(); other != nullptr; other = other->GetNext()) {
SchedulingNode* other_node = GetNode(other); if (other_node->IsSchedulingBarrier()) { // We have reached a scheduling barrier so we can stop further // processing. // // As a "other" dependency is not set up if a data dependency exists, we need to check that // one of them must exist.
DCHECK(other_node->HasOtherDependency(instruction_node)
|| other_node->HasDataDependency(instruction_node)); break;
} if (side_effect_dependency_analysis_.HasSideEffectDependency(other, instruction)) { if (dep_chain_candidate != nullptr &&
side_effect_dependency_analysis_.HasSideEffectDependency(other, dep_chain_candidate)) { // Skip an explicit dependency to reduce memory usage, rely on the transitive dependency.
} else {
AddOtherDependency(other_node, instruction_node);
} // Check if `other` is a better candidate which more likely will have other instructions // depending on it. if (dep_chain_candidate == nullptr ||
IsBetterCandidateWithMoreLikelyDependencies(other, dep_chain_candidate)) {
dep_chain_candidate = other;
}
}
}
}
// Environment dependencies. // We do not need to process those if the instruction is a scheduling barrier, // since the barrier already has non-data dependencies on all following // instructions. if (!is_scheduling_barrier) { for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) { // Note that here we could stop processing if the environment holder is // across a scheduling barrier. But checking this would likely require // more work than simply iterating through environment uses.
AddOtherDependency(GetNode(use.GetUser()->GetHolder()), instruction_node);
}
}
// Ideally we would reuse the graph visualizer code, but it is not available // from here and it is not worth moving all that code only for our use. staticvoid DumpAsDotNode(std::ostream& output, const SchedulingNode* node) { const HInstruction* instruction = node->GetInstruction(); // Use the instruction typed id as the node identifier.
std::string instruction_id = InstructionTypeId(instruction);
output << instruction_id << "[shape=record, label=\""
<< instruction_id << ' ' << instruction->DebugName() << " ["; // List the instruction's inputs in its description. When visualizing the // graph this helps differentiating data inputs from other dependencies. constchar* seperator = ""; for (const HInstruction* input : instruction->GetInputs()) {
output << seperator << InstructionTypeId(input);
seperator = ",";
}
output << "]"; // Other properties of the node.
output << "\\ninternal_latency: " << node->GetInternalLatency();
output << "\\ncritical_path: " << node->GetCriticalPath(); if (node->IsSchedulingBarrier()) {
output << "\\n(barrier)";
}
output << "\"];\n"; // We want program order to go from top to bottom in the graph output, so we // reverse the edges and specify `dir=back`. for (const SchedulingNode* predecessor : node->GetDataPredecessors()) { const HInstruction* predecessor_instruction = predecessor->GetInstruction();
output << InstructionTypeId(predecessor_instruction) << ":s -> " << instruction_id << ":n "
<< "[label=\"" << predecessor->GetLatency() << "\",dir=back]\n";
} for (const SchedulingNode* predecessor : node->GetOtherPredecessors()) { const HInstruction* predecessor_instruction = predecessor->GetInstruction();
output << InstructionTypeId(predecessor_instruction) << ":s -> " << instruction_id << ":n "
<< "[dir=back,color=blue]\n";
}
}
void SchedulingGraph::DumpAsDotGraph(const std::string& description, const ScopedArenaVector<SchedulingNode*>& initial_candidates) { // TODO(xueliang): ideally we should move scheduling information into HInstruction, after that // we should move this dotty graph dump feature to visualizer, and have a compiler option for it.
std::ofstream output("scheduling_graphs.dot", std::ofstream::out | std::ofstream::app); // Description of this graph, as a comment.
output << "// " << description << "\n"; // Start the dot graph. Use an increasing index for easier differentiation.
output << "digraph G {\n"; for (constauto& entry : nodes_map_) {
SchedulingNode* node = entry.second.get();
DumpAsDotNode(output, node);
} // Create a fake 'end_of_scheduling' node to help visualization of critical_paths. for (SchedulingNode* node : initial_candidates) { const HInstruction* instruction = node->GetInstruction();
output << InstructionTypeId(instruction) << ":s -> end_of_scheduling:n "
<< "[label=\"" << node->GetLatency() << "\",dir=back]\n";
} // End of the dot graph.
output << "}\n";
output.close();
}
SchedulingNode* CriticalPathSchedulingNodeSelector::SelectMaterializedCondition(
ScopedArenaVector<SchedulingNode*>* nodes, const SchedulingGraph& graph) const { // Schedule condition inputs that can be materialized immediately before their use. // In following example, after we've scheduled HSelect, we want LessThan to be scheduled // immediately, because it is a materialized condition, and will be emitted right before HSelect // in codegen phase. // // i20 HLessThan [...] HLessThan HAdd HAdd // i21 HAdd [...] ===> | | | // i22 HAdd [...] +----------+---------+ // i23 HSelect [i21, i22, i20] HSelect
if ((condition_node != nullptr) &&
condition->HasOnlyOneNonEnvironmentUse() &&
ContainsElement(*nodes, condition_node)) {
DCHECK(!condition_node->HasUnscheduledSuccessors()); // Remove the condition from the list of candidates and schedule it.
RemoveElement(*nodes, condition_node); return condition_node;
}
// Optimize for materialized condition and its emit before use scenario.
select_node = SelectMaterializedCondition(nodes, graph);
if (select_node == nullptr) { // Get highest priority node based on critical path information.
select_node = (*nodes)[0];
size_t select = 0; for (size_t i = 1, e = nodes->size(); i < e; i++) {
SchedulingNode* check = (*nodes)[i];
SchedulingNode* candidate = (*nodes)[select];
select_node = GetHigherPrioritySchedulingNode(candidate, check); if (select_node == check) {
select = i;
}
}
DeleteNodeAtIndex(nodes, select);
}
prev_select_ = select_node; return select_node;
}
SchedulingNode* CriticalPathSchedulingNodeSelector::GetHigherPrioritySchedulingNode(
SchedulingNode* candidate, SchedulingNode* check) const {
uint32_t candidate_path = candidate->GetCriticalPath();
uint32_t check_path = check->GetCriticalPath(); // First look at the critical_path. if (check_path != candidate_path) { return check_path < candidate_path ? check : candidate;
} // If both critical paths are equal, schedule instructions with a higher latency // first in program order. return check->GetLatency() < candidate->GetLatency() ? check : candidate;
}
void HScheduler::Schedule(HGraph* graph) { // We run lsa here instead of in a separate pass to better control whether we // should run the analysis or not. const HeapLocationCollector* heap_location_collector = nullptr;
ScopedArenaAllocator allocator(graph->GetArenaStack());
LoadStoreAnalysis lsa(graph, /*stats=*/nullptr, &allocator); if (!only_optimize_loop_blocks_ || graph->HasLoops()) {
lsa.Run();
heap_location_collector = &lsa.GetHeapLocationCollector();
}
for (HBasicBlock* block : graph->GetReversePostOrder()) { if (IsSchedulable(block)) {
Schedule(block, heap_location_collector);
}
}
}
// The list of candidates for scheduling. A node becomes a candidate when all // its predecessors have been scheduled.
ScopedArenaVector<SchedulingNode*> candidates(allocator.Adapter(kArenaAllocScheduler));
// Find the initial candidates for scheduling. for (SchedulingNode* node : scheduling_nodes) { if (!node->HasUnscheduledSuccessors()) {
node->MaybeUpdateCriticalPath(node->GetLatency());
candidates.push_back(node);
}
}
ScopedArenaVector<SchedulingNode*> initial_candidates(allocator.Adapter(kArenaAllocScheduler)); if (kDumpDotSchedulingGraphs) { // Remember the list of initial candidates for debug output purposes.
initial_candidates.assign(candidates.begin(), candidates.end());
}
// Schedule all nodes.
selector_->Reset(); while (!candidates.empty()) {
SchedulingNode* node = selector_->PopHighestPriorityNode(&candidates, scheduling_graph);
Schedule(node, &candidates);
}
if (kDumpDotSchedulingGraphs) { // Dump the graph in `dot` format.
HGraph* graph = block->GetGraph();
std::stringstream description;
description << graph->GetDexFile().PrettyMethod(graph->GetMethodIdx())
<< " B" << block->GetBlockId();
scheduling_graph.DumpAsDotGraph(description.str(), initial_candidates);
}
}
void HScheduler::Schedule(SchedulingNode* scheduling_node, /*inout*/ ScopedArenaVector<SchedulingNode*>* candidates) { // Check whether any of the node's predecessors will be valid candidates after // this node is scheduled.
uint32_t path_to_node = scheduling_node->GetCriticalPath(); for (SchedulingNode* predecessor : scheduling_node->GetDataPredecessors()) {
predecessor->MaybeUpdateCriticalPath(
path_to_node + predecessor->GetInternalLatency() + predecessor->GetLatency());
predecessor->DecrementNumberOfUnscheduledSuccessors(); if (!predecessor->HasUnscheduledSuccessors()) {
candidates->push_back(predecessor);
}
} for (SchedulingNode* predecessor : scheduling_node->GetOtherPredecessors()) { // Do not update the critical path. // The 'other' (so 'non-data') dependencies (usually) do not represent a // 'material' dependency of nodes on others. They exist for program // correctness. So we do not use them to compute the critical path.
predecessor->DecrementNumberOfUnscheduledSuccessors(); if (!predecessor->HasUnscheduledSuccessors()) {
candidates->push_back(predecessor);
}
}
Schedule(scheduling_node->GetInstruction());
}
// Move an instruction after cursor instruction inside one basic block. staticvoid MoveAfterInBlock(HInstruction* instruction, HInstruction* cursor) {
DCHECK_EQ(instruction->GetBlock(), cursor->GetBlock());
DCHECK_NE(cursor, cursor->GetBlock()->GetLastInstruction());
DCHECK(!instruction->IsControlFlow());
DCHECK(!cursor->IsControlFlow());
instruction->MoveBefore(cursor->GetNext(), /* do_checks= */ false);
}
bool HScheduler::IsSchedulable(const HInstruction* instruction) const { // We want to avoid exhaustively listing all instructions, so we first check // for instruction categories that we know are safe. if (instruction->IsControlFlow() ||
instruction->IsConstant()) { returntrue;
} // Currently all unary and binary operations are safe to schedule, so avoid // checking for each of them individually. // Since nothing prevents a new scheduling-unsafe HInstruction to subclass // HUnaryOperation (or HBinaryOperation), check in debug mode that we have // the exhaustive lists here. if (instruction->IsUnaryOperation()) {
DCHECK(instruction->IsAbs() ||
instruction->IsBooleanNot() ||
instruction->IsNot() ||
instruction->IsNeg()) << "unexpected instruction " << instruction->DebugName(); returntrue;
} if (instruction->IsBinaryOperation()) {
DCHECK(instruction->IsAdd() ||
instruction->IsAnd() ||
instruction->IsCompare() ||
instruction->IsCondition() ||
instruction->IsDiv() ||
instruction->IsMin() ||
instruction->IsMax() ||
instruction->IsMul() ||
instruction->IsOr() ||
instruction->IsRem() ||
instruction->IsRor() ||
instruction->IsShl() ||
instruction->IsShr() ||
instruction->IsSub() ||
instruction->IsUShr() ||
instruction->IsXor()) << "unexpected instruction " << instruction->DebugName(); returntrue;
} // The scheduler should not see any of these.
DCHECK(!instruction->IsParallelMove()) << "unexpected instruction " << instruction->DebugName(); // List of instructions explicitly excluded: // HClearException // HClinitCheck // HDeoptimize // HLoadClass // HLoadException // HMemoryBarrier // HMonitorOperation // HNop // HThrow // HTryBoundary // All unresolved field access instructions // All volatile field access instructions, e.g. HInstanceFieldGet // TODO: Some of the instructions above may be safe to schedule (maybe as // scheduling barriers). return instruction->IsArrayGet() ||
instruction->IsArraySet() ||
instruction->IsArrayLength() ||
instruction->IsBoundType() ||
instruction->IsBoundsCheck() ||
instruction->IsCheckCast() ||
instruction->IsClassTableGet() ||
instruction->IsCurrentMethod() ||
instruction->IsDivZeroCheck() ||
(instruction->IsInstanceFieldGet() && !instruction->AsInstanceFieldGet()->IsVolatile()) ||
(instruction->IsInstanceFieldSet() && !instruction->AsInstanceFieldSet()->IsVolatile()) ||
instruction->IsInstanceOf() ||
instruction->IsInvokeInterface() ||
instruction->IsInvokeStaticOrDirect() ||
instruction->IsInvokeUnresolved() ||
instruction->IsInvokeVirtual() ||
instruction->IsLoadString() ||
instruction->IsNewArray() ||
instruction->IsNewInstance() ||
instruction->IsNullCheck() ||
instruction->IsPackedSwitch() ||
instruction->IsParameterValue() ||
instruction->IsPhi() ||
instruction->IsReturn() ||
instruction->IsReturnVoid() ||
instruction->IsSelect() ||
(instruction->IsStaticFieldGet() && !instruction->AsStaticFieldGet()->IsVolatile()) ||
(instruction->IsStaticFieldSet() && !instruction->AsStaticFieldSet()->IsVolatile()) ||
instruction->IsSuspendCheck() ||
instruction->IsTypeConversion();
}
bool HScheduler::IsSchedulable(const HBasicBlock* block) const { // We may be only interested in loop blocks. if (only_optimize_loop_blocks_ && !block->IsInLoop()) { returnfalse;
} if (block->GetTryCatchInformation() != nullptr) { // Do not schedule blocks that are part of try-catch. // Because scheduler cannot see if catch block has assumptions on the instruction order in // the try block. In following example, if we enable scheduler for the try block, // MulitiplyAccumulate may be scheduled before DivZeroCheck, // which can result in an incorrect value in the catch block. // try { // a = a/b; // DivZeroCheck // // Div // c = c*d+e; // MulitiplyAccumulate // } catch {System.out.print(c); } returnfalse;
} // Check whether all instructions in this block are schedulable. for (HInstructionIteratorPrefetchNext it(block->GetInstructions()); !it.Done(); it.Advance()) { if (!IsSchedulable(it.Current())) { returnfalse;
}
} returntrue;
}
bool HInstructionScheduling::Run(bool only_optimize_loop_blocks, bool schedule_randomly) { #ifdefined(ART_ENABLE_CODEGEN_arm64) || defined(ART_ENABLE_CODEGEN_arm) // Phase-local allocator that allocates scheduler internal data structures like // scheduling nodes, internel nodes map, dependencies, etc.
CriticalPathSchedulingNodeSelector critical_path_selector; // Do not create the `RandomSchedulingNodeSelector` if not requested. // The construction is expensive, including a call to `srand()`.
std::optional<RandomSchedulingNodeSelector> random_selector;
SchedulingNodeSelector* selector = &critical_path_selector; if (schedule_randomly) {
random_selector.emplace();
selector = &random_selector.value();
} #else // Avoid compilation error when compiling for unsupported instruction set.
UNUSED(only_optimize_loop_blocks);
UNUSED(schedule_randomly);
UNUSED(codegen_); #endif
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.