include order
include collections_impl
¤
Arrays
Array types represent maps from an interval [0,end) of a type "domain" to a type "range". An array type thing.t can be created like this:
instance thing : array(domain.t,range.t)
The implementation of this type requires that domain.t be interpreted as 'int'.
module array(domain,range) = {
type this
alias t = this
action empty returns (a:t)
action create(s:domain,y:range) returns (a:t)
action set(a:t,x:domain,y:range) returns (a:t)
action get(a:t,x:domain) returns (y:range)
action size(a:t) returns (s:domain)
action resize(a:t,s:domain,v:range) returns (a:t)
action append(a:t,v:range) returns (a:t)
action extend(a:t,b:t) returns (a:t)
¤
Representation
Function "end" gives the end value of an array while value(a,x) gives the value that x maps to in a.
function begin(A:t) : domain
definition begin(A) = 0
function end(A:t) : domain
function value(A:t,X:domain) : range
function eq(X:t,Y:t) = (end(X) = end(Y) & forall I. 0 <= I & I < end(X) -> value(X,I) = value(Y,I))
function segment(A:t, LO:domain, HI:domain) : t
action back(a:t) returns (res:range)
action pop_back(a:t) returns (a:t)
action reverse(a:t) returns (a:t)
¤
Specification
Notice that get and set have the precondition that x is in [0,end).
object spec = {
property end(X) >= 0
after empty {
assert end(a) = 0
}
before create {
assert 0 <= s
}
after create {
assert end(a) = s & value(a,X) = y
}
before set {
assert 0 <= x & x < end(a)
}
after set {
assert end(a) = end(old a);
assert value(a,X) = y if X = x else value(old a,X)
}
before get {
assert 0 <= x & x < end(a)
}
after get {
assert value(a,x) = y
}
after size {
assert s = end(a)
}
after resize{
assert end(a) = s;
assert 0 <= X & X < end(old a) -> value(a,X) = value(old a,X);
assert end(old a) <= X & X < s -> value(a,X) = v
}
after append {
assert end(a) > end(old a) & ~(end(old a) < X & X < end(a));
assert 0 <= X & X < end(old a) -> value(a,X) = value(old a,X);
assert value(a,end(old a)) = v
}
theorem extensionality = {
property end(X) = end(Y) & forall I. 0 <= I & I < end(X) -> value(X,I) = value(Y,I)
property X:t = Y
}
}
instantiate array_impl
trusted isolate iso = spec,impl
attribute test = impl
}
¤
Use of the type array[domain][range], where domain and range are types, implicitly creates an instance of array(domain,range) called array[domain][range].
autoinstance array[domain][range] : array(domain,range)
¤
Vector types
A vector is the same as an array, except that the domain type is implicitly
an instance of unbounded sequence.
module vector(range) = {
instantiate array(index,range)
}
¤
Use of the type vector[range], where range is a type, implicitly creates an instance of vector(range) called vector[range].
autoinstance vector[range] : vector(range)
¤
A bounded vector of size max can hold up to max-1 elements.
module vector_bounded(range,max) = {
type domain = {0..max}
instantiate array(domain,range)
specification {
before append(a:t,v:range) {
assume a.end < max;
}
}
}
¤
rel_array
This is the same as array, except that its specification exposes a
relational abstraction. Thus, instead of a function value(A,X)
giving the value in the array A at position X, it provides a
relatiion value(A,X,V), such that V is the value of array A at
position X. This can be helpful to avoid function cycles.
module rel_array(domain,range) = {
type this
alias t = this
action empty returns (a:t)
action create(s:domain,y:range) returns (a:t)
action set(a:t,x:domain,y:range) returns (a:t)
action get(a:t,x:domain) returns (y:range)
action size(a:t) returns (s:domain)
action resize(a:t,s:domain,v:range) returns (a:t)
action append(a:t,v:range) returns (a:t)
action extend(a:t,b:t) returns (a:t)
function segment(A:t, LO:domain, HI:domain) : t
action back(a:t) returns (res:range)
action pop_back(a:t) returns (a:t)
action reverse(a:t) returns (a:t)
¤
Representation
Function "end" gives the end value of an array while value(a,x) gives the value that x maps to in a.
function end(A:t) : domain
function value(A:t,X:domain) : range
relation value_is(A:t,X:domain,Y:range)
¤
Specification
Notice that get and set have the precondition that x is in [0,end).
object spec = {
property end(X) >= 0
property value_is(A,X,Y1) & value_is(A,X,Y2) -> Y1 = Y2
after empty {
assert end(a) = 0
}
before create {
assert 0 <= s
}
after create {
assert end(a) = s & value_is(a,X,y)
}
before set {
assert 0 <= x & x < end(a)
}
after set {
assert end(a) = end(old a);
assert value_is(a,x,Y) <-> Y = y;
assert X ~= x -> (value_is(a,X,Y) <-> value_is(old a,X,Y))
}
before get {
assert 0 <= x & x < end(a)
}
after get {
assert value_is(a,x,y)
}
after size {
assert s = end(a)
}
after resize {
assert end(a) = s;
assert 0 <= X & X < end(old a) -> (value_is(a,X,Y) <-> value_is(old a,X,Y));
assert end(old a) <= X & X < s -> (value_is(a,X,Y) <-> Y = v)
}
after append {
assert end(a) > end(old a) & ~(end(old a) < X & X < end(a));
assert 0 <= X & X < end(old a) -> (value_is(a,X,Y) <-> value_is(old a,X,Y));
assert value_is(a,end(old a),Y) <-> Y = v
}
theorem extensionality = {
property end(X) = end(Y) & forall I,V. 0 <= I & I < end(X) -> (value_is(X,I,V) <-> value_is(Y,I,V))
property X:t = Y
}
}
instantiate array_impl
object impl = {...
definition value_is(A,X,V) = (value(A,X) = V)
}
trusted isolate iso = spec,impl
attribute test = impl
}
¤
Option types
An option is either empty or has value or type range_t.
module option(range_t) = {
type domain_t
instantiate array(domain_t,range_t)
derived is_empty(X:t) = (end(X) = 0)
derived contents(X:t) = value(X,0)
action just(inp:range_t) returns(out:t) = {
out := create(1,inp);
}
interpret domain_t -> bv[1]
}
¤
Use of the type option[range], where range is a type, implicitly creates an instance of option(range) called option[range].
autoinstance option[range] : option(range)
¤
Null number types
A Null number type is one that has a special value representing "not a number".
The "not a number" value is encoded as the constant null_value.
module null_type(range_t,null_value) = {
type t = struct {
is_null : bool,
contents : range_t
}
<<< encode `t`
std::ostream &operator <<(std::ostream &s, const `t` &a) {
if (a.is_null)
s << "`null_value`";
else
s << a.contents;
return s;
}
template <>
`t` _arg<`t`>(std::vector<ivy_value> &args, unsigned idx, long long bound) {
ivy_value &arg = args[idx];
if (arg.fields.size())
throw out_of_bounds(idx);
`t` a;
a.is_null = (arg.atom == "`null_value`");
a.contents = _arg<`range_t`>(args,idx,bound);
return a;
}
template <>
void __ser<`t`>(ivy_ser &res, const `t` &t){
if (t.is_null)
res.set((long long)strtoull("`null_value`",NULL,0));
else
__ser(res,t.contents);
}
template <>
void __deser<`t`>(ivy_deser &inp, `t` &res){
long long val;
inp.get(val);
res.is_null = (val == (long long)strtoull("`null_value`",NULL,0));
std::vector<ivy_value> args;
args.resize(1);
std::ostringstream sts;
sts << val;
args[0].atom = sts.str();
res.contents = _arg<`range_t`>(args,0,0);
}
template <>
void __ser<`t`>(ivy_ser_128 &res, const `t` &t){
if (t.is_null)
res.set((long long)strtoull("`null_value`",NULL,0));
else
__ser(res,t.contents);
}
template <>
void __deser<`t`>(ivy_deser_128 &inp, `t` &res){
long long val;
inp.get(val);
res.is_null = (val == (long long)strtoull("`null_value`",NULL,0));
std::vector<ivy_value> args;
args.resize(1);
std::ostringstream sts;
sts << val;
args[0].atom = sts.str();
res.contents = _arg<`range_t`>(args,0,0);
}
>>>
}
¤
Paths
A path is similar to an array but differs in the way it is serialized. That is, a path with elements e_0, e_1, ... is serialized as a string of the form "e_0/e_1/...". To prevent misinterpretation, a separator character '/' is replaced by '\/' and '\' by '\'. Also, string quatation marks '"' are elided from e_0, e_1 ... A further difference between paths and strings is that paths are extensional -- if two paths have the same sequence of elements, they are equal. This means that to stay decidable, the index type of paths must be finite.
A path type thing.t can be created like this:
instance thing : path(domain.t,range.t)
The implementation of this type requires that domain.t be interpreted as an integer type.
module path(domain,range) = {
type t
action empty returns (a:t)
action create(s:domain,y:range) returns (a:t)
action set(a:t,x:domain,y:range) returns (a:t)
action get(a:t,x:domain) returns (y:range)
action size(a:t) returns (s:domain)
action resize(a:t,s:domain,v:range) returns (a:t)
action append(a:t,v:range) returns (a:t)
¤
Representation
Function "end" gives the end value of an array while value(a,x) gives the value that x maps to in a.
function end(A:t) : domain
function value(A:t,X:domain) : range
¤
Specification
Notice that get and set have the precondition that x is in [0,end).
property [extensionality]
forall X,Y. (end(X) = end(Y))
& (forall I. (I < end(X) -> value(X,I) = value(Y,I)))
-> X = Y
object spec = {
property end(X) >= 0
after empty {
assert end(a) = 0
}
before create {
assert 0 <= s
}
after create {
assert end(a) = s & value(a,X) = y
}
before set {
assert 0 <= x & x < end(a)
}
after set {
assert value(a,X) = y if X = x else value(old a,X)
}
before get {
assert 0 <= x & x < end(a)
}
after get {
assert value(a,x) = y
}
after size {
assert s = end(a)
}
after resize {
assert end(a) = s;
assert 0 <= X & X < end(old a) -> value(a,X) = value(old a,X);
assert end(old a) <= X & X < s -> value(a,X) = v
}
after append {
assert end(a) > end(old a) & ~(end(old a) < X & X < end(a));
assert 0 <= X & X < end(old a) -> value(a,X) = value(old a,X);
assert value(a,end(old a)) = v
}
}
object impl = {
<<< member
class t_ : std::vector<range> {};
interpret t -> <<< t_ >>>
interpret t -> <<< std::vector<`range`> >>>
definition value(a:t,i:domain) = <<< (0 <= `i` && `i` < `a`.size()) ? `a`[`i`] : val >>>
definition end(a:t) = <<< `a`.size() >>>
implement create {
<<<
`a`.resize(`s`);
for (unsigned i = 0; i < `s`; i++)
`a`[i] = y;
>>>
}
implement empty {
<<<
>>>
}
implement set {
<<<
if (0 <= `x` && `x` < (`domain`)`a`.size())
`a`[`x`] = `y`;
>>>
}
implement get {
<<<
if (0 <= `x` && `x` < (`domain`)`a`.size())
`y` = `a`[`x`];
>>>
}
implement size {
<<<
`s` = (`domain`) `a`.size();
>>>
}
implement resize {
<<<
unsigned __old_size = `a`.size();
`a`.resize(`s`);
for (unsigned i = __old_size; i < (unsigned)`s`; i++)
`a`[i] = v;
>>>
}
implement append {
<<<
`a`.push_back(`v`);
>>>
}
<<< impl
std::ostream &operator <<(std::ostream &s, const `t` &a) {
s << '\"';
for (unsigned i = 0; i < a.size(); i++) {
if (i != 0)
s << '/';
std::ostringstream os;
os << a[i];
std::string st = os.str();
for (unsigned j = 0; j < st.size(); j++) {
char c = st[j];
if (c == '/')
s << "\\/";
else if (c == '\\')
s << "\\\\";
else if (c != '\"')
s << c;
}
}
s << '\"';
return s;
}
template <>
`t` _arg<`t`>(std::vector<ivy_value> &args, unsigned idx, long long bound) {
ivy_value &arg = args[idx];
if (arg.fields.size())
throw out_of_bounds(idx);
`t` a;
ivy_value tmp;
tmp.fields.resize(1);
std::string str = arg.atom;
for (unsigned i = 0; i < str.size(); i++) {
char c = str[i];
if (c == '\\' && i+1 < str.size())
tmp.fields.back().atom.push_back(str[++i]);
else if (c == '/')
tmp.fields.resize(tmp.fields.size()+1);
else if (c != '\"')
tmp.fields.back().atom.push_back(c);
}
a.resize(tmp.fields.size());
for (unsigned i = 0; i < a.size(); i++) {
a[i] = _arg<`range`>(tmp.fields,i,0);
}
return a;
}
template <>
void __deser<`t`>(ivy_deser &inp, `t` &res) {
inp.open_list();
while(inp.open_list_elem()) {
res.resize(res.size()+1);
__deser(inp,res.back());
inp.close_list_elem();
}
inp.close_list();
}
template <>
void __ser<`t`>(ivy_ser &res, const `t` &inp) {
int sz = inp.size();
res.open_list(sz);
for (unsigned i = 0; i < (unsigned)sz; i++) {
res.open_list_elem();
__ser(res,inp[i]);
res.close_list_elem();
}
res.close_list();
}
template <>
void __deser<`t`>(ivy_deser_128 &inp, `t` &res) {
inp.open_list();
while(inp.open_list_elem()) {
res.resize(res.size()+1);
__deser(inp,res.back());
inp.close_list_elem();
}
inp.close_list();
}
template <>
void __ser<`t`>(ivy_ser_128 &res, const `t` &inp) {
int sz = inp.size();
res.open_list(sz);
for (unsigned i = 0; i < (unsigned)sz; i++) {
res.open_list_elem();
__ser(res,inp[i]);
res.close_list_elem();
}
res.close_list();
}
template <>
z3::expr __to_solver(gen& g, const z3::expr& z3val, `t`& val) {
z3::expr z3end = g.apply("`end`",z3val);
z3::expr cond = z3end == g.int_to_z3(z3end.get_sort(),val.size());
for (unsigned i = 0; i < val.size(); i++)
cond = cond && __to_solver(g,g.apply("`value`",z3val,g.int_to_z3(g.sort("`domain`"),i)),val[i]);
return cond;
}
template <>
void __from_solver<`t`>( gen &g, const z3::expr &v,`t` &res){
`domain` __end;
__from_solver(g,g.apply("`end`",v),__end);
unsigned __sz = (unsigned) __end;
res.resize(__sz);
for (unsigned __i = 0; __i < __sz; ++__i)
__from_solver(g,g.apply("`value`",v,g.int_to_z3(g.sort("`domain`"),__i)),res[__i]);
}
template <>
void __randomize<`t`>( gen &g, const z3::expr &v, const std::string &sort_name){
unsigned __sz = rand() % 4;
z3::expr val_expr = g.int_to_z3(g.sort("`domain`"),__sz);
z3::expr pred = g.apply("`end`",v) == val_expr;
g.add_alit(pred);
for (unsigned __i = 0; __i < __sz; ++__i)
__randomize<`range`>(g,g.apply("`value`",v,g.int_to_z3(g.sort("`domain`"),__i)),"`range`");
}
>>>
}
trusted isolate iso = spec,impl
}
¤
Maps.
This module provides a relational model of a function from a type dom to a type rng. Initially, everything is mapped to the element zero.
The key invariant of a map is that it is a function, i.e., for every X there exists a Y such that map(X,Y). The module does not state this invariant, however, since the quantifier alternation could destroy stratification. Instead it provides a "lemma" that allows the user to instantiate Y for particular values of X.
Parameters:
dom The domain type rng The range type zero The initial value for every domain element
module map(dom,rng,zero) = {
relation map(X:dom,Y:rng)
action set_(x:dom,y:rng)
action get(x:dom) returns (y:rng)
action lemma(x:dom)
object spec = {
after init {
map(X,Y) := (Y = zero)
}
implement set_(x:dom,y:rng) {
call lemma(x);
map(x,Y) := Y = y
}
implement get(x:dom) returns (y:rng) {
assume map(x,y)
}
implement lemma(x:dom) {
assume exists Y. map(x,Y)
}
}
conjecture map(K,L) & map(K,M) -> L = M
}
¤
Partial functions
This module represents a mutable partial function type dom to type rng. The state consists of relation map that stores the function as a set of tuples, and an auxiliary set pre that gives the pre-image.
¤
module partial_function(dom,rng) = {
relation map(X:dom,Y:rng)
relation pre(X:dom)
action remap(x:dom,y:rng)
action remove(x:dom)
action get(x:dom) returns (y:rng)
action present(x:dom) returns (r:bool)
action lemma(x:dom)
object spec = {
after init {
map(X,Y) := false;
pre(X) := false
}
implement remap {
call lemma(x);
map(x,Y) := Y = y;
pre(x) := true
}
implement remove {
call lemma(x);
map(x,Y) := false;
pre(x) := false
}
after get {
assume pre(x) -> map(x,y)
}
after present {
assume r = pre(x)
}
implement lemma(x:dom) {
assume pre(x) -> exists Y. map(x,Y)
}
explicit invariant [img] pre(X) -> exists Y. map(X,Y)
}
conjecture map(K,L) & map(K,M) -> L = M
trusted isolate iso = this
}
¤
Witness
This is similar to partial_function, but has no domain. Useful for storing a witness to an existential quantifier without creating a function cycle.
¤
module witness(rng) = {
action set(v:rng)
action get returns (v:rng)
action is_empty returns (r:bool)
action clear
relation is(Y:rng)
relation present
object spec = {
after init {
is(Y) := false;
present := false
}
after set {
is(Y) := Y = v;
present := true
}
after get {
assert present -> is(v)
}
after clear {
is(Y) := false;
present := false
}
after is_empty {
assert r <-> ~present
}
}
object impl = {
relation full
individual val : rng
after init {
full := false
}
implement set {
val := v;
full := true
}
implement get {
v := val
}
implement clear {
full := false
}
implement is_empty {
r := ~full
}
conjecture (present <-> full) & (full -> is(val))
}
isolate iso = this
attribute test = impl
}
module functional(rel) = {
property [totality] exists Y. rel(X,Y)
property [injectivity] rel(X,Y) & rel(X,Z) -> Y = Z
}
module inverse_functional(rel) = {
property [totality] exists Y. rel(Y,X)
property [injectivity] rel(Y,X) & rel(Z,X) -> Y = Z
}
module set_wrapper(key) = {
<<< header
>>>
<<< header
template <class key, class compare_thunk>
struct set_wrapper_less_t {
compare_thunk less;
bool operator()(const key &x , const key &y) {
return less(x,y);
}
set_wrapper_less_t(compare_thunk less) : less(less) {}
};
>>>
type repr_t
interpret repr_t -> <<< primitive std::set<`key.t`,std::function<bool(const `key.t` &, const `key.t` &)> > * >>>
var s : repr_t
<<< init
auto less = `less`;
std::function<bool(const `key.t` &, const `key.t` &)> fun = [less](const `key.t` &x, const `key.t` &y) {return less(x,y);};
`s` = new std::set<`key.t`,std::function<bool(const `key.t` &, const `key.t` &)> > (fun);
// `s`->insert(`0:key`);
>>>
action less(x:key.t,y:key.t) returns (res:bool) = {
res := x < y;
}
implement insert(x:key) {
<<<
`s`->insert(`x`);
>>>
}
implement member(x:key.t) returns (y:bool) {
<<<
auto it = `s`->find(`x`);
`y` = it != `s`->end();
>>>
}
implement erase(lo:key.iter.t,hi:key.iter.t) {
<<<
if (!`lo`.is_end && (`hi`.is_end || `less`(`lo`.val,`hi`.val)))
`s`->erase(`lo`.is_end ? `s`->end() : `s`->lower_bound(`lo`.val),
`hi`.is_end ? `s`->end() : `s`->lower_bound(`hi`.val));
>>>
}
implement glb(it:key.iter.t) returns (res:key.iter.t) {
<<<
auto __it = `it`.is_end ? `s`->end() : `s`->upper_bound(`it`.val);
`res`.is_end = false;
if (__it != `s`->begin())
`res`.val = *(--__it);
>>>
}
implement lub(it:key.iter.t) returns (res:key.iter.t) {
<<<
if (`it`.is_end) {
`res`.is_end = true;
} else {
auto __it = `s`->lower_bound(`it`.val);
if (__it == `s`->end()) {
`res`.is_end = true;
} else {
`res`.is_end = false;
`res`.val = *__it;
}
}
>>>
}
implement begin returns (res:key.iter.t) {
<<<
auto __it = `s`->begin();
if (__it == `s`->end()) {
`res`.is_end = true;
} else {
`res`.is_end = false;
`res`.val = *__it;
}
>>>
}
implement next(inp:key.iter.t) returns (res:key.iter.t) {
<<<
auto __it = `s`->upper_bound(`inp`.val);
if (__it == `s`->end()) {
`res`.is_end = true;
} else {
`res`.is_end = false;
`res`.val = *__it;
}
>>>
}
action show = {
<<<
std::cout << "{";
for(auto __it = `s`->begin(), __en = `s`->end(); __it != __en; ++__it)
std::cout << *__it << ",";
std::cout << "}" << std::endl;
>>>
}
}
¤
Ordered set representation
This is intended to be implemented using the STL set template.
Ordered sets assume the element type has a total non-strict order, with a least element 0. They provide insertion of elements (in log time), deletion of ranges (in n log(n) time) and finding greatest lower bounds (log n).
For help with proofs, this module also provides an auxiliary map "succ" that gives the successor of every element in the set. The "successor" of the maximal element in the set is 0.
module ordered_set(key) = {
action insert(nkey:key)
action member(nkey:key) returns (res:bool)
action erase(lo:key.iter.t,hi:key.iter.t)
action lub(it:key.iter.t) returns (res:key.iter.t)
action glb(it:key.iter.t) returns (res:key.iter.t)
action next(it:key.iter.t) returns (res:key.iter.t)
action begin returns (res:key.iter.t)
action end returns (res:key.iter.t) = {
res := key.iter.end;
}
relation s(K:key)
object spec = { after init { s(K) := K = 0; } # insert one element before insert { s(nkey) := true;
# following is ghost code to update the successor relation
local v:key {
if some lub:key. ~(lub <= nkey) & s(lub) minimizing lub {
v := lub
}
else {
v := 0
};
call succ.set_(nkey,v)
};
if some glb:key. ~(nkey <= glb) & s(glb) maximizing glb {
call succ.lemma(glb); # instantiate succ(glb)
call succ.set_(glb,nkey)
}
}
# erase elements in a closed interval
before erase {
s(K) := s(K) & ~(lo <= K & K <= hi)
}
# the the greatest element <= k
after glb {
if some glb:key. glb <= k & s(glb) maximizing glb {
call succ.lemma(glb); # instantiate succ(glb)
assume res = glb
}
}
}
Useful invariants of the "succ" relation. The successor of K is¤
also in the set, and nothing between K and its successor is in¤
the set. Here, if L = 0 it means K is the maximim element, so we¤
have to treat this as a special case.¤
conjecture s(K) & succ.map(K,L) & L ~= 0 -> ~(L <= K) & s(L) conjecture s(K) & succ.map(K,L) & ~(M <= K) & (L = 0 | ~(L <= M)) -> ~s(M)
instance internal : set_wrapper(key)
}
module map_wrapper(key,value) = {
<<< header