Library Kildall

Solvers for dataflow inequations.

Require Import Coqlib.
Require Import Iteration.
Require Import Maps.
Require Import Lattice.

A forward dataflow problem is a set of inequations of the form
  • X(s) >= transf n X(n) if program point s is a successor of program point n
  • X(n) >= a if (n, a) belongs to a given list of (program points, approximations).


The unknowns are the X(n), indexed by program points (e.g. nodes in the CFG graph of a RTL function). They range over a given ordered set that represents static approximations of the program state at each point. The transf function is the abstract transfer function: it computes an approximation transf n X(n) of the program state after executing instruction at point n, as a function of the approximation X(n) of the program state before executing that instruction.

Symmetrically, a backward dataflow problem is a set of inequations of the form
  • X(n) >= transf s X(s) if program point s is a successor of program point n
  • X(n) >= a if (n, a) belongs to a given list of (program points, approximations).


The only difference with a forward dataflow problem is that the transfer function transf now computes the approximation before a program point s from the approximation X(s) after point s.

This file defines three solvers for dataflow problems. The first two solve (optimally) forward and backward problems using Kildall's worklist algorithm. They assume that the unknowns range over a semi-lattice, that is, an ordered type equipped with a least upper bound operation.

The last solver corresponds to propagation over extended basic blocks: it returns approximate solutions of forward problems where the unknowns range over any ordered type having a greatest element top. It simply sets X(n) = top for all merge points n, that is, program points having several predecessors. This solver is useful when least upper bounds of approximations do not exist or are too expensive to compute.

Solving forward dataflow problems using Kildall's algorithm


Definition successors_list (successors: PTree.t (list positive)) (pc: positive) : list positive :=
  match successors!pc with None => nil | Some l => l end.

Notation "a !!! b" := (successors_list a b) (at level 1).

A forward dataflow solver has the following generic interface. Unknowns range over the type L.t, which is equipped with semi-lattice operations (see file Lattice).

Module Type DATAFLOW_SOLVER.

  Declare Module L: SEMILATTICE.

  Variable fixpoint:
    forall (successors: PTree.t (list positive))
           (transf: positive -> L.t -> L.t)
           (entrypoints: list (positive * L.t)),
    option (PMap.t L.t).

fixpoint successors transf entrypoints is the solver. It returns either an error or a mapping from program points to values of type L.t representing the solution. successors is a finite map returning the list of successors of the given program point. transf is the transfer function, and entrypoints the additional constraints imposed on the solution.

  Hypothesis fixpoint_solution:
    forall successors transf entrypoints res n s,
    fixpoint successors transf entrypoints = Some res ->
    In s successors!!!n ->
    L.ge res!!s (transf n res!!n).

The fixpoint_solution theorem shows that the returned solution, if any, satisfies the dataflow inequations.

  Hypothesis fixpoint_entry:
    forall successors transf entrypoints res n v,
    fixpoint successors transf entrypoints = Some res ->
    In (n, v) entrypoints ->
    L.ge res!!n v.

The fixpoint_entry theorem shows that the returned solution, if any, satisfies the additional constraints expressed by entrypoints.

  Hypothesis fixpoint_invariant:
    forall successors transf entrypoints
           (P: L.t -> Prop),
    P L.bot ->
    (forall x y, P x -> P y -> P (L.lub x y)) ->
    (forall pc x, P x -> P (transf pc x)) ->
    (forall n v, In (n, v) entrypoints -> P v) ->
    forall res pc,
    fixpoint successors transf entrypoints = Some res ->
    P res!!pc.

Finally, any property that is preserved by L.lub and transf and that holds for L.bot also holds for the results of the analysis.

End DATAFLOW_SOLVER.

Kildall's algorithm manipulates worklists, which are sets of CFG nodes equipped with a ``pick next node to examine'' operation. The algorithm converges faster if the nodes are chosen in an order consistent with a reverse postorder traversal of the CFG. For now, we parameterize the dataflow solver over a module that implements sets of CFG nodes.

Module Type NODE_SET.

  Variable t: Type.
  Variable add: positive -> t -> t.
  Variable pick: t -> option (positive * t).
  Variable initial: PTree.t (list positive) -> t.

  Variable In: positive -> t -> Prop.
  Hypothesis add_spec:
    forall n n' s, In n' (add n s) <-> n = n' \/ In n' s.
  Hypothesis pick_none:
    forall s n, pick s = None -> ~In n s.
  Hypothesis pick_some:
    forall s n s', pick s = Some(n, s') ->
    forall n', In n' s <-> n = n' \/ In n' s'.
  Hypothesis initial_spec:
    forall successors n s,
    successors!n = Some s -> In n (initial successors).

