3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-08 10:25:18 +00:00

eager quantifier instantiation for quantified array properties

This commit is contained in:
Arie Gurfinkel 2017-07-31 16:05:06 -04:00
parent 2c7a39d580
commit 7168451201
6 changed files with 651 additions and 2 deletions

View file

@ -21,6 +21,8 @@ z3_add_component(transforms
dl_mk_unbound_compressor.cpp
dl_mk_unfold.cpp
dl_transforms.cpp
dl_mk_array_eq_rewrite.cpp
dl_mk_array_instantiation.cpp
COMPONENT_DEPENDENCIES
dataflow
hilbert

View file

@ -0,0 +1,140 @@
/*++
Module Name:
dl_mk_array_eq_rewrite.h
Abstract:
Selects a representative for array equivalence classes.
Author:
Julien Braine
Revision History:
--*/
#include "dl_mk_array_eq_rewrite.h"
#include "dl_context.h"
#include "pattern_inference.h"
#include "dl_context.h"
#include "expr_safe_replace.h"
#include "expr_abstract.h"
#include"fixedpoint_params.hpp"
#include "../spacer/obj_equiv_class.h"
namespace datalog {
mk_array_eq_rewrite::mk_array_eq_rewrite(
context & ctx, unsigned priority):
plugin(priority),
m(ctx.get_manager()),
m_ctx(ctx),
m_a(m)
{
}
rule_set * mk_array_eq_rewrite::operator()(rule_set const & source)
{
src_set = &source;
rule_set * result = alloc(rule_set, m_ctx);
result->inherit_predicates(source);
dst=result;
unsigned nbrules = source.get_num_rules();
src_manager = &source.get_rule_manager();
for(unsigned i =0;i<nbrules;i++)
{
rule & r = *source.get_rule(i);
instantiate_rule(r, *result);
}
return result;
}
void mk_array_eq_rewrite::instantiate_rule(const rule& r, rule_set & dest)
{
//Reset everything
cnt = src_manager->get_counter().get_max_rule_var(r)+1;
expr_ref_vector new_tail(m);
unsigned nb_predicates = r.get_uninterpreted_tail_size();
unsigned tail_size = r.get_tail_size();
for(unsigned i=0;i<nb_predicates;i++)
{
new_tail.push_back(r.get_tail(i));
}
spacer::expr_equiv_class array_eq_classes(m);
for(unsigned i=nb_predicates;i<tail_size;i++)
{
expr* cond = r.get_tail(i);
expr* e1, *e2;
if(m.is_eq(cond, e1, e2) && m_a.is_array(get_sort(e1)))
{
array_eq_classes.merge(e1, e2);
}
else
{
new_tail.push_back(cond);
}
}
for(spacer::expr_equiv_class::equiv_iterator c_eq = array_eq_classes.begin();
c_eq != array_eq_classes.end();++c_eq)
{
expr* representative = *(*c_eq).begin();
for(spacer::expr_equiv_class::iterator it = (*c_eq).begin();
it!=(*c_eq).end(); ++it)
{
if(!is_var(*it))
{
representative = *it;
break;
}
}
for(spacer::expr_equiv_class::iterator it = (*c_eq).begin();
it!=(*c_eq).end(); ++it)
{
for(unsigned i=0;i<new_tail.size();i++)
new_tail[i] = replace(new_tail[i].get(), representative, *it);
}
for(spacer::expr_equiv_class::iterator it = (*c_eq).begin();
it!=(*c_eq).end(); ++it)
{
new_tail.push_back(m.mk_eq(*it, representative));
}
}
params_ref select_over_store;
select_over_store.set_bool("expand_select_store", true);
th_rewriter t(m, select_over_store);
expr_ref_vector res_conjs(m);
for(unsigned i=0;i<new_tail.size();i++)
{
expr_ref tmp(m);
t(new_tail[i].get(), tmp);
res_conjs.push_back(tmp);
}
proof_ref pr(m);
src_manager->mk_rule(m.mk_implies(m.mk_and(res_conjs.size(), res_conjs.c_ptr()), r.get_head()), pr, dest, r.name());
}
expr* mk_array_eq_rewrite::replace(expr* e, expr* new_val, expr* old_val)
{
if(e==old_val)
return new_val;
else if(!is_app(e))
{
return e;
}
app*f = to_app(e);
ptr_vector<expr> n_args;
for(unsigned i=0;i<f->get_num_args();i++)
{
n_args.push_back(replace(f->get_arg(i), new_val, old_val));
}
return m.mk_app(f->get_decl(), n_args.size(), n_args.c_ptr());
}
}

View file

@ -0,0 +1,54 @@
/*++
Module Name:
dl_mk_array_eq_rewrite.h
Abstract:
Selects a representative for array equivalence classes.
Author:
Julien Braine
Revision History:
--*/
#ifndef DL_MK_ARRAY_EQ_REWRITE_H_
#define DL_MK_ARRAY_EQ_REWRITE_H_
#include "dl_rule_transformer.h"
#include "../spacer/obj_equiv_class.h"
namespace datalog {
class context;
class mk_array_eq_rewrite : public rule_transformer::plugin {
//Context objects
ast_manager& m;
context& m_ctx;
array_util m_a;
//Rule set context
const rule_set*src_set;
rule_set*dst;
rule_manager* src_manager;
unsigned cnt;//Index for new variables
expr* replace(expr* e, expr* new_val, expr* old_val);
void instantiate_rule(const rule& r, rule_set & dest);
public:
mk_array_eq_rewrite(context & ctx, unsigned priority);
rule_set * operator()(rule_set const & source);
virtual ~mk_array_eq_rewrite(){}
};
};
#endif /* DL_MK_ARRAY_EQ_REWRITE_H_ */

View file

@ -0,0 +1,324 @@
/*++
Module Name:
dl_mk_array_instantiation.h
Abstract:
Does array instantiation
Author:
Julien Braine
Revision History:
--*/
#include "dl_mk_array_instantiation.h"
#include "dl_context.h"
#include "pattern_inference.h"
#include "dl_context.h"
#include "expr_safe_replace.h"
#include "expr_abstract.h"
#include"fixedpoint_params.hpp"
#include "../spacer/obj_equiv_class.h"
namespace datalog {
mk_array_instantiation::mk_array_instantiation(
context & ctx, unsigned priority):
plugin(priority),
m(ctx.get_manager()),
m_ctx(ctx),
m_a(m),
eq_classes(m),
ownership(m)
{
}
rule_set * mk_array_instantiation::operator()(rule_set const & source)
{
std::cout<<"Array Instantiation called with parameters :"
<<" enforce="<<m_ctx.get_params().xform_instantiate_arrays_enforce()
<<" nb_quantifier="<<m_ctx.get_params().xform_instantiate_arrays_nb_quantifier()
<<" slice_technique="<<m_ctx.get_params().xform_instantiate_arrays_slice_technique()
<<"\n";
std::cout<<"Input rules = \n";
source.display(std::cout);
src_set = &source;
rule_set * result = alloc(rule_set, m_ctx);
dst=result;
unsigned nbrules = source.get_num_rules();
src_manager = &source.get_rule_manager();
for(unsigned i =0;i<nbrules;i++)
{
rule & r = *source.get_rule(i);
instantiate_rule(r, *result);
}
std::cout<<"\n\nOutput rules = \n";
result->display(std::cout);
return result;
}
void mk_array_instantiation::instantiate_rule(const rule& r, rule_set & dest)
{
//Reset everything
selects.reset();
eq_classes.reset();
cnt = src_manager->get_counter().get_max_rule_var(r)+1;
done_selects.reset();
ownership.reset();
expr_ref_vector phi(m);
expr_ref_vector preds(m);
expr_ref new_head = create_head(to_app(r.get_head()));
unsigned nb_predicates = r.get_uninterpreted_tail_size();
unsigned tail_size = r.get_tail_size();
for(unsigned i=0;i<nb_predicates;i++)
{
preds.push_back(r.get_tail(i));
}
for(unsigned i=nb_predicates;i<tail_size;i++)
{
phi.push_back(r.get_tail(i));
}
//Retrieve selects
for(unsigned i=0;i<phi.size();i++)
retrieve_selects(phi[i].get());
//Rewrite the predicates
expr_ref_vector new_tail(m);
for(unsigned i=0;i<preds.size();i++)
{
new_tail.append(instantiate_pred(to_app(preds[i].get())));
}
new_tail.append(phi);
for(obj_map<expr, var*>::iterator it = done_selects.begin(); it!=done_selects.end(); ++it)
{
expr_ref tmp(m);
tmp = &it->get_key();
new_tail.push_back(m.mk_eq(it->get_value(), tmp));
}
proof_ref pr(m);
src_manager->mk_rule(m.mk_implies(m.mk_and(new_tail.size(), new_tail.c_ptr()), new_head), pr, dest, r.name());
}
expr_ref mk_array_instantiation::create_head(app* old_head)
{
expr_ref_vector new_args(m);
for(unsigned i=0;i<old_head->get_num_args();i++)
{
expr*arg = old_head->get_arg(i);
if(m_a.is_array(get_sort(arg)))
{
for(unsigned k=0; k< m_ctx.get_params().xform_instantiate_arrays_nb_quantifier();k++)
{
expr_ref_vector dummy_args(m);
dummy_args.push_back(arg);
for(unsigned i=0;i<get_array_arity(get_sort(arg));i++)
{
dummy_args.push_back(m.mk_var(cnt, get_array_domain(get_sort(arg), i)));
cnt++;
}
expr_ref select(m);
select = m_a.mk_select(dummy_args.size(), dummy_args.c_ptr());
new_args.push_back(select);
selects.insert_if_not_there(arg, ptr_vector<expr>());
selects[arg].push_back(select);
}
if(!m_ctx.get_params().xform_instantiate_arrays_enforce())
new_args.push_back(arg);
}
else
new_args.push_back(arg);
}
return create_pred(old_head, new_args);
}
void mk_array_instantiation::retrieve_selects(expr* e)
{
//If the expression is not a function application, we ignore it
if (!is_app(e)) {
return;
}
app*f=to_app(e);
//Call the function recursively on all arguments
unsigned nbargs = f->get_num_args();
for(unsigned i=0;i<nbargs;i++)
{
retrieve_selects(f->get_arg(i));
}
//If it is a select, then add it to selects
if(m_a.is_select(f))
{
SASSERT(!m_a.is_array(get_sort(e)));
selects.insert_if_not_there(f->get_arg(0), ptr_vector<expr>());
selects[f->get_arg(0)].push_back(e);
}
//If it is a condition between arrays, for example the result of a store, then add it to the equiv_classes
if(m_a.is_store(f))
{
eq_classes.merge(e, f->get_arg(0));
}
else if(m.is_eq(f) && m_a.is_array(get_sort(f->get_arg(0))))
{
eq_classes.merge(f->get_arg(0), f->get_arg(1));
}
}
expr_ref_vector mk_array_instantiation::getId(app*old_pred, const expr_ref_vector& n_args)
{
expr_ref_vector res(m);
for(unsigned i=0;i<n_args.size(); i++)
{
if(m_a.is_select(n_args[i]))
{
app*select = to_app(n_args[i]);
for(unsigned j=1;j<select->get_num_args();j++)
{
res.push_back(select->get_arg(j));
}
}
}
return res;
}
expr_ref mk_array_instantiation::create_pred(app*old_pred, expr_ref_vector& n_args)
{
expr_ref_vector new_args(m);
new_args.append(n_args);
new_args.append(getId(old_pred, n_args));
for(unsigned i=0;i<new_args.size();i++)
{
if(m_a.is_select(new_args[i].get()))
{
new_args[i] = mk_select_var(new_args[i].get());
}
}
sort_ref_vector new_sorts(m);
for(unsigned i=0;i<new_args.size();i++)
new_sorts.push_back(get_sort(new_args[i].get()));
expr_ref res(m);
func_decl_ref fun_decl(m);
fun_decl = m.mk_func_decl(symbol((old_pred->get_decl()->get_name().str()+"!inst").c_str()), new_sorts.size(), new_sorts.c_ptr(), old_pred->get_decl()->get_range());
m_ctx.register_predicate(fun_decl, false);
if(src_set->is_output_predicate(old_pred->get_decl()))
dst->set_output_predicate(fun_decl);
res=m.mk_app(fun_decl,new_args.size(), new_args.c_ptr());
return res;
}
var * mk_array_instantiation::mk_select_var(expr* select)
{
var*result;
if(!done_selects.find(select, result))
{
ownership.push_back(select);
result = m.mk_var(cnt, get_sort(select));
cnt++;
done_selects.insert(select, result);
}
return result;
}
expr_ref mk_array_instantiation::rewrite_select(expr*array, expr*select)
{
app*s = to_app(select);
expr_ref res(m);
expr_ref_vector args(m);
args.push_back(array);
for(unsigned i=1; i<s->get_num_args();i++)
{
args.push_back(s->get_arg(i));
}
res = m_a.mk_select(args.size(), args.c_ptr());
return res;
}
expr_ref_vector mk_array_instantiation::retrieve_all_selects(expr*array)
{
expr_ref_vector all_selects(m);
for(spacer::expr_equiv_class::iterator it = eq_classes.begin(array);
it != eq_classes.end(array); ++it)
{
selects.insert_if_not_there(*it, ptr_vector<expr>());
ptr_vector<expr>& select_ops = selects[*it];
for(unsigned i=0;i<select_ops.size();i++)
{
all_selects.push_back(rewrite_select(array, select_ops[i]));
}
}
if(all_selects.size()==0)
{
expr_ref_vector dummy_args(m);
dummy_args.push_back(array);
for(unsigned i=0;i<get_array_arity(get_sort(array));i++)
{
dummy_args.push_back(m.mk_var(cnt, get_array_domain(get_sort(array), i)));
cnt++;
}
all_selects.push_back(m_a.mk_select(dummy_args.size(), dummy_args.c_ptr()));
}
return all_selects;
}
expr_ref_vector mk_array_instantiation::instantiate_pred(app*old_pred)
{
unsigned nb_old_args=old_pred->get_num_args();
//Stores, for each old position, the list of a new possible arguments
vector<expr_ref_vector> arg_correspondance;
for(unsigned i=0;i<nb_old_args;i++)
{
expr_ref arg(old_pred->get_arg(i), m);
if(m_a.is_array(get_sort(arg)))
{
vector<expr_ref_vector> arg_possibilities(m_ctx.get_params().xform_instantiate_arrays_nb_quantifier(), retrieve_all_selects(arg));
arg_correspondance.append(arg_possibilities);
if(!m_ctx.get_params().xform_instantiate_arrays_enforce())
{
expr_ref_vector tmp(m);
tmp.push_back(arg);
arg_correspondance.push_back(tmp);
}
}
else
{
expr_ref_vector tmp(m);
tmp.push_back(arg);
arg_correspondance.push_back(tmp);
}
}
//Now, we need to deal with every combination
expr_ref_vector res(m);
svector<unsigned> chosen(arg_correspondance.size(), 0u);
while(1)
{
expr_ref_vector new_args(m);
for(unsigned i=0;i<chosen.size();i++)
{
new_args.push_back(arg_correspondance[i][chosen[i]].get());
}
res.push_back(create_pred(old_pred, new_args));
unsigned pos=-1;
do
{
pos++;
if(pos==chosen.size())
{
return res;
}
}while(chosen[pos]+1>=arg_correspondance[pos].size());
chosen[pos]++;
}
}
}

View file

@ -0,0 +1,123 @@
/*++
Module Name:
dl_mk_array_instantiation.h
Abstract:
Transforms predicates so that array invariants can be discovered.
Motivation : Given a predicate P(a), no quantifier-free solution can express that P(a) <=> forall i, P(a[i]) = 0
Solution : Introduce a fresh variable i, and transform P(a) into P!inst(i, a).
Now, (P!inst(i,a) := a[i] = 0) <=> P(a) := forall i, a[i] = 0.
Transformation on Horn rules:
P(a, args) /\ phi(a, args, args') => P'(args') (for simplicity, assume there are no arrays in args').
Is transformed into:
(/\_r in read_indices(phi) P!inst(r, a, args)) /\ phi(a, args, args') => P'(args')
Limitations : This technique can only discover invariants on arrays that depend on one quantifier.
Related work : Techniques relying on adding quantifiers and eliminating them. See dl_mk_quantifier_abstraction and dl_mk_quantifier_instantiation
Implementation:
The implementation follows the solution suggested above, with more options. The addition of options implies that in the simple
case described above, we in fact have P(a) transformed into P(i, a[i], a).
1) Dealing with multiple quantifiers -> The options fixedpoint.xform.instantiate_arrays.nb_quantifier gives the number of quantifiers per array.
2) Inforcing the instantiation -> We suggest an option (enforce_instantiation) to enforce this abstraction. This transforms
P(a) into P(i, a[i]). This enforces the solver to limit the space search at the cost of imprecise results. This option
corresponds to fixedpoint.xform.instantiate_arrays.enforce
3) Adding slices in the mix -> We wish to have the possibility to further restrict the search space: we want to smash cells, given a smashing rule.
For example, in for loops j=0; j<n; j++, it might be relevant to restrict the search space and look for invariants that only depend on whether
0<=i<j or j<=i, where i is the quantified variable.
Formally, a smashing rule is a function from the Index set (usually integer) to integers (the id set).
GetId(i) should return the id of the set i belongs in.
In our example, we can give 0 as the id of the set {n, 0<=n<j} and 1 for the set {n, j<=n}, and -1 for the set {n, n<0}. We then have
GetId(i) = ite(i<0, -1, ite(i<j, 0, 1))
Given that GetId function, P(a) /\ phi(a, ...) => P'(...) is transformed into
(/\_r in read_indices(phi) P!inst(id_r, a[r], a) /\ GetId(r) = id_r) /\ phi(a, ...) => P'(...).
Note : when no slicing is done, GetId(i) = i.
This option corresponds to fixedpoint.xform.instantiate_arrays.slice_technique
Although we described GetId as returning integers, there is no reason to restrict the type of ids to integers. A more direct method,
for the 0<=i<j or j<=i case could be :
GetId(i) = (i<0, i<j)
GetId is even more powerful as we deal with the multiple quantifiers on multiple arrays.
For example, we can use GetId to look for the same quantifiers in each array.
Assume we have arrays a and b, instantiated with one quantifier each i and j.
We can have GetId(i,j) = ite(i=j, (i, true), (fresh, false))
4) Reducing the set of r in read_indices(phi): in fact, we do not need to "instantiate" on all read indices of phi,
we can restrict ourselves to those "linked" to a, through equalities and stores.
Author:
Julien Braine
Revision History:
--*/
#ifndef DL_MK_ARRAY_INSTANTIATION_H_
#define DL_MK_ARRAY_INSTANTIATION_H_
#include "dl_rule_transformer.h"
#include "../spacer/obj_equiv_class.h"
namespace datalog {
class context;
class mk_array_instantiation : public rule_transformer::plugin {
//Context objects
ast_manager& m;
context& m_ctx;
array_util m_a;
//Rule set context
const rule_set*src_set;
rule_set*dst;
rule_manager* src_manager;
//Rule context
obj_map<expr, ptr_vector<expr> > selects;
spacer::expr_equiv_class eq_classes;
unsigned cnt;//Index for new variables
obj_map<expr, var*> done_selects;
expr_ref_vector ownership;
//Helper functions
void instantiate_rule(const rule& r, rule_set & dest);//Instantiates the rule
void retrieve_selects(expr* e);//Retrieves all selects (fills the selects and eq_classes members)
expr_ref rewrite_select(expr*array, expr*select);//Rewrites select(a, args) to select(array, args)
expr_ref_vector retrieve_all_selects(expr*array);//Retrieves all selects linked to a given array (using eq classes and selects)
expr_ref_vector instantiate_pred(app*old_pred);//Returns all the instantiation of a given predicate
expr_ref create_pred(app*old_pred, expr_ref_vector& new_args);//Creates a predicate
expr_ref create_head(app* old_head);//Creates the new head
var * mk_select_var(expr* select);
/*Given the old predicate, and the new arguments for the new predicate, returns the new setId arguments.
By default getId(P(x, y, a, b), (x, y, a[i], a[j], a, b[k], b[l], b)) (nb_quantifier=2, enforce=false)
returns (i,j,k,l)
So that the final created predicate is P!inst(x, y, a[i], a[j], a, b[k], b[l], b, i, j, k, l)
*/
expr_ref_vector getId(app*old_pred, const expr_ref_vector& new_args);
public:
mk_array_instantiation(context & ctx, unsigned priority);
rule_set * operator()(rule_set const & source);
virtual ~mk_array_instantiation(){}
};
};
#endif /* DL_MK_ARRAY_INSTANTIATION_H_ */

View file

@ -33,7 +33,8 @@ Revision History:
#include "muz/transforms/dl_mk_quantifier_instantiation.h"
#include "muz/transforms/dl_mk_subsumption_checker.h"
#include "muz/transforms/dl_mk_scale.h"
#include"fixedpoint_params.hpp"
#include "muz/base/fixedpoint_params.hpp"
#include "muz/transforms/dl_mk_array_eq_rewrite.h"
namespace datalog {
@ -46,6 +47,11 @@ namespace datalog {
transf.register_plugin(alloc(datalog::mk_coi_filter, ctx));
transf.register_plugin(alloc(datalog::mk_interp_tail_simplifier, ctx));
if (ctx.get_params().xform_instantiate_arrays()) {
transf.register_plugin(alloc(datalog::mk_array_instantiation, ctx, 34999));
}
if(ctx.get_params().xform_transform_arrays())
transf.register_plugin(alloc(datalog::mk_array_eq_rewrite, ctx, 34998));
if (ctx.get_params().xform_quantify_arrays()) {
transf.register_plugin(alloc(datalog::mk_quantifier_abstraction, ctx, 38000));
}
@ -83,7 +89,7 @@ namespace datalog {
transf.register_plugin(alloc(datalog::mk_karr_invariants, ctx, 36010));
transf.register_plugin(alloc(datalog::mk_scale, ctx, 36030));
if (!ctx.get_params().xform_quantify_arrays()) {
transf.register_plugin(alloc(datalog::mk_array_blast, ctx, 36000));
transf.register_plugin(alloc(datalog::mk_array_blast, ctx, 35999));
}
if (ctx.get_params().xform_magic()) {
transf.register_plugin(alloc(datalog::mk_magic_symbolic, ctx, 36020));