3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-24 01:25:31 +00:00

wip - alpha support for polymorphism

An initial update to support polymorphism from SMTLIB3 and the API (so far C, Python).

The WIP SMTLIB3 format is assumed to be supporting the following declaration

```
(declare-type-var A)
```
Whenever A is used in a type signature of a function/constant or bound quantified variable, it is taken to mean that all instantiations of A are included in the signature and assertions.
For example, if the function f is declared with signature A -> A, then there is a version of f for all instances of A.
The semantics of polymorphism appears to follow previous proposals: the instances are effectively different functions.
This may clash with some other notions, such as the type signature forall 'a . 'a -> 'a would be inhabited by a unique function (the identity), while this is not enforced in this version (and hopefully never because it is more busy work).

The C API has the function 'Z3_mk_type_variable' to create a type variable and applying functions modulo polymorphic type signatures is possible.
The kind Z3_TYPE_VAR is added to sort discriminators.

This version is considered as early alpha. It passes a first rudimentary unit test involving quantified axioms, declare-fun, define-fun, and define-fun-rec.
This commit is contained in:
Nikolaj Bjorner 2023-07-12 18:09:02 -07:00
parent d6f2c23627
commit 939bf1c725
16 changed files with 987 additions and 42 deletions

View file

@ -37,6 +37,8 @@ z3_add_component(ast
num_occurs.cpp
occurs.cpp
pb_decl_plugin.cpp
polymorphism_inst.cpp
polymorphism_util.cpp
pp.cpp
quantifier_stat.cpp
recfun_decl_plugin.cpp

View file

@ -2049,8 +2049,8 @@ func_decl * ast_manager::mk_func_decl(symbol const & name, unsigned arity, sort
}
func_decl* new_node = new (mem) func_decl(name, arity, domain, range, info);
new_node = register_node(new_node);
if (is_polymorphic_root)
m_poly_roots.insert(new_node, new_node);
if (is_polymorphic_root)
m_poly_roots.insert(new_node, new_node);
return new_node;
}
@ -2774,8 +2774,8 @@ bool ast_manager::has_type_var(unsigned n, sort* const* domain, sort* range) con
func_decl* ast_manager::instantiate_polymorphic(func_decl* f, unsigned arity, sort * const* domain, sort * range) {
SASSERT(f->is_polymorphic());
func_decl* g = mk_func_decl(f->get_name(), arity, domain, range, f->get_info());
m_poly_roots.insert(f, g);
SASSERT(g->is_polymorphic());
m_poly_roots.insert(g, f);
// SASSERT(g->is_polymorphic());
return g;
}

View file

