// Copyright (c) 2018 Google LLC. // // 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 "opt/loop_dependence.h" #include #include #include #include #include #include #include "opt/basic_block.h" #include "opt/instruction.h" #include "opt/scalar_analysis.h" #include "opt/scalar_analysis_nodes.h" namespace spvtools { namespace opt { bool LoopDependenceAnalysis::IsZIV( const std::pair& subscript_pair) { return CountInductionVariables(subscript_pair.first, subscript_pair.second) == 0; } bool LoopDependenceAnalysis::IsSIV( const std::pair& subscript_pair) { return CountInductionVariables(subscript_pair.first, subscript_pair.second) == 1; } bool LoopDependenceAnalysis::IsMIV( const std::pair& subscript_pair) { return CountInductionVariables(subscript_pair.first, subscript_pair.second) > 1; } SENode* LoopDependenceAnalysis::GetLowerBound(const ir::Loop* loop) { ir::Instruction* cond_inst = loop->GetConditionInst(); if (!cond_inst) { return nullptr; } ir::Instruction* lower_inst = GetOperandDefinition(cond_inst, 0); switch (cond_inst->opcode()) { case SpvOpULessThan: case SpvOpSLessThan: case SpvOpULessThanEqual: case SpvOpSLessThanEqual: case SpvOpUGreaterThan: case SpvOpSGreaterThan: case SpvOpUGreaterThanEqual: case SpvOpSGreaterThanEqual: { // If we have a phi we are looking at the induction variable. We look // through the phi to the initial value of the phi upon entering the loop. if (lower_inst->opcode() == SpvOpPhi) { lower_inst = GetOperandDefinition(lower_inst, 0); // We don't handle looking through multiple phis. if (lower_inst->opcode() == SpvOpPhi) { return nullptr; } } return scalar_evolution_.SimplifyExpression( scalar_evolution_.AnalyzeInstruction(lower_inst)); } default: return nullptr; } } SENode* LoopDependenceAnalysis::GetUpperBound(const ir::Loop* loop) { ir::Instruction* cond_inst = loop->GetConditionInst(); if (!cond_inst) { return nullptr; } ir::Instruction* upper_inst = GetOperandDefinition(cond_inst, 1); switch (cond_inst->opcode()) { case SpvOpULessThan: case SpvOpSLessThan: { // When we have a < condition we must subtract 1 from the analyzed upper // instruction. SENode* upper_bound = scalar_evolution_.SimplifyExpression( scalar_evolution_.CreateSubtraction( scalar_evolution_.AnalyzeInstruction(upper_inst), scalar_evolution_.CreateConstant(1))); return upper_bound; } case SpvOpUGreaterThan: case SpvOpSGreaterThan: { // When we have a > condition we must add 1 to the analyzed upper // instruction. SENode* upper_bound = scalar_evolution_.SimplifyExpression(scalar_evolution_.CreateAddNode( scalar_evolution_.AnalyzeInstruction(upper_inst), scalar_evolution_.CreateConstant(1))); return upper_bound; } case SpvOpULessThanEqual: case SpvOpSLessThanEqual: case SpvOpUGreaterThanEqual: case SpvOpSGreaterThanEqual: { // We don't need to modify the results of analyzing when we have <= or >=. SENode* upper_bound = scalar_evolution_.SimplifyExpression( scalar_evolution_.AnalyzeInstruction(upper_inst)); return upper_bound; } default: return nullptr; } } bool LoopDependenceAnalysis::IsWithinBounds(int64_t value, int64_t bound_one, int64_t bound_two) { if (bound_one < bound_two) { // If |bound_one| is the lower bound. return (value >= bound_one && value <= bound_two); } else if (bound_one > bound_two) { // If |bound_two| is the lower bound. return (value >= bound_two && value <= bound_one); } else { // Both bounds have the same value. return value == bound_one; } } bool LoopDependenceAnalysis::IsProvablyOutsideOfLoopBounds( const ir::Loop* loop, SENode* distance, SENode* coefficient) { // We test to see if we can reduce the coefficient to an integral constant. SEConstantNode* coefficient_constant = coefficient->AsSEConstantNode(); if (!coefficient_constant) { PrintDebug( "IsProvablyOutsideOfLoopBounds could not reduce coefficient to a " "SEConstantNode so must exit."); return false; } SENode* lower_bound = GetLowerBound(loop); SENode* upper_bound = GetUpperBound(loop); if (!lower_bound || !upper_bound) { PrintDebug( "IsProvablyOutsideOfLoopBounds could not get both the lower and upper " "bounds so must exit."); return false; } // If the coefficient is positive we calculate bounds as upper - lower // If the coefficient is negative we calculate bounds as lower - upper SENode* bounds = nullptr; if (coefficient_constant->FoldToSingleValue() >= 0) { PrintDebug( "IsProvablyOutsideOfLoopBounds found coefficient >= 0.\n" "Using bounds as upper - lower."); bounds = scalar_evolution_.SimplifyExpression( scalar_evolution_.CreateSubtraction(upper_bound, lower_bound)); } else { PrintDebug( "IsProvablyOutsideOfLoopBounds found coefficient < 0.\n" "Using bounds as lower - upper."); bounds = scalar_evolution_.SimplifyExpression( scalar_evolution_.CreateSubtraction(lower_bound, upper_bound)); } // We can attempt to deal with symbolic cases by subtracting |distance| and // the bound nodes. If we can subtract, simplify and produce a SEConstantNode // we can produce some information. SEConstantNode* distance_minus_bounds = scalar_evolution_ .SimplifyExpression( scalar_evolution_.CreateSubtraction(distance, bounds)) ->AsSEConstantNode(); if (distance_minus_bounds) { PrintDebug( "IsProvablyOutsideOfLoopBounds found distance - bounds as a " "SEConstantNode with value " + ToString(distance_minus_bounds->FoldToSingleValue())); // If distance - bounds > 0 we prove the distance is outwith the loop // bounds. if (distance_minus_bounds->FoldToSingleValue() > 0) { PrintDebug( "IsProvablyOutsideOfLoopBounds found distance escaped the loop " "bounds."); return true; } } return false; } const ir::Loop* LoopDependenceAnalysis::GetLoopForSubscriptPair( const std::pair& subscript_pair) { // Collect all the SERecurrentNodes. std::vector source_nodes = std::get<0>(subscript_pair)->CollectRecurrentNodes(); std::vector destination_nodes = std::get<1>(subscript_pair)->CollectRecurrentNodes(); // Collect all the loops stored by the SERecurrentNodes. std::unordered_set loops{}; for (auto source_nodes_it = source_nodes.begin(); source_nodes_it != source_nodes.end(); ++source_nodes_it) { loops.insert((*source_nodes_it)->GetLoop()); } for (auto destination_nodes_it = destination_nodes.begin(); destination_nodes_it != destination_nodes.end(); ++destination_nodes_it) { loops.insert((*destination_nodes_it)->GetLoop()); } // If we didn't find 1 loop |subscript_pair| is a subscript over multiple or 0 // loops. We don't handle this so return nullptr. if (loops.size() != 1) { PrintDebug("GetLoopForSubscriptPair found loops.size() != 1."); return nullptr; } return *loops.begin(); } DistanceEntry* LoopDependenceAnalysis::GetDistanceEntryForLoop( const ir::Loop* loop, DistanceVector* distance_vector) { if (!loop) { return nullptr; } DistanceEntry* distance_entry = nullptr; for (size_t loop_index = 0; loop_index < loops_.size(); ++loop_index) { if (loop == loops_[loop_index]) { distance_entry = &(distance_vector->GetEntries()[loop_index]); break; } } return distance_entry; } DistanceEntry* LoopDependenceAnalysis::GetDistanceEntryForSubscriptPair( const std::pair& subscript_pair, DistanceVector* distance_vector) { const ir::Loop* loop = GetLoopForSubscriptPair(subscript_pair); return GetDistanceEntryForLoop(loop, distance_vector); } SENode* LoopDependenceAnalysis::GetTripCount(const ir::Loop* loop) { ir::BasicBlock* condition_block = loop->FindConditionBlock(); if (!condition_block) { return nullptr; } ir::Instruction* induction_instr = loop->FindConditionVariable(condition_block); if (!induction_instr) { return nullptr; } ir::Instruction* cond_instr = loop->GetConditionInst(); if (!cond_instr) { return nullptr; } size_t iteration_count = 0; // We have to check the instruction type here. If the condition instruction // isn't a supported type we can't calculate the trip count. if (loop->IsSupportedCondition(cond_instr->opcode())) { if (loop->FindNumberOfIterations(induction_instr, &*condition_block->tail(), &iteration_count)) { return scalar_evolution_.CreateConstant( static_cast(iteration_count)); } } return nullptr; } SENode* LoopDependenceAnalysis::GetFirstTripInductionNode( const ir::Loop* loop) { ir::BasicBlock* condition_block = loop->FindConditionBlock(); if (!condition_block) { return nullptr; } ir::Instruction* induction_instr = loop->FindConditionVariable(condition_block); if (!induction_instr) { return nullptr; } int64_t induction_initial_value = 0; if (!loop->GetInductionInitValue(induction_instr, &induction_initial_value)) { return nullptr; } SENode* induction_init_SENode = scalar_evolution_.SimplifyExpression( scalar_evolution_.CreateConstant(induction_initial_value)); return induction_init_SENode; } SENode* LoopDependenceAnalysis::GetFinalTripInductionNode( const ir::Loop* loop, SENode* induction_coefficient) { SENode* first_trip_induction_node = GetFirstTripInductionNode(loop); if (!first_trip_induction_node) { return nullptr; } // Get trip_count as GetTripCount - 1 // This is because the induction variable is not stepped on the first // iteration of the loop SENode* trip_count = scalar_evolution_.SimplifyExpression(scalar_evolution_.CreateSubtraction( GetTripCount(loop), scalar_evolution_.CreateConstant(1))); // Return first_trip_induction_node + trip_count * induction_coefficient return scalar_evolution_.SimplifyExpression(scalar_evolution_.CreateAddNode( first_trip_induction_node, scalar_evolution_.CreateMultiplyNode(trip_count, induction_coefficient))); } std::set LoopDependenceAnalysis::CollectLoops( const std::vector& recurrent_nodes) { // We don't handle loops with more than one induction variable. Therefore we // can identify the number of induction variables by collecting all of the // loops the collected recurrent nodes belong to. std::set loops{}; for (auto recurrent_nodes_it = recurrent_nodes.begin(); recurrent_nodes_it != recurrent_nodes.end(); ++recurrent_nodes_it) { loops.insert((*recurrent_nodes_it)->GetLoop()); } return loops; } int64_t LoopDependenceAnalysis::CountInductionVariables(SENode* node) { if (!node) { return -1; } std::vector recurrent_nodes = node->CollectRecurrentNodes(); // We don't handle loops with more than one induction variable. Therefore we // can identify the number of induction variables by collecting all of the // loops the collected recurrent nodes belong to. std::set loops = CollectLoops(recurrent_nodes); return static_cast(loops.size()); } std::set LoopDependenceAnalysis::CollectLoops( SENode* source, SENode* destination) { if (!source || !destination) { return {}; } std::vector source_nodes = source->CollectRecurrentNodes(); std::vector destination_nodes = destination->CollectRecurrentNodes(); std::set loops = CollectLoops(source_nodes); std::set destination_loops = CollectLoops(destination_nodes); loops.insert(std::begin(destination_loops), std::end(destination_loops)); return loops; } int64_t LoopDependenceAnalysis::CountInductionVariables(SENode* source, SENode* destination) { if (!source || !destination) { return -1; } std::set loops = CollectLoops(source, destination); return static_cast(loops.size()); } ir::Instruction* LoopDependenceAnalysis::GetOperandDefinition( const ir::Instruction* instruction, int id) { return context_->get_def_use_mgr()->GetDef( instruction->GetSingleWordInOperand(id)); } std::vector LoopDependenceAnalysis::GetSubscripts( const ir::Instruction* instruction) { ir::Instruction* access_chain = GetOperandDefinition(instruction, 0); std::vector subscripts; for (auto i = 1u; i < access_chain->NumInOperandWords(); ++i) { subscripts.push_back(GetOperandDefinition(access_chain, i)); } return subscripts; } SENode* LoopDependenceAnalysis::GetConstantTerm(const ir::Loop* loop, SERecurrentNode* induction) { SENode* offset = induction->GetOffset(); SENode* lower_bound = GetLowerBound(loop); if (!offset || !lower_bound) { return nullptr; } SENode* constant_term = scalar_evolution_.SimplifyExpression( scalar_evolution_.CreateSubtraction(offset, lower_bound)); return constant_term; } bool LoopDependenceAnalysis::CheckSupportedLoops( std::vector loops) { for (auto loop : loops) { if (!IsSupportedLoop(loop)) { return false; } } return true; } void LoopDependenceAnalysis::MarkUnsusedDistanceEntriesAsIrrelevant( const ir::Instruction* source, const ir::Instruction* destination, DistanceVector* distance_vector) { std::vector source_subscripts = GetSubscripts(source); std::vector destination_subscripts = GetSubscripts(destination); std::set used_loops{}; for (ir::Instruction* source_inst : source_subscripts) { SENode* source_node = scalar_evolution_.SimplifyExpression( scalar_evolution_.AnalyzeInstruction(source_inst)); std::vector recurrent_nodes = source_node->CollectRecurrentNodes(); for (SERecurrentNode* recurrent_node : recurrent_nodes) { used_loops.insert(recurrent_node->GetLoop()); } } for (ir::Instruction* destination_inst : destination_subscripts) { SENode* destination_node = scalar_evolution_.SimplifyExpression( scalar_evolution_.AnalyzeInstruction(destination_inst)); std::vector recurrent_nodes = destination_node->CollectRecurrentNodes(); for (SERecurrentNode* recurrent_node : recurrent_nodes) { used_loops.insert(recurrent_node->GetLoop()); } } for (size_t i = 0; i < loops_.size(); ++i) { if (used_loops.find(loops_[i]) == used_loops.end()) { distance_vector->GetEntries()[i].dependence_information = DistanceEntry::DependenceInformation::IRRELEVANT; } } } bool LoopDependenceAnalysis::IsSupportedLoop(const ir::Loop* loop) { std::vector inductions{}; loop->GetInductionVariables(inductions); if (inductions.size() != 1) { return false; } ir::Instruction* induction = inductions[0]; SENode* induction_node = scalar_evolution_.SimplifyExpression( scalar_evolution_.AnalyzeInstruction(induction)); if (!induction_node->AsSERecurrentNode()) { return false; } SENode* induction_step = induction_node->AsSERecurrentNode()->GetCoefficient(); if (!induction_step->AsSEConstantNode()) { return false; } if (!(induction_step->AsSEConstantNode()->FoldToSingleValue() == 1 || induction_step->AsSEConstantNode()->FoldToSingleValue() == -1)) { return false; } return true; } void LoopDependenceAnalysis::PrintDebug(std::string debug_msg) { if (debug_stream_) { (*debug_stream_) << debug_msg << "\n"; } } bool Constraint::operator==(const Constraint& other) const { // A distance of |d| is equivalent to a line |x - y = -d| if ((GetType() == ConstraintType::Distance && other.GetType() == ConstraintType::Line) || (GetType() == ConstraintType::Line && other.GetType() == ConstraintType::Distance)) { auto is_distance = AsDependenceLine() != nullptr; auto as_distance = is_distance ? AsDependenceDistance() : other.AsDependenceDistance(); auto distance = as_distance->GetDistance(); auto line = other.AsDependenceLine(); auto scalar_evolution = distance->GetParentAnalysis(); auto neg_distance = scalar_evolution->SimplifyExpression( scalar_evolution->CreateNegation(distance)); return *scalar_evolution->CreateConstant(1) == *line->GetA() && *scalar_evolution->CreateConstant(-1) == *line->GetB() && *neg_distance == *line->GetC(); } if (GetType() != other.GetType()) { return false; } if (AsDependenceDistance()) { return *AsDependenceDistance()->GetDistance() == *other.AsDependenceDistance()->GetDistance(); } if (AsDependenceLine()) { auto this_line = AsDependenceLine(); auto other_line = other.AsDependenceLine(); return *this_line->GetA() == *other_line->GetA() && *this_line->GetB() == *other_line->GetB() && *this_line->GetC() == *other_line->GetC(); } if (AsDependencePoint()) { auto this_point = AsDependencePoint(); auto other_point = other.AsDependencePoint(); return *this_point->GetSource() == *other_point->GetSource() && *this_point->GetDestination() == *other_point->GetDestination(); } return true; } bool Constraint::operator!=(const Constraint& other) const { return !(*this == other); } } // namespace opt } // namespace spvtools