include/Analyzer.h (209 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <boost/optional.hpp>
#include <functional>
#include <memory>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "AbstractDomain.h"
namespace sparta {
// To achieve a high level of metaprogramming, we use a little C++ trick to
// check for existence of a member function. This is implemented with SFINAE.
//
// More details:
// https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Member_Detector
// https://stackoverflow.com/questions/257288/templated-check-for-the-existence-of-a-class-member-function
#define HAS_MEM_FUNC(func, name) \
template <typename T, typename Sign> \
struct name { \
typedef char yes[1]; \
typedef char no[2]; \
template <typename U, U> \
struct type_check; \
template <typename _1> \
static yes& chk(type_check<Sign, &_1::func>*); \
template <typename> \
static no& chk(...); \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}
template <bool C, typename T = void>
struct enable_if {
typedef T type;
};
template <typename T>
struct enable_if<false, T> {};
HAS_MEM_FUNC(analyze_edge, has_analyze_edge);
// The compiler is going to optionally enable either of the following
template <typename Callsite, typename Edge, typename Domain>
typename enable_if<
has_analyze_edge<Callsite,
Domain (Callsite::*)(const Edge&, const Domain&)>::value,
Domain>::type
optionally_analyze_edge_if_exist(Callsite*,
const Edge& edge,
const Domain& domain) {
static Callsite c2;
return c2.analyze_edge(edge, domain);
}
template <typename Callsite, typename Edge, typename Domain>
typename enable_if<
!has_analyze_edge<Callsite,
Domain (Callsite::*)(const Edge&, const Domain&)>::value,
Domain>::type
optionally_analyze_edge_if_exist(Callsite* c,
const Edge&,
const Domain& domain) {
return domain;
}
// Function level analyzer need to extend this class. This class supports the
// static assertion in the InterproceduralAnalyzer. This choice is made so that
// the compiler will throw reasonable errors when the provided function
// analyzers don't implement the necessary methods. We prefer this over template
// errors.
template <typename FunctionSummaries,
typename CallerContext,
typename CallGraph,
typename AnalysisParameters = void>
class Intraprocedural {
public:
virtual void analyze() = 0;
virtual void summarize() = 0;
virtual ~Intraprocedural() {}
void set_summaries(FunctionSummaries* summaries) {
this->m_summaries = summaries;
}
void set_caller_context(CallerContext* context) {
this->m_caller_context = context;
}
void set_call_graph(const CallGraph* graph) { this->m_call_graph = graph; }
void set_analysis_parameters(AnalysisParameters* parameters) {
this->m_analysis_parameters = parameters;
}
protected:
FunctionSummaries* get_summaries() const { return this->m_summaries; }
CallerContext* get_caller_context() const { return this->m_caller_context; }
const CallGraph* get_call_graph() const { return this->m_call_graph; }
AnalysisParameters* get_analysis_parameters() const {
return this->m_analysis_parameters;
}
private:
FunctionSummaries* m_summaries;
CallerContext* m_caller_context;
const CallGraph* m_call_graph;
AnalysisParameters* m_analysis_parameters;
};
class AbstractRegistry {
public:
virtual bool has_update() const = 0;
virtual void materialize_update() = 0;
virtual ~AbstractRegistry() {}
};
// Typical Usage:
// struct IRAdaptor /* defined for the IR */ {
// type Function (an analysis unit),
// type Program (The data structure that holds functions),
// type CallGraphInterface (The interface used in fixpoint iterators),
// function call_graph_of,
// };
// struct Analysis : public IRAdaptor {
// type Registry (Function Summaries)
// type FunctionAnalyzer (A class that extends `Intraprocedural`),
// type Callsite (Calling context),
// type MonotonicFixpointIterator (Choice of `MonotonicFixpointIterator`s),
// };
// struct Callsite {
// type Domain
// analyze_edge (optional)
// }
template <typename Analysis, typename AnalysisParameters = void>
class InterproceduralAnalyzer {
public:
using Function = typename Analysis::Function;
using Program = typename Analysis::Program;
using Registry = typename Analysis::Registry;
using CallGraphInterface = typename Analysis::CallGraphInterface;
using CallGraph = typename CallGraphInterface::Graph;
using Callsite = typename Analysis::Callsite;
using CallerContext = typename Callsite::Domain;
using FunctionAnalyzer = typename Analysis::template FunctionAnalyzer<
Intraprocedural<Registry, CallerContext, CallGraph, AnalysisParameters>>;
using IntraFn = std::function<std::shared_ptr<FunctionAnalyzer>(
const Function&, Registry*, CallerContext*)>;
Registry registry;
class CallGraphFixpointIterator final
: public Analysis::template FixpointIteratorBase<
CallGraphInterface,
typename Callsite::Domain> {
private:
IntraFn m_intraprocedural;
Registry* m_registry;
public:
static CallerContext initial_domain() { return CallerContext(); }
explicit CallGraphFixpointIterator(const CallGraph& graph,
Registry* registry,
const IntraFn& intraprocedural)
: Analysis::template FixpointIteratorBase<CallGraphInterface,
CallerContext>(graph),
m_intraprocedural(intraprocedural),
m_registry(registry) {}
virtual void analyze_node(const typename CallGraphInterface::NodeId& node,
CallerContext* current_state) const override {
m_intraprocedural(Analysis::function_by_node_id(node), this->m_registry,
current_state)
->summarize();
}
CallerContext analyze_edge(
const typename CallGraphInterface::EdgeId& edge,
const CallerContext& exit_state_at_source) const override {
return optionally_analyze_edge_if_exist<
Callsite, typename CallGraphInterface::EdgeId, CallerContext>(
nullptr, edge, exit_state_at_source);
}
};
virtual ~InterproceduralAnalyzer() {
static_assert(std::is_base_of<AbstractRegistry, Registry>::value,
"Registry must inherit from sparta::AbstractRegistry");
static_assert(
std::is_base_of<Intraprocedural<Registry, CallerContext, CallGraph,
AnalysisParameters>,
FunctionAnalyzer>::value,
"FunctionAnalyzer must inherit from sparta::Intraprocedural");
static_assert(
std::is_base_of<AbstractDomain<CallerContext>, CallerContext>::value,
"Callsite::Domain must inherit from sparta::AbstractDomain");
}
InterproceduralAnalyzer(Program program,
int max_iteration,
AnalysisParameters* parameters = nullptr)
: m_program(std::move(program)),
m_max_iteration(max_iteration),
m_parameters(parameters) {}
virtual std::shared_ptr<CallGraphFixpointIterator> run(
bool rebuild_callgraph_on_each_iteration = false) {
// keep a copy of old function summaries, do fixpoint on this level.
std::shared_ptr<CallGraphFixpointIterator> fp = nullptr;
boost::optional<CallGraph> callgraph = boost::none;
for (int iteration = 0; iteration < m_max_iteration; iteration++) {
if (m_logger) {
(*m_logger)(std::string("Iteration ") + std::to_string(iteration + 1));
}
if (!callgraph || rebuild_callgraph_on_each_iteration) {
callgraph = Analysis::call_graph_of(m_program, &this->registry);
}
if (!fp || rebuild_callgraph_on_each_iteration) {
// If the callgraph requires to be rebuilt, we need to rebuild the
// iterator as well as weak partial ordering should be updated.
fp = std::make_shared<CallGraphFixpointIterator>(
*callgraph,
&this->registry,
[this, callgraph](
const Function& func, Registry* reg,
CallerContext* context) -> std::shared_ptr<FunctionAnalyzer> {
// intraprocedural part
return this->run_on_function(func, reg, context, &*callgraph);
});
}
fp->run(CallGraphFixpointIterator::initial_domain());
if (this->registry.has_update()) {
this->registry.materialize_update();
} else {
if (m_logger) {
(*m_logger)(std::string("Global fixpoint reached after ") +
std::to_string(iteration + 1) + " iterations.");
}
break;
}
}
return fp;
}
virtual std::shared_ptr<FunctionAnalyzer> run_on_function(
const Function& function,
Registry* reg,
CallerContext* context,
const CallGraph* graph) {
auto analyzer = std::make_shared<FunctionAnalyzer>(function);
analyzer->set_summaries(reg);
analyzer->set_caller_context(context);
analyzer->set_call_graph(graph);
analyzer->set_analysis_parameters(m_parameters);
analyzer->analyze();
return analyzer;
}
void set_logger(const std::function<void(const std::string&)>& logger) {
m_logger = logger;
}
private:
Program m_program;
int m_max_iteration;
AnalysisParameters* m_parameters;
boost::optional<std::function<void(const std::string&)>> m_logger =
boost::none;
};
} // namespace sparta