End NODE_SET.

We now define a generic solver that works over any semi-lattice structure.

Module Dataflow_Solver (LAT: SEMILATTICE) (NS: NODE_SET):
                          DATAFLOW_SOLVER with Module L := LAT.

Module L := LAT.

Section Kildall.

Variable successors: PTree.t (list positive).
Variable transf: positive -> L.t -> L.t.
Variable entrypoints: list (positive * L.t).

The state of the iteration has two components:
  • A mapping from program points to values of type L.t representing the candidate solution found so far.
  • A worklist of program points that remain to be considered.

Record state : Type :=
  mkstate { st_in: PMap.t L.t; st_wrk: NS.t }.

Kildall's algorithm, in pseudo-code, is as follows:
    while st_wrk is not empty, do
        extract a node n from st_wrk
        compute out = transf n st_in[n]
        for each successor s of n:
            compute in = lub st_in[s] out
            if in <> st_in[s]:
                st_in[s] := in
                st_wrk := st_wrk union {s}
            end if
        end for
    end while
    return st_in


The initial state is built as follows:
  • The initial mapping sets all program points to L.bot, except those mentioned in the entrypoints list, for which we take the associated approximation as initial value. Since a program point can be mentioned several times in entrypoints, with different approximations, we actually take the l.u.b. of these approximations.
  • The initial worklist contains all the program points.

Fixpoint start_state_in (ep: list (positive * L.t)) : PMap.t L.t :=
  match ep with
  | nil =>
      PMap.init L.bot
  | (n, v) :: rem =>
      let m := start_state_in rem in PMap.set n (L.lub m!!n v) m
  end.

Definition start_state :=
  mkstate (start_state_in entrypoints) (NS.initial successors).

propagate_succ corresponds, in the pseudocode, to the body of the for loop iterating over all successors.

Definition propagate_succ (s: state) (out: L.t) (n: positive) :=
  let oldl := s.(st_in)!!n in
  let newl := L.lub oldl out in
  if L.beq oldl newl
  then s
  else mkstate (PMap.set n newl s.(st_in)) (NS.add n s.(st_wrk)).

propagate_succ_list corresponds, in the pseudocode, to the for loop iterating over all successors.

Fixpoint propagate_succ_list (s: state) (out: L.t) (succs: list positive)
                             {struct succs} : state :=
  match succs with
  | nil => s
  | n :: rem => propagate_succ_list (propagate_succ s out n) out rem
  end.

step corresponds to the body of the outer while loop in the pseudocode.

Definition step (s: state) : PMap.t L.t + state :=
  match NS.pick s.(st_wrk) with
  | None =>
      inl _ s.(st_in)
  | Some(n, rem) =>
      inr _ (propagate_succ_list
              (mkstate s.(st_in) rem)
              (transf n s.(st_in)!!n)
              (successors!!!n))
  end.

The whole fixpoint computation is the iteration of step from the start state.

Definition fixpoint : option (PMap.t L.t) :=
  PrimIter.iterate _ _ step start_state.

Monotonicity properties


We first show that the st_in part of the state evolves monotonically: at each step, the values of the st_in[n] either remain the same or increase with respect to the L.ge ordering.

Definition in_incr (in1 in2: PMap.t L.t) : Prop :=
  forall n, L.ge in2!!n in1!!n.

Lemma in_incr_refl:
  forall in1, in_incr in1 in1.


Lemma in_incr_trans:
  forall in1 in2 in3, in_incr in1 in2 -> in_incr in2 in3 -> in_incr in1 in3.


Lemma propagate_succ_incr:
  forall st out n,
  in_incr st.(st_in) (propagate_succ st out n).(st_in).


Lemma propagate_succ_list_incr:
  forall out scs st,
  in_incr st.(st_in) (propagate_succ_list st out scs).(st_in).


Lemma fixpoint_incr:
  forall res,
  fixpoint = Some res -> in_incr (start_state_in entrypoints) res.


Correctness invariant


The following invariant is preserved at each iteration of Kildall's algorithm: for all program points n, either n is in the worklist, or the inequations associated with n (st_in[s] >= transf n st_in[n] for all successors s of n) hold. In other terms, the worklist contains all nodes that do not yet satisfy their inequations.

