Capucine Example: Sparse ArraysOur solution for one of the VACID challenges. |
This example is an implementation of sparse arrays, which are one of the VACID challenges. It features interesting invariants and a main program which handles two arrays, which Capucine is able to separate easily. To compile it, go into the capucine directory and type:
make examples/sparse.gwhy
type larray ('a)
logic store (a: larray ('a), i: int, v: 'a): larray ('a)
logic select (a: larray ('a), i: int): 'a
axiom select_eq:
forall a: larray ('a).
forall i: int.
forall v: 'a.
select(store(a, i, v), i) = v
axiom select_neq:
forall a: larray ('a).
forall i: int.
forall j: int.
forall v: 'a.
i <> j ==>
select(store(a, i, v), j) = select(a, j)
class array ('a)
{
length: int;
contents: larray ('a);
invariant length >= 0
}
fun array_create [R: array ('a)] (size: int): [R]
consumes R^e
produces R^c
pre size >= 0
post get(R, result, length) = size
{
let res = new [R];
res.length <- size;
return res
}
fun array_get [R: array ('a)] (a: [R], i: int): 'a
consumes R^c
produces R^c
pre 0 <= i && i < get(R, a, length)
post result = select(get(R, a, contents), i)
{
return #select(a.contents, i)
}
fun array_set [R: array ('a)] (a: [R], i: int, v: 'a): unit
consumes R^c
produces R^c
pre 0 <= i && i < get(R, a, length)
post
get(R, a, contents) = store(get(R, a, contents)@pre, i, v) &&
get(R, a, length) = get(R, a, length)@pre
{
use invariant a;
a.contents <- #store(a.contents, i, v);
}
logic interval (a: int, x: int, b: int) = a <= x && x < b
class sparse ('a)
{
single Rvalue: array ('a);
single Ridx: array (int);
single Rback: array (int);
value: [Rvalue];
idx: [Ridx];
back: [Rback];
n: int;
default: 'a;
size: int;
invariant
0 <= n && n <= size &&
get(Rvalue, value, length) = size &&
get(Ridx, idx, length) = size &&
get(Rback, back, length) = size &&
forall i: int.
interval(0, i, n) ==>
interval(0, select(get(Rback, back, contents), i), size) &&
select(get(Ridx, idx, contents), select(get(Rback, back, contents), i)) = i
}
logic is_elt [R: sparse ('a)] (a: [R], i: int) =
interval(
0,
select(
get(get(R, a, Ridx), get(R, a, idx), contents),
i
),
get(R, a, n)) &&
select(
get(get(R, a, Rback), get(R, a, back), contents),
select(
get(get(R, a, Ridx), get(R, a, idx), contents),
i
)
) = i
logic model [R: sparse ('a)] (a: [R], i: int): 'a
axiom model_in:
forall region R: sparse ('a).
forall a: [?].
(* pas besoin de "a in R" : on est en train de définir "model" *)
forall i: int.
is_elt [R] (a, i) ==>
model [R] (a, i) =
select(get(get(R, a, Rvalue), get(R, a, value), contents), i)
axiom model_out:
forall region R: sparse ('a).
forall a: [?].
(* pas besoin de "a in R" : on est en train de définir "model" *)
forall i: int.
not is_elt [R] (a, i) ==>
model [R] (a, i) = get(R, a, default)
(* Sans chapeaux : prouvé entièrement par CVC3 grâce à l'assert.
Avec chapeaux : prouvé entièrement par Alt-Ergo sans l'assert. *)
fun create [R: sparse ('a)] (sz: int, def: 'a): [R]
consumes R^e
produces R^c
pre 0 <= sz
post
get(R, result, size) = sz &&
forall i: int. model [R] (result, i) = def
{
let a = new [R];
let a_value = array_create [a.Rvalue] (sz);
a.value <- a_value;
let a_idx = array_create [a.Ridx] (sz);
a.idx <- a_idx;
let a_back = array_create [a.Rback] (sz);
a.back <- a_back;
a.n <- 0;
a.default <- def;
a.size <- sz;
(* assert forall i: int. not (is_elt [R] (a, i));*)
return a
}
class Ref ('a)
{
ref_value: 'a;
}
(* Sans chapeaux : prouvé entièrement par CVC3 grâce à l'assert.
Avec chapeaux : prouvé entièrement par Alt-Ergo sans l'assert. *)
(* TODO: read-only permission for array_get, so as not to have to unpack a *)
fun sget [R: sparse ('a)] (a: [R], i: int): 'a
consumes R^c
produces R^c
pre interval(0, i, get(R, a, size))
post result = model [R] (a, i)
{
use invariant a;
let index = array_get(a.idx, i); (* unpack inféré pour récupérer a.idx^c *)
let region Rres: Ref ('a);
let res = new [Rres];
if 0 <= index && index < a.n then
{
let back_index = array_get(a.back, index);
if back_index = i then
{
let value_i = array_get(a.value, i);
res.ref_value <- value_i;
}
else
{
res.ref_value <- a.default;
};
}
else
{
(* assert not (is_elt [R] (a, i));*)
res.ref_value <- a.default;
};
return res.ref_value
}
(* pas besoin de focus, grâce au "single" dans la définition de la classe *)
(* le assert final est inutile avec les chapeaux et c'est même plus
rapide en l'enlevant *)
fun sset [R: sparse ('a)] (a: [R], i: int, v: 'a): unit
consumes R^c
produces R^c
pre interval(0, i, get(R, a, size))
post
get(R, a, size) = get(R, a, size)@pre &&
(forall j: int. j <> i ==> model [R] (a, j) = model [R] (a, j)@pre) &&
model [R] (a, i) = v
{
use invariant a;
let x = array_set [a.Rvalue] (a.value, i, v);
let index = array_get [a.Ridx] (a.idx, i);
if 0 <= index && index < a.n then
{
let back_index = array_get [a.Rback] (a.back, index);
if back_index = i then
{
(* assert is_elt [R] (a, i);*)
}
else
{
assert get(R, a, n) < get(R, a, size); (* pigeons *)
(* assert not (is_elt [R] (a, i));*)
let x = array_set [a.Ridx] (a.idx, i, a.n);
let x = array_set [a.Rback] (a.back, a.n, i);
a.n <- a.n + 1;
(* assert is_elt [R] (a, i);*)
};
}
else
{
assert get(R, a, n) < get(R, a, size); (* pigeons *)
(* assert not (is_elt [R] (a, i));*)
let x = array_set [a.Ridx] (a.idx, i, a.n);
let x = array_set [a.Rback] (a.back, a.n, i);
a.n <- a.n + 1;
(* assert is_elt [R] (a, i);*)
};
(* permet de prouver model(a, i) = v quand on change idx et back *)
(* assert is_elt [R] (a, i);*)
}
(*
Autre version factorisée avec une référence sur un booléen mais les PO
deviennent illisibles : en fait c'est moins bien.
(* pas besoin de focus, grâce au "single" dans la définition de la classe *)
fun sset [R: sparse ('a)] (a: [R], i: int, v: 'a): unit
consumes R^c
produces R^c
pre interval(0, i, get(R, a, size))
post
get(R, a, size) = get(R, a, size)@pre &&
(forall j: int. j <> i ==> model [R] (a, j) = model [R] (a, j)@pre) &&
model [R] (a, i) = v
{
use invariant a;
let x = array_set [a.Rvalue] (a.value, i, v);
let index = array_get [a.Ridx] (a.idx, i);
let region Rnew: Ref (bool);
let is_new = new [Rnew];
if 0 <= index && index < a.n then
{
let back_index = array_get [a.Rback] (a.back, index);
if back_index = i then
{
is_new.ref_value <- false;
}
else
{
is_new.ref_value <- true;
};
}
else
{
is_new.ref_value <- true;
};
if is_new.ref_value then
{
assert get(R, a, n) < get(R, a, size); (* pidgeons *)
let x = array_set [a.Ridx] (a.idx, i, a.n);
let x = array_set [a.Rback] (a.back, a.n, i);
a.n <- a.n + 1;
}
else {};
}
*)
fun test(x: unit): unit
{
let region Ra: sparse (int);
let region Rb: sparse (int);
let default = 0;
let a = create [Ra] (10, default);
let b = create [Rb] (10, default);
let x = sget [Ra] (a, 5);
let y = sget [Rb] (b, 7);
assert x = default && y = default;
let ignore = sset [Ra] (a, 5, 1);
let ignore = sset [Rb] (b, 7, 2);
let x = sget [Ra] (a, 5);
let y = sget [Rb] (b, 7);
assert x = 1 && y = 2;
let x = sget [Ra] (a, 7);
let y = sget [Rb] (b, 5);
assert x = default && y = default;
let x = sget [Ra] (a, 0);
let y = sget [Rb] (b, 0);
assert x = default && y = default;
let x = sget [Ra] (a, 9);
let y = sget [Rb] (b, 9);
assert false;
}