Skip to content

Commit

Permalink
Abstract interpretation framework: allow selective domain retention
Browse files Browse the repository at this point in the history
This allows users of the `ait` abstract interpretation framework to specify that only certain states are needed after
its fixpoint computation is finished. For example, perhaps a particular analysis, or a particular user of that analysis,
only needs to know the results at END_FUNCTION instructions. In this case when a state does not need to be retained, and
only has a single successor, that state will be altered in place and passed to its successor instruction using std::move,
permitting an analysis to optimise away much (potentially all) state copying.

In the best case (a long, straight-line function whose state grows with every instruction, such as a constant propagator
reading "int x = 1; int y = 2; int z = 3; ...") this reduces analysis space and time complexity from quadratic to linear.
  • Loading branch information
smowton committed Jul 11, 2018
1 parent 7c4b5aa commit 82f11c9
Show file tree
Hide file tree
Showing 4 changed files with 527 additions and 12 deletions.
77 changes: 68 additions & 9 deletions src/analyses/ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,15 +334,33 @@ bool ai_baset::visit(

statet &current=get_state(l);

for(const auto &to_l : goto_program.get_successors(l))
// Check if our user wants to be able to inspect this state after the
// fixpoint algorithm terminates.
// Default if the instantiating code hasn't supplied any specific advice is
// to retain all states.
bool must_retain_current_state = must_retain_state(l, goto_program);

std::list<locationt> successors = goto_program.get_successors(l);
for(const auto &to_l : successors)
{
if(to_l==goto_program.instructions.end())
continue;

std::unique_ptr<statet> tmp_state(
make_temporary_state(current));
// If permissible, alter the existing instruction's state in place;
// otherwise use a temporary copy:
std::unique_ptr<statet> tmp_state;

statet &new_values=*tmp_state;
// Note this condition is stricter than `!must_retain_current_state` because
// when we have multiple successors `transform` may do something different
// for different successors, in which case we must use a clean copy of the
// pre-existing state each time.
bool may_work_in_place =
successors.size() == 1 && !must_retain_current_state;

if(!may_work_in_place)
tmp_state = make_temporary_state(current);

statet &new_values = may_work_in_place ? current : *tmp_state;

bool have_new_values=false;

Expand All @@ -367,8 +385,7 @@ bool ai_baset::visit(

new_values.transform(l, to_l, *this, ns);

if(merge(new_values, l, to_l))
have_new_values=true;
have_new_values |= merge(std::move(new_values), l, to_l);
}

if(have_new_values)
Expand All @@ -378,6 +395,12 @@ bool ai_baset::visit(
}
}

if(!must_retain_current_state)
{
// If this state isn't needed any longer, destroy it now:
current.make_bottom();
}

return new_data;
}

Expand All @@ -400,7 +423,7 @@ bool ai_baset::do_function_call(
std::unique_ptr<statet> tmp_state(make_temporary_state(get_state(l_call)));
tmp_state->transform(l_call, l_return, *this, ns);

return merge(*tmp_state, l_call, l_return);
return merge(std::move(*tmp_state), l_call, l_return);
}

assert(!goto_function.body.instructions.empty());
Expand All @@ -420,7 +443,7 @@ bool ai_baset::do_function_call(
bool new_data=false;

// merge the new stuff
if(merge(*tmp_state, l_call, l_begin))
if(merge(std::move(*tmp_state), l_call, l_begin))
new_data=true;

// do we need to do/re-do the fixedpoint of the body?
Expand All @@ -445,7 +468,7 @@ bool ai_baset::do_function_call(
tmp_state->transform(l_end, l_return, *this, ns);

// Propagate those
return merge(*tmp_state, l_end, l_return);
return merge(std::move(*tmp_state), l_end, l_return);
}
}

Expand Down Expand Up @@ -523,6 +546,42 @@ bool ai_baset::do_function_call_rec(
return new_data;
}

