Section Kildall.
Context {A: Type}.
Variable code: PTree.t A.
Variable successors: A -> list positive.
Variable transf: positive -> L.t -> L.t.
(** The state of the iteration has three components: - [aval]: A mapping from program points to values of type [L.t] representing the candidate solution found so far. - [worklist]: A worklist of program points that remain to be considered. - [visited]: A set of program points that were visited already (i.e. put on the worklist at some point in the past).  Only the first two components are computationally relevant.  The third is a ghost variable used only for stating and proving invariants. For this reason, [visited] is defined at sort [Prop] so that it is erased during program extraction. *)
Record state : Type :=
mkstate { aval: PTree.t L.t; worklist: NS.t; visited: positive -> Prop }.
Definition abstr_value (n: positive) (s: state) : L.t :=
match s.(aval)!n with
| None => L.bot
| Some v => v
end.
(** Kildall's algorithm, in pseudo-code, is as follows: << while worklist is not empty, do extract a node n from worklist compute out = transf n aval[n] for each successor s of n: compute in = lub aval[s] out if in <> aval[s]: aval[s] := in worklist := worklist union {s} visited := visited union {s} end if end for end while return aval >> *)
(** [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) :=
match s.(aval)!n with
| None =>
{| aval := PTree.set n out s.(aval);
worklist := NS.add n s.(worklist);
visited := fun p => p = n \/ s.(visited) p |}
| Some oldl =>
let newl := L.lub oldl out in
if L.beq oldl newl
then s
else {| aval := PTree.set n newl s.(aval);
worklist := NS.add n s.(worklist);
visited := fun p => p = n \/ s.(visited) p |}
end.
(** [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.(worklist) with
| None =>
inl _ (L.bot, s.(aval))
| Some(n, rem) =>
match code!n with
| None =>
inr _ {| aval := s.(aval); worklist := rem; visited := s.(visited) |}
| Some instr =>
inr _ (propagate_succ_list
{| aval := s.(aval); worklist := rem; visited := s.(visited) |}
(transf n (abstr_value n s))
(successors instr))
end
end.
(** The whole fixpoint computation is the iteration of [step] from an initial state. *)
Definition fixpoint_from (start: state) : option (PMap.t L.t) :=
PrimIter.iterate _ _ step start.
(** There are several ways to build the initial state.  For forward dataflow analyses, the initial worklist is the function entry point, and the initial mapping sets the function entry point to the given abstract value, and leaves unset all other program points, which corresponds to [L.bot] initial abstract values. *)
Definition start_state (enode: positive) (eval: L.t) :=
{| aval := PTree.set enode eval (PTree.empty L.t);
worklist := NS.add enode NS.empty;
visited := fun n => n = enode |}.
Definition fixpoint (enode: positive) (eval: L.t) :=
fixpoint_from (start_state enode eval).
(** For backward analyses (viewed as forward analyses on the reversed CFG), the following two variants are more useful.  Both start with an empty initial mapping, where all program points start at [L.bot]. The first initializes the worklist with a given set of entry points in the reversed CFG.  (See the backward dataflow solver below for how this list is computed.)  The second start state construction initializes the worklist with all program points of the given CFG. *)
Definition start_state_nodeset (enodes: NS.t) :=
{| aval := PTree.empty L.t;
worklist := enodes;
visited := fun n => NS.In n enodes |}.
Definition fixpoint_nodeset (enodes: NS.t) :=
fixpoint_from (start_state_nodeset enodes).
Definition start_state_allnodes :=
{| aval := PTree.empty L.t;
worklist := NS.all_nodes code;
visited := fun n => exists instr, code!n = Some instr |}.
Definition fixpoint_allnodes :=
fixpoint_from start_state_allnodes.
(** ** Characterization of the propagation functions *)
Inductive optge: option L.t -> option L.t -> Prop :=
| optge_some: forall l l',
L.ge l l' -> optge (Some l) (Some l')
| optge_none: forall ol,
optge ol None.
Remark optge_refl: forall ol, optge ol ol.
Remark optge_abstr_value:
forall st st' n,
optge st.(aval)!n st'.(aval)!n ->
L.ge (abstr_value n st) (abstr_value n st').
(** Characterization of [fixpoint_from]. *)
Inductive steps: state -> state -> Prop :=
| steps_base: forall s, steps s s
| steps_right: forall s1 s2 s3, steps s1 s2 -> step s2 = inr s3 -> steps s1 s3.
Scheme steps_ind := Induction for steps Sort Prop.
(** ** Monotonicity properties *)
(** We first show that the [aval] and [visited] parts of the state evolve monotonically: - at each step, the values of the [aval[n]] either remain the same or increase with respect to the [optge] ordering; - every node visited in the past remains visited in the future. *)
(** ** Correctness invariant *)
(** The following invariant is preserved at each iteration of Kildall's algorithm: for all visited program point [n], either [n] is in the worklist, or the inequations associated with [n] ([aval[s] >= transf n aval[n]] for all successors [s] of [n]) hold.  In other terms, the worklist contains all nodes that were visited but do not yet satisfy their inequations.  The second part of the invariant shows that nodes that already have an abstract value associated with them have been visited. *)
Record good_state (st: state) : Prop := {
gs_stable: forall n,
st.(visited) n ->
NS.In n st.(worklist) \/
(forall i s,
code!n = Some i -> In s (successors i) ->
optge st.(aval)!s (Some (transf n (abstr_value n st))));
gs_defined: forall n v,
st.(aval)!n = Some v -> st.(visited) n
}.
(** We show that the [step] function preserves this invariant. *)
(** We show that initial states satisfy the invariant. *)
(** Reachability in final states. *)
(** ** Correctness of the solution returned by [fixpoint]. *)
(** As a consequence of the [good_state] invariant, the result of [fixpoint], if defined, is a solution of the dataflow inequations. This assumes that the transfer function maps [L.bot] to [L.bot]. *)
(** Moreover, the result of [fixpoint], if defined, satisfies the additional constraint given on the entry point. *)
(** For [fixpoint_allnodes], we show that the result is a solution without assuming [transf n L.bot = L.bot]. *)
(** For [fixpoint_nodeset], we show that the result is a solution at all program points that are reachable from the given entry points. *)
(** ** Preservation of a property over solutions *)

Theorem fixpoint_invariant:
forall ep ev
(P: L.t -> Prop)
(P_bot: P L.bot)
(P_lub: forall x y, P x -> P y -> P (L.lub x y))
(P_transf: forall pc instr x, code!pc = Some instr -> P x -> P (transf pc x))
(P_entrypoint: P ev)
res pc,
fixpoint ep ev = Some res ->
P res!!pc.