Definition good_state (st: state) : Prop :=
  forall n,
  NS.In n st.(st_wrk) \/
  (forall s, In s (successors!!!n) ->
             L.ge st.(st_in)!!s (transf n st.(st_in)!!n)).

We show that the start state satisfies the invariant, and that the step function preserves it.

Lemma start_state_good:
  good_state start_state.


Lemma propagate_succ_charact:
  forall st out n,
  let st' := propagate_succ st out n in
  L.ge st'.(st_in)!!n out /\
  (forall s, n <> s -> st'.(st_in)!!s = st.(st_in)!!s).


Lemma propagate_succ_list_charact:
  forall out scs st,
  let st' := propagate_succ_list st out scs in
  forall s,
  (In s scs -> L.ge st'.(st_in)!!s out) /\
  (~(In s scs) -> st'.(st_in)!!s = st.(st_in)!!s).


Lemma propagate_succ_incr_worklist:
  forall st out n x,
  NS.In x st.(st_wrk) -> NS.In x (propagate_succ st out n).(st_wrk).


Lemma propagate_succ_list_incr_worklist:
  forall out scs st x,
  NS.In x st.(st_wrk) -> NS.In x (propagate_succ_list st out scs).(st_wrk).


Lemma propagate_succ_records_changes:
  forall st out n s,
  let st' := propagate_succ st out n in
  NS.In s st'.(st_wrk) \/ st'.(st_in)!!s = st.(st_in)!!s.


Lemma propagate_succ_list_records_changes:
  forall out scs st s,
  let st' := propagate_succ_list st out scs in
  NS.In s st'.(st_wrk) \/ st'.(st_in)!!s = st.(st_in)!!s.


Lemma step_state_good:
  forall st n rem,
  NS.pick st.(st_wrk) = Some(n, rem) ->
  good_state st ->
  good_state (propagate_succ_list (mkstate st.(st_in) rem)
                                  (transf n st.(st_in)!!n)
                                  (successors!!!n)).


Correctness of the solution returned by iterate.


As a consequence of the good_state invariant, the result of fixpoint, if defined, is a solution of the dataflow inequations, since st_wrk is empty when the iteration terminates.

Theorem fixpoint_solution:
  forall res n s,
  fixpoint = Some res ->
  In s (successors!!!n) ->
  L.ge res!!s (transf n res!!n).


As a consequence of the monotonicity property, the result of fixpoint, if defined, is pointwise greater than or equal the initial mapping. Therefore, it satisfies the additional constraints stated in entrypoints.

Lemma start_state_in_entry:
  forall ep n v,
  In (n, v) ep ->
  L.ge (start_state_in ep)!!n v.


Theorem fixpoint_entry:
  forall res n v,
  fixpoint = Some res ->
  In (n, v) entrypoints ->
  L.ge res!!n v.


Preservation of a property over solutions


Variable P: L.t -> Prop.
Hypothesis P_bot: P L.bot.
Hypothesis P_lub: forall x y, P x -> P y -> P (L.lub x y).
Hypothesis P_transf: forall pc x, P x -> P (transf pc x).
Hypothesis P_entrypoints: forall n v, In (n, v) entrypoints -> P v.

Theorem fixpoint_invariant:
  forall res pc,
  fixpoint = Some res ->
  P res!!pc.


End Kildall.

End Dataflow_Solver.

Solving backward dataflow problems using Kildall's algorithm


A backward dataflow problem on a given flow graph is a forward dataflow program on the reversed flow graph, where predecessors replace successors. We exploit this observation to cheaply derive a backward solver from the forward solver.

Construction of the predecessor relation


Section Predecessor.

Variable successors: PTree.t (list positive).

Fixpoint add_successors (pred: PTree.t (list positive))
                        (from: positive) (tolist: list positive)
                        {struct tolist} : PTree.t (list positive) :=
  match tolist with
  | nil => pred
  | to :: rem => add_successors (PTree.set to (from :: pred!!!to) pred) from rem
  end.

Lemma add_successors_correct:
  forall tolist from pred n s,
  In n pred!!!s \/ (n = from /\ In s tolist) ->
  In n (add_successors pred from tolist)!!!s.


Definition make_predecessors : PTree.t (list positive) :=
  PTree.fold add_successors successors (PTree.empty (list positive)).

