| // 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 "source/opt/loop_dependence.h" | 
 |  | 
 | #include <ostream> | 
 | #include <set> | 
 | #include <string> | 
 | #include <unordered_set> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "source/opt/basic_block.h" | 
 | #include "source/opt/instruction.h" | 
 | #include "source/opt/scalar_analysis.h" | 
 | #include "source/opt/scalar_analysis_nodes.h" | 
 |  | 
 | namespace spvtools { | 
 | namespace opt { | 
 |  | 
 | bool LoopDependenceAnalysis::IsZIV( | 
 |     const std::pair<SENode*, SENode*>& subscript_pair) { | 
 |   return CountInductionVariables(subscript_pair.first, subscript_pair.second) == | 
 |          0; | 
 | } | 
 |  | 
 | bool LoopDependenceAnalysis::IsSIV( | 
 |     const std::pair<SENode*, SENode*>& subscript_pair) { | 
 |   return CountInductionVariables(subscript_pair.first, subscript_pair.second) == | 
 |          1; | 
 | } | 
 |  | 
 | bool LoopDependenceAnalysis::IsMIV( | 
 |     const std::pair<SENode*, SENode*>& subscript_pair) { | 
 |   return CountInductionVariables(subscript_pair.first, subscript_pair.second) > | 
 |          1; | 
 | } | 
 |  | 
 | SENode* LoopDependenceAnalysis::GetLowerBound(const Loop* loop) { | 
 |   Instruction* cond_inst = loop->GetConditionInst(); | 
 |   if (!cond_inst) { | 
 |     return nullptr; | 
 |   } | 
 |   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 Loop* loop) { | 
 |   Instruction* cond_inst = loop->GetConditionInst(); | 
 |   if (!cond_inst) { | 
 |     return nullptr; | 
 |   } | 
 |   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 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 Loop* LoopDependenceAnalysis::GetLoopForSubscriptPair( | 
 |     const std::pair<SENode*, SENode*>& subscript_pair) { | 
 |   // Collect all the SERecurrentNodes. | 
 |   std::vector<SERecurrentNode*> source_nodes = | 
 |       std::get<0>(subscript_pair)->CollectRecurrentNodes(); | 
 |   std::vector<SERecurrentNode*> destination_nodes = | 
 |       std::get<1>(subscript_pair)->CollectRecurrentNodes(); | 
 |  | 
 |   // Collect all the loops stored by the SERecurrentNodes. | 
 |   std::unordered_set<const Loop*> 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 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<SENode*, SENode*>& subscript_pair, | 
 |     DistanceVector* distance_vector) { | 
 |   const Loop* loop = GetLoopForSubscriptPair(subscript_pair); | 
 |  | 
 |   return GetDistanceEntryForLoop(loop, distance_vector); | 
 | } | 
 |  | 
 | SENode* LoopDependenceAnalysis::GetTripCount(const Loop* loop) { | 
 |   BasicBlock* condition_block = loop->FindConditionBlock(); | 
 |   if (!condition_block) { | 
 |     return nullptr; | 
 |   } | 
 |   Instruction* induction_instr = loop->FindConditionVariable(condition_block); | 
 |   if (!induction_instr) { | 
 |     return nullptr; | 
 |   } | 
 |   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<int64_t>(iteration_count)); | 
 |     } | 
 |   } | 
 |  | 
 |   return nullptr; | 
 | } | 
 |  | 
 | SENode* LoopDependenceAnalysis::GetFirstTripInductionNode(const Loop* loop) { | 
 |   BasicBlock* condition_block = loop->FindConditionBlock(); | 
 |   if (!condition_block) { | 
 |     return nullptr; | 
 |   } | 
 |   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 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<const Loop*> LoopDependenceAnalysis::CollectLoops( | 
 |     const std::vector<SERecurrentNode*>& 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<const Loop*> 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<SERecurrentNode*> 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<const Loop*> loops = CollectLoops(recurrent_nodes); | 
 |  | 
 |   return static_cast<int64_t>(loops.size()); | 
 | } | 
 |  | 
 | std::set<const Loop*> LoopDependenceAnalysis::CollectLoops( | 
 |     SENode* source, SENode* destination) { | 
 |   if (!source || !destination) { | 
 |     return std::set<const Loop*>{}; | 
 |   } | 
 |  | 
 |   std::vector<SERecurrentNode*> source_nodes = source->CollectRecurrentNodes(); | 
 |   std::vector<SERecurrentNode*> destination_nodes = | 
 |       destination->CollectRecurrentNodes(); | 
 |  | 
 |   std::set<const Loop*> loops = CollectLoops(source_nodes); | 
 |   std::set<const Loop*> 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<const Loop*> loops = CollectLoops(source, destination); | 
 |  | 
 |   return static_cast<int64_t>(loops.size()); | 
 | } | 
 |  | 
 | Instruction* LoopDependenceAnalysis::GetOperandDefinition( | 
 |     const Instruction* instruction, int id) { | 
 |   return context_->get_def_use_mgr()->GetDef( | 
 |       instruction->GetSingleWordInOperand(id)); | 
 | } | 
 |  | 
 | std::vector<Instruction*> LoopDependenceAnalysis::GetSubscripts( | 
 |     const Instruction* instruction) { | 
 |   Instruction* access_chain = GetOperandDefinition(instruction, 0); | 
 |  | 
 |   std::vector<Instruction*> subscripts; | 
 |  | 
 |   for (auto i = 1u; i < access_chain->NumInOperandWords(); ++i) { | 
 |     subscripts.push_back(GetOperandDefinition(access_chain, i)); | 
 |   } | 
 |  | 
 |   return subscripts; | 
 | } | 
 |  | 
 | SENode* LoopDependenceAnalysis::GetConstantTerm(const 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<const Loop*> loops) { | 
 |   for (auto loop : loops) { | 
 |     if (!IsSupportedLoop(loop)) { | 
 |       return false; | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | void LoopDependenceAnalysis::MarkUnsusedDistanceEntriesAsIrrelevant( | 
 |     const Instruction* source, const Instruction* destination, | 
 |     DistanceVector* distance_vector) { | 
 |   std::vector<Instruction*> source_subscripts = GetSubscripts(source); | 
 |   std::vector<Instruction*> destination_subscripts = GetSubscripts(destination); | 
 |  | 
 |   std::set<const Loop*> used_loops{}; | 
 |  | 
 |   for (Instruction* source_inst : source_subscripts) { | 
 |     SENode* source_node = scalar_evolution_.SimplifyExpression( | 
 |         scalar_evolution_.AnalyzeInstruction(source_inst)); | 
 |     std::vector<SERecurrentNode*> recurrent_nodes = | 
 |         source_node->CollectRecurrentNodes(); | 
 |     for (SERecurrentNode* recurrent_node : recurrent_nodes) { | 
 |       used_loops.insert(recurrent_node->GetLoop()); | 
 |     } | 
 |   } | 
 |  | 
 |   for (Instruction* destination_inst : destination_subscripts) { | 
 |     SENode* destination_node = scalar_evolution_.SimplifyExpression( | 
 |         scalar_evolution_.AnalyzeInstruction(destination_inst)); | 
 |     std::vector<SERecurrentNode*> 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 Loop* loop) { | 
 |   std::vector<Instruction*> inductions{}; | 
 |   loop->GetInductionVariables(inductions); | 
 |   if (inductions.size() != 1) { | 
 |     return false; | 
 |   } | 
 |   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 |