From ab2cc7d8b886b61e80f70ac2727b7046d8973412 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Sat, 3 Jun 2023 11:37:38 +0200 Subject: [PATCH 01/13] Add worklist for Phase I in IDESolver --- .../DataFlow/IfdsIde/Solver/IDESolver.h | 163 ++++++++++-------- .../phasar/DataFlow/IfdsIde/Solver/PathEdge.h | 17 +- 2 files changed, 108 insertions(+), 72 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index 4564a2489b..a3b533d691 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -19,6 +19,8 @@ #include "phasar/Config/Configuration.h" #include "phasar/DB/ProjectIRDBBase.h" +#include "phasar/DataFlow/IfdsIde/EdgeFunction.h" +#include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" #include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" @@ -154,6 +156,16 @@ class IDESolver { START_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full); // We start our analysis and construct exploded supergraph submitInitialSeeds(); + + while (!WorkList.empty()) { + auto [Edge, EF] = std::move(WorkList.back()); + WorkList.pop_back(); + + auto [SourceVal, Target, TargetVal] = Edge.consume(); + propagate(std::move(SourceVal), std::move(Target), std::move(TargetVal), + std::move(EF)); + } + STOP_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full); if (SolverConfig.computeValues()) { START_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full); @@ -303,44 +315,6 @@ class IDESolver { } protected: - // have a shared point to allow for a copy constructor of IDESolver - IDETabulationProblem &IDEProblem; - d_t ZeroValue; - const i_t *ICF; - IFDSIDESolverConfig &SolverConfig; - unsigned PathEdgeCount = 0; - - FlowEdgeFunctionCache CachedFlowEdgeFunctions; - - Table> ComputedIntraPathEdges; - - Table> ComputedInterPathEdges; - - EdgeFunction AllTop; - - std::shared_ptr> JumpFn; - - std::map, std::vector>> - IntermediateEdgeFunctions; - - // stores summaries that were queried before they were computed - // see CC 2010 paper by Naeem, Lhotak and Rodriguez - Table>> EndsummaryTab; - - // edges going along calls - // see CC 2010 paper by Naeem, Lhotak and Rodriguez - Table> IncomingTab; - - // stores the return sites (inside callers) to which we have unbalanced - // returns if SolverConfig.followReturnPastSeeds is enabled - std::set UnbalancedRetSites; - - InitialSeeds Seeds; - - Table ValTab; - - std::map, size_t> FSummaryReuse; - /// Lines 13-20 of the algorithm; processing a call site in the caller's /// context. /// @@ -408,7 +382,9 @@ class IDESolver { DEBUG, "Queried Summary Edge Function: " << SumEdgFnE); PHASAR_LOG_LEVEL(DEBUG, "Compose: " << SumEdgFnE << " * " << f << '\n')); - propagate(d1, ReturnSiteN, d3, f.composeWith(SumEdgFnE), n, false); + WorkList.emplace_back(PathEdge(d1, ReturnSiteN, std::move(d3)), + f.composeWith(SumEdgFnE)); + // propagate(d1, ReturnSiteN, d3, f.composeWith(SumEdgFnE)); } } } else { @@ -435,10 +411,10 @@ class IDESolver { // create initial self-loop PHASAR_LOG_LEVEL(DEBUG, "Create initial self-loop with D: " << IDEProblem.DtoString(d3)); - propagate(d3, SP, d3, EdgeIdentity{}, n, - false); // line 15 - // register the fact that has an incoming edge from - // line 15.1 of Naeem/Lhotak/Rodriguez + WorkList.emplace_back(PathEdge(d3, SP, d3), EdgeIdentity{}); + // propagate(d3, SP, d3, EdgeIdentity{}); // line 15 + // register the fact that has an incoming edge from + // line 15.1 of Naeem/Lhotak/Rodriguez addIncoming(SP, d3, n, d2); // line 15.2, copy to avoid concurrent modification exceptions by // other threads @@ -506,8 +482,11 @@ class IDESolver { d_t d5_restoredCtx = restoreContextOnReturnedFact(n, d2, d5); // propagte the effects of the entire call PHASAR_LOG_LEVEL(DEBUG, "Compose: " << fPrime << " * " << f); - propagate(d1, RetSiteN, d5_restoredCtx, f.composeWith(fPrime), - n, false); + WorkList.emplace_back( + PathEdge(d1, RetSiteN, std::move(d5_restoredCtx)), + f.composeWith(fPrime)); + // propagate(d1, RetSiteN, d5_restoredCtx, + // f.composeWith(fPrime)); } } } @@ -541,7 +520,9 @@ class IDESolver { auto fPrime = f.composeWith(EdgeFnE); PHASAR_LOG_LEVEL(DEBUG, "Compose: " << EdgeFnE << " * " << f << " = " << fPrime); - propagate(d1, ReturnSiteN, d3, fPrime, n, false); + WorkList.emplace_back(PathEdge(d1, ReturnSiteN, std::move(d3)), + std::move(fPrime)); + // propagate(d1, ReturnSiteN, d3, fPrime); } } } @@ -550,15 +531,14 @@ class IDESolver { /// Simply propagate normal, intra-procedural flows. /// @param edge /// - virtual void processNormalFlow(const PathEdge Edge) { + virtual void processNormalFlow(PathEdge Edge) { PAMM_GET_INSTANCE; INC_COUNTER("Process Normal", 1, PAMM_SEVERITY_LEVEL::Full); PHASAR_LOG_LEVEL(DEBUG, "Process normal at target: " << IDEProblem.NtoString(Edge.getTarget())); - d_t d1 = Edge.factAtSource(); - n_t n = Edge.getTarget(); - d_t d2 = Edge.factAtTarget(); EdgeFunction f = jumpFunction(Edge); + auto [d1, n, d2] = Edge.consume(); + for (const auto nPrime : ICF->getSuccsOf(n)) { FlowFunctionPtrType FlowFunc = CachedFlowEdgeFunctions.getNormalFlowFunction(n, nPrime); @@ -579,7 +559,9 @@ class IDESolver { PHASAR_LOG_LEVEL(DEBUG, "Compose: " << g << " * " << f << " = " << fPrime); INC_COUNTER("EF Queries", 1, PAMM_SEVERITY_LEVEL::Full); - propagate(d1, nPrime, d3, fPrime, nullptr, false); + WorkList.emplace_back(PathEdge(d1, nPrime, std::move(d3)), + std::move(fPrime)); + // propagate(d1, nPrime, d3, fPrime); } } } @@ -703,7 +685,7 @@ class IDESolver { } // should be made a callable at some point - void pathEdgeProcessingTask(const PathEdge Edge) { + void pathEdgeProcessingTask(PathEdge Edge) { PAMM_GET_INSTANCE; INC_COUNTER("JumpFn Construction", 1, PAMM_SEVERITY_LEVEL::Full); IF_LOG_ENABLED( @@ -730,10 +712,10 @@ class IDESolver { processExit(Edge); } if (!ICF->getSuccsOf(Edge.getTarget()).empty()) { - processNormalFlow(Edge); + processNormalFlow(std::move(Edge)); } } else { - processCall(Edge); + processCall(std::move(Edge)); } } @@ -861,8 +843,10 @@ class IDESolver { if (!IDEProblem.isZeroValue(Fact)) { INC_COUNTER("Gen facts", 1, PAMM_SEVERITY_LEVEL::Core); } - propagate(Fact, StartPoint, Fact, EdgeIdentity{}, nullptr, false); - JumpFn->addFunction(Fact, StartPoint, Fact, EdgeIdentity{}); + WorkList.emplace_back(PathEdge(Fact, StartPoint, Fact), + EdgeIdentity{}); + // propagate(Fact, StartPoint, Fact, EdgeIdentity{}); + // JumpFn->addFunction(Fact, StartPoint, Fact, EdgeIdentity{}); } } } @@ -956,8 +940,11 @@ class IDESolver { d_t d3 = ValAndFunc.first; d_t d5_restoredCtx = restoreContextOnReturnedFact(c, d4, d5); PHASAR_LOG_LEVEL(DEBUG, "Compose: " << fPrime << " * " << f3); - propagate(d3, RetSiteC, d5_restoredCtx, - f3.composeWith(fPrime), c, false); + WorkList.emplace_back(PathEdge(std::move(d3), RetSiteC, + std::move(d5_restoredCtx)), + f3.composeWith(fPrime)); + // propagate(d3, RetSiteC, d5_restoredCtx, + // f3.composeWith(fPrime)); } } } @@ -1019,9 +1006,11 @@ class IDESolver { void propagteUnbalancedReturnFlow(n_t RetSiteC, d_t TargetVal, EdgeFunction EdgeFunc, - n_t RelatedCallSite) { - propagate(ZeroValue, RetSiteC, TargetVal, std::move(EdgeFunc), - RelatedCallSite, true); + n_t /*RelatedCallSite*/) { + WorkList.emplace_back( + PathEdge(ZeroValue, std::move(RetSiteC), std::move(TargetVal)), + std::move(EdgeFunc)); + // propagate(ZeroValue, RetSiteC, TargetVal, std::move(EdgeFunc)); } /// This method will be called for each incoming edge and can be used to @@ -1124,11 +1113,7 @@ class IDESolver { /// but may be useful for subclasses of {@link IDESolver}) /// void propagate(d_t SourceVal, n_t Target, d_t TargetVal, - const EdgeFunction &f, - /* deliberately exposed to clients */ - n_t /*RelatedCallSite*/, - /* deliberately exposed to clients */ - bool /*IsUnbalancedReturn*/) { + EdgeFunction f) { PHASAR_LOG_LEVEL(DEBUG, "Propagate flow"); PHASAR_LOG_LEVEL(DEBUG, "Source value : " << IDEProblem.DtoString(SourceVal)); @@ -1166,9 +1151,9 @@ class IDESolver { PHASAR_LOG_LEVEL(DEBUG, ' ')); if (NewFunction) { JumpFn->addFunction(SourceVal, Target, TargetVal, fPrime); - const PathEdge Edge(SourceVal, Target, TargetVal); + PathEdge Edge(SourceVal, Target, TargetVal); PathEdgeCount++; - pathEdgeProcessingTask(Edge); + pathEdgeProcessingTask(std::move(Edge)); IF_LOG_ENABLED(if (!IDEProblem.isZeroValue(TargetVal)) { PHASAR_LOG_LEVEL( @@ -1735,6 +1720,48 @@ class IDESolver { return StrIDLess(ICF->getStatementId(Lhs), ICF->getStatementId(Rhs)); } }; + + /// -- Data members + + IDETabulationProblem &IDEProblem; + d_t ZeroValue; + const i_t *ICF; + IFDSIDESolverConfig &SolverConfig; + + std::vector, EdgeFunction>> WorkList; + + size_t PathEdgeCount = 0; + + FlowEdgeFunctionCache CachedFlowEdgeFunctions; + + Table> ComputedIntraPathEdges; + + Table> ComputedInterPathEdges; + + EdgeFunction AllTop; + + std::shared_ptr> JumpFn; + + std::map, std::vector>> + IntermediateEdgeFunctions; + + // stores summaries that were queried before they were computed + // see CC 2010 paper by Naeem, Lhotak and Rodriguez + Table>> EndsummaryTab; + + // edges going along calls + // see CC 2010 paper by Naeem, Lhotak and Rodriguez + Table> IncomingTab; + + // stores the return sites (inside callers) to which we have unbalanced + // returns if SolverConfig.followReturnPastSeeds is enabled + std::set UnbalancedRetSites; + + InitialSeeds Seeds; + + Table ValTab; + + std::map, size_t> FSummaryReuse; }; template diff --git a/include/phasar/DataFlow/IfdsIde/Solver/PathEdge.h b/include/phasar/DataFlow/IfdsIde/Solver/PathEdge.h index d5bfffb60d..874d821846 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/PathEdge.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/PathEdge.h @@ -24,15 +24,24 @@ template class PathEdge { PathEdge(D DSource, N Target, D DTarget) noexcept(std::is_nothrow_move_constructible_v &&std::is_nothrow_move_constructible_v) - : Target(std::move(Target)), DSource(std::move(DSource)), + : DSource(std::move(DSource)), Target(std::move(Target)), DTarget(std::move(DTarget)) {} - [[nodiscard]] ByConstRef getTarget() const noexcept { return Target; } - [[nodiscard]] ByConstRef factAtSource() const noexcept { return DSource; } + [[nodiscard]] ByConstRef getTarget() const noexcept { return Target; } + [[nodiscard]] ByConstRef factAtTarget() const noexcept { return DTarget; } + [[nodiscard]] std::tuple, ByConstRef, ByConstRef> + get() const noexcept { + return {DSource, Target, DTarget}; + } + + [[nodiscard]] std::tuple consume() noexcept { + return {std::move(DSource), std::move(Target), std::move(DTarget)}; + } + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const PathEdge &Edge) { return OS << "<" << Edge.DSource << "> -> <" << Edge.Target << "," @@ -40,8 +49,8 @@ template class PathEdge { } private: - N Target; D DSource; + N Target; D DTarget; }; From f8e910f38a346b4e76ffecb4eebae7703fe6ef85 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Sat, 3 Jun 2023 11:50:12 +0200 Subject: [PATCH 02/13] Add worklist for Phase II in IDESolver --- .../DataFlow/IfdsIde/Solver/IDESolver.h | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index a3b533d691..12b73cad1d 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -619,7 +619,8 @@ class IDESolver { l_t LPrime = joinValueAt(NHashN, NHashD, ValNHash, L); if (!(LPrime == ValNHash)) { setVal(NHashN, NHashD, std::move(LPrime)); - valuePropagationTask(std::pair(NHashN, NHashD)); + ValuePropWL.emplace_back(std::move(NHashN), std::move(NHashD)); + // valuePropagationTask(std::pair(NHashN, NHashD)); } } @@ -720,7 +721,7 @@ class IDESolver { } // should be made a callable at some point - void valuePropagationTask(const std::pair NAndD) { + void valuePropagationTask(std::pair NAndD) { n_t n = NAndD.first; // our initial seeds are not necessarily method-start points but here they // should be treated as such the same also for unbalanced return sites in @@ -771,10 +772,7 @@ class IDESolver { DestVals.end()); } - /// Computes the final values for edge functions. - void computeValues() { - PHASAR_LOG_LEVEL(DEBUG, "Start computing values"); - // Phase II(i) + void submitInitialValues() { std::map> AllSeeds = Seeds.getSeeds(); for (n_t UnbalancedRetSite : UnbalancedRetSites) { if (AllSeeds.find(UnbalancedRetSite) == AllSeeds.end()) { @@ -793,9 +791,22 @@ class IDESolver { // information at the beginning of the value computation problem setVal(StartPoint, Fact, Value); std::pair SuperGraphNode(StartPoint, Fact); - valuePropagationTask(SuperGraphNode); + valuePropagationTask(std::move(SuperGraphNode)); } } + } + + /// Computes the final values for edge functions. + void computeValues() { + PHASAR_LOG_LEVEL(DEBUG, "Start computing values"); + // Phase II(i) + submitInitialValues(); + while (!ValuePropWL.empty()) { + auto NAndD = std::move(ValuePropWL.back()); + ValuePropWL.pop_back(); + valuePropagationTask(std::move(NAndD)); + } + // Phase II(ii) // we create an array of all nodes and then dispatch fractions of this // array to multiple threads @@ -1729,6 +1740,7 @@ class IDESolver { IFDSIDESolverConfig &SolverConfig; std::vector, EdgeFunction>> WorkList; + std::vector> ValuePropWL; size_t PathEdgeCount = 0; From 49b4b08023bbaff52a293b3d6619f4748a33e828 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Sun, 11 Jun 2023 18:55:11 +0200 Subject: [PATCH 03/13] Remove out-commented recursive calls in IDESolver --- .../phasar/DataFlow/IfdsIde/Solver/IDESolver.h | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index 12b73cad1d..5b717e8fb8 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -384,7 +384,6 @@ class IDESolver { << '\n')); WorkList.emplace_back(PathEdge(d1, ReturnSiteN, std::move(d3)), f.composeWith(SumEdgFnE)); - // propagate(d1, ReturnSiteN, d3, f.composeWith(SumEdgFnE)); } } } else { @@ -411,8 +410,8 @@ class IDESolver { // create initial self-loop PHASAR_LOG_LEVEL(DEBUG, "Create initial self-loop with D: " << IDEProblem.DtoString(d3)); - WorkList.emplace_back(PathEdge(d3, SP, d3), EdgeIdentity{}); - // propagate(d3, SP, d3, EdgeIdentity{}); // line 15 + WorkList.emplace_back(PathEdge(d3, SP, d3), + EdgeIdentity{}); // line 15 // register the fact that has an incoming edge from // line 15.1 of Naeem/Lhotak/Rodriguez addIncoming(SP, d3, n, d2); @@ -485,8 +484,6 @@ class IDESolver { WorkList.emplace_back( PathEdge(d1, RetSiteN, std::move(d5_restoredCtx)), f.composeWith(fPrime)); - // propagate(d1, RetSiteN, d5_restoredCtx, - // f.composeWith(fPrime)); } } } @@ -522,7 +519,6 @@ class IDESolver { << fPrime); WorkList.emplace_back(PathEdge(d1, ReturnSiteN, std::move(d3)), std::move(fPrime)); - // propagate(d1, ReturnSiteN, d3, fPrime); } } } @@ -561,7 +557,6 @@ class IDESolver { INC_COUNTER("EF Queries", 1, PAMM_SEVERITY_LEVEL::Full); WorkList.emplace_back(PathEdge(d1, nPrime, std::move(d3)), std::move(fPrime)); - // propagate(d1, nPrime, d3, fPrime); } } } @@ -620,7 +615,6 @@ class IDESolver { if (!(LPrime == ValNHash)) { setVal(NHashN, NHashD, std::move(LPrime)); ValuePropWL.emplace_back(std::move(NHashN), std::move(NHashD)); - // valuePropagationTask(std::pair(NHashN, NHashD)); } } @@ -856,8 +850,6 @@ class IDESolver { } WorkList.emplace_back(PathEdge(Fact, StartPoint, Fact), EdgeIdentity{}); - // propagate(Fact, StartPoint, Fact, EdgeIdentity{}); - // JumpFn->addFunction(Fact, StartPoint, Fact, EdgeIdentity{}); } } } @@ -954,8 +946,6 @@ class IDESolver { WorkList.emplace_back(PathEdge(std::move(d3), RetSiteC, std::move(d5_restoredCtx)), f3.composeWith(fPrime)); - // propagate(d3, RetSiteC, d5_restoredCtx, - // f3.composeWith(fPrime)); } } } @@ -1021,7 +1011,6 @@ class IDESolver { WorkList.emplace_back( PathEdge(ZeroValue, std::move(RetSiteC), std::move(TargetVal)), std::move(EdgeFunc)); - // propagate(ZeroValue, RetSiteC, TargetVal, std::move(EdgeFunc)); } /// This method will be called for each incoming edge and can be used to From 2965ef256b788594869183bf3216493b360ea0de Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Wed, 14 Jun 2023 19:47:06 +0200 Subject: [PATCH 04/13] Add IDE solving kind that allows interrupting the analysis or injecting code --- .../DataFlow/IfdsIde/Solver/IDESolver.h | 54 +++++- .../Solver/InteractiveIDESolverMixin.h | 156 ++++++++++++++++++ .../phasar/DataFlow/IfdsIde/SolverResults.h | 9 +- 3 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index 5b717e8fb8..21a9f422f0 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -27,6 +27,7 @@ #include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" #include "phasar/DataFlow/IfdsIde/InitialSeeds.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h" +#include "phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h" #include "phasar/DataFlow/IfdsIde/Solver/JumpFunctions.h" #include "phasar/DataFlow/IfdsIde/Solver/PathEdge.h" #include "phasar/DataFlow/IfdsIde/SolverResults.h" @@ -59,7 +60,10 @@ namespace psr { /// can then be queried by using resultAt() and resultsAt(). template > -class IDESolver { +class IDESolver + : public InteractiveIDESolverMixin> { + friend InteractiveIDESolverMixin>; + public: using ProblemTy = IDETabulationProblem; using container_type = typename ProblemTy::container_type; @@ -1711,6 +1715,7 @@ class IDESolver { OS << G; } +private: /// @brief: Allows less-than comparison based on the statement ID. struct StmtLess { const i_t *ICF; @@ -1721,6 +1726,53 @@ class IDESolver { } }; + /// -- InteractiveIDESolverMixin implementation + + bool doInitialize() { + PHASAR_LOG_LEVEL(INFO, "IDE solver is solving the specified problem"); + PHASAR_LOG_LEVEL(INFO, + "Submit initial seeds, construct exploded super graph"); + // We start our analysis and construct exploded supergraph + submitInitialSeeds(); + return !WorkList.empty(); + } + + bool doNext() { + assert(!WorkList.empty()); + auto [Edge, EF] = std::move(WorkList.back()); + WorkList.pop_back(); + + auto [SourceVal, Target, TargetVal] = Edge.consume(); + propagate(std::move(SourceVal), std::move(Target), std::move(TargetVal), + std::move(EF)); + + return !WorkList.empty(); + } + void finalizeInternal() { + if (SolverConfig.computeValues()) { + + // Computing the final values for the edge functions + PHASAR_LOG_LEVEL( + INFO, "Compute the final values according to the edge functions"); + computeValues(); + } + PHASAR_LOG_LEVEL(INFO, "Problem solved"); + if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::Core) { + computeAndPrintStatistics(); + } + if (SolverConfig.emitESG()) { + emitESGAsDot(); + } + } + SolverResults doFinalize() & { + finalizeInternal(); + return getSolverResults(); + } + OwningSolverResults doFinalize() && { + finalizeInternal(); + return consumeSolverResults(); + } + /// -- Data members IDETabulationProblem &IDEProblem; diff --git a/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h new file mode 100644 index 0000000000..aadbd52756 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h @@ -0,0 +1,156 @@ +/****************************************************************************** + * Copyright (c) 2023 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#ifndef PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H +#define PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H + +#include "phasar/Utils/Logger.h" + +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include +#include +#include +#include + +namespace psr { +template class InteractiveIDESolverMixin { +public: + [[nodiscard]] bool initialize() { return self().doInitialize(); } + [[nodiscard]] bool next() { return self().doNext(); } + [[nodiscard]] bool nextN(size_t MaxNumIterations) { + PHASAR_LOG_LEVEL(DEBUG, + "[nextN]: Next " << MaxNumIterations << " Iterations"); + + for (size_t I = 0, End = MaxNumIterations; I != End; ++I) { + if (!next()) { + PHASAR_LOG_LEVEL(DEBUG, "[nextN]: > done after " << I << " iterations"); + return false; + } + } + PHASAR_LOG_LEVEL(DEBUG, "[nextN]: > has next"); + return true; + } + decltype(auto) finalize() & { return self().doFinalize(); } + decltype(auto) finalize() && { return std::move(self()).doFinalize(); } + + /// Solves the analysis problen and periodically checks with Frequency whether + /// CancellationRequested evaluates to true. + /// + /// \returns True, iff the solving process was cancelled, else false. + template || + std::is_invocable_r_v>> + auto + solveUntil(CancellationRequest CancellationRequested, + std::chrono::milliseconds Frequency = std::chrono::seconds{1}) & { + return solveUntilImpl(self(), std::move(CancellationRequested), Frequency); + } + + /// Solves the analysis problen and periodically checks with Frequency whether + /// CancellationRequested evaluates to true. + /// + /// \returns True, iff the solving process was cancelled, else false. + template || + std::is_invocable_r_v>> + auto + solveUntil(CancellationRequest CancellationRequested, + std::chrono::milliseconds Frequency = std::chrono::seconds{1}) && { + return solveUntilImpl(std::move(self()), std::move(CancellationRequested), + Frequency); + } + + /// Solves the analysis problen and periodically checks with Frequency whether + /// the Timeout has been exceeded. + /// + /// \returns True, iff the solving process was cancelled, else false. + bool solveTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Frequency) { + return solveUntil( + [Timeout, Start = std::chrono::steady_clock::now()]( + std::chrono::steady_clock::time_point TimeStamp) { + return TimeStamp - Start >= Timeout; + }, + Frequency); + } + +private: + [[nodiscard]] Derived &self() &noexcept { + static_assert(std::is_base_of_v, + "Invalid CRTP instantiation"); + return static_cast(*this); + } + + template + static auto solveUntilImpl(SelfTy &&Self, + CancellationRequest CancellationRequested, + std::chrono::milliseconds Frequency) { + using RetTy = std::optional< + std::decay_t(Self).finalize())>>; + + size_t NumIterations = Frequency.count() * 500; + if (!Self.initialize()) { + return RetTy(std::forward(Self).finalize()); + } + + auto IsCancellationRequested = + [&CancellationRequested]( + std::chrono::steady_clock::time_point TimeStamp) { + if constexpr (std::is_invocable_r_v< + bool, CancellationRequest, + std::chrono::steady_clock::time_point>) { + return std::invoke(CancellationRequested, TimeStamp); + } else { + return std::invoke(CancellationRequested); + } + }; + + auto Start = std::chrono::steady_clock::now(); + + if (IsCancellationRequested(Start)) { + return RetTy(); + } + + while (true) { + if (!Self.nextN(NumIterations)) { + return RetTy(std::forward(Self).finalize()); + } + + auto End = std::chrono::steady_clock::now(); + using milliseconds_d = std::chrono::duration; + + auto DeltaTime = std::chrono::duration_cast(End - Start); + Start = End; + + if (IsCancellationRequested(End)) { + return RetTy(); + } + + // Adjust NumIterations + auto IterationsPerMilli = double(NumIterations) / DeltaTime.count(); + auto NewNumIterations = + size_t(IterationsPerMilli * double(Frequency.count())); + NumIterations = (NumIterations + 2 * NewNumIterations) / 3; + } + + llvm_unreachable( + "We do not break out the above loop except for explicit returns"); + } +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H diff --git a/include/phasar/DataFlow/IfdsIde/SolverResults.h b/include/phasar/DataFlow/IfdsIde/SolverResults.h index 6a5671a50e..d16e21e60f 100644 --- a/include/phasar/DataFlow/IfdsIde/SolverResults.h +++ b/include/phasar/DataFlow/IfdsIde/SolverResults.h @@ -51,8 +51,8 @@ class SolverResultsBase { return self().Results.get(Stmt, Node); } - [[nodiscard]] std::unordered_map - resultsAt(ByConstRef Stmt, bool StripZero = false) const { + [[nodiscard]] std::unordered_map resultsAt(ByConstRef Stmt, + bool StripZero) const { std::unordered_map Result = self().Results.row(Stmt); if (StripZero) { Result.erase(self().ZV); @@ -60,6 +60,11 @@ class SolverResultsBase { return Result; } + [[nodiscard]] const std::unordered_map & + resultsAt(ByConstRef Stmt) const { + return self().Results.row(Stmt); + } + // this function only exists for IFDS problems which use BinaryDomain as their // value domain L template Date: Fri, 23 Jun 2023 15:02:34 +0200 Subject: [PATCH 05/13] Comments + some minor refactoring --- .../DataFlow/IfdsIde/Solver/IDESolver.h | 3 + .../Solver/InteractiveIDESolverMixin.h | 118 +++++++++++++----- 2 files changed, 87 insertions(+), 34 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index 95de3079af..bfd4650bc2 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -1733,6 +1733,7 @@ class IDESolver return !WorkList.empty(); } + void finalizeInternal() { if (SolverConfig.computeValues()) { @@ -1749,10 +1750,12 @@ class IDESolver emitESGAsDot(); } } + SolverResults doFinalize() & { finalizeInternal(); return getSolverResults(); } + OwningSolverResults doFinalize() && { finalizeInternal(); return consumeSolverResults(); diff --git a/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h index aadbd52756..436b7b419c 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h @@ -25,8 +25,31 @@ namespace psr { template class InteractiveIDESolverMixin { public: + /// Initialize the IDE solver for step-wise solving (iteratively calling + /// next() or nextN()). + /// For a more high-level API use solveUntil() or solveTimeout(). + /// + /// \returns True, iff it is valid to call next() or nextN() afterwards. [[nodiscard]] bool initialize() { return self().doInitialize(); } + + /// Performs one tiny step towards the analysis' fixpoint. For a + /// more high-level API use solveUntil() or solveTimeout(). + /// + /// Requires that initialize() has been called before once and returned true + /// and that all previous next() or nextN() calls returned true as well. + /// + /// \returns True, iff there are more steps to process before calling + /// finalize() [[nodiscard]] bool next() { return self().doNext(); } + + /// Performs N tiny steps towards the analysis' fixpoint. + /// For a more high-level API use solveUntil() or solveTimeout(). + /// + /// Requires that initialize() has been called before once and returned true + /// and that all previous next() or nextN() calls returned true as well. + /// + /// \returns True, iff there are more steps to process before calling + /// finalize() [[nodiscard]] bool nextN(size_t MaxNumIterations) { PHASAR_LOG_LEVEL(DEBUG, "[nextN]: Next " << MaxNumIterations << " Iterations"); @@ -40,13 +63,25 @@ template class InteractiveIDESolverMixin { PHASAR_LOG_LEVEL(DEBUG, "[nextN]: > has next"); return true; } + + /// Computes the final analysis results after the analysis has reached its + /// fixpoint, i.e. either initialize() returned false or the last next() or + /// nextN() call returned false. + /// + /// Returns a view into the computed analysis results decltype(auto) finalize() & { return self().doFinalize(); } + /// Computes the final analysis results after the analysis has reached its + /// fixpoint, i.e. either initialize() returned false or the last next() or + /// nextN() call returned false. + /// + /// Returns a the computed analysis results decltype(auto) finalize() && { return std::move(self()).doFinalize(); } /// Solves the analysis problen and periodically checks with Frequency whether /// CancellationRequested evaluates to true. /// - /// \returns True, iff the solving process was cancelled, else false. + /// \returns An std::optional holding a view into the analysis results or + /// std::nullopt if the analysis was cancelled. template || @@ -61,7 +96,8 @@ template class InteractiveIDESolverMixin { /// Solves the analysis problen and periodically checks with Frequency whether /// CancellationRequested evaluates to true. /// - /// \returns True, iff the solving process was cancelled, else false. + /// \returns An std::optional holding the analysis results or std::nullopt if + /// the analysis was cancelled. template || @@ -77,15 +113,31 @@ template class InteractiveIDESolverMixin { /// Solves the analysis problen and periodically checks with Frequency whether /// the Timeout has been exceeded. /// - /// \returns True, iff the solving process was cancelled, else false. - bool solveTimeout(std::chrono::milliseconds Timeout, - std::chrono::milliseconds Frequency) { - return solveUntil( + /// \returns An std::optional holding a view into the analysis results or + /// std::nullopt if the analysis was cancelled. + auto solveTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Frequency) & { + auto CancellatioNRequested = [Timeout, Start = std::chrono::steady_clock::now()]( std::chrono::steady_clock::time_point TimeStamp) { return TimeStamp - Start >= Timeout; - }, - Frequency); + }; + return solveUntilImpl(self(), CancellatioNRequested, Frequency); + } + + /// Solves the analysis problen and periodically checks with Frequency whether + /// the Timeout has been exceeded. + /// + /// \returns An std::optional holding a view into the analysis results or + /// std::nullopt if the analysis was cancelled. + auto solveTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Frequency) && { + auto CancellatioNRequested = + [Timeout, Start = std::chrono::steady_clock::now()]( + std::chrono::steady_clock::time_point TimeStamp) { + return TimeStamp - Start >= Timeout; + }; + return solveUntilImpl(std::move(self()), CancellatioNRequested, Frequency); } private: @@ -103,10 +155,6 @@ template class InteractiveIDESolverMixin { std::decay_t(Self).finalize())>>; size_t NumIterations = Frequency.count() * 500; - if (!Self.initialize()) { - return RetTy(std::forward(Self).finalize()); - } - auto IsCancellationRequested = [&CancellationRequested]( std::chrono::steady_clock::time_point TimeStamp) { @@ -119,36 +167,38 @@ template class InteractiveIDESolverMixin { } }; - auto Start = std::chrono::steady_clock::now(); + if (Self.initialize()) { + auto Start = std::chrono::steady_clock::now(); - if (IsCancellationRequested(Start)) { - return RetTy(); - } - - while (true) { - if (!Self.nextN(NumIterations)) { - return RetTy(std::forward(Self).finalize()); + if (IsCancellationRequested(Start)) { + return RetTy(); } - auto End = std::chrono::steady_clock::now(); - using milliseconds_d = std::chrono::duration; + while (Self.nextN(NumIterations)) { + auto End = std::chrono::steady_clock::now(); + using milliseconds_d = std::chrono::duration; - auto DeltaTime = std::chrono::duration_cast(End - Start); - Start = End; + auto DeltaTime = + std::chrono::duration_cast(End - Start); + Start = End; - if (IsCancellationRequested(End)) { - return RetTy(); - } + if (IsCancellationRequested(End)) { + return RetTy(); + } - // Adjust NumIterations - auto IterationsPerMilli = double(NumIterations) / DeltaTime.count(); - auto NewNumIterations = - size_t(IterationsPerMilli * double(Frequency.count())); - NumIterations = (NumIterations + 2 * NewNumIterations) / 3; + // Adjust NumIterations + auto IterationsPerMilli = double(NumIterations) / DeltaTime.count(); + auto NewNumIterations = + size_t(IterationsPerMilli * double(Frequency.count())); + NumIterations = (NumIterations + 2 * NewNumIterations) / 3; + } } - llvm_unreachable( - "We do not break out the above loop except for explicit returns"); + auto End = std::chrono::steady_clock::now(); + if (IsCancellationRequested(End)) { + return RetTy(); + } + return RetTy(std::forward(Self).finalize()); } }; } // namespace psr From d390eaa61a40134124674ff057019b75edda6fb3 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Fri, 23 Jun 2023 15:44:01 +0200 Subject: [PATCH 06/13] Add unittest --- .../DataFlow/IfdsIde/Solver/IDESolver.h | 117 ++++++++------- .../Solver/InteractiveIDESolverMixin.h | 6 +- .../DataFlow/IfdsIde/CMakeLists.txt | 1 + .../IfdsIde/InteractiveIDESolverTest.cpp | 133 ++++++++++++++++++ 4 files changed, 201 insertions(+), 56 deletions(-) create mode 100644 unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index bfd4650bc2..d10a382d62 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -132,60 +132,15 @@ class IDESolver } /// \brief Runs the solver on the configured problem. This can take some time. - virtual void solve() { - PAMM_GET_INSTANCE; - REG_COUNTER("Gen facts", 0, PAMM_SEVERITY_LEVEL::Core); - REG_COUNTER("Kill facts", 0, PAMM_SEVERITY_LEVEL::Core); - REG_COUNTER("Summary-reuse", 0, PAMM_SEVERITY_LEVEL::Core); - REG_COUNTER("Intra Path Edges", 0, PAMM_SEVERITY_LEVEL::Core); - REG_COUNTER("Inter Path Edges", 0, PAMM_SEVERITY_LEVEL::Core); - REG_COUNTER("FF Queries", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("EF Queries", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("Value Propagation", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("Value Computation", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("SpecialSummary-FF Application", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("SpecialSummary-EF Queries", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("JumpFn Construction", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("Process Call", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("Process Normal", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("Process Exit", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("[Calls] getAliasSet", 0, PAMM_SEVERITY_LEVEL::Full); - REG_HISTOGRAM("Data-flow facts", PAMM_SEVERITY_LEVEL::Full); - REG_HISTOGRAM("Points-to", PAMM_SEVERITY_LEVEL::Full); - - PHASAR_LOG_LEVEL(INFO, "IDE solver is solving the specified problem"); - PHASAR_LOG_LEVEL(INFO, - "Submit initial seeds, construct exploded super graph"); - // computations starting here - START_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full); - // We start our analysis and construct exploded supergraph - submitInitialSeeds(); - - while (!WorkList.empty()) { - auto [Edge, EF] = std::move(WorkList.back()); - WorkList.pop_back(); - - auto [SourceVal, Target, TargetVal] = Edge.consume(); - propagate(std::move(SourceVal), std::move(Target), std::move(TargetVal), - std::move(EF)); - } + auto solve() & { + doSolve(); + return getSolverResults(); + } - STOP_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full); - if (SolverConfig.computeValues()) { - START_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full); - // Computing the final values for the edge functions - PHASAR_LOG_LEVEL( - INFO, "Compute the final values according to the edge functions"); - computeValues(); - STOP_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full); - } - PHASAR_LOG_LEVEL(INFO, "Problem solved"); - if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::Core) { - computeAndPrintStatistics(); - } - if (SolverConfig.emitESG()) { - emitESGAsDot(); - } + /// \brief Runs the solver on the configured problem. This can take some time. + auto solve() && { + doSolve(); + return consumeSolverResults(); } /// Returns the L-type result for the given value at the given statement. @@ -319,6 +274,62 @@ class IDESolver } protected: + virtual void doSolve() { + PAMM_GET_INSTANCE; + REG_COUNTER("Gen facts", 0, PAMM_SEVERITY_LEVEL::Core); + REG_COUNTER("Kill facts", 0, PAMM_SEVERITY_LEVEL::Core); + REG_COUNTER("Summary-reuse", 0, PAMM_SEVERITY_LEVEL::Core); + REG_COUNTER("Intra Path Edges", 0, PAMM_SEVERITY_LEVEL::Core); + REG_COUNTER("Inter Path Edges", 0, PAMM_SEVERITY_LEVEL::Core); + REG_COUNTER("FF Queries", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("EF Queries", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("Value Propagation", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("Value Computation", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("SpecialSummary-FF Application", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("SpecialSummary-EF Queries", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("JumpFn Construction", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("Process Call", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("Process Normal", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("Process Exit", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("[Calls] getAliasSet", 0, PAMM_SEVERITY_LEVEL::Full); + REG_HISTOGRAM("Data-flow facts", PAMM_SEVERITY_LEVEL::Full); + REG_HISTOGRAM("Points-to", PAMM_SEVERITY_LEVEL::Full); + + PHASAR_LOG_LEVEL(INFO, "IDE solver is solving the specified problem"); + PHASAR_LOG_LEVEL(INFO, + "Submit initial seeds, construct exploded super graph"); + // computations starting here + START_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full); + // We start our analysis and construct exploded supergraph + submitInitialSeeds(); + + while (!WorkList.empty()) { + auto [Edge, EF] = std::move(WorkList.back()); + WorkList.pop_back(); + + auto [SourceVal, Target, TargetVal] = Edge.consume(); + propagate(std::move(SourceVal), std::move(Target), std::move(TargetVal), + std::move(EF)); + } + + STOP_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full); + if (SolverConfig.computeValues()) { + START_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full); + // Computing the final values for the edge functions + PHASAR_LOG_LEVEL( + INFO, "Compute the final values according to the edge functions"); + computeValues(); + STOP_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full); + } + PHASAR_LOG_LEVEL(INFO, "Problem solved"); + if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::Core) { + computeAndPrintStatistics(); + } + if (SolverConfig.emitESG()) { + emitESGAsDot(); + } + } + /// Lines 13-20 of the algorithm; processing a call site in the caller's /// context. /// diff --git a/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h index 436b7b419c..3ffe700617 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h @@ -7,8 +7,8 @@ * Fabian Schiebel and others *****************************************************************************/ -#ifndef PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H -#define PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H #include "phasar/Utils/Logger.h" @@ -203,4 +203,4 @@ template class InteractiveIDESolverMixin { }; } // namespace psr -#endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H +#endif // PHASAR_DATAFLOW_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt b/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt index c53b41c9d2..9ab94b5db4 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(Problems) set(IfdsIdeSources EdgeFunctionComposerTest.cpp EdgeFunctionSingletonCacheTest.cpp + InteractiveIDESolverTest.cpp ) foreach(TEST_SRC ${IfdsIdeSources}) diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp new file mode 100644 index 0000000000..998fc5483a --- /dev/null +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp @@ -0,0 +1,133 @@ +#include "phasar/DataFlow/IfdsIde/Solver/IDESolver.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h" +#include "phasar/PhasarLLVM/HelperAnalyses.h" +#include "phasar/PhasarLLVM/SimpleAnalysisConstructor.h" + +#include "TestConfig.h" +#include "gtest/gtest.h" + +#include +#include + +using namespace psr; + +/* ============== TEST FIXTURE ============== */ +class LinearConstant : public ::testing::TestWithParam { +protected: + static constexpr auto PathToLlFiles = + PHASAR_BUILD_SUBFOLDER("linear_constant/"); + const std::vector EntryPoints = {"main"}; + +}; // Test Fixture + +TEST_P(LinearConstant, ResultsEquivalentSolveUntil) { + HelperAnalyses HA(PathToLlFiles + GetParam(), EntryPoints); + + // Compute the ICFG to possibly create the runtime model + auto &ICFG = HA.getICFG(); + + auto HasGlobalCtor = HA.getProjectIRDB().getFunctionDefinition( + LLVMBasedICFG::GlobalCRuntimeModelName) != nullptr; + + auto LCAProblem = createAnalysisProblem( + HA, + std::vector{HasGlobalCtor ? LLVMBasedICFG::GlobalCRuntimeModelName.str() + : "main"}); + + auto AtomicResults = IDESolver(LCAProblem, &ICFG).solve(); + auto InteractiveResults = IDESolver(LCAProblem, &ICFG).solveUntil(FalseFn{}); + // if (PrintDump) { + // HA.getProjectIRDB().dump(); + // ICFG.print(); + // AtomicResults.dumpResults(ICFG, LCAProblem); + // } + + ASSERT_TRUE(InteractiveResults.has_value()); + for (auto &&Cell : AtomicResults.getAllResultEntries()) { + auto InteractiveRes = + InteractiveResults->resultAt(Cell.getRowKey(), Cell.getColumnKey()); + EXPECT_EQ(InteractiveRes, Cell.getValue()); + } +} + +static constexpr std::string_view LCATestFiles[] = { + "basic_01_cpp_dbg.ll", + "basic_02_cpp_dbg.ll", + "basic_03_cpp_dbg.ll", + "basic_04_cpp_dbg.ll", + "basic_05_cpp_dbg.ll", + "basic_06_cpp_dbg.ll", + "basic_07_cpp_dbg.ll", + "basic_08_cpp_dbg.ll", + "basic_09_cpp_dbg.ll", + "basic_10_cpp_dbg.ll", + "basic_11_cpp_dbg.ll", + "basic_12_cpp_dbg.ll", + + "branch_01_cpp_dbg.ll", + "branch_02_cpp_dbg.ll", + "branch_03_cpp_dbg.ll", + "branch_04_cpp_dbg.ll", + "branch_05_cpp_dbg.ll", + "branch_06_cpp_dbg.ll", + "branch_07_cpp_dbg.ll", + + "while_01_cpp_dbg.ll", + "while_02_cpp_dbg.ll", + "while_03_cpp_dbg.ll", + "while_04_cpp_dbg.ll", + "while_05_cpp_dbg.ll", + "for_01_cpp_dbg.ll", + + "call_01_cpp_dbg.ll", + "call_02_cpp_dbg.ll", + "call_03_cpp_dbg.ll", + "call_04_cpp_dbg.ll", + "call_05_cpp_dbg.ll", + "call_06_cpp_dbg.ll", + "call_07_cpp_dbg.ll", + "call_08_cpp_dbg.ll", + "call_09_cpp_dbg.ll", + "call_10_cpp_dbg.ll", + "call_11_cpp_dbg.ll", + + "recursion_01_cpp_dbg.ll", + "recursion_02_cpp_dbg.ll", + "recursion_03_cpp_dbg.ll", + + "global_01_cpp_dbg.ll", + "global_02_cpp_dbg.ll", + "global_03_cpp_dbg.ll", + "global_04_cpp_dbg.ll", + "global_05_cpp_dbg.ll", + "global_06_cpp_dbg.ll", + "global_07_cpp_dbg.ll", + "global_08_cpp_dbg.ll", + "global_09_cpp_dbg.ll", + "global_10_cpp_dbg.ll", + "global_11_cpp_dbg.ll", + "global_12_cpp_dbg.ll", + "global_13_cpp_dbg.ll", + "global_14_cpp_dbg.ll", + "global_15_cpp_dbg.ll", + "global_16_cpp_dbg.ll", + + "overflow_add_cpp_dbg.ll", + "overflow_sub_cpp_dbg.ll", + "overflow_mul_cpp_dbg.ll", + "overflow_div_min_by_neg_one_cpp_dbg.ll", + + "ub_division_by_zero_cpp_dbg.ll", + "ub_modulo_by_zero_cpp_dbg.ll", +}; + +INSTANTIATE_TEST_SUITE_P(InteractiveIDESolverTest, LinearConstant, + ::testing::ValuesIn(LCATestFiles)); + +// main function for the test case +int main(int Argc, char **Argv) { + ::testing::InitGoogleTest(&Argc, Argv); + return RUN_ALL_TESTS(); +} From 0267699c9c895cd728b9c2cf8477ad5e89befef0 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Fri, 23 Jun 2023 16:26:19 +0200 Subject: [PATCH 07/13] Generalize the IDESolver mixin + more comments --- .../DataFlow/IfdsIde/Solver/IDESolver.h | 101 +++++------------- ...veIDESolverMixin.h => IDESolverAPIMixin.h} | 90 ++++++++++++---- tools/example-tool/myphasartool.cpp | 6 +- 3 files changed, 100 insertions(+), 97 deletions(-) rename include/phasar/DataFlow/IfdsIde/Solver/{InteractiveIDESolverMixin.h => IDESolverAPIMixin.h} (68%) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index d10a382d62..87731e1fb1 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -27,7 +27,7 @@ #include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" #include "phasar/DataFlow/IfdsIde/InitialSeeds.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h" -#include "phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h" +#include "phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h" #include "phasar/DataFlow/IfdsIde/Solver/JumpFunctions.h" #include "phasar/DataFlow/IfdsIde/Solver/PathEdge.h" #include "phasar/DataFlow/IfdsIde/SolverResults.h" @@ -61,8 +61,8 @@ namespace psr { template > class IDESolver - : public InteractiveIDESolverMixin> { - friend InteractiveIDESolverMixin>; + : public IDESolverAPIMixin> { + friend IDESolverAPIMixin>; public: using ProblemTy = IDETabulationProblem; @@ -131,18 +131,6 @@ class IDESolver return J; } - /// \brief Runs the solver on the configured problem. This can take some time. - auto solve() & { - doSolve(); - return getSolverResults(); - } - - /// \brief Runs the solver on the configured problem. This can take some time. - auto solve() && { - doSolve(); - return consumeSolverResults(); - } - /// Returns the L-type result for the given value at the given statement. [[nodiscard]] l_t resultAt(n_t Stmt, d_t Value) { return getSolverResults().resultAt(Stmt, Value); @@ -274,62 +262,6 @@ class IDESolver } protected: - virtual void doSolve() { - PAMM_GET_INSTANCE; - REG_COUNTER("Gen facts", 0, PAMM_SEVERITY_LEVEL::Core); - REG_COUNTER("Kill facts", 0, PAMM_SEVERITY_LEVEL::Core); - REG_COUNTER("Summary-reuse", 0, PAMM_SEVERITY_LEVEL::Core); - REG_COUNTER("Intra Path Edges", 0, PAMM_SEVERITY_LEVEL::Core); - REG_COUNTER("Inter Path Edges", 0, PAMM_SEVERITY_LEVEL::Core); - REG_COUNTER("FF Queries", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("EF Queries", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("Value Propagation", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("Value Computation", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("SpecialSummary-FF Application", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("SpecialSummary-EF Queries", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("JumpFn Construction", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("Process Call", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("Process Normal", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("Process Exit", 0, PAMM_SEVERITY_LEVEL::Full); - REG_COUNTER("[Calls] getAliasSet", 0, PAMM_SEVERITY_LEVEL::Full); - REG_HISTOGRAM("Data-flow facts", PAMM_SEVERITY_LEVEL::Full); - REG_HISTOGRAM("Points-to", PAMM_SEVERITY_LEVEL::Full); - - PHASAR_LOG_LEVEL(INFO, "IDE solver is solving the specified problem"); - PHASAR_LOG_LEVEL(INFO, - "Submit initial seeds, construct exploded super graph"); - // computations starting here - START_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full); - // We start our analysis and construct exploded supergraph - submitInitialSeeds(); - - while (!WorkList.empty()) { - auto [Edge, EF] = std::move(WorkList.back()); - WorkList.pop_back(); - - auto [SourceVal, Target, TargetVal] = Edge.consume(); - propagate(std::move(SourceVal), std::move(Target), std::move(TargetVal), - std::move(EF)); - } - - STOP_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full); - if (SolverConfig.computeValues()) { - START_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full); - // Computing the final values for the edge functions - PHASAR_LOG_LEVEL( - INFO, "Compute the final values according to the edge functions"); - computeValues(); - STOP_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full); - } - PHASAR_LOG_LEVEL(INFO, "Problem solved"); - if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::Core) { - computeAndPrintStatistics(); - } - if (SolverConfig.emitESG()) { - emitESGAsDot(); - } - } - /// Lines 13-20 of the algorithm; processing a call site in the caller's /// context. /// @@ -1725,9 +1657,32 @@ class IDESolver /// -- InteractiveIDESolverMixin implementation bool doInitialize() { + PAMM_GET_INSTANCE; + REG_COUNTER("Gen facts", 0, PAMM_SEVERITY_LEVEL::Core); + REG_COUNTER("Kill facts", 0, PAMM_SEVERITY_LEVEL::Core); + REG_COUNTER("Summary-reuse", 0, PAMM_SEVERITY_LEVEL::Core); + REG_COUNTER("Intra Path Edges", 0, PAMM_SEVERITY_LEVEL::Core); + REG_COUNTER("Inter Path Edges", 0, PAMM_SEVERITY_LEVEL::Core); + REG_COUNTER("FF Queries", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("EF Queries", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("Value Propagation", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("Value Computation", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("SpecialSummary-FF Application", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("SpecialSummary-EF Queries", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("JumpFn Construction", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("Process Call", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("Process Normal", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("Process Exit", 0, PAMM_SEVERITY_LEVEL::Full); + REG_COUNTER("[Calls] getAliasSet", 0, PAMM_SEVERITY_LEVEL::Full); + REG_HISTOGRAM("Data-flow facts", PAMM_SEVERITY_LEVEL::Full); + REG_HISTOGRAM("Points-to", PAMM_SEVERITY_LEVEL::Full); + PHASAR_LOG_LEVEL(INFO, "IDE solver is solving the specified problem"); PHASAR_LOG_LEVEL(INFO, "Submit initial seeds, construct exploded super graph"); + // computations starting here + START_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full); + // We start our analysis and construct exploded supergraph submitInitialSeeds(); return !WorkList.empty(); @@ -1746,12 +1701,14 @@ class IDESolver } void finalizeInternal() { + STOP_TIMER("DFA Phase I", PAMM_SEVERITY_LEVEL::Full); if (SolverConfig.computeValues()) { - + START_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full); // Computing the final values for the edge functions PHASAR_LOG_LEVEL( INFO, "Compute the final values according to the edge functions"); computeValues(); + STOP_TIMER("DFA Phase II", PAMM_SEVERITY_LEVEL::Full); } PHASAR_LOG_LEVEL(INFO, "Problem solved"); if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::Core) { diff --git a/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h similarity index 68% rename from include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h rename to include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h index 3ffe700617..e0685c2eb1 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/InteractiveIDESolverMixin.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h @@ -7,8 +7,8 @@ * Fabian Schiebel and others *****************************************************************************/ -#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H -#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H +#ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_IDESOLVERAPIMIXIN_H +#define PHASAR_DATAFLOW_IFDSIDE_SOLVER_IDESOLVERAPIMIXIN_H #include "phasar/Utils/Logger.h" @@ -23,7 +23,7 @@ #include namespace psr { -template class InteractiveIDESolverMixin { +template class IDESolverAPIMixin { public: /// Initialize the IDE solver for step-wise solving (iteratively calling /// next() or nextN()). @@ -68,18 +68,43 @@ template class InteractiveIDESolverMixin { /// fixpoint, i.e. either initialize() returned false or the last next() or /// nextN() call returned false. /// - /// Returns a view into the computed analysis results + /// \returns A view into the computed analysis results decltype(auto) finalize() & { return self().doFinalize(); } /// Computes the final analysis results after the analysis has reached its /// fixpoint, i.e. either initialize() returned false or the last next() or /// nextN() call returned false. /// - /// Returns a the computed analysis results + /// \returns The computed analysis results decltype(auto) finalize() && { return std::move(self()).doFinalize(); } - /// Solves the analysis problen and periodically checks with Frequency whether + /// Runs the solver on the configured problem. This can take some time and + /// cannot be interrupted. If you need the ability to interrupt the solving + /// process consider using solveUntil() or solveTimeout(). + /// + /// \returns A view into the computed analysis results + auto solve() & { + solveImpl(self()); + return finalize(); + } + + /// Runs the solver on the configured problem. This can take some time and + /// cannot be interrupted. If you need the ability to interrupt the solving + /// process consider using solveUntil() or solveTimeout(). + /// + /// \returns The computed analysis results + auto solve() && { + solveImpl(self()); + return std::move(*this).finalize(); + } + + /// Solves the analysis problen and periodically checks every Interval whether /// CancellationRequested evaluates to true. /// + /// Note: Shortening the cancellation-check interval will make the + /// cancellation-time more precise (by having more frequent calls to + /// CancellationRequested) but also have negative impact on the solver's + /// performance. + /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. template class InteractiveIDESolverMixin { std::chrono::steady_clock::time_point>>> auto solveUntil(CancellationRequest CancellationRequested, - std::chrono::milliseconds Frequency = std::chrono::seconds{1}) & { - return solveUntilImpl(self(), std::move(CancellationRequested), Frequency); + std::chrono::milliseconds Interval = std::chrono::seconds{1}) & { + return solveUntilImpl(self(), std::move(CancellationRequested), Interval); } - /// Solves the analysis problen and periodically checks with Frequency whether + /// Solves the analysis problen and periodically checks every Interval whether /// CancellationRequested evaluates to true. /// + /// Note: Shortening the cancellation-check interval will make the + /// cancellation-time more precise (by having more frequent calls to + /// CancellationRequested) but also have negative impact on the solver's + /// performance. + /// /// \returns An std::optional holding the analysis results or std::nullopt if /// the analysis was cancelled. template class InteractiveIDESolverMixin { std::chrono::steady_clock::time_point>>> auto solveUntil(CancellationRequest CancellationRequested, - std::chrono::milliseconds Frequency = std::chrono::seconds{1}) && { + std::chrono::milliseconds Interval = std::chrono::seconds{1}) && { return solveUntilImpl(std::move(self()), std::move(CancellationRequested), - Frequency); + Interval); } - /// Solves the analysis problen and periodically checks with Frequency whether + /// Solves the analysis problen and periodically checks every Interval whether /// the Timeout has been exceeded. /// + /// Note: Shortening the timeout-check interval will make the timeout more + /// precise but also have negative impact on the solver's performance. + /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. auto solveTimeout(std::chrono::milliseconds Timeout, - std::chrono::milliseconds Frequency) & { + std::chrono::milliseconds Interval) & { auto CancellatioNRequested = [Timeout, Start = std::chrono::steady_clock::now()]( std::chrono::steady_clock::time_point TimeStamp) { return TimeStamp - Start >= Timeout; }; - return solveUntilImpl(self(), CancellatioNRequested, Frequency); + return solveUntilImpl(self(), CancellatioNRequested, Interval); } - /// Solves the analysis problen and periodically checks with Frequency whether + /// Solves the analysis problen and periodically checks every Interval whether /// the Timeout has been exceeded. /// + /// Note: Shortening the timeout-check interval will make the timeout more + /// precise but also have negative impact on the solver's performance. + /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. auto solveTimeout(std::chrono::milliseconds Timeout, - std::chrono::milliseconds Frequency) && { + std::chrono::milliseconds Interval) && { auto CancellatioNRequested = [Timeout, Start = std::chrono::steady_clock::now()]( std::chrono::steady_clock::time_point TimeStamp) { return TimeStamp - Start >= Timeout; }; - return solveUntilImpl(std::move(self()), CancellatioNRequested, Frequency); + return solveUntilImpl(std::move(self()), CancellatioNRequested, Interval); } private: [[nodiscard]] Derived &self() &noexcept { - static_assert(std::is_base_of_v, + static_assert(std::is_base_of_v, "Invalid CRTP instantiation"); return static_cast(*this); } + static void solveImpl(Derived &Self) { + if (Self.initialize()) { + while (Self.next()) { + // no interrupt in normal solving process + } + } + // Note: Do not call finalize() here, because depending on the + // reference-category of self(), we may need to call a different function. + } + template static auto solveUntilImpl(SelfTy &&Self, CancellationRequest CancellationRequested, - std::chrono::milliseconds Frequency) { + std::chrono::milliseconds Interval) { using RetTy = std::optional< std::decay_t(Self).finalize())>>; - size_t NumIterations = Frequency.count() * 500; + size_t NumIterations = Interval.count() * 500; auto IsCancellationRequested = [&CancellationRequested]( std::chrono::steady_clock::time_point TimeStamp) { @@ -189,7 +235,7 @@ template class InteractiveIDESolverMixin { // Adjust NumIterations auto IterationsPerMilli = double(NumIterations) / DeltaTime.count(); auto NewNumIterations = - size_t(IterationsPerMilli * double(Frequency.count())); + size_t(IterationsPerMilli * double(Interval.count())); NumIterations = (NumIterations + 2 * NewNumIterations) / 3; } } @@ -203,4 +249,4 @@ template class InteractiveIDESolverMixin { }; } // namespace psr -#endif // PHASAR_DATAFLOW_IFDSIDE_SOLVER_INTERACTIVEIDESOLVERMIXIN_H +#endif // PHASAR_DATAFLOW_IFDSIDE_SOLVER_IDESOLVERAPIMIXIN_H diff --git a/tools/example-tool/myphasartool.cpp b/tools/example-tool/myphasartool.cpp index 02ee6b2fcc..642c6fd975 100644 --- a/tools/example-tool/myphasartool.cpp +++ b/tools/example-tool/myphasartool.cpp @@ -41,12 +41,12 @@ int main(int Argc, const char **Argv) { llvm::outs() << "Testing IFDS:\n"; auto L = createAnalysisProblem(HA, EntryPoints); IFDSSolver S(L, &HA.getICFG()); - S.solve(); - S.dumpResults(); + auto IFDSResults = S.solve(); + IFDSResults.dumpResults(HA.getICFG(), L); + // IDE template parametrization test llvm::outs() << "Testing IDE:\n"; auto M = createAnalysisProblem(HA, EntryPoints); - // Alternative way of solving an IFDS/IDEProblem: auto IDEResults = solveIDEProblem(M, HA.getICFG()); IDEResults.dumpResults(HA.getICFG(), M); From 28600ccd0924719d562101a2cb79496d62bc43a6 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Sat, 24 Jun 2023 18:47:47 +0200 Subject: [PATCH 08/13] Add async cancellation --- .../IfdsIde/Solver/IDESolverAPIMixin.h | 101 ++++++++++++++---- 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h index e0685c2eb1..515009fd66 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h @@ -15,6 +15,7 @@ #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include @@ -82,7 +83,7 @@ template class IDESolverAPIMixin { /// process consider using solveUntil() or solveTimeout(). /// /// \returns A view into the computed analysis results - auto solve() & { + decltype(auto) solve() & { solveImpl(self()); return finalize(); } @@ -92,7 +93,7 @@ template class IDESolverAPIMixin { /// process consider using solveUntil() or solveTimeout(). /// /// \returns The computed analysis results - auto solve() && { + decltype(auto) solve() && { solveImpl(self()); return std::move(*this).finalize(); } @@ -115,7 +116,13 @@ template class IDESolverAPIMixin { auto solveUntil(CancellationRequest CancellationRequested, std::chrono::milliseconds Interval = std::chrono::seconds{1}) & { - return solveUntilImpl(self(), std::move(CancellationRequested), Interval); + using RetTy = std::optional>; + return [&]() -> RetTy { + if (solveUntilImpl(std::move(CancellationRequested), Interval)) { + return finalize(); + } + return std::nullopt; + }(); } /// Solves the analysis problen and periodically checks every Interval whether @@ -136,8 +143,14 @@ template class IDESolverAPIMixin { auto solveUntil(CancellationRequest CancellationRequested, std::chrono::milliseconds Interval = std::chrono::seconds{1}) && { - return solveUntilImpl(std::move(self()), std::move(CancellationRequested), - Interval); + using RetTy = + std::optional>; + return [&]() -> RetTy { + if (solveUntilImpl(std::move(CancellationRequested), Interval)) { + return std::move(self()).finalize(); + } + return std::nullopt; + }(); } /// Solves the analysis problen and periodically checks every Interval whether @@ -150,12 +163,13 @@ template class IDESolverAPIMixin { /// std::nullopt if the analysis was cancelled. auto solveTimeout(std::chrono::milliseconds Timeout, std::chrono::milliseconds Interval) & { - auto CancellatioNRequested = + auto CancellationRequested = [Timeout, Start = std::chrono::steady_clock::now()]( std::chrono::steady_clock::time_point TimeStamp) { return TimeStamp - Start >= Timeout; }; - return solveUntilImpl(self(), CancellatioNRequested, Interval); + + return solveUntil(CancellationRequested, Interval); } /// Solves the analysis problen and periodically checks every Interval whether @@ -173,7 +187,40 @@ template class IDESolverAPIMixin { std::chrono::steady_clock::time_point TimeStamp) { return TimeStamp - Start >= Timeout; }; - return solveUntilImpl(std::move(self()), CancellatioNRequested, Interval); + return std::move(*this).solveUntil(CancellatioNRequested, Interval); + } + + // -- Async cancellation + + /// Solves the analysis problen and periodically checks whether + /// IsCancelled is true. + /// + /// \returns An std::optional holding a view into the analysis results or + /// std::nullopt if the analysis was cancelled. + auto solveWithAsyncCancellation(std::atomic_bool &IsCancelled) & { + using RetTy = std::optional>; + return [&]() -> RetTy { + if (solveWithAsyncCancellationImpl(IsCancelled)) { + return self().finalize(); + } + return std::nullopt; + }(); + } + + /// Solves the analysis problen and periodically checks whether + /// IsCancelled is true. + /// + /// \returns An std::optional holding the analysis results or std::nullopt if + /// the analysis was cancelled. + auto solveWithAsyncCancellation(std::atomic_bool &IsCancelled) && { + using RetTy = + std::optional>; + return [&]() -> RetTy { + if (solveWithAsyncCancellationImpl(IsCancelled)) { + return std::move(self()).finalize(); + } + return std::nullopt; + }(); } private: @@ -189,16 +236,11 @@ template class IDESolverAPIMixin { // no interrupt in normal solving process } } - // Note: Do not call finalize() here, because depending on the - // reference-category of self(), we may need to call a different function. } - template - static auto solveUntilImpl(SelfTy &&Self, - CancellationRequest CancellationRequested, - std::chrono::milliseconds Interval) { - using RetTy = std::optional< - std::decay_t(Self).finalize())>>; + template + [[nodiscard]] bool solveUntilImpl(CancellationRequest CancellationRequested, + std::chrono::milliseconds Interval) { size_t NumIterations = Interval.count() * 500; auto IsCancellationRequested = @@ -213,14 +255,14 @@ template class IDESolverAPIMixin { } }; - if (Self.initialize()) { + if (self().initialize()) { auto Start = std::chrono::steady_clock::now(); if (IsCancellationRequested(Start)) { - return RetTy(); + return false; } - while (Self.nextN(NumIterations)) { + while (self().nextN(NumIterations)) { auto End = std::chrono::steady_clock::now(); using milliseconds_d = std::chrono::duration; @@ -229,7 +271,7 @@ template class IDESolverAPIMixin { Start = End; if (IsCancellationRequested(End)) { - return RetTy(); + return false; } // Adjust NumIterations @@ -241,10 +283,23 @@ template class IDESolverAPIMixin { } auto End = std::chrono::steady_clock::now(); - if (IsCancellationRequested(End)) { - return RetTy(); + return !IsCancellationRequested(End); + } + + [[nodiscard]] bool + solveWithAsyncCancellationImpl(std::atomic_bool &IsCancelled) { + if (self().initialize()) { + if (IsCancelled.load()) { + return false; + } + while (self().next()) { + if (IsCancelled.load()) { + return false; + } + } } - return RetTy(std::forward(Self).finalize()); + + return !IsCancelled.load(); } }; } // namespace psr From 9a0050dd11281d343c8392003bc2c1f1ef02aa01 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Mon, 26 Jun 2023 16:58:41 +0200 Subject: [PATCH 09/13] Adding continuations --- .../IfdsIde/Solver/IDESolverAPIMixin.h | 329 +++++++++++++++--- 1 file changed, 278 insertions(+), 51 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h index 515009fd66..572f07198b 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h @@ -84,7 +84,7 @@ template class IDESolverAPIMixin { /// /// \returns A view into the computed analysis results decltype(auto) solve() & { - solveImpl(self()); + solveImpl(); return finalize(); } @@ -94,7 +94,41 @@ template class IDESolverAPIMixin { /// /// \returns The computed analysis results decltype(auto) solve() && { - solveImpl(self()); + solveImpl(); + return std::move(*this).finalize(); + } + + /// Continues running the solver on the configured problem after it got + /// interrupted in a previous run. This can take some time and cannot be + /// interrupted. If you need the ability to interrupt the solving process + /// consider using solveUntil() or solveTimeout(). + /// + /// \remark Please make sure to *not* call this function on an IDESolver where + /// the solving process is interrupted, i.e. one of hte interruptable solving + /// methods returned std::nullopt. It is *invalid* to call this function on an + /// IDESolver that has not yet started solvong or has already flinalized + /// solving. + /// + /// \returns A view into the computed analysis results + decltype(auto) continueSolving() & { + continueImpl(); + return finalize(); + } + + /// Continues running the solver on the configured problem after it got + /// interrupted in a previous run. This can take some time and cannot be + /// interrupted. If you need the ability to interrupt the solving process + /// consider using solveUntil() or solveTimeout(). + /// + /// \remark Please make sure to *not* call this function on an IDESolver where + /// the solving process is interrupted, i.e. one of hte interruptable solving + /// methods returned std::nullopt. It is *invalid* to call this function on an + /// IDESolver that has not yet started solvong or has already flinalized + /// solving. + /// + /// \returns The computed analysis results + decltype(auto) continueSolving() && { + continueImpl(); return std::move(*this).finalize(); } @@ -116,7 +150,7 @@ template class IDESolverAPIMixin { auto solveUntil(CancellationRequest CancellationRequested, std::chrono::milliseconds Interval = std::chrono::seconds{1}) & { - using RetTy = std::optional>; + using RetTy = std::optional>; return [&]() -> RetTy { if (solveUntilImpl(std::move(CancellationRequested), Interval)) { return finalize(); @@ -144,10 +178,79 @@ template class IDESolverAPIMixin { solveUntil(CancellationRequest CancellationRequested, std::chrono::milliseconds Interval = std::chrono::seconds{1}) && { using RetTy = - std::optional>; + std::optional>; return [&]() -> RetTy { if (solveUntilImpl(std::move(CancellationRequested), Interval)) { - return std::move(self()).finalize(); + return std::move(*this).finalize(); + } + return std::nullopt; + }(); + } + + /// Continues running the solver on the configured problem after it got + /// interrupted in a previous run. Periodically checks every Interval whether + /// CancellationRequested evaluates to true. + /// + /// \remark Please make sure to *not* call this function on an IDESolver where + /// the solving process is interrupted, i.e. one of hte interruptable solving + /// methods returned std::nullopt. It is *invalid* to call this function on an + /// IDESolver that has not yet started solvong or has already flinalized + /// solving. + /// + /// Note: Shortening the cancellation-check interval will make the + /// cancellation-time more precise (by having more frequent calls to + /// CancellationRequested) but also have negative impact on the solver's + /// performance. + /// + /// \returns An std::optional holding a view into the analysis results or + /// std::nullopt if the analysis was cancelled. + template || + std::is_invocable_r_v>> + auto continueUntil(CancellationRequest CancellationRequested, + std::chrono::milliseconds Interval = std::chrono::seconds{ + 1}) & { + using RetTy = std::optional>; + return [&]() -> RetTy { + if (continueUntilImpl(std::move(CancellationRequested), Interval)) { + return finalize(); + } + return std::nullopt; + }(); + } + + /// Continues running the solver on the configured problem after it got + /// interrupted in a previous run. Periodically checks every Interval whether + /// CancellationRequested evaluates to true. + /// + /// \remark Please make sure to *not* call this function on an IDESolver where + /// the solving process is interrupted, i.e. one of hte interruptable solving + /// methods returned std::nullopt. It is *invalid* to call this function on an + /// IDESolver that has not yet started solvong or has already flinalized + /// solving. + /// + /// Note: Shortening the cancellation-check interval will make the + /// cancellation-time more precise (by having more frequent calls to + /// CancellationRequested) but also have negative impact on the solver's + /// performance. + /// + /// \returns An std::optional holding a view into the analysis results or + /// std::nullopt if the analysis was cancelled. + template || + std::is_invocable_r_v>> + auto continueUntil(CancellationRequest CancellationRequested, + std::chrono::milliseconds Interval = std::chrono::seconds{ + 1}) && { + using RetTy = + std::optional>; + return [&]() -> RetTy { + if (continueUntilImpl(std::move(CancellationRequested), Interval)) { + return std::move(*this).finalize(); } return std::nullopt; }(); @@ -161,8 +264,8 @@ template class IDESolverAPIMixin { /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. - auto solveTimeout(std::chrono::milliseconds Timeout, - std::chrono::milliseconds Interval) & { + auto solveWithTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Interval) & { auto CancellationRequested = [Timeout, Start = std::chrono::steady_clock::now()]( std::chrono::steady_clock::time_point TimeStamp) { @@ -180,8 +283,8 @@ template class IDESolverAPIMixin { /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. - auto solveTimeout(std::chrono::milliseconds Timeout, - std::chrono::milliseconds Interval) && { + auto solveWithTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Interval) && { auto CancellatioNRequested = [Timeout, Start = std::chrono::steady_clock::now()]( std::chrono::steady_clock::time_point TimeStamp) { @@ -190,6 +293,58 @@ template class IDESolverAPIMixin { return std::move(*this).solveUntil(CancellatioNRequested, Interval); } + /// Continues running the solver on the configured problem after it got + /// interrupted in a previous run. Periodically checks every Interval whether + /// the Timeout has been exceeded. + /// + /// \remark Please make sure to *not* call this function on an IDESolver where + /// the solving process is interrupted, i.e. one of hte interruptable solving + /// methods returned std::nullopt. It is *invalid* to call this function on an + /// IDESolver that has not yet started solvong or has already flinalized + /// solving. + /// + /// Note: Shortening the timeout-check interval will make the timeout more + /// precise but also have negative impact on the solver's performance. + /// + /// \returns An std::optional holding a view into the analysis results or + /// std::nullopt if the analysis was cancelled. + auto continueWithTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Interval) & { + auto CancellationRequested = + [Timeout, Start = std::chrono::steady_clock::now()]( + std::chrono::steady_clock::time_point TimeStamp) { + return TimeStamp - Start >= Timeout; + }; + + return continueUntil(CancellationRequested, Interval); + } + + /// Continues running the solver on the configured problem after it got + /// interrupted in a previous run. Periodically checks every Interval whether + /// the Timeout has been exceeded. + /// + /// \remark Please make sure to *not* call this function on an IDESolver where + /// the solving process is interrupted, i.e. one of hte interruptable solving + /// methods returned std::nullopt. It is *invalid* to call this function on an + /// IDESolver that has not yet started solvong or has already flinalized + /// solving. + /// + /// Note: Shortening the timeout-check interval will make the timeout more + /// precise but also have negative impact on the solver's performance. + /// + /// \returns An std::optional holding a view into the analysis results or + /// std::nullopt if the analysis was cancelled. + auto continueWithTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Interval) && { + auto CancellationRequested = + [Timeout, Start = std::chrono::steady_clock::now()]( + std::chrono::steady_clock::time_point TimeStamp) { + return TimeStamp - Start >= Timeout; + }; + + return std::move(*this).continueUntil(CancellationRequested, Interval); + } + // -- Async cancellation /// Solves the analysis problen and periodically checks whether @@ -198,10 +353,10 @@ template class IDESolverAPIMixin { /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. auto solveWithAsyncCancellation(std::atomic_bool &IsCancelled) & { - using RetTy = std::optional>; + using RetTy = std::optional>; return [&]() -> RetTy { if (solveWithAsyncCancellationImpl(IsCancelled)) { - return self().finalize(); + return finalize(); } return std::nullopt; }(); @@ -214,10 +369,55 @@ template class IDESolverAPIMixin { /// the analysis was cancelled. auto solveWithAsyncCancellation(std::atomic_bool &IsCancelled) && { using RetTy = - std::optional>; + std::optional>; return [&]() -> RetTy { if (solveWithAsyncCancellationImpl(IsCancelled)) { - return std::move(self()).finalize(); + return std::move(*this).finalize(); + } + return std::nullopt; + }(); + } + + /// Continues running the solver on the configured problem after it got + /// interrupted in a previous run. Periodically checks whether + /// IsCancelled is true. + /// + /// \remark Please make sure to *not* call this function on an IDESolver where + /// the solving process is interrupted, i.e. one of hte interruptable solving + /// methods returned std::nullopt. It is *invalid* to call this function on an + /// IDESolver that has not yet started solvong or has already flinalized + /// solving. + /// + /// \returns An std::optional holding a view into the analysis results or + /// std::nullopt if the analysis was cancelled. + auto continueWithAsyncCancellation(std::atomic_bool &IsCancelled) & { + using RetTy = std::optional>; + return [&]() -> RetTy { + if (continueWithAsyncCancellationImpl(IsCancelled)) { + return finalize(); + } + return std::nullopt; + }(); + } + + /// Continues running the solver on the configured problem after it got + /// interrupted in a previous run. Periodically checks whether + /// IsCancelled is true. + /// + /// \remark Please make sure to *not* call this function on an IDESolver where + /// the solving process is interrupted, i.e. one of hte interruptable solving + /// methods returned std::nullopt. It is *invalid* to call this function on an + /// IDESolver that has not yet started solvong or has already flinalized + /// solving. + /// + /// \returns An std::optional holding a view into the analysis results or + /// std::nullopt if the analysis was cancelled. + auto continueWithAsyncCancellation(std::atomic_bool &IsCancelled) && { + using RetTy = + std::optional>; + return [&]() -> RetTy { + if (continueWithAsyncCancellationImpl(IsCancelled)) { + return std::move(*this).finalize(); } return std::nullopt; }(); @@ -230,19 +430,22 @@ template class IDESolverAPIMixin { return static_cast(*this); } - static void solveImpl(Derived &Self) { - if (Self.initialize()) { - while (Self.next()) { - // no interrupt in normal solving process - } + void continueImpl() { + while (next()) { + // no interrupt in normal solving process } } - template - [[nodiscard]] bool solveUntilImpl(CancellationRequest CancellationRequested, - std::chrono::milliseconds Interval) { + void solveImpl() { + if (initialize()) { + continueImpl(); + } + } - size_t NumIterations = Interval.count() * 500; + template + [[nodiscard]] bool + continueUntilImpl(CancellationRequest CancellationRequested, + std::chrono::milliseconds Interval) { auto IsCancellationRequested = [&CancellationRequested]( std::chrono::steady_clock::time_point TimeStamp) { @@ -255,52 +458,76 @@ template class IDESolverAPIMixin { } }; - if (self().initialize()) { - auto Start = std::chrono::steady_clock::now(); - - if (IsCancellationRequested(Start)) { - return false; - } + size_t NumIterations = Interval.count() * 500; - while (self().nextN(NumIterations)) { - auto End = std::chrono::steady_clock::now(); - using milliseconds_d = std::chrono::duration; + auto Start = std::chrono::steady_clock::now(); - auto DeltaTime = - std::chrono::duration_cast(End - Start); - Start = End; + while (nextN(NumIterations)) { + auto End = std::chrono::steady_clock::now(); + using milliseconds_d = std::chrono::duration; - if (IsCancellationRequested(End)) { - return false; - } + auto DeltaTime = std::chrono::duration_cast(End - Start); + Start = End; - // Adjust NumIterations - auto IterationsPerMilli = double(NumIterations) / DeltaTime.count(); - auto NewNumIterations = - size_t(IterationsPerMilli * double(Interval.count())); - NumIterations = (NumIterations + 2 * NewNumIterations) / 3; + if (IsCancellationRequested(End)) { + return false; } - } + // Adjust NumIterations + auto IterationsPerMilli = double(NumIterations) / DeltaTime.count(); + auto NewNumIterations = + size_t(IterationsPerMilli * double(Interval.count())); + NumIterations = (NumIterations + 2 * NewNumIterations) / 3; + } auto End = std::chrono::steady_clock::now(); return !IsCancellationRequested(End); } + template + [[nodiscard]] bool solveUntilImpl(CancellationRequest CancellationRequested, + std::chrono::milliseconds Interval) { + auto IsCancellationRequested = + [&CancellationRequested]( + std::chrono::steady_clock::time_point TimeStamp) { + if constexpr (std::is_invocable_r_v< + bool, CancellationRequest, + std::chrono::steady_clock::time_point>) { + return std::invoke(CancellationRequested, TimeStamp); + } else { + return std::invoke(CancellationRequested); + } + }; + + bool Initialized = initialize(); + auto TimeStamp = std::chrono::steady_clock::now(); + if (IsCancellationRequested(TimeStamp)) { + return false; + } + + return Initialized + ? continueUntilImpl(std::move(CancellationRequested), Interval) + : true; + } + [[nodiscard]] bool - solveWithAsyncCancellationImpl(std::atomic_bool &IsCancelled) { - if (self().initialize()) { + continueWithAsyncCancellationImpl(std::atomic_bool &IsCancelled) { + while (next()) { if (IsCancelled.load()) { return false; } - while (self().next()) { - if (IsCancelled.load()) { - return false; - } - } } - return !IsCancelled.load(); } + + [[nodiscard]] bool + solveWithAsyncCancellationImpl(std::atomic_bool &IsCancelled) { + bool Initialized = initialize(); + if (IsCancelled.load()) { + return false; + } + + return Initialized ? continueWithAsyncCancellationImpl(IsCancelled) : true; + } }; } // namespace psr From 4034a6833ebc0afe8ab8954dae8d7f9f4a078c19 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Mon, 26 Jun 2023 17:02:39 +0200 Subject: [PATCH 10/13] Make the PAMM tests sleep less --- unittests/Utils/PAMMTest.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/unittests/Utils/PAMMTest.cpp b/unittests/Utils/PAMMTest.cpp index bec561d750..0450569a55 100644 --- a/unittests/Utils/PAMMTest.cpp +++ b/unittests/Utils/PAMMTest.cpp @@ -29,9 +29,9 @@ class PAMMTest : public ::testing::Test { TEST_F(PAMMTest, HandleTimer) { // PAMM &pamm = PAMM::getInstance(); PAMM::getInstance().startTimer("timer1"); - std::this_thread::sleep_for(std::chrono::milliseconds(1200)); + std::this_thread::sleep_for(std::chrono::milliseconds(120)); PAMM::getInstance().stopTimer("timer1"); - EXPECT_GE(PAMM::getInstance().elapsedTime("timer1"), 1200U); + EXPECT_GE(PAMM::getInstance().elapsedTime("timer1"), 120U); } TEST_F(PAMMTest, HandleCounter) { @@ -50,6 +50,8 @@ TEST_F(PAMMTest, HandleCounter) { } TEST_F(PAMMTest, HandleJSONOutput) { + GTEST_SKIP() << "We should really assert something in this test..."; + PAMM &Pamm = PAMM::getInstance(); Pamm.regCounter("timerCount"); Pamm.regCounter("setOpCount"); From f22a944071aaa89df648421051b5c823f088fda7 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Wed, 28 Jun 2023 18:38:51 +0200 Subject: [PATCH 11/13] Add tests for async cancellation and continuing --- .../IfdsIde/InteractiveIDESolverTest.cpp | 79 ++++++++++++++++--- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp index 998fc5483a..6ff07a20fd 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp @@ -4,6 +4,7 @@ #include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h" #include "phasar/PhasarLLVM/HelperAnalyses.h" #include "phasar/PhasarLLVM/SimpleAnalysisConstructor.h" +#include "phasar/Utils/TypeTraits.h" #include "TestConfig.h" #include "gtest/gtest.h" @@ -37,17 +38,77 @@ TEST_P(LinearConstant, ResultsEquivalentSolveUntil) { : "main"}); auto AtomicResults = IDESolver(LCAProblem, &ICFG).solve(); - auto InteractiveResults = IDESolver(LCAProblem, &ICFG).solveUntil(FalseFn{}); - // if (PrintDump) { - // HA.getProjectIRDB().dump(); - // ICFG.print(); - // AtomicResults.dumpResults(ICFG, LCAProblem); - // } - - ASSERT_TRUE(InteractiveResults.has_value()); + { + auto InteractiveResults = + IDESolver(LCAProblem, &ICFG).solveUntil(FalseFn{}); + + ASSERT_TRUE(InteractiveResults.has_value()); + for (auto &&Cell : AtomicResults.getAllResultEntries()) { + auto InteractiveRes = + InteractiveResults->resultAt(Cell.getRowKey(), Cell.getColumnKey()); + EXPECT_EQ(InteractiveRes, Cell.getValue()); + } + } + auto InterruptedResults = [&] { + IDESolver Solver(LCAProblem, &ICFG); + auto Result = Solver.solveUntil(TrueFn{}); + EXPECT_EQ(std::nullopt, Result); + if (!Result) { + return std::move(Solver).continueSolving(); + } + return Solver.consumeSolverResults(); + }(); + + for (auto &&Cell : AtomicResults.getAllResultEntries()) { + auto InteractiveRes = + InterruptedResults.resultAt(Cell.getRowKey(), Cell.getColumnKey()); + EXPECT_EQ(InteractiveRes, Cell.getValue()); + } +} + +TEST_P(LinearConstant, ResultsEquivalentSolveUntilAsync) { + HelperAnalyses HA(PathToLlFiles + GetParam(), EntryPoints); + + // Compute the ICFG to possibly create the runtime model + auto &ICFG = HA.getICFG(); + + auto HasGlobalCtor = HA.getProjectIRDB().getFunctionDefinition( + LLVMBasedICFG::GlobalCRuntimeModelName) != nullptr; + + auto LCAProblem = createAnalysisProblem( + HA, + std::vector{HasGlobalCtor ? LLVMBasedICFG::GlobalCRuntimeModelName.str() + : "main"}); + + auto AtomicResults = IDESolver(LCAProblem, &ICFG).solve(); + + { + std::atomic_bool IsCancelled = false; + auto InteractiveResults = + IDESolver(LCAProblem, &ICFG).solveWithAsyncCancellation(IsCancelled); + + ASSERT_TRUE(InteractiveResults.has_value()); + for (auto &&Cell : AtomicResults.getAllResultEntries()) { + auto InteractiveRes = + InteractiveResults->resultAt(Cell.getRowKey(), Cell.getColumnKey()); + EXPECT_EQ(InteractiveRes, Cell.getValue()); + } + } + auto InterruptedResults = [&] { + IDESolver Solver(LCAProblem, &ICFG); + std::atomic_bool IsCancelled = true; + auto Result = Solver.solveWithAsyncCancellation(IsCancelled); + EXPECT_EQ(std::nullopt, Result); + if (!Result) { + IsCancelled = false; + return std::move(Solver).solveWithAsyncCancellation(IsCancelled).value(); + } + return Solver.consumeSolverResults(); + }(); + for (auto &&Cell : AtomicResults.getAllResultEntries()) { auto InteractiveRes = - InteractiveResults->resultAt(Cell.getRowKey(), Cell.getColumnKey()); + InterruptedResults.resultAt(Cell.getRowKey(), Cell.getColumnKey()); EXPECT_EQ(InteractiveRes, Cell.getValue()); } } From d35330b58d217b30bf3f912a484de52999ff3190 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Wed, 28 Jun 2023 19:23:14 +0200 Subject: [PATCH 12/13] Update comments --- .../IfdsIde/Solver/IDESolverAPIMixin.h | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h index 572f07198b..2b0fd0bbe2 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h @@ -103,11 +103,11 @@ template class IDESolverAPIMixin { /// interrupted. If you need the ability to interrupt the solving process /// consider using solveUntil() or solveTimeout(). /// - /// \remark Please make sure to *not* call this function on an IDESolver where - /// the solving process is interrupted, i.e. one of hte interruptable solving - /// methods returned std::nullopt. It is *invalid* to call this function on an - /// IDESolver that has not yet started solvong or has already flinalized - /// solving. + /// \remark Please make sure to *only* call this function on an IDESolver + /// where the solving process is interrupted, i.e. one of the interruptable + /// solving methods returned std::nullopt. It is *invalid* to call this + /// function on an IDESolver that has not yet started solving or has already + /// flinalized solving. /// /// \returns A view into the computed analysis results decltype(auto) continueSolving() & { @@ -120,11 +120,11 @@ template class IDESolverAPIMixin { /// interrupted. If you need the ability to interrupt the solving process /// consider using solveUntil() or solveTimeout(). /// - /// \remark Please make sure to *not* call this function on an IDESolver where - /// the solving process is interrupted, i.e. one of hte interruptable solving - /// methods returned std::nullopt. It is *invalid* to call this function on an - /// IDESolver that has not yet started solvong or has already flinalized - /// solving. + /// \remark Please make sure to *only* call this function on an IDESolver + /// where the solving process is interrupted, i.e. one of the interruptable + /// solving methods returned std::nullopt. It is *invalid* to call this + /// function on an IDESolver that has not yet started solving or has already + /// flinalized solving. /// /// \returns The computed analysis results decltype(auto) continueSolving() && { @@ -191,11 +191,11 @@ template class IDESolverAPIMixin { /// interrupted in a previous run. Periodically checks every Interval whether /// CancellationRequested evaluates to true. /// - /// \remark Please make sure to *not* call this function on an IDESolver where - /// the solving process is interrupted, i.e. one of hte interruptable solving - /// methods returned std::nullopt. It is *invalid* to call this function on an - /// IDESolver that has not yet started solvong or has already flinalized - /// solving. + /// \remark Please make sure to *only* call this function on an IDESolver + /// where the solving process is interrupted, i.e. one of the interruptable + /// solving methods returned std::nullopt. It is *invalid* to call this + /// function on an IDESolver that has not yet started solving or has already + /// flinalized solving. /// /// Note: Shortening the cancellation-check interval will make the /// cancellation-time more precise (by having more frequent calls to @@ -225,11 +225,11 @@ template class IDESolverAPIMixin { /// interrupted in a previous run. Periodically checks every Interval whether /// CancellationRequested evaluates to true. /// - /// \remark Please make sure to *not* call this function on an IDESolver where - /// the solving process is interrupted, i.e. one of hte interruptable solving - /// methods returned std::nullopt. It is *invalid* to call this function on an - /// IDESolver that has not yet started solvong or has already flinalized - /// solving. + /// \remark Please make sure to *only* call this function on an IDESolver + /// where the solving process is interrupted, i.e. one of the interruptable + /// solving methods returned std::nullopt. It is *invalid* to call this + /// function on an IDESolver that has not yet started solving or has already + /// flinalized solving. /// /// Note: Shortening the cancellation-check interval will make the /// cancellation-time more precise (by having more frequent calls to @@ -297,11 +297,11 @@ template class IDESolverAPIMixin { /// interrupted in a previous run. Periodically checks every Interval whether /// the Timeout has been exceeded. /// - /// \remark Please make sure to *not* call this function on an IDESolver where - /// the solving process is interrupted, i.e. one of hte interruptable solving - /// methods returned std::nullopt. It is *invalid* to call this function on an - /// IDESolver that has not yet started solvong or has already flinalized - /// solving. + /// \remark Please make sure to *only* call this function on an IDESolver + /// where the solving process is interrupted, i.e. one of the interruptable + /// solving methods returned std::nullopt. It is *invalid* to call this + /// function on an IDESolver that has not yet started solving or has already + /// flinalized solving. /// /// Note: Shortening the timeout-check interval will make the timeout more /// precise but also have negative impact on the solver's performance. @@ -323,11 +323,11 @@ template class IDESolverAPIMixin { /// interrupted in a previous run. Periodically checks every Interval whether /// the Timeout has been exceeded. /// - /// \remark Please make sure to *not* call this function on an IDESolver where - /// the solving process is interrupted, i.e. one of hte interruptable solving - /// methods returned std::nullopt. It is *invalid* to call this function on an - /// IDESolver that has not yet started solvong or has already flinalized - /// solving. + /// \remark Please make sure to *only* call this function on an IDESolver + /// where the solving process is interrupted, i.e. one of the interruptable + /// solving methods returned std::nullopt. It is *invalid* to call this + /// function on an IDESolver that has not yet started solving or has already + /// flinalized solving. /// /// Note: Shortening the timeout-check interval will make the timeout more /// precise but also have negative impact on the solver's performance. @@ -382,11 +382,11 @@ template class IDESolverAPIMixin { /// interrupted in a previous run. Periodically checks whether /// IsCancelled is true. /// - /// \remark Please make sure to *not* call this function on an IDESolver where - /// the solving process is interrupted, i.e. one of hte interruptable solving - /// methods returned std::nullopt. It is *invalid* to call this function on an - /// IDESolver that has not yet started solvong or has already flinalized - /// solving. + /// \remark Please make sure to *only* call this function on an IDESolver + /// where the solving process is interrupted, i.e. one of the interruptable + /// solving methods returned std::nullopt. It is *invalid* to call this + /// function on an IDESolver that has not yet started solving or has already + /// flinalized solving. /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. @@ -404,11 +404,11 @@ template class IDESolverAPIMixin { /// interrupted in a previous run. Periodically checks whether /// IsCancelled is true. /// - /// \remark Please make sure to *not* call this function on an IDESolver where - /// the solving process is interrupted, i.e. one of hte interruptable solving - /// methods returned std::nullopt. It is *invalid* to call this function on an - /// IDESolver that has not yet started solvong or has already flinalized - /// solving. + /// \remark Please make sure to *only* call this function on an IDESolver + /// where the solving process is interrupted, i.e. one of the interruptable + /// solving methods returned std::nullopt. It is *invalid* to call this + /// function on an IDESolver that has not yet started solving or has already + /// flinalized solving. /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. From 886ffbdb8074799c1a1f91129412c7e56a4ee9ce Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Thu, 29 Jun 2023 13:03:30 +0200 Subject: [PATCH 13/13] Apply review comments --- .../DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h | 16 +++++++++------- unittests/Utils/PAMMTest.cpp | 6 ++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h index 2b0fd0bbe2..65ae8a8908 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h @@ -55,7 +55,7 @@ template class IDESolverAPIMixin { PHASAR_LOG_LEVEL(DEBUG, "[nextN]: Next " << MaxNumIterations << " Iterations"); - for (size_t I = 0, End = MaxNumIterations; I != End; ++I) { + for (size_t I = 0; I != MaxNumIterations; ++I) { if (!next()) { PHASAR_LOG_LEVEL(DEBUG, "[nextN]: > done after " << I << " iterations"); return false; @@ -132,7 +132,7 @@ template class IDESolverAPIMixin { return std::move(*this).finalize(); } - /// Solves the analysis problen and periodically checks every Interval whether + /// Solves the analysis problem and periodically checks every Interval whether /// CancellationRequested evaluates to true. /// /// Note: Shortening the cancellation-check interval will make the @@ -159,7 +159,7 @@ template class IDESolverAPIMixin { }(); } - /// Solves the analysis problen and periodically checks every Interval whether + /// Solves the analysis problem and periodically checks every Interval whether /// CancellationRequested evaluates to true. /// /// Note: Shortening the cancellation-check interval will make the @@ -256,7 +256,7 @@ template class IDESolverAPIMixin { }(); } - /// Solves the analysis problen and periodically checks every Interval whether + /// Solves the analysis problem and periodically checks every Interval whether /// the Timeout has been exceeded. /// /// Note: Shortening the timeout-check interval will make the timeout more @@ -275,7 +275,7 @@ template class IDESolverAPIMixin { return solveUntil(CancellationRequested, Interval); } - /// Solves the analysis problen and periodically checks every Interval whether + /// Solves the analysis problem and periodically checks every Interval whether /// the Timeout has been exceeded. /// /// Note: Shortening the timeout-check interval will make the timeout more @@ -347,7 +347,7 @@ template class IDESolverAPIMixin { // -- Async cancellation - /// Solves the analysis problen and periodically checks whether + /// Solves the analysis problem and periodically checks whether /// IsCancelled is true. /// /// \returns An std::optional holding a view into the analysis results or @@ -362,7 +362,7 @@ template class IDESolverAPIMixin { }(); } - /// Solves the analysis problen and periodically checks whether + /// Solves the analysis problem and periodically checks whether /// IsCancelled is true. /// /// \returns An std::optional holding the analysis results or std::nullopt if @@ -458,6 +458,8 @@ template class IDESolverAPIMixin { } }; + // Some initial number of propagations to get an idea, how long a + // propagation takes. This may be adjusted in the future size_t NumIterations = Interval.count() * 500; auto Start = std::chrono::steady_clock::now(); diff --git a/unittests/Utils/PAMMTest.cpp b/unittests/Utils/PAMMTest.cpp index 0450569a55..bec561d750 100644 --- a/unittests/Utils/PAMMTest.cpp +++ b/unittests/Utils/PAMMTest.cpp @@ -29,9 +29,9 @@ class PAMMTest : public ::testing::Test { TEST_F(PAMMTest, HandleTimer) { // PAMM &pamm = PAMM::getInstance(); PAMM::getInstance().startTimer("timer1"); - std::this_thread::sleep_for(std::chrono::milliseconds(120)); + std::this_thread::sleep_for(std::chrono::milliseconds(1200)); PAMM::getInstance().stopTimer("timer1"); - EXPECT_GE(PAMM::getInstance().elapsedTime("timer1"), 120U); + EXPECT_GE(PAMM::getInstance().elapsedTime("timer1"), 1200U); } TEST_F(PAMMTest, HandleCounter) { @@ -50,8 +50,6 @@ TEST_F(PAMMTest, HandleCounter) { } TEST_F(PAMMTest, HandleJSONOutput) { - GTEST_SKIP() << "We should really assert something in this test..."; - PAMM &Pamm = PAMM::getInstance(); Pamm.regCounter("timerCount"); Pamm.regCounter("setOpCount");