Lemma make_predecessors_correct:
  forall n s,
  In s successors!!!n ->
  In n make_predecessors!!!s.


End Predecessor.

Solving backward dataflow problems


The interface to a backward dataflow solver is as follows.

Module Type BACKWARD_DATAFLOW_SOLVER.

  Declare Module L: SEMILATTICE.

  Variable fixpoint:
    PTree.t (list positive) ->
    (positive -> L.t -> L.t) ->
    list (positive * L.t) ->
    option (PMap.t L.t).

  Hypothesis fixpoint_solution:
    forall successors transf entrypoints res n s,
    fixpoint successors transf entrypoints = Some res ->
    In s (successors!!!n) ->
    L.ge res!!n (transf s res!!s).

  Hypothesis fixpoint_entry:
    forall successors transf entrypoints res n v,
    fixpoint successors transf entrypoints = Some res ->
    In (n, v) entrypoints ->
    L.ge res!!n v.

  Hypothesis fixpoint_invariant:
    forall successors transf entrypoints (P: L.t -> Prop),
    P L.bot ->
    (forall x y, P x -> P y -> P (L.lub x y)) ->
    (forall pc x, P x -> P (transf pc x)) ->
    (forall n v, In (n, v) entrypoints -> P v) ->
    forall res pc,
    fixpoint successors transf entrypoints = Some res ->
    P res!!pc.

End BACKWARD_DATAFLOW_SOLVER.

We construct a generic backward dataflow solver, working over any semi-lattice structure, by applying the forward dataflow solver with the predecessor relation instead of the successor relation.

Module Backward_Dataflow_Solver (LAT: SEMILATTICE) (NS: NODE_SET):
                   BACKWARD_DATAFLOW_SOLVER with Module L := LAT.

Module L := LAT.

Module DS := Dataflow_Solver L NS.

Section Kildall.

Variable successors: PTree.t (list positive).
Variable transf: positive -> L.t -> L.t.
Variable entrypoints: list (positive * L.t).

Definition fixpoint :=
  DS.fixpoint (make_predecessors successors) transf entrypoints.

Theorem fixpoint_solution:
  forall res n s,
  fixpoint = Some res ->
  In s (successors!!!n) ->
  L.ge res!!n (transf s res!!s).


Theorem fixpoint_entry:
  forall res n v,
  fixpoint = Some res ->
  In (n, v) entrypoints ->
  L.ge res!!n v.


Theorem fixpoint_invariant:
  forall (P: L.t -> Prop),
  P L.bot ->
  (forall x y, P x -> P y -> P (L.lub x y)) ->
  (forall pc x, P x -> P (transf pc x)) ->
  (forall n v, In (n, v) entrypoints -> P v) ->
  forall res pc,
  fixpoint = Some res ->
  P res!!pc.


End Kildall.

End Backward_Dataflow_Solver.

Analysis on extended basic blocks


We now define an approximate solver for forward dataflow problems that proceeds by forward propagation over extended basic blocks. In other terms, program points with multiple predecessors are mapped to L.top (the greatest, or coarsest, approximation) and the other program points are mapped to transf p X[p] where p is their unique predecessor.

This analysis applies to any type of approximations equipped with an ordering and a greatest element.

Module Type ORDERED_TYPE_WITH_TOP.

  Variable t: Type.
  Variable ge: t -> t -> Prop.
  Variable top: t.
  Hypothesis top_ge: forall x, ge top x.
  Hypothesis refl_ge: forall x, ge x x.

End ORDERED_TYPE_WITH_TOP.

The interface of the solver is similar to that of Kildall's forward solver. We provide one additional theorem fixpoint_invariant stating that any property preserved by the transf function holds for the returned solution.

Module Type BBLOCK_SOLVER.

  Declare Module L: ORDERED_TYPE_WITH_TOP.

  Variable fixpoint:
    PTree.t (list positive) ->
    (positive -> L.t -> L.t) ->
    positive ->
    option (PMap.t L.t).

  Hypothesis fixpoint_solution:
    forall successors transf entrypoint res n s,
    fixpoint successors transf entrypoint = Some res ->
    In s (successors!!!n) ->
    L.ge res!!s (transf n res!!n).

  Hypothesis fixpoint_entry:
    forall successors transf entrypoint res,
    fixpoint successors transf entrypoint = Some res ->
    res!!entrypoint = L.top.

  Hypothesis fixpoint_invariant:
    forall successors transf entrypoint
           (P: L.t -> Prop),
    P L.top ->
    (forall pc x, P x -> P (transf pc x)) ->
    forall res pc,
    fixpoint successors transf entrypoint = Some res ->
    P res!!pc.