/// Determine whether we must retain the state for program point `l` even after
/// fixpoint analysis completes (e.g. for our user to inspect), or if it solely
/// for internal use and can be thrown away as and when suits us.
/// \param l: program point
/// \param l_program: program that l belongs to
/// \return true if we must retain the state for that program point
bool ai_baset::must_retain_state(
locationt l, const goto_programt &l_program) const
{
// If the derived class doesn't specify otherwise, assume the old default
// behaviour: always retain state.
if(!must_retain_state_callback)
return true;

// Regardless, always keep states with multiple predecessors, which is
// required for termination when loops are present, and saves redundant
// re-investigation of successors for other kinds of convergence.
if(l->incoming_edges.size() > 1)
return true;

// Similarly retain function head instructions, which may have multiple
// incoming call-graph edges:
if(l == l_program.instructions.begin())
return true;

// ...and retain function end states, as they will be propagated to many
// different callers (they have multiple successors, even though they appear
// to have none intraprocedurally).
if(l->is_end_function())
return true;

// Finally, retain states where the particular subclass specifies they
// should be kept:
return must_retain_state_callback(l);
}

void ai_baset::sequential_fixedpoint(
const goto_functionst &goto_functions,
const namespacet &ns)
Expand Down
29 changes: 26 additions & 3 deletions src/analyses/ai.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,19 @@ class ai_domain_baset
// also add
//
// bool merge(const T &b, locationt from, locationt to);
// and optionally
// bool merge(T &&b, location from, locationt to);
//
// This computes the join between "this" and "b".
// These compute the join between "this" and "b".
// Return true if "this" has changed.
// In the usual case, "b" is the updated state after "from"
// and "this" is the state before "to".
//
// PRECONDITION(from.is_dereferenceable(), "Must not be _::end()")
// PRECONDITION(to.is_dereferenceable(), "Must not be _::end()")
//
// The T &&b overload, if any, must leave `b` in such a state that
// `b.make_bottom()` or destruction of `b` could happen next without error.

// This method allows an expression to be simplified / evaluated using the
// current state. It is used to evaluate assertions and in program
Expand Down Expand Up @@ -131,7 +136,9 @@ class ai_baset
typedef ai_domain_baset statet;
typedef goto_programt::const_targett locationt;

ai_baset()
explicit ai_baset(
std::function<bool(locationt)> must_retain_state_callback) :
must_retain_state_callback(must_retain_state_callback)
{
}

Expand Down Expand Up @@ -308,6 +315,11 @@ class ai_baset
// the work-queue is sorted by location number
typedef std::map<unsigned, locationt> working_sett;

// Callback that indicates if the user of this instance wants a particular
// state (program point) to be retained for inspection once the fixpoint is
// found. If no callback is supplied we by default retain all states.
std::function<bool(locationt)> must_retain_state_callback;

locationt get_next(working_sett &working_set);

void put_in_working_set(
Expand Down Expand Up @@ -358,8 +370,11 @@ class ai_baset
const exprt::operandst &arguments,
const namespacet &ns);

bool must_retain_state(locationt, const goto_programt &) const;

// abstract methods

virtual bool merge(statet &&src, locationt from, locationt to)=0;
virtual bool merge(const statet &src, locationt from, locationt to)=0;
// for concurrent fixedpoint
virtual bool merge_shared(
Expand All @@ -378,7 +393,8 @@ class ait:public ai_baset
{
public:
// constructor
ait():ai_baset()
ait(std::function<bool(locationt)> must_retain_state_callback = nullptr) :
ai_baset(must_retain_state_callback)
{
}

Expand Down Expand Up @@ -436,6 +452,13 @@ class ait:public ai_baset
return it->second;
}

bool merge(statet &&src, locationt from, locationt to) override
{
statet &dest=get_state(to);
return static_cast<domainT &>(dest).merge(
static_cast<domainT &&>(src), from, to);
}

bool merge(const statet &src, locationt from, locationt to) override
{
statet &dest=get_state(to);
Expand Down
1 change: 1 addition & 0 deletions unit/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ SRC = unit_tests.cpp \
# Test source files
SRC += unit_tests.cpp \
analyses/ai/ai_simplify_lhs.cpp \
analyses/ai/ai_selective_domains.cpp \
analyses/call_graph.cpp \
analyses/constant_propagator.cpp \
analyses/disconnect_unreachable_nodes_in_graph.cpp \
Expand Down
Loading

0 comments on commit 82f11c9

Please sign in to comment.