diff --git a/jlm/llvm/opt/ScalarEvolution.cpp b/jlm/llvm/opt/ScalarEvolution.cpp index a6c0b04a6..46b35a6e8 100644 --- a/jlm/llvm/opt/ScalarEvolution.cpp +++ b/jlm/llvm/opt/ScalarEvolution.cpp @@ -3,15 +3,17 @@ * See COPYING for terms of redistribution. */ +#include #include +#include +#include +#include #include #include -#include #include #include #include -#include #include namespace jlm::llvm @@ -78,15 +80,16 @@ class ScalarEvolution::Context final ChrecMap_.insert_or_assign(&output, SCEV::CloneAs(*chrec)); } - std::unordered_map> - GetChrecMap() const + const std::unordered_map> & + GetChrecMap() const noexcept { - std::unordered_map> mapCopy{}; - for (auto & [output, chrec] : ChrecMap_) - { - mapCopy.emplace(output, SCEV::CloneAs(*chrec)); - } - return mapCopy; + return ChrecMap_; + } + + const std::unordered_map> & + GetSCEVMap() const noexcept + { + return SCEVMap_; } int @@ -171,7 +174,12 @@ ScalarEvolution::~ScalarEvolution() noexcept = default; std::unordered_map> ScalarEvolution::GetChrecMap() const { - return Context_->GetChrecMap(); + std::unordered_map> mapCopy{}; + for (auto & [output, chrec] : Context_->GetChrecMap()) + { + mapCopy.emplace(output, SCEV::CloneAs(*chrec)); + } + return mapCopy; } void @@ -220,10 +228,6 @@ ScalarEvolution::AnalyzeRegion(const rvsdg::Region & region) } } -/** - * Goes through all chain recurrences stored in the context (across different loops), and - * stitches them together wherever possible. - */ void ScalarEvolution::CombineChrecsAcrossLoops() { @@ -231,34 +235,34 @@ ScalarEvolution::CombineChrecsAcrossLoops() do { changed = false; + + std::vector>> pending; for (const auto & [output, chrec] : Context_->GetChrecMap()) { if (auto newSCEV = TryReplaceInitForSCEV(*chrec)) { - // Check if the result is actually a chrec - if (dynamic_cast(newSCEV->get())) - { - Context_->InsertChrec(*output, SCEV::CloneAs(**newSCEV)); - } - else - { - // The transformation produced a non-chrec SCEV (n-ary expression), store it in the SCEV - // map instead - Context_->InsertSCEV(*output, std::move(*newSCEV)); - } + pending.emplace_back(output, std::move(*newSCEV)); changed = true; } } + + for (auto & [output, scev] : pending) + { + // Check if the result is actually a chrec + if (auto * chrec = dynamic_cast(scev.get())) + { + Context_->InsertChrec(*output, SCEV::CloneAs(*chrec)); + } + else + { + // The transformation produced a non-chrec SCEV (n-ary expression), store it in the SCEV + // map instead + Context_->InsertSCEV(*output, std::move(scev)); + } + } } while (changed); } -/** - * Recursively traverses chain recurrences to find Init nodes that can be replaced with their (now - * computed) corresponding chain recurrences and replaces them - * - * @param scev The SCEV expression to be traversed - * @return The resulting recurrence, or std::nullopt if no change was made - */ std::optional> ScalarEvolution::TryReplaceInitForSCEV(const SCEV & scev) { @@ -337,30 +341,27 @@ ScalarEvolution::PerformSCEVAnalysis(const rvsdg::ThetaNode & thetaNode) for (const auto loopVar : nonStateLoopVars) { const auto post = loopVar.post; + // We compute the SCEV for each non-state loop variable in a recursive bottom up fashion, + // starting at the post's origin auto scev = GetOrCreateSCEVForOutput(*post->origin()); Context_->InsertSCEV(*loopVar.output, scev); // Save the SCEV at the theta outputs as well } - auto dependencyGraph = CreateDependencyGraph(nonStateLoopVars); - util::HashSet validIVs{}; - for (const auto loopVar : nonStateLoopVars) + auto dependencyGraph = CreateDependencyGraph(thetaNode); + + util::HashSet validOutputs{}; + for (const auto & [output, deps] : dependencyGraph) { - if (dependencyGraph[loopVar.pre].size() == 0) - { - // If the expression doesn't depend on at least one loop variable (including itself), it is - // not an induction variable. Replace it with a SCEVUnknown - Context_->InsertSCEV(*loopVar.post->origin(), SCEVUnknown::Create()); - } - else if (IsValidInductionVariable(*loopVar.pre, dependencyGraph)) - validIVs.insert(loopVar.pre); + if (CanCreateChainRecurrence(*output, dependencyGraph)) + validOutputs.insert(output); } - // Filter the dependency graph to only contain the IVs that are valid and update dependencies - // accordingly + // Filter the dependency graph to only contain the outputs of the SCEVs that are valid chain + // recurrences and update dependencies accordingly auto filteredDependencyGraph = dependencyGraph; for (auto it = filteredDependencyGraph.begin(); it != filteredDependencyGraph.end();) { - if (!validIVs.Contains(it->first)) + if (!validOutputs.Contains(it->first)) { for (auto & [node, deps] : filteredDependencyGraph) deps.erase(it->first); @@ -372,35 +373,34 @@ ScalarEvolution::PerformSCEVAnalysis(const rvsdg::ThetaNode & thetaNode) const auto order = TopologicalSort(filteredDependencyGraph); - std::vector allVars{}; - // Add valid IVs to the set (in the correct order) - for (const auto & indVarPre : order) - allVars.push_back(indVarPre); - for (const auto loopVar : nonStateLoopVars) + for (const auto output : order) { - if (std::find(order.begin(), order.end(), loopVar.pre) == order.end()) - // This is not a valid IV, so it hasn't been added yet - allVars.push_back(loopVar.pre); - } - - // Process valid induction variables - for (auto loopVarPre : order) - { - const auto loopVar = thetaNode.MapPreLoopVar(*loopVarPre); - const auto loopVarPost = loopVar.post; - const auto scev = Context_->TryGetSCEVForOutput(*loopVarPost->origin()); + std::unique_ptr scev{}; + if (const auto theta = rvsdg::TryGetRegionParentNode(*output); + &thetaNode == theta) + { + // For loop variables, we need to retrieve and use the SCEV saved at the post's origin, + // equivalent to a "backedge" which describes how the value at the pre pointer is updated + const auto & newOutput = *thetaNode.MapPreLoopVar(*output).post->origin(); + scev = Context_->TryGetSCEVForOutput(newOutput); + } + else + scev = Context_->TryGetSCEVForOutput(*output); JLM_ASSERT(scev); - Context_->InsertChrec(*loopVarPre, GetOrCreateChainRecurrence(*loopVarPre, *scev, thetaNode)); + + auto chrec = GetOrCreateChainRecurrence(*output, *scev, thetaNode); + Context_->InsertChrec(*output, chrec); } - // Handle invalid induction variables - for (size_t i = order.size(); i < allVars.size(); ++i) + for (const auto & [output, scev] : Context_->GetSCEVMap()) { - const auto loopVarPre = allVars[i]; - auto unknownChainRecurrence = SCEVChainRecurrence::Create(thetaNode); - unknownChainRecurrence->AddOperand(SCEVUnknown::Create()); - Context_->InsertChrec(*loopVarPre, unknownChainRecurrence); + if (std::find(order.begin(), order.end(), output) == order.end()) + { + auto unknownChainRecurrence = SCEVChainRecurrence::Create(thetaNode); + unknownChainRecurrence->AddOperand(SCEVUnknown::Create()); + Context_->InsertChrec(*output, unknownChainRecurrence); + } } } @@ -419,6 +419,42 @@ ScalarEvolution::GetOrCreateSCEVForOutput(const rvsdg::Output & output) } if (const auto simpleNode = rvsdg::TryGetOwnerNode(output)) { + if (rvsdg::is(simpleNode->GetOperation())) + { + const auto addressInputOrigin = LoadOperation::AddressInput(*simpleNode).origin(); + result = SCEVLoad::Create(GetOrCreateSCEVForOutput(*addressInputOrigin)); + } + if (rvsdg::is(simpleNode->GetOperation())) + { + const auto barredInputOrigin = IOBarrierOperation::BarredInput(*simpleNode).origin(); + result = GetOrCreateSCEVForOutput(*barredInputOrigin); + } + if (rvsdg::is(simpleNode->GetOperation())) + { + JLM_ASSERT(simpleNode->ninputs() == 1); + result = GetOrCreateSCEVForOutput(*simpleNode->input(0)->origin()); + } + if (rvsdg::is(simpleNode->GetOperation())) + { + JLM_ASSERT(simpleNode->ninputs() >= 2); + const auto baseIndex = simpleNode->input(0)->origin(); + JLM_ASSERT(is(baseIndex->Type())); + + const auto gepOp = dynamic_cast(&simpleNode->GetOperation()); + const auto & pointeeType = gepOp->GetPointeeType(); + + auto baseScev = GetOrCreateSCEVForOutput(*baseIndex); + + auto baseOffsetIndex = GetOrCreateSCEVForOutput(*simpleNode->input(1)->origin()); + + const auto wholeTypeSize = GetTypeAllocSize(pointeeType); + std::unique_ptr offset = + SCEVMulExpr::Create(std::move(baseOffsetIndex), SCEVConstant::Create(wholeTypeSize)); + if (auto innerOffset = ComputeSCEVForGepInnerOffset(*simpleNode, 2, pointeeType)) + offset = SCEVAddExpr::Create(std::move(offset), std::move(innerOffset)); + + result = SCEVAddExpr::Create(std::move(baseScev), std::move(offset)); + } if (rvsdg::is(simpleNode->GetOperation())) { const auto constOp = @@ -461,6 +497,55 @@ ScalarEvolution::GetOrCreateSCEVForOutput(const rvsdg::Output & output) return result; } +std::unique_ptr +ScalarEvolution::ComputeSCEVForGepInnerOffset( + const rvsdg::SimpleNode & gepNode, + const size_t inputIndex, + const rvsdg::Type & type) +{ + JLM_ASSERT(inputIndex >= 2); + + if (inputIndex >= gepNode.ninputs()) + { + return nullptr; + } + + const auto gepInput = gepNode.input(inputIndex); + if (const auto arrayType = dynamic_cast(&type)) + { + const auto & elementType = *arrayType->GetElementType(); + auto offset = SCEVMulExpr::Create( + GetOrCreateSCEVForOutput(*gepInput->origin()), + SCEVConstant::Create(GetTypeAllocSize(elementType))); + + auto subOffset = ComputeSCEVForGepInnerOffset(gepNode, inputIndex + 1, elementType); + + if (!subOffset) + return offset; + + return SCEVAddExpr::Create(std::move(offset), std::move(subOffset)); + } + if (const auto structType = dynamic_cast(&type)) + { + const auto indexingValue = tryGetConstantSignedInteger(*gepInput->origin()); + + if (!indexingValue.has_value()) + return nullptr; + + const auto & fieldType = structType->getElementType(*indexingValue); + + auto offset = SCEVConstant::Create(structType->GetFieldOffset(*indexingValue)); + + auto subOffset = ComputeSCEVForGepInnerOffset(gepNode, inputIndex + 1, *fieldType); + + if (!subOffset) + return offset; + + return SCEVAddExpr::Create(std::move(offset), std::move(subOffset)); + } + throw std::logic_error("Unknown GEP type!"); +} + void ScalarEvolution::FindDependenciesForSCEV( const SCEV & scev, @@ -496,31 +581,35 @@ ScalarEvolution::FindDependenciesForSCEV( } } -ScalarEvolution::IVDependencyGraph -ScalarEvolution::CreateDependencyGraph( - const std::vector & loopVars) const +ScalarEvolution::DependencyGraph +ScalarEvolution::CreateDependencyGraph(const rvsdg::ThetaNode & thetaNode) const { - IVDependencyGraph graph{}; - for (const auto loopVar : loopVars) + DependencyGraph graph{}; + + for (const auto & [output, scev] : Context_->GetSCEVMap()) { - const auto post = loopVar.post; - if (const auto scev = Context_->TryGetSCEVForOutput(*post->origin())) + DependencyMap dependencies{}; + if (const auto theta = rvsdg::TryGetRegionParentNode(*output); + theta == &thetaNode) { - DependencyMap dependencies{}; + // We know this is a pre pointer, so we map it to loop var and use the SCEV for the + // post's origin (backedge) instead + const auto loopVar = theta->MapPreLoopVar(*output); + auto newScev = Context_->TryGetSCEVForOutput(*loopVar.post->origin()); + FindDependenciesForSCEV(*newScev.get(), dependencies); + } + else FindDependenciesForSCEV(*scev.get(), dependencies); - const auto pre = loopVar.pre; - graph[pre] = dependencies; - } + graph[output] = dependencies; } - return graph; } // Implementation of Kahn's algorithm for topological sort std::vector -ScalarEvolution::TopologicalSort(const IVDependencyGraph & dependencyGraph) +ScalarEvolution::TopologicalSort(const DependencyGraph & dependencyGraph) { const size_t numVertices = dependencyGraph.size(); std::unordered_map indegree(numVertices); @@ -623,6 +712,11 @@ ScalarEvolution::GetOrCreateStepForSCEV( chrec->AddOperand(scevConstant->Clone()); return chrec; } + if (dynamic_cast(&scevTree)) + { + // The load operation relies on memory, which we treat as opaque + chrec->AddOperand(SCEVUnknown::Create()); + } if (const auto scevPlaceholder = dynamic_cast(&scevTree)) { if (scevPlaceholder->GetPrePointer() == &output) @@ -658,11 +752,6 @@ ScalarEvolution::GetOrCreateStepForSCEV( return chrec; } -/** - * \brief Try to combine the constants in an n-ary expression (Add or Mul) into themselves. - * @param expression The expression to be folded - * @return The unique ptr to the expression - */ std::unique_ptr ScalarEvolution::FoldNAryExpression(SCEVNAryExpr & expression) { @@ -715,12 +804,6 @@ ScalarEvolution::FoldNAryExpression(SCEVNAryExpr & expression) return expression.Clone(); } -/** - * \brief Apply folding rules for addition to combine two SCEV operands into one. - * @param lhsOperand The left-hand side operand of the add operation - * @param rhsOperand The right-hand side operand of the add operation - * @return A unique ptr to the new operand - */ std::unique_ptr ScalarEvolution::ApplyAddFolding(const SCEV * lhsOperand, const SCEV * rhsOperand) { @@ -738,10 +821,10 @@ ScalarEvolution::ApplyAddFolding(const SCEV * lhsOperand, const SCEV * rhsOperan // For constants and unknowns this is trivial, however it becomes a bit complicated when we // factor in SCEVInit nodes. These nodes represent the initial value of an IV in the case where // the exact value is unknown at compile time. E.g. function argument or result from a - // call-instruction. In the cases where we have to fold one or more of these init-nodes, we create - // an n-ary add expression (add expression with an arbitrary number of operands), and add this to - // the chrec. Folding two of these n-ary add expressions will result in another n-ary add - // expression, which consists of all the operands in both the left and the right expression. + // call-instruction. In the cases where we have to fold one or more of these init-nodes, we + // create an n-ary add expression (add expression with an arbitrary number of operands), and add + // this to the chrec. Folding two of these n-ary add expressions will result in another n-ary + // add expression, which consists of all the operands in both the left and the right expression. // The if-chain below goes through each of the possible combinations of lhs and rhs values if (const auto *lhsUnknown = dynamic_cast(lhsOperand), @@ -826,8 +909,8 @@ ScalarEvolution::ApplyAddFolding(const SCEV * lhsOperand, const SCEV * rhsOperan const auto rhsNAryAddExpr = dynamic_cast(rhsOperand); if ((lhsNAryMulExpr && rhsNAryAddExpr) || (rhsNAryMulExpr && lhsNAryAddExpr)) { - // Multiply expression with add expression - Clone the add expression and add the multiply as a - // term + // Multiply expression with add expression - Clone the add expression and add the multiply as + // a term const auto * mulExpr = lhsNAryMulExpr ? lhsNAryMulExpr : rhsNAryMulExpr; auto * addExpr = lhsNAryAddExpr ? lhsNAryAddExpr : rhsNAryAddExpr; auto newAddExpr = SCEV::CloneAs(*addExpr); @@ -940,12 +1023,6 @@ ScalarEvolution::ApplyAddFolding(const SCEV * lhsOperand, const SCEV * rhsOperan return SCEVUnknown::Create(); } -/** - * \brief Apply folding rules for multiplication to combine two SCEV operands into one. - * @param lhsOperand The left-hand side operand of the mul operation - * @param rhsOperand The right-hand side operand of the mul operation - * @return A unique ptr to the new operand - */ std::unique_ptr ScalarEvolution::ApplyMulFolding(const SCEV * lhsOperand, const SCEV * rhsOperand) { @@ -1218,18 +1295,20 @@ ScalarEvolution::IsUnknown(const SCEVChainRecurrence & chrec) } bool -ScalarEvolution::IsValidInductionVariable( - const rvsdg::Output & variable, - IVDependencyGraph & dependencyGraph) +ScalarEvolution::CanCreateChainRecurrence( + const rvsdg::Output & output, + DependencyGraph & dependencyGraph) { - // First check that variable has only one self-reference, - if (dependencyGraph[&variable][&variable].count != 1) - return false; + auto deps = dependencyGraph[&output]; + if (deps.find(&output) != deps.end()) + if (deps[&output].count != 1) + { + // First check that variable has only one self-reference + return false; + } // Check that it has no reference via a mult-operation - // (results in a geometric sequence - which we treat as an invalid induction variable) - auto deps = dependencyGraph[&variable]; - for (auto [output, dependencyInfo] : deps) + for (auto [out, dependencyInfo] : deps) { if (dependencyInfo.operation == DependencyOp::Mul) { @@ -1240,41 +1319,41 @@ ScalarEvolution::IsValidInductionVariable( // Then check for cycles through other variables std::unordered_set visited{}; std::unordered_set recursionStack{}; - return !HasCycleThroughOthers(variable, variable, dependencyGraph, visited, recursionStack); + return !HasCycleThroughOthers(output, output, dependencyGraph, visited, recursionStack); } bool ScalarEvolution::HasCycleThroughOthers( - const rvsdg::Output & currentIV, - const rvsdg::Output & originalIV, - IVDependencyGraph & dependencyGraph, + const rvsdg::Output & currentOutput, + const rvsdg::Output & originalOutput, + DependencyGraph & dependencyGraph, std::unordered_set & visited, std::unordered_set & recursionStack) { - visited.insert(¤tIV); - recursionStack.insert(¤tIV); + visited.insert(¤tOutput); + recursionStack.insert(¤tOutput); - for (const auto & [depPtr, depCount] : dependencyGraph[¤tIV]) + for (const auto & [depPtr, depCount] : dependencyGraph[¤tOutput]) { // Ignore self-references - if (depPtr == ¤tIV) + if (depPtr == ¤tOutput) continue; // Found a cycle back to the ORIGINAL node we started from - // This means the original IV is explicitly part of the cycle - if (depPtr == &originalIV) + // This means the original output is explicitly part of the cycle + if (depPtr == &originalOutput) return true; - // Already explored this branch, no cycle containing the original IV + // Already explored this branch, no cycle containing the original output if (visited.find(depPtr) != visited.end()) continue; // Recursively check dependencies, keeping track of the original node - if (HasCycleThroughOthers(*depPtr, originalIV, dependencyGraph, visited, recursionStack)) + if (HasCycleThroughOthers(*depPtr, originalOutput, dependencyGraph, visited, recursionStack)) return true; } - recursionStack.erase(¤tIV); + recursionStack.erase(¤tOutput); return false; } diff --git a/jlm/llvm/opt/ScalarEvolution.hpp b/jlm/llvm/opt/ScalarEvolution.hpp index dcc72430a..f0d2e14a8 100644 --- a/jlm/llvm/opt/ScalarEvolution.hpp +++ b/jlm/llvm/opt/ScalarEvolution.hpp @@ -9,8 +9,6 @@ #include #include -#include - namespace jlm::llvm { class SCEV @@ -99,6 +97,43 @@ class SCEVInit final : public SCEV const rvsdg::Output * PrePointer_; }; +class SCEVLoad final : public SCEV +{ +public: + explicit SCEVLoad(std::unique_ptr scev) + : AddressSCEV_{ std::move(scev) } + {} + + std::string + DebugString() const override + { + std::ostringstream oss; + oss << "Load" << "(" << AddressSCEV_->DebugString() << ")"; + return oss.str(); + } + + std::unique_ptr + Clone() const override + { + return std::make_unique(AddressSCEV_->Clone()); + } + + static std::unique_ptr + Create(std::unique_ptr scev) + { + return std::make_unique(scev->Clone()); + } + + SCEV * + GetAddressSCEV() const + { + return AddressSCEV_.get(); + } + +private: + std::unique_ptr AddressSCEV_; +}; + class SCEVPlaceholder final : public SCEV { public: @@ -533,7 +568,7 @@ class ScalarEvolution final : public jlm::rvsdg::Transformation typedef std::unordered_map DependencyMap; - typedef std::unordered_map IVDependencyGraph; + typedef std::unordered_map DependencyGraph; ~ScalarEvolution() noexcept override; @@ -549,6 +584,10 @@ class ScalarEvolution final : public jlm::rvsdg::Transformation ScalarEvolution & operator=(ScalarEvolution &&) = delete; + /** + * Returns a copy of the chrec map containing the computed chain recurrences after running + * the analysis. + */ std::unordered_map> GetChrecMap() const; @@ -558,6 +597,10 @@ class ScalarEvolution final : public jlm::rvsdg::Transformation void AnalyzeRegion(const rvsdg::Region & region); + /** + * Goes through all chain recurrences stored in the context (across different loops), and + * stitches them together wherever possible. + */ void CombineChrecsAcrossLoops(); @@ -571,18 +614,31 @@ class ScalarEvolution final : public jlm::rvsdg::Transformation std::unique_ptr GetOrCreateSCEVForOutput(const rvsdg::Output & output); - IVDependencyGraph - CreateDependencyGraph(const std::vector & loopVars) const; + DependencyGraph + CreateDependencyGraph(const rvsdg::ThetaNode & thetaNode) const; static void FindDependenciesForSCEV(const SCEV & scev, DependencyMap & dependencies, DependencyOp op); static std::vector - TopologicalSort(const IVDependencyGraph & dependencyGraph); + TopologicalSort(const DependencyGraph & dependencyGraph); void PerformSCEVAnalysis(const rvsdg::ThetaNode & thetaNode); + std::unique_ptr + ComputeSCEVForGepInnerOffset( + const rvsdg::SimpleNode & gepNode, + size_t inputIndex, + const rvsdg::Type & type); + + /** + * Recursively traverses chain recurrences to find Init nodes that can be replaced with their (now + * computed) corresponding chain recurrences and replaces them + * + * @param scev The SCEV expression to be traversed + * @return The resulting recurrence, or std::nullopt if no change was made + */ std::optional> TryReplaceInitForSCEV(const SCEV & scev); @@ -598,17 +654,49 @@ class ScalarEvolution final : public jlm::rvsdg::Transformation const SCEV & scevTree, const rvsdg::ThetaNode & thetaNode); + /** + * \brief Apply folding rules for addition to combine two SCEV operands into one. + * @param lhsOperand The left-hand side operand of the add operation + * @param rhsOperand The right-hand side operand of the add operation + * @return A unique ptr to the new operand + */ static std::unique_ptr ApplyAddFolding(const SCEV * lhsOperand, const SCEV * rhsOperand); + /** + * \brief Apply folding rules for multiplication to combine two SCEV operands into one. + * @param lhsOperand The left-hand side operand of the mul operation + * @param rhsOperand The right-hand side operand of the mul operation + * @return A unique ptr to the new operand + */ static std::unique_ptr ApplyMulFolding(const SCEV * lhsOperand, const SCEV * rhsOperand); + /** + * \brief Try to combine the constants in an n-ary expression (Add or Mul) into themselves. + * @param expression The expression to be folded + * @return The unique ptr to the expression + */ static std::unique_ptr FoldNAryExpression(SCEVNAryExpr & expression); + /** + * Checks the dependencies of the input variable to determine if we can create a chain recurrence + * using it's SCEV. + * + * The requirements are: + * - No more than one self-dependency (indicates a self-dependent variable) + * - No cyclic dependencies (A depends on B and B depends on A) + * - No dependencies via multiplication (results in a geometric update sequence, which we treat as + * an invalid induction variable) + * + * @param output The output to check. + * @param dependencyGraph The dependency graph which stores the dependencies of all outputs in the + * loop. + * @return True if the requirements are fulfilled, false otherwise. + */ static bool - IsValidInductionVariable(const rvsdg::Output & variable, IVDependencyGraph & dependencyGraph); + CanCreateChainRecurrence(const rvsdg::Output & output, DependencyGraph & dependencyGraph); /** * Checks the operands of the given \p chrec to see if any of them are unknown. @@ -621,9 +709,9 @@ class ScalarEvolution final : public jlm::rvsdg::Transformation static bool HasCycleThroughOthers( - const rvsdg::Output & currentIV, - const rvsdg::Output & originalIV, - IVDependencyGraph & dependencyGraph, + const rvsdg::Output & currentOutput, + const rvsdg::Output & originalOutput, + DependencyGraph & dependencyGraph, std::unordered_set & visited, std::unordered_set & recursionStack); diff --git a/tests/jlm/llvm/opt/ScalarEvolutionTests.cpp b/tests/jlm/llvm/opt/ScalarEvolutionTests.cpp index 66a159e3c..9277f04c2 100644 --- a/tests/jlm/llvm/opt/ScalarEvolutionTests.cpp +++ b/tests/jlm/llvm/opt/ScalarEvolutionTests.cpp @@ -3,12 +3,15 @@ * See COPYING for terms of redistribution. */ +#include +#include #include #include +#include +#include +#include #include #include -#include -#include #include #include @@ -23,7 +26,7 @@ static std:: return scalarEvolution.GetChrecMap(); } -TEST(ScalarEvolutionTests, ConstantInductionVariable) +TEST(ScalarEvolutionTests, NonEvolvingVariable) { using namespace jlm::llvm; @@ -45,16 +48,13 @@ TEST(ScalarEvolutionTests, ConstantInductionVariable) jlm::rvsdg::view(graph, stdout); // Act - const auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 1u); - // Since lv1 is not a valid induction variable, it's chain recurrence should be - // {Unknown} - auto testChrec = SCEVChainRecurrence(*theta); - testChrec.AddOperand(SCEVUnknown::Create()); - EXPECT_TRUE(ScalarEvolution::StructurallyEqual(testChrec, *chrecMap.at(lv1.pre))); + // Since lv1 is a variable which does not depend on the previous value (no evolution). There + // should be no computed chrec for it + EXPECT_EQ(chrecMap.find(lv1.pre), chrecMap.end()); } TEST(ScalarEvolutionTests, SimpleInductionVariable) @@ -89,10 +89,9 @@ TEST(ScalarEvolutionTests, SimpleInductionVariable) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 2u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -149,10 +148,9 @@ TEST(ScalarEvolutionTests, RecursiveInductionVariable) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 2u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -205,10 +203,9 @@ TEST(ScalarEvolutionTests, PolynomialInductionVariable) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 2u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -264,10 +261,9 @@ TEST(ScalarEvolutionTests, ThirdDegreePolynomialInductionVariable) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 3u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv3.pre), chrecMap.end()); @@ -335,10 +331,9 @@ TEST(ScalarEvolutionTests, InductionVariableWithMultiplication) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 2u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -387,10 +382,9 @@ TEST(ScalarEvolutionTests, InvalidInductionVariableWithMultiplication) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 1u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); // lv1 is not an induction variable because of illegal mult operation @@ -439,10 +433,9 @@ TEST(ScalarEvolutionTests, PolynomialInductionVariableWithMultiplication) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 2u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -501,10 +494,9 @@ TEST(ScalarEvolutionTests, InvalidPolynomialInductionVariableWithMultiplication) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 2u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -552,10 +544,9 @@ TEST(ScalarEvolutionTests, InductionVariableWithSubtraction) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 1u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); // lv1 is a simple negative induction variable with the recurrence {10,+,-1} @@ -601,10 +592,9 @@ TEST(ScalarEvolutionTests, PolynomialInductionVariableWithSubtraction) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 2u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -679,10 +669,9 @@ TEST(ScalarEvolutionTests, InductionVariablesWithNonConstantInitialValues) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 4u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv3.pre), chrecMap.end()); @@ -787,10 +776,9 @@ TEST(ScalarEvolutionTests, InductionVariablesWithNonConstantInitialValuesAndMult jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 4u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv3.pre), chrecMap.end()); @@ -878,10 +866,9 @@ TEST(ScalarEvolutionTests, SelfRecursiveInductionVariable) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 1u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); // lv1 is not an induction variable because of self dependency. Should be {Unknown} @@ -925,10 +912,9 @@ TEST(ScalarEvolutionTests, DependentOnInvalidInductionVariable) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 2u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -980,10 +966,9 @@ TEST(ScalarEvolutionTests, MutuallyDependentInductionVariables) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 2u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -1039,10 +1024,9 @@ TEST(ScalarEvolutionTests, MultiLayeredMutuallyDependentInductionVariables) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 4u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv3.pre), chrecMap.end()); @@ -1062,6 +1046,7 @@ TEST(ScalarEvolutionTests, InductionVariablesInNestedLoops) // Tests "stitching" of induction variables in outer loops using namespace jlm::llvm; + // Arrange const auto intType = jlm::rvsdg::BitType::Create(32); LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", ""); @@ -1102,10 +1087,9 @@ TEST(ScalarEvolutionTests, InductionVariablesInNestedLoops) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 3u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -1133,6 +1117,7 @@ TEST(ScalarEvolutionTests, InductionVariablesInNestedLoopsWithFolding) { using namespace jlm::llvm; + // Arrange const auto intType = jlm::rvsdg::BitType::Create(32); LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", ""); @@ -1205,10 +1190,9 @@ TEST(ScalarEvolutionTests, InductionVariablesInNestedLoopsWithFolding) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 8u); EXPECT_NE(chrecMap.find(lv1_1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2_1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv3_1.pre), chrecMap.end()); @@ -1289,6 +1273,7 @@ TEST(ScalarEvolutionTests, InductionVariablesInSisterLoops) // loops" using namespace jlm::llvm; + // Arrange const auto intType = jlm::rvsdg::BitType::Create(32); LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", ""); @@ -1330,10 +1315,9 @@ TEST(ScalarEvolutionTests, InductionVariablesInSisterLoops) jlm::rvsdg::view(graph, stdout); // Act - auto chrecMap = RunScalarEvolution(rvsdgModule); + const auto & chrecMap = RunScalarEvolution(rvsdgModule); // Assert - EXPECT_EQ(chrecMap.size(), 3u); EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); @@ -1360,3 +1344,237 @@ TEST(ScalarEvolutionTests, InductionVariablesInSisterLoops) lv3TestChrec.AddOperand(lv2InnerChrec.Clone()); EXPECT_TRUE(ScalarEvolution::StructurallyEqual(lv3TestChrec, *chrecMap.at(lv3.pre))); } + +TEST(ScalarEvolutionTests, ComputeRecurrenceForSimpleArrayGEP) +{ + using namespace jlm::llvm; + + // Arrange + const auto intType = jlm::rvsdg::BitType::Create(32); + const auto intArrayType = ArrayType::Create(intType, 5); + const auto pointerType = PointerType::Create(); + const auto memoryStateType = MemoryStateType::Create(); + + LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", ""); + const auto & graph = rvsdgModule.Rvsdg(); + + auto lambda = jlm::rvsdg::LambdaNode::Create( + graph.GetRootRegion(), + LlvmLambdaOperation::Create( + jlm::rvsdg::FunctionType::Create( + { pointerType, memoryStateType }, + { intType, memoryStateType }), + "f", + Linkage::externalLinkage)); + + const auto & delta = jlm::rvsdg::DeltaNode::Create( + &graph.GetRootRegion(), + DeltaOperation::Create(intArrayType, "", Linkage::externalLinkage, "", false)); + + const auto & arrayC1 = IntegerConstantOperation::Create(*delta->subregion(), 32, 1); + const auto & arrayC2 = IntegerConstantOperation::Create(*delta->subregion(), 32, 2); + const auto & arrayC3 = IntegerConstantOperation::Create(*delta->subregion(), 32, 3); + const auto & arrayC4 = IntegerConstantOperation::Create(*delta->subregion(), 32, 4); + const auto & arrayC5 = IntegerConstantOperation::Create(*delta->subregion(), 32, 5); + + const auto & constantArray = ConstantDataArray::Create({ arrayC1.output(0), + arrayC2.output(0), + arrayC3.output(0), + arrayC4.output(0), + arrayC5.output(0) }); + delta->finalize(constantArray); + + const auto cv1 = lambda->AddContextVar(delta->output()); + + const auto & c0_1 = IntegerConstantOperation::Create(*lambda->subregion(), 32, 0); + const auto & c1_1 = IntegerConstantOperation::Create(*lambda->subregion(), 32, 1); + const auto & theta = jlm::rvsdg::ThetaNode::create(lambda->subregion()); + + const auto memoryState = theta->AddLoopVar(lambda->GetFunctionArguments()[1]); + const auto lv1 = theta->AddLoopVar(c0_1.output(0)); // i + const auto lv2 = theta->AddLoopVar(c1_1.output(0)); // sum + const auto lv3 = theta->AddLoopVar(cv1.inner); // arr ptr + + const auto & c1_2 = IntegerConstantOperation::Create(*theta->subregion(), 32, 1); + + const auto & addNode1 = + jlm::rvsdg::CreateOpNode({ lv1.pre, c1_2.output(0) }, 32); + const auto res1 = addNode1.output(0); + + const auto & c0_2 = IntegerConstantOperation::Create(*theta->subregion(), 64, 0); + const auto & lv1SExt = SExtOperation::create(64, lv1.pre); + + const auto gep = GetElementPtrOperation::Create( + lv3.pre, + { c0_2.output(0), lv1SExt }, + intArrayType, + pointerType); + + auto loadedValue = LoadNonVolatileOperation::Create(gep, { memoryState.pre }, intType, 32)[0]; + + auto & addNode2 = jlm::rvsdg::CreateOpNode({ lv2.pre, loadedValue }, 32); + const auto res2 = addNode2.output(0); + + const auto & c4 = IntegerConstantOperation::Create(*theta->subregion(), 32, 4); + auto & sltNode = jlm::rvsdg::CreateOpNode({ res1, c4.output(0) }, 32); + const auto matchResult = + jlm::rvsdg::MatchOperation::Create(*sltNode.output(0), { { 1, 1 } }, 0, 2); + + theta->set_predicate(matchResult); + + lv1.post->divert_to(res1); + lv2.post->divert_to(res2); + + jlm::rvsdg::view(graph, stdout); + + // Act + const auto & chrecMap = RunScalarEvolution(rvsdgModule); + + // Assert + EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); + EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); + EXPECT_NE(chrecMap.find(lv3.pre), chrecMap.end()); + EXPECT_NE(chrecMap.find(gep), chrecMap.end()); + + // lv1 is a simple induction variable with the recurrence {0,+,1} + auto lv1TestChrec = SCEVChainRecurrence(*theta); + lv1TestChrec.AddOperand(SCEVConstant::Create(0)); + lv1TestChrec.AddOperand(SCEVConstant::Create(1)); + EXPECT_TRUE(ScalarEvolution::StructurallyEqual(lv1TestChrec, *chrecMap.at(lv1.pre))); + + // lv2 is reliant on the result from a LOAD operation, and should therefore be {1,+,Unknown} + auto lv2TestChrec = SCEVChainRecurrence(*theta); + lv2TestChrec.AddOperand(SCEVConstant::Create(1)); + lv2TestChrec.AddOperand(SCEVUnknown::Create()); + EXPECT_TRUE(ScalarEvolution::StructurallyEqual(lv2TestChrec, *chrecMap.at(lv2.pre))); + + // lv3 (base pointer in GEP) is unchanged, it's recurrence should be {Init(a3)} + auto lv3TestChrec = SCEVChainRecurrence(*theta); + lv3TestChrec.AddOperand(SCEVInit::Create(*lv3.pre)); + EXPECT_TRUE(ScalarEvolution::StructurallyEqual(lv3TestChrec, *chrecMap.at(lv3.pre))); + + // The GEP should have the following SCEV: (PH(a3) + ((0 * 20) + (PH(a1) * 4))) + // Replacing the placeholders gives us ({Init(a3)} + (0 * 20) + ({0,+,1} * 4)}) + // Folding constants together gives us ({Init(a3)} + {0,+,4}) = {Init(a3),+,4} which is the + // resulting recurrence + auto gepTestChrec = SCEVChainRecurrence(*theta); + gepTestChrec.AddOperand(SCEVInit::Create(*lv3.pre)); + gepTestChrec.AddOperand(SCEVConstant::Create(4)); + EXPECT_TRUE(ScalarEvolution::StructurallyEqual(gepTestChrec, *chrecMap.at(gep))); +} + +TEST(ScalarEvolutionTests, ComputeRecurrenceForSimpleStructGEP) +{ + using namespace jlm::llvm; + + // Arrange + const auto intType = jlm::rvsdg::BitType::Create(32); + const auto intStructType = + StructType::CreateLiteral({ intType, intType, intType, intType, intType }, false); + const auto pointerType = PointerType::Create(); + const auto memoryStateType = MemoryStateType::Create(); + + LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", ""); + const auto & graph = rvsdgModule.Rvsdg(); + + auto lambda = jlm::rvsdg::LambdaNode::Create( + graph.GetRootRegion(), + LlvmLambdaOperation::Create( + jlm::rvsdg::FunctionType::Create( + { pointerType, memoryStateType }, + { intType, memoryStateType }), + "f", + Linkage::externalLinkage)); + + const auto & delta = jlm::rvsdg::DeltaNode::Create( + &graph.GetRootRegion(), + DeltaOperation::Create(intStructType, "", Linkage::externalLinkage, "", false)); + + const auto & structC1 = IntegerConstantOperation::Create(*delta->subregion(), 32, 1); + const auto & structC2 = IntegerConstantOperation::Create(*delta->subregion(), 32, 2); + const auto & structC3 = IntegerConstantOperation::Create(*delta->subregion(), 32, 3); + const auto & structC4 = IntegerConstantOperation::Create(*delta->subregion(), 32, 4); + const auto & structC5 = IntegerConstantOperation::Create(*delta->subregion(), 32, 5); + + auto & constantStruct = ConstantStruct::Create( + *delta->subregion(), + { structC1.output(0), + structC2.output(0), + structC3.output(0), + structC4.output(0), + structC5.output(0) }, + intStructType); + delta->finalize(&constantStruct); + + const auto cv1 = lambda->AddContextVar(delta->output()); + + const auto & c0_1 = IntegerConstantOperation::Create(*lambda->subregion(), 32, 0); + const auto & c1_1 = IntegerConstantOperation::Create(*lambda->subregion(), 32, 1); + const auto & theta = jlm::rvsdg::ThetaNode::create(lambda->subregion()); + + const auto memoryState = theta->AddLoopVar(lambda->GetFunctionArguments()[1]); + const auto lv1 = theta->AddLoopVar(c0_1.output(0)); // i + const auto lv2 = theta->AddLoopVar(c1_1.output(0)); // sum + const auto lv3 = theta->AddLoopVar(cv1.inner); // struct ptr + + const auto & c1_2 = IntegerConstantOperation::Create(*theta->subregion(), 32, 1); + + const auto & addNode1 = + jlm::rvsdg::CreateOpNode({ lv1.pre, c1_2.output(0) }, 32); + const auto res1 = addNode1.output(0); + + const auto & lv1SExt = SExtOperation::create(64, lv1.pre); + const auto gep = GetElementPtrOperation::Create(lv3.pre, { lv1SExt }, intType, pointerType); + + auto loadedValue = LoadNonVolatileOperation::Create(gep, { memoryState.pre }, intType, 32)[0]; + + auto & addNode2 = jlm::rvsdg::CreateOpNode({ lv2.pre, loadedValue }, 32); + const auto res2 = addNode2.output(0); + + const auto & c4 = IntegerConstantOperation::Create(*theta->subregion(), 32, 4); + auto & sltNode = jlm::rvsdg::CreateOpNode({ res1, c4.output(0) }, 32); + const auto matchResult = + jlm::rvsdg::MatchOperation::Create(*sltNode.output(0), { { 1, 1 } }, 0, 2); + + theta->set_predicate(matchResult); + + lv1.post->divert_to(res1); + lv2.post->divert_to(res2); + + jlm::rvsdg::view(graph, stdout); + + // Act + const auto & chrecMap = RunScalarEvolution(rvsdgModule); + + // Assert + EXPECT_NE(chrecMap.find(lv1.pre), chrecMap.end()); + EXPECT_NE(chrecMap.find(lv2.pre), chrecMap.end()); + EXPECT_NE(chrecMap.find(lv3.pre), chrecMap.end()); + EXPECT_NE(chrecMap.find(gep), chrecMap.end()); + + // lv1 is a simple induction variable with the recurrence {0,+,1} + auto lv1TestChrec = SCEVChainRecurrence(*theta); + lv1TestChrec.AddOperand(SCEVConstant::Create(0)); + lv1TestChrec.AddOperand(SCEVConstant::Create(1)); + EXPECT_TRUE(ScalarEvolution::StructurallyEqual(lv1TestChrec, *chrecMap.at(lv1.pre))); + + // lv2 is reliant on the result from a LOAD operation, and should therefore be {1,+,Unknown} + auto lv2TestChrec = SCEVChainRecurrence(*theta); + lv2TestChrec.AddOperand(SCEVConstant::Create(1)); + lv2TestChrec.AddOperand(SCEVUnknown::Create()); + EXPECT_TRUE(ScalarEvolution::StructurallyEqual(lv2TestChrec, *chrecMap.at(lv2.pre))); + + // lv3 (base pointer in GEP) is unchanged, it's recurrence should be {Init(a3)} + auto lv3TestChrec = SCEVChainRecurrence(*theta); + lv3TestChrec.AddOperand(SCEVInit::Create(*lv3.pre)); + EXPECT_TRUE(ScalarEvolution::StructurallyEqual(lv3TestChrec, *chrecMap.at(lv3.pre))); + + // The GEP should have the following SCEV: (PH(a3) + (PH(a1) * 4)) + // Replacing the placeholders gives us ({Init(a3)} + ({0,+,1} * 4)) + // Folding constants together gives us ({Init(a3)} + {0,+,4}) = {Init(a3),+,4} which is the + // resulting recurrence + auto gepTestChrec = SCEVChainRecurrence(*theta); + gepTestChrec.AddOperand(SCEVInit::Create(*lv3.pre)); + gepTestChrec.AddOperand(SCEVConstant::Create(4)); + EXPECT_TRUE(ScalarEvolution::StructurallyEqual(gepTestChrec, *chrecMap.at(gep))); +}