End BBLOCK_SOLVER.

The implementation of the ``extended basic block'' solver is a functor parameterized by any ordered type with a top element.

Module BBlock_solver(LAT: ORDERED_TYPE_WITH_TOP):
                        BBLOCK_SOLVER with Module L := LAT.

Module L := LAT.

Section Solver.

Variable successors: PTree.t (list positive).
Variable transf: positive -> L.t -> L.t.
Variable entrypoint: positive.
Variable P: L.t -> Prop.
Hypothesis Ptop: P L.top.
Hypothesis Ptransf: forall pc x, P x -> P (transf pc x).

Definition bbmap := positive -> bool.
Definition result := PMap.t L.t.

As in Kildall's solver, the state of the iteration has two components:
  • A mapping from program points to values of type L.t representing the candidate solution found so far.
  • A worklist of program points that remain to be considered.

Record state : Type := mkstate
  { st_in: result; st_wrk: list positive }.

The ``extended basic block'' algorithm, in pseudo-code, is as follows:
    st_wrk := the set of all points n having multiple predecessors
    st_in  := the mapping n -> L.top

    while st_wrk is not empty, do
        extract a node n from st_wrk
        compute out = transf n st_in[n]
        for each successor s of n:
            if s has only one predecessor (namely, n):
                st_in[s] := out
                st_wrk := st_wrk union {s}
            end if
        end for
    end while
    return st_in


)



Fixpoint propagate_successors (bb: bbmap) (succs: list positive) (l: L.t) (st: state) {struct succs} : state := match succs with | nil => st | s1 :: sl => if bb s1 then propagate_successors bb sl l st else propagate_successors bb sl l (mkstate (PMap.set s1 l st.(st_in)) (s1 :: st.(st_wrk))) end.

Definition step (bb: bbmap) (st: state) : result + state := match st.(st_wrk) with | nil => inl _ st.(st_in) | pc :: rem => inr _ (propagate_successors bb (successors!!!pc) (transf pc st.(st_in)!!pc) (mkstate st.(st_in) rem)) end.

(** Recognition of program points that have more than one predecessor.

Definition is_basic_block_head
    (preds: PTree.t (list positive)) (pc: positive) : bool :=
  if peq pc entrypoint then true else
    match preds!!!pc with
    | nil => false
    | s :: nil => peq s pc
    | _ :: _ :: _ => true
    end.

Definition basic_block_map : bbmap :=
  is_basic_block_head (make_predecessors successors).

Definition basic_block_list (bb: bbmap) : list positive :=
  PTree.fold (fun l pc scs => if bb pc then pc :: l else l)
             successors nil.

The computation of the approximate solution.

Definition fixpoint : option result :=
  let bb := basic_block_map in
  PrimIter.iterate _ _ (step bb) (mkstate (PMap.init L.top) (basic_block_list bb)).

Properties of predecessors and multiple-predecessors nodes


Definition predecessors := make_predecessors successors.

Lemma predecessors_correct:
  forall n s,
  In s successors!!!n -> In n predecessors!!!s.


Lemma multiple_predecessors:
  forall s n1 n2,
  In s (successors!!!n1) ->
  In s (successors!!!n2) ->
  n1 <> n2 ->
  basic_block_map s = true.


Lemma no_self_loop:
  forall n,
  In n (successors!!!n) -> basic_block_map n = true.


Correctness invariant


The invariant over the state is as follows:
  • Points with several predecessors are mapped to L.top
  • Points not in the worklist satisfy their inequations (as in Kildall's algorithm).

Definition state_invariant (st: state) : Prop :=
  (forall n, basic_block_map n = true -> st.(st_in)!!n = L.top)
/\
  (forall n,
   In n st.(st_wrk) \/
   (forall s, In s (successors!!!n) ->
               L.ge st.(st_in)!!s (transf n st.(st_in)!!n))).

Lemma propagate_successors_charact1:
  forall bb succs l st,
  incl st.(st_wrk)
       (propagate_successors bb succs l st).(st_wrk).


Lemma propagate_successors_charact2:
  forall bb succs l st n,
  let st' := propagate_successors bb succs l st in
  (In n succs -> bb n = false -> In n st'.(st_wrk) /\ st'.(st_in)!!n = l)
/\ (~In n succs \/ bb n = true -> st'.(st_in)!!n = st.(st_in)!!n).


Lemma propagate_successors_invariant:
  forall pc res rem,
  state_invariant (mkstate res (pc :: rem)) ->
  state_invariant
    (propagate_successors basic_block_map (successors!!!pc)
                          (transf pc res!!pc)
                          (mkstate res rem)).


Lemma initial_state_invariant:
  state_invariant (mkstate (PMap.init L.top) (basic_block_list basic_block_map)).


Lemma analyze_invariant:
  forall res,
  fixpoint = Some res ->
  state_invariant (mkstate res nil).


Correctness of the returned solution


Theorem fixpoint_solution:
  forall res n s,
  fixpoint = Some res ->
  In s (successors!!!n) ->
  L.ge res!!s (transf n res!!n).


Theorem fixpoint_entry:
  forall res,
  fixpoint = Some res ->
  res!!entrypoint = L.top.


Preservation of a property over solutions


Definition Pstate (st: state) : Prop :=
  forall pc, P st.(st_in)!!pc.

Lemma propagate_successors_P:
  forall bb l,
  P l ->
  forall succs st,
  Pstate st ->
  Pstate (propagate_successors bb succs l st).


Theorem fixpoint_invariant:
  forall res pc, fixpoint = Some res -> P res!!pc.


End Solver.

End BBlock_solver.

Node sets


We now define implementations of the NODE_SET interface appropriate for forward and backward dataflow analysis. As mentioned earlier, we aim for a traversal of the CFG nodes in reverse postorder (for forward analysis) or postorder (for backward analysis). We take advantage of the following fact, valid for all CFG generated by translation from Cminor: the enumeration n-1, n-2, ..., 3, 2, 1 where n is the top CFG node is a reverse postorder traversal. Therefore, for forward analysis, we will use an implementation of NODE_SET where the pick operation selects the greatest node in the working list. For backward analysis, we will similarly pick the smallest node in the working list.

Require Import FSets.
Require Import FSetAVL.
Require Import Ordered.

Module PositiveSet := FSetAVL.Make(OrderedPositive).
Module PositiveSetFacts := FSetFacts.Facts(PositiveSet).

Module NodeSetForward <: NODE_SET.
  Definition t := PositiveSet.t.
  Definition add (n: positive) (s: t) : t := PositiveSet.add n s.
  Definition pick (s: t) :=
    match PositiveSet.max_elt s with
    | Some n => Some(n, PositiveSet.remove n s)
    | None => None
    end.
  Definition initial (successors: PTree.t (list positive)) :=
    PTree.fold (fun s pc scs => PositiveSet.add pc s) successors PositiveSet.empty.
  Definition In := PositiveSet.In.

  Lemma add_spec:
    forall n n' s, In n' (add n s) <-> n = n' \/ In n' s.
  

  Lemma pick_none:
    forall s n, pick s = None -> ~In n s.
  

  Lemma pick_some:
    forall s n s', pick s = Some(n, s') ->
    forall n', In n' s <-> n = n' \/ In n' s'.
  

  Lemma initial_spec:
    forall successors n s,
    successors!n = Some s -> In n (initial successors).
  
End NodeSetForward.

Module NodeSetBackward <: NODE_SET.
  Definition t := PositiveSet.t.
  Definition add (n: positive) (s: t) : t := PositiveSet.add n s.
  Definition pick (s: t) :=
    match PositiveSet.min_elt s with
    | Some n => Some(n, PositiveSet.remove n s)
    | None => None
    end.
  Definition initial (successors: PTree.t (list positive)) :=
    PTree.fold (fun s pc scs => PositiveSet.add pc s) successors PositiveSet.empty.
  Definition In := PositiveSet.In.

  Lemma add_spec:
    forall n n' s, In n' (add n s) <-> n = n' \/ In n' s.
  Proof NodeSetForward.add_spec.

  Lemma pick_none:
    forall s n, pick s = None -> ~In n s.
  

  Lemma pick_some:
    forall s n s', pick s = Some(n, s') ->
    forall n', In n' s <-> n = n' \/ In n' s'.
  

  Lemma initial_spec:
    forall successors n s,
    successors!n = Some s -> In n (initial successors).
  Proof NodeSetForward.initial_spec.
End NodeSetBackward.