Capucine Example: Sparse Arrays

Our solution for one of the VACID challenges.

Back to Capucine

Discussion

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

Code

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;
}