@ -65,6 +65,13 @@ void ast_translation::collect_decl_extra_children(decl * d) {
}
void ast_translation::push_frame(ast * n) {
// ensure poly roots are pushed first.
if (m_from_manager.has_type_vars() && n->get_kind() == AST_FUNC_DECL && to_func_decl(n)->is_polymorphic()) {
func_decl* g = m_from_manager.poly_root(to_func_decl(n));
if (n != g && m_cache.contains(g)) {
m_frame_stack.push_back(frame(n, 0, m_extra_children_stack.size(), m_result_stack.size()));
}
}
m_frame_stack.push_back(frame(n, 0, m_extra_children_stack.size(), m_result_stack.size()));
switch (n->get_kind()) {
case AST_SORT:
@ -153,6 +160,10 @@ void ast_translation::mk_func_decl(func_decl * f, frame & fr) {
new_domain,
new_range);
}
else if (f->is_polymorphic() && m_from_manager.poly_root(f) != f) {
func_decl* fr = to_func_decl(m_cache[m_from_manager.poly_root(f)]);
new_f = m_to_manager.instantiate_polymorphic(fr, f->get_arity(), new_domain, new_range);
}
else {
buffer<parameter> ps;
copy_params(f, fr.m_rpos, ps);

View file

@ -87,7 +87,7 @@ bool occurs(func_decl * d, expr * n) {
bool occurs(sort* s1, sort* s2) {
sort_proc p(s1);
try {
for_each_ast(p, s2);
for_each_ast(p, s2, true);
}
catch (const found&) {
return true;

View file

@ -0,0 +1,138 @@
/*++
Copyright (c) 2023 Microsoft Corporation
Module Name:
polymorphism_inst.cpp
Abstract:
Utilities for instantiating polymorphic assertions.
Author:
Nikolaj Bjorner (nbjorner) 2023-7-8
--*/
#include "ast/polymorphism_inst.h"
#include "ast/ast_pp.h"
namespace polymorphism {
void inst::add(expr* e) {
if (!m.has_type_vars())
return;
if (m_from_instantiation.contains(e))
return;
instances inst;
u.collect_poly_instances(e, inst.m_poly_fns);
if (inst.m_poly_fns.empty())
return;
if (m_instances.contains(e))
return;
add_instantiations(e, inst.m_poly_fns);
if (!u.has_type_vars(e))
return;
// insert e into the occurs list for polymorphic roots
ast_mark seen;
for (auto* f : inst.m_poly_fns) {
f = m.poly_root(f);
if (seen.is_marked(f))
continue;
seen.mark(f, true);
if (!m_occurs.contains(f)) {
m_occurs.insert(f, ptr_vector<expr>());
t.push(insert_map(m_occurs, f));
}
auto& es = m_occurs.find(f);
es.push_back(e);
t.push(remove_back(m_occurs, f));
}
m_assertions.push_back(e);
t.push(push_back_vector(m_assertions));
u.collect_type_vars(e, inst.m_tvs);
inst.m_subst = alloc(substitutions);
inst.m_subst->insert(alloc(substitution, m));
m_instances.insert(e, inst);
t.push(new_obj_trail(inst.m_subst));
t.push(insert_map(m_instances, e));
}
void inst::collect_instantiations(expr* e) {
ptr_vector<func_decl> instances;
u.collect_poly_instances(e, instances);
add_instantiations(e, instances);
}
void inst::add_instantiations(expr* e, ptr_vector<func_decl> const& instances) {
for (auto* f : instances) {
if (m_in_decl_queue.is_marked(f))
continue;
m_in_decl_queue.mark(f, true);
m_decl_queue.push_back(f);
t.push(add_decl_queue(*this));
}
}
void inst::instantiate(vector<instantiation>& instances) {
unsigned num_decls = m_decl_queue.size();
if (m_assertions_qhead < m_assertions.size()) {
t.push(value_trail(m_assertions_qhead));
for (; m_assertions_qhead < m_assertions.size(); ++m_assertions_qhead) {
expr* e = m_assertions.get(m_assertions_qhead);
for (unsigned i = 0; i < num_decls; ++i)
instantiate(m_decl_queue.get(i), e, instances);
}
}
if (m_decl_qhead < num_decls) {
t.push(value_trail(m_decl_qhead));
for (; m_decl_qhead < num_decls; ++m_decl_qhead) {
func_decl* p = m_decl_queue.get(m_decl_qhead);
for (expr* e : m_occurs[m.poly_root(p)])
instantiate(p, e, instances);
}
}
}
void inst::instantiate(func_decl* f1, expr* e, vector<instantiation>& instances) {
auto const& [tv, fns, substs] = m_instances[e];
for (auto* f2 : fns) {
substitution sub1(m), new_sub(m);
if (!u.unify(f1, f2, sub1))
continue;
if (substs->contains(&sub1))
continue;
substitutions new_substs;
for (auto* sub2 : *substs) {
if (!u.unify(sub1, *sub2, new_sub))
continue;
if (substs->contains(&new_sub))
continue;
if (new_substs.contains(&new_sub))
continue;
expr_ref e_inst = new_sub(e);
if (!m_from_instantiation.contains(e_inst)) {
collect_instantiations(e_inst);
auto* new_sub1 = alloc(substitution, new_sub);
instances.push_back(instantiation(e, e_inst, new_sub1));
new_substs.insert(new_sub1);
m_from_instantiation.insert(e_inst);
m.inc_ref(e_inst);
t.push(insert_ref_map(m, m_from_instantiation, e_inst));
}
}
for (auto* sub2 : new_substs) {
SASSERT(!substs->contains(sub2));
substs->insert(sub2);
t.push(new_obj_trail(sub2));
t.push(insert_map(*substs, sub2));
}
}
}
}

View file

@ -0,0 +1,91 @@
/*++
Copyright (c) 2023 Microsoft Corporation
Module Name:
polymorphism_inst.h
Abstract:
Utilities for instantiating polymorphic assertions.
Author:
Nikolaj Bjorner (nbjorner) 2023-7-8
--*/
#pragma once
#include "util/trail.h"
#include "ast/ast.h"
#include "ast/polymorphism_util.h"
namespace polymorphism {
struct instantiation {
expr* orig;
expr_ref inst;
substitution* sub;
instantiation(expr* orig, expr_ref& inst, substitution* s):
orig(orig), inst(inst), sub(s) {}
};
class inst {
ast_manager& m;
trail_stack& t;
util u;
struct instances {
ptr_vector<sort> m_tvs;
ptr_vector<func_decl> m_poly_fns;
substitutions* m_subst = nullptr;
};
func_decl_ref_vector m_poly_roots;
obj_map<func_decl, ptr_vector<expr>> m_occurs;
obj_map<expr, instances> m_instances;
func_decl_ref_vector m_decl_queue;
unsigned m_decl_qhead = 0;
ast_mark m_in_decl_queue;
expr_ref_vector m_assertions;
unsigned m_assertions_qhead = 0;
obj_hashtable<expr> m_from_instantiation;
struct add_decl_queue : public trail {
inst& i;
add_decl_queue(inst& i): i(i) {}
void undo() override {
i.m_in_decl_queue.mark(i.m_decl_queue.back(), false);
i.m_decl_queue.pop_back();
};
};
struct remove_back : public trail {
obj_map<func_decl, ptr_vector<expr>>& occ;
func_decl* f;
remove_back(obj_map<func_decl, ptr_vector<expr>>& occ, func_decl* f):
occ(occ), f(f) {}
void undo() override {
occ.find(f).pop_back();
}
};
void instantiate(func_decl* p, expr* e, vector<instantiation>& instances);
void collect_instantiations(expr* e);
void add_instantiations(expr* e, ptr_vector<func_decl> const& insts);
public:
inst(ast_manager& m, trail_stack& t):
m(m), t(t), u(m), m_poly_roots(m), m_decl_queue(m), m_assertions(m) {}
void add(expr* e);
void instantiate(vector<instantiation>& instances);
bool pending() const { return m_decl_qhead < m_decl_queue.size() || m_assertions_qhead < m_assertions.size(); }
};
}

View file

@ -0,0 +1,349 @@
/*++
Copyright (c) 2023 Microsoft Corporation
Module Name:
polymorphism_util.cpp
Abstract:
Utilities for supporting polymorphic type signatures.
Author:
Nikolaj Bjorner (nbjorner) 2023-7-8
--*/
#include "ast/polymorphism_util.h"
#include "ast/for_each_ast.h"
#include "ast/occurs.h"
#include "ast/ast_pp.h"
namespace polymorphism {
sort_ref_vector substitution::operator()(sort_ref_vector const& s) {
sort_ref_vector r(m);
for (auto* srt : s)
r.push_back((*this)(srt));
return r;
}
sort_ref substitution::operator()(sort* s) {
if (!m.has_type_var(s))
return sort_ref(s, m);
if (s->is_type_var()) {
if (m_sub.find(s, s))
return (*this)(s);
return sort_ref(s, m);
}
unsigned n = s->get_num_parameters();
vector<parameter> ps;
for (unsigned i = 0; i < n; ++i) {
auto p = s->get_parameter(i);
if (p.is_ast() && is_sort(p.get_ast()))
ps.push_back(parameter((*this)(to_sort(p.get_ast()))));
else
ps.push_back(p);
}
sort_info si(s->get_family_id(), s->get_decl_kind(), n, ps.data(), s->private_parameters());
return sort_ref(m.mk_sort(s->get_name(), si), m);
}
expr_ref substitution::operator()(expr* e) {
ptr_vector<expr> todo;
expr_ref_vector result(m);
todo.push_back(e);
auto in_cache = [&](expr* a) {
return result.size() > a->get_id() && result.get(a->get_id());
};
ptr_buffer<expr> args;
sort_ref_buffer domain(m);
while (!todo.empty()) {
expr* a = todo.back();
if (in_cache(a)) {
todo.pop_back();
continue;
}
if (is_var(a)) {
if (m.has_type_var(a->get_sort()))
result.setx(a->get_id(), m.mk_var(to_var(a)->get_idx(), (*this)(a->get_sort())));
else
result.setx(a->get_id(), a);
todo.pop_back();
}
else if (is_quantifier(a)) {
quantifier* q = to_quantifier(a);
bool pending = false;
if (!in_cache(q->get_expr())) {
todo.push_back(q->get_expr());
pending = true;
}
ptr_buffer<expr> patterns, no_patterns;
unsigned np = q->get_num_patterns();
for (unsigned i = 0; i < np; ++i) {
if (!in_cache(q->get_pattern(i))) {
todo.push_back(q->get_pattern(i));
pending = true;
}
else
patterns.push_back(result.get(q->get_pattern(i)->get_id()));
}
np = q->get_num_no_patterns();
for (unsigned i = 0; i < np; ++i) {
if (!in_cache(q->get_no_pattern(i))) {
todo.push_back(q->get_no_pattern(i));
pending = true;
}
else
no_patterns.push_back(result.get(q->get_no_pattern(i)->get_id()));
}
if (pending)
continue;
todo.pop_back();
ptr_buffer<sort> sorts;
for (unsigned i = 0; i < q->get_num_decls(); ++i)
sorts.push_back((*this)(q->get_decl_sort(i)));
quantifier* q2 =
m.mk_quantifier(q->get_kind(), q->get_num_decls(), sorts.data(), q->get_decl_names(), result.get(q->get_expr()->get_id()),
q->get_weight(),
q->get_qid(), q->get_skid(),
q->get_num_patterns(), patterns.data(), q->get_num_no_patterns(), no_patterns.data()
);
result.setx(q->get_id(), q2);
}
else if (is_app(a)) {
args.reset();
unsigned n = todo.size();
for (expr* arg : *to_app(a)) {
if (!in_cache(arg))
todo.push_back(arg);
else
args.push_back(result.get(arg->get_id()));
}
if (n < todo.size())
continue;
func_decl* f = to_app(a)->get_decl();
if (f->is_polymorphic()) {
domain.reset();
for (unsigned i = 0; i < f->get_arity(); ++i)
domain.push_back((*this)(f->get_domain(i)));
sort_ref range = (*this)(f->get_range());
f = m.instantiate_polymorphic(f, f->get_arity(), domain.data(), range);
}
result.setx(a->get_id(), m.mk_app(f, args));
todo.pop_back();
}
}
return expr_ref(result.get(e->get_id()), m);
}
bool substitution::unify(sort* s1, sort* s2) {
if (s1 == s2)
return true;
if (s1->is_type_var() && m_sub.find(s1, s1))
return unify(s1, s2);
if (s2->is_type_var() && m_sub.find(s2, s2))
return unify(s1, s2);
if (s2->is_type_var() && !s1->is_type_var())
std::swap(s1, s2);
if (s1->is_type_var()) {
auto s22 = (*this)(s2);
if (occurs(s1, s22))
return false;
m_trail.push_back(s22);
m_trail.push_back(s1);
m_sub.insert(s1, s22);
return true;
}
if (s1->get_family_id() != s2->get_family_id())
return false;
if (s1->get_decl_kind() != s2->get_decl_kind())
return false;
if (s1->get_name() != s2->get_name())
return false;
if (s1->get_num_parameters() != s2->get_num_parameters())
return false;
for (unsigned i = s1->get_num_parameters(); i-- > 0;) {
auto p1 = s1->get_parameter(i);
auto p2 = s2->get_parameter(i);
if (p1.is_ast() && is_sort(p1.get_ast())) {
if (!p2.is_ast())
return false;
if (!is_sort(p2.get_ast()))
return false;
if (!unify(to_sort(p1.get_ast()), to_sort(p2.get_ast())))
return false;
continue;
}
if (p1 != p2)
return false;
}
return true;
}
bool substitution::match(sort* s1, sort* s2) {
if (s1 == s2)
return true;
if (s1->is_type_var() && m_sub.find(s1, s1))
return match(s1, s2);
if (s1->is_type_var()) {
m_trail.push_back(s2);
m_trail.push_back(s1);
m_sub.insert(s1, s2);
return true;
}
if (s1->get_family_id() != s2->get_family_id())
return false;
if (s1->get_decl_kind() != s2->get_decl_kind())
return false;
if (s1->get_name() != s2->get_name())
return false;
if (s1->get_num_parameters() != s2->get_num_parameters())
return false;
for (unsigned i = s1->get_num_parameters(); i-- > 0;) {
auto p1 = s1->get_parameter(i);
auto p2 = s2->get_parameter(i);
if (p1.is_ast() && is_sort(p1.get_ast())) {
if (!p2.is_ast())
return false;
if (!is_sort(p2.get_ast()))
return false;
if (!match(to_sort(p1.get_ast()), to_sort(p2.get_ast())))
return false;
continue;
}
if (p1 != p2)
return false;
}
return true;
}
// util
bool util::unify(sort* s1, sort* s2, substitution& sub) {
return sub.unify(s1, s2);
}
bool util::unify(func_decl* f1, func_decl* f2, substitution& sub) {
if (f1 == f2)
return true;
if (!f1->is_polymorphic() || !f2->is_polymorphic())
return false;
if (m.poly_root(f1) != m.poly_root(f2))
return false;
for (unsigned i = f1->get_arity(); i-- > 0; )
if (!sub.unify(fresh(f1->get_domain(i)), f2->get_domain(i)))
return false;
return sub.unify(fresh(f1->get_range()), f2->get_range());
}
bool util::unify(substitution const& s1, substitution const& s2,
substitution& sub) {
sort* v2;
for (auto const& [k, v] : s1)
sub.insert(k, v);
for (auto const& [k, v] : s2) {
if (sub.find(k, v2)) {
if (!sub.unify(sub(v), v2))
return false;
}
else
sub.insert(k, sub(v));
}
return true;
}
bool util::match(substitution& sub, sort* s1, sort* s_ground) {
return sub.match(s1, s_ground);
}
/**
* Create fresh variables, but with caching.
* So "fresh" variables are not truly fresh globally.
* This can block some unifications and therefore block some instantiations of
* polymorphic assertions. A different caching scheme could be created to
* ensure that fresh variables are introduced at the right time, or use other
* tricks such as creating variable/offset pairs to distinguish name spaces without
* incurring costs.
*/
sort_ref util::fresh(sort* s) {
sort* s1;
if (m_fresh.find(s, s1))
return sort_ref(s1, m);
if (m.is_type_var(s)) {
s1 = m.mk_type_var(symbol("fresh!" + std::to_string(m_counter)));
m_trail.push_back(s1);
m_trail.push_back(s);
m_fresh.insert(s, s1);
return sort_ref(s1, m);
}
vector<parameter> params;
for (unsigned i = 0; i < s->get_num_parameters(); ++i) {
parameter p = s->get_parameter(i);
if (p.is_ast() && is_sort(p.get_ast()))
params.push_back(parameter(fresh(to_sort(p.get_ast()))));
else
params.push_back(p);
}
sort_info info(s->get_family_id(), s->get_decl_kind(), params.size(), params.data(), s->private_parameters());
s1 = m.mk_sort(s->get_name(), info);
m_trail.push_back(s1);
m_trail.push_back(s);
m_fresh.insert(s, s1);
return sort_ref(s1, m);
}
sort_ref_vector util::fresh(unsigned n, sort* const* s) {
sort_ref_vector r(m);
for (unsigned i = 0; i < n; ++i)
r.push_back(fresh(s[i]));
return r;
}
void util::collect_poly_instances(expr* e, ptr_vector<func_decl>& instances) {
struct proc {
ast_manager& m;
ptr_vector<func_decl>& instances;
proc(ast_manager& m, ptr_vector<func_decl>& instances) : m(m), instances(instances) {}
void operator()(func_decl* f) {
if (f->is_polymorphic() && !m.is_eq(f) && !is_decl_of(f, pattern_family_id, OP_PATTERN))
instances.push_back(f);
}
void operator()(ast* a) {}
};
proc proc(m, instances);
for_each_ast(proc, e, false);
}
bool util::has_type_vars(expr* e) {
struct proc {
ast_manager& m;
bool found = false;
proc(ast_manager& m) : m(m) {}
void operator()(sort* f) {
if (m.has_type_var(f))
found = true;
}
void operator()(ast* a) {}
};
proc proc(m);
for_each_ast(proc, e, false);
return proc.found;
}
void util::collect_type_vars(expr* e, ptr_vector<sort>& tvs) {
struct proc {
ast_manager& m;
ptr_vector<sort>& tvs;
proc(ast_manager& m, ptr_vector<sort>& tvs) : m(m), tvs(tvs) {}
void operator()(sort* s) {
if (m.is_type_var(s))
tvs.push_back(s);
}
void operator()(ast* a) {}
};
proc proc(m, tvs);
for_each_ast(proc, e, true);
}
}

112
src/ast/polymorphism_util.h Normal file
View file

@ -0,0 +1,112 @@
/*++
Copyright (c) 2023 Microsoft Corporation
Module Name:
polymorphism_util.h
Abstract:
Utilities for supporting polymorphic type signatures.
Author:
Nikolaj Bjorner (nbjorner) 2023-7-8
--*/
#pragma once
#include "ast/ast.h"
#include "util/hashtable.h"
namespace polymorphism {
class substitution {
ast_manager& m;
obj_map<sort, sort*> m_sub;
sort_ref_vector m_trail;
public:
substitution(ast_manager& m): m(m), m_trail(m) {}
sort_ref_vector operator()(sort_ref_vector const& s);
sort_ref operator()(sort* s);
expr_ref operator()(expr* e);
bool unify(sort* s1, sort* s2);
bool match(sort* s1, sort* s_ground);
obj_map<sort, sort*>::iterator begin() const { return m_sub.begin(); }
obj_map<sort, sort*>::iterator end() const { return m_sub.end(); }
void insert(sort* v, sort* t) { m_trail.push_back(v).push_back(t); m_sub.insert(v, t); }
bool find(sort* v, sort*& t) const { return m_sub.find(v, t); }
unsigned size() const { return m_sub.size(); }
/**
* weak equality: strong equality considers applying substitutions recursively in range
* because substitutions may be in triangular form.
*/
struct eq {
bool operator()(substitution const* s1, substitution const* s2) const {
if (s1->size() != s2->size())
return false;
sort* v2;
for (auto const& [k, v] : *s1) {
if (!s2->find(k, v2))
return false;
if (v != v2)
return false;
}
return true;
}
};
struct hash {
unsigned operator()(substitution const* s) const {
unsigned hash = 0xfabc1234 + s->size();
for (auto const& [k, v] : *s)
hash ^= k->hash() + 2 * v->hash();
return hash;
}
};
};
typedef hashtable<substitution*, substitution::hash, substitution::eq> substitutions;
class util {
ast_manager& m;
sort_ref_vector m_trail;
obj_map<sort, sort*> m_fresh;
unsigned m_counter = 0;
sort_ref fresh(sort* s);
sort_ref_vector fresh(unsigned n, sort* const* s);
public:
util(ast_manager& m): m(m), m_trail(m) {}
bool unify(sort* s1, sort* s2, substitution& sub);
bool unify(func_decl* f1, func_decl* f2, substitution& sub);
bool unify(substitution const& s1, substitution const& s2,
substitution& sub);
bool match(substitution& sub, sort* s1, sort* s_ground);
// collect instantiations of polymorphic functions
void collect_poly_instances(expr* e, ptr_vector<func_decl>& instances);
// test if expression contains polymorphic variable.
bool has_type_vars(expr* e);
void collect_type_vars(expr* e, ptr_vector<sort>& tvs);
};
}