From b6cf5ea4ee5d2a3205eb725a5a2f564baf77c097 Mon Sep 17 00:00:00 2001
From: Valentin Perrelle <valentin.perrelle@cea.fr>
Date: Fri, 19 Oct 2018 15:46:12 +0200
Subject: [PATCH] [Eva] Uniformize transfer functions and use association list
 instead of maps for flow

---
 src/kernel_services/abstract_interp/bottom.ml |   5 +
 .../abstract_interp/bottom.mli                |   1 +
 src/plugins/value/engine/partition.ml         | 278 +++++++--------
 src/plugins/value/engine/partition.mli        |  65 ++--
 .../value/engine/partitioned_dataflow.ml      | 325 +++++++++---------
 .../value/engine/state_partitioning.mli       |  66 ++--
 .../value/engine/trace_partitioning.ml        | 188 ++++++----
 7 files changed, 498 insertions(+), 430 deletions(-)

diff --git a/src/kernel_services/abstract_interp/bottom.ml b/src/kernel_services/abstract_interp/bottom.ml
index c6ae61d80ae..def68537529 100644
--- a/src/kernel_services/abstract_interp/bottom.ml
+++ b/src/kernel_services/abstract_interp/bottom.ml
@@ -134,6 +134,11 @@ let to_list = function
   | `Bottom  -> []
   | `Value v -> [v]
 
+let of_list = function
+  | [] -> `Bottom
+  | [v] -> `Value v
+  | _ -> assert false
+
 let bot_of_list = function
   | [] -> `Bottom
   | l  -> `Value l
diff --git a/src/kernel_services/abstract_interp/bottom.mli b/src/kernel_services/abstract_interp/bottom.mli
index 7fca1504bf3..8c5a6ab97ce 100644
--- a/src/kernel_services/abstract_interp/bottom.mli
+++ b/src/kernel_services/abstract_interp/bottom.mli
@@ -69,6 +69,7 @@ module Bound_Lattice
 
 (** Conversion functions. *)
 val to_list: 'a or_bottom -> 'a list
+val of_list: 'a list -> 'a or_bottom
 val bot_of_list: 'a list -> 'a list or_bottom
 val list_of_bot: 'a list or_bottom -> 'a list
 val all: 'a or_bottom list -> 'a list
diff --git a/src/plugins/value/engine/partition.ml b/src/plugins/value/engine/partition.ml
index 54a03072c68..cc63597feab 100644
--- a/src/plugins/value/engine/partition.ml
+++ b/src/plugins/value/engine/partition.ml
@@ -20,25 +20,50 @@
 (*                                                                        *)
 (**************************************************************************)
 
-(* Utility function on options *)
-let opt_flatten (type a) (o : a option option) : a option =
-  Extlib.opt_conv None o
-
 module ExpMap = Cil_datatype.ExpStructEq.Map
-module LoopList = Datatype.List (Datatype.Pair (Datatype.Int) (Datatype.Int))
+module IntPair = Datatype.Pair (Datatype.Int) (Datatype.Int)
+module LoopList = Datatype.List (IntPair)
 module BranchList = Datatype.List (Datatype.Int)
 
 type branch = int
 
 type key = {
-  ration_stamp : int option;
-  transfer_stamp : int option;
+  ration_stamp : (int * int) option;
   branches : branch list;
   loops : (int * int) list;
   static_split : Integer.t ExpMap.t;
   dynamic_split : Integer.t ExpMap.t;
 }
 
+let zero_key : key = {
+  ration_stamp = None;
+  branches = [];
+  loops = [];
+  static_split = ExpMap.empty;
+  dynamic_split = ExpMap.empty;
+}
+
+let pretty_key fmt key =
+  begin match key.ration_stamp with
+    | Some (n,_) -> Format.fprintf fmt "#%d" n
+    | None -> ()
+  end;
+  Pretty_utils.pp_list ~pre:"[@[" ~sep:" ;@ " ~suf:"@]]"
+    Format.pp_print_int
+    fmt
+    key.branches;
+  Pretty_utils.pp_list ~pre:"(@[" ~sep:" ;@ " ~suf:"@])"
+    (fun fmt (i,_j) -> Format.pp_print_int fmt i)
+    fmt
+    key.loops;
+  Pretty_utils.pp_list ~pre:"{@[" ~sep:" ;@ " ~suf:"@]}"
+    (fun fmt (e,i) -> Format.fprintf fmt "%a:%a"
+        Cil_printer.pp_exp e
+        (Integer.pretty ~hexa:false) i)
+    fmt
+    (ExpMap.bindings key.static_split @ ExpMap.bindings key.dynamic_split)
+
+
 module Key =
 struct
   type t = key
@@ -47,8 +72,7 @@ struct
     let (<?>) c (cmp,x,y) =
       if c = 0 then cmp x y else c
     in
-    Extlib.opt_compare (-) k1.ration_stamp k2.ration_stamp
-    <?> (Extlib.opt_compare (-), k1.transfer_stamp, k2.transfer_stamp)
+    Extlib.opt_compare IntPair.compare k1.ration_stamp k2.ration_stamp
     <?> (LoopList.compare, k1.loops, k2.loops)
     <?> (ExpMap.compare Integer.compare, k1.static_split, k2.static_split)
     <?> (ExpMap.compare Integer.compare, k1.dynamic_split, k2.dynamic_split)
@@ -61,20 +85,6 @@ module KMap = Map.Make (Key)
 type 'a partition = 'a KMap.t
 type 'a transfer_function = (key * 'a) list -> (key * 'a) list
 
-let stamp_after_transfer k = function
-  | [x] -> [(k,x)]
-  | l ->
-    let t = ref 0 in
-    let add acc x  =
-      let k' = { k with transfer_stamp = Some !t } in
-      incr t;
-      (k',x) :: acc
-    in
-    List.fold_left add [] l
-
-let update_after_call k l =
-  List.map (fun x -> k,x) l
-
 type unroll_limit =
   | ExpLimit of Cil_types.exp
   | IntLimit of int
@@ -85,8 +95,7 @@ type action =
   | Incr_loop
   | Branch of branch * int
   | Ration of int
-  | Ration_merge of int option
-  | Transfer_merge
+  | Ration_merge of (int*int) option
   | Static_split of Cil_types.exp
   | Dynamic_split of Cil_types.exp
   | Static_merge of Cil_types.exp
@@ -108,46 +117,82 @@ sig
 end
 
 
-module Make (Domain : InputDomain) =
+let empty : 'a partition =
+  KMap.empty
+
+let is_empty (p : 'a partition) : bool =
+  KMap.is_empty p
+
+let size (p : 'a partition) : int =
+  KMap.fold (fun _k _x n -> n + 1) p 0
+
+let to_list (p : 'a partition) : 'a list =
+  KMap.fold (fun _k x l -> x :: l) p []
+
+let find = KMap.find
+let replace = KMap.add
+
+let merge (f : 'a option -> 'b option -> 'c option) (p1 : 'a partition)
+    (p2 : 'b partition) : 'c partition =
+  KMap.merge (fun _k o1 o2 -> f o1 o2) p1 p2
+
+let iter (f : 'a -> unit) (p : 'a partition) : unit =
+  KMap.iter (fun _k x -> f x) p
+
+let iteri (f : key -> 'a -> unit) (p : 'a partition) : unit =
+  KMap.iter f p
+
+
+(* Utility function on options *)
+let opt_flatten (type a) (o : a option option) : a option =
+  Extlib.opt_conv None o
+
+let map_states (f : 'a -> 'a) (p : 'a partition) : 'a partition =
+  KMap.map f p
+
+let filter_keys (f : key -> bool) (p : 'a partition) : 'a partition =
+  KMap.filter (fun k _x -> f k) p
+
+let map_filter (f : key -> 'a -> 'b option) (p : 'a partition) : 'b partition =
+  KMap.merge (fun k o _ -> opt_flatten (Extlib.opt_map (f k) o)) p KMap.empty
+
+
+module MakeFlow (Domain : InputDomain) =
 struct
-  type t = Domain.t partition
   type state = Domain.t
+  type t =  (key * state) list
 
-  let empty : 'a partition =
-    KMap.empty
-
-  let empty_key : key = {
-    ration_stamp = None;
-    transfer_stamp = None;
-    branches = [];
-    loops = [];
-    static_split = ExpMap.empty;
-    dynamic_split = ExpMap.empty;
-  }
-
-  let is_empty (p : 'a partition) : bool =
-    KMap.is_empty p
-
-  let initial (l : 'a list) : 'a partition =
-    let stamp = ref 0 in
-    let add p state =
-      let k = { empty_key with ration_stamp = Some !stamp } in
-      incr stamp;
-      KMap.add k state p
-    in
-    List.fold_left add KMap.empty l
-
-  let add (p : t) (k : key) (x : state) : t =
-    (* Join states with the same key *)
-    let x =
-      try
-        Domain.join (KMap.find k p) x
-      with Not_found -> x
+  let empty = []
+
+  let initial (p : 'a list) : t =
+    List.map (fun state -> zero_key, state) p
+
+  let to_list (f : t) : state list =
+    List.map snd f
+
+  let of_partition (p : state partition) : t =
+    KMap.fold (fun k x l -> (k,x) :: l) p []
+
+  let to_partition (p : t) : state partition =
+    let add p (k,x) =
+      (* Join states with the same key *)
+      let x' =
+        try
+          Domain.join (KMap.find k p) x
+        with Not_found -> x
+      in
+      KMap.add k x' p
     in
-    KMap.add k x p
+    List.fold_left add KMap.empty p
 
-  let add_list (p : t) (l : (key * state) list) : t =
-    List.fold_left (fun p (k,x) -> add p k x) p l
+  let is_empty (p : t) =
+    p = []
+
+  let size (p : t) =
+    List.length p
+
+  let union (p1 : t) (p2 : t) : t =
+    p1 @ p2
 
   let split_state ~(static : bool) (exp : Cil_types.exp)
       (key : key) (state : state) : (key * state) list =
@@ -166,32 +211,29 @@ struct
       [(key,state)]
 
   let split ~(static : bool) (p : t) (exp : Cil_types.exp) =
-    let add_split key state p =
-      add_list p (split_state ~static exp key state)
+    let add_split acc (key,state) =
+      split_state ~static exp key state @ acc
     in
-    KMap.fold add_split p KMap.empty
+    List.fold_left add_split [] p
 
   let update_dynamic_splits p =
     (* Update one state *)
-    let update_state key state p =
+    let update_state acc (key,state) =
       (* Split the states in the list l for the given exp *)
       let update_exp exp _ l =
         let static = false in
-        List.fold_left (fun l (k,s) -> split_state ~static exp k s @ l) [] l
+        List.fold_left (fun l (k,x) -> split_state ~static exp k x @ l) [] l
       in
       (* Foreach exp in original state: split *)
-      let l = ExpMap.fold update_exp key.dynamic_split [(key,state)] in
-      add_list p l
+      ExpMap.fold update_exp key.dynamic_split [(key,state)] @ acc
     in
-    KMap.fold update_state p KMap.empty
+    List.fold_left update_state [] p
 
-  let map_keys (f : key -> state -> key) (p : t) =
-    KMap.fold (fun k x acc -> add acc (f k x) x) p empty
+  let map_keys (f : key -> state -> key) (p : t) : t =
+    List.map (fun (k,x) -> f k x, x) p
 
   let transfer (f : state transfer_function)  (p : t) : t =
-    let l = KMap.fold (fun k x l -> (k,x) :: l) p [] in
-    let l' = f l in
-    add_list empty (l')
+    f p
 
   let transfer_keys p = function
     | Static_split exp ->
@@ -252,16 +294,13 @@ struct
         | Ration (min) ->
           let r = ref min in
           fun k _x ->
-            let ration_stamp = Some !r in
+            let ration_stamp = Some (!r, 0) in
             incr r;
             { k with ration_stamp }
 
-        | Ration_merge ration_stamp -> fun k _x ->
+        | Ration_merge ration_stamp  -> fun k _x ->
           { k with ration_stamp }
 
-        | Transfer_merge -> fun k _x ->
-          { k with transfer_stamp = None }
-
         | Static_merge exp -> fun k _x ->
           { k with static_split = ExpMap.remove exp k.static_split }
 
@@ -270,22 +309,27 @@ struct
       in
       map_keys transfer p
 
-  let map_states (f : 'a -> 'a) (p : 'a partition) : 'a partition =
-    KMap.map f p
-
-  let transfer_states (f : 'a -> 'a list) (p : 'a partition) : 'a partition =
-    let transfer_one k x p =
-      let l = stamp_after_transfer k (f x) in
-      List.fold_left (fun p (k,x) -> KMap.add k x p) p l
+  let transfer_states (f : state -> state list) (p : t) : t =
+    let n = ref 0 in
+    let transfer acc (k,x) =
+      let add =
+        match k.ration_stamp with
+          (* No ration stam, just add the state to the list *)
+        | None -> fun l y -> (k,y) :: l
+          (* There is a ration stamp, set the second part of the stamp to a unique transfer number *)
+        | Some (s,_) -> fun l y ->
+          let k' = { k with ration_stamp = Some (s, !n) } in
+          incr n;
+          (k',y) :: l
+      in
+      List.fold_left add acc (f x)
     in
-    KMap.fold transfer_one p KMap.empty
+    List.fold_left transfer [] p
 
-        (*
-  let legacy_transfer_states (f : 'a list -> 'a list) (p : 'a partition)
-    : 'a partition =
+  let legacy_transfer_states (f : state list -> state list) (p : t) : t =
     (* Group the states in buckets, where each bucket is a list of states
        with the same key except for the ration stamp *)
-    let fill_buckets k x buckets =
+    let fill_buckets buckets (k,x)  =
       (* Ignore the ration stamp *)
       let k = { k with ration_stamp = None } in
       (* Find the bucket *)
@@ -296,55 +340,15 @@ struct
       (* Add the state to the bucket *)
       KMap.add k (x :: contents) buckets
     in
-    let buckets = KMap.fold fill_buckets p KMap.empty in
+    let buckets = List.fold_left fill_buckets KMap.empty p in
     (* Apply the transfer function to each bucket *)
     let result = KMap.map f buckets in
-    (* Rebuild a partition by rationing out all the states *)
-    let r = ref 0 in
-    let ration_bucket k bucket acc =
-      let ration_one acc x =
-        let k' = { k with ration_stamp = Some !r } in
-        incr r;
-        KMap.add k' x acc
-      in
-      List.fold_left ration_one acc bucket
-    in
-    KMap.fold ration_bucket result KMap.empty *)
-
-  let find = KMap.find
-  let replace = KMap.add
-
-  let to_list (p : 'a partition) : 'a list =
-    KMap.fold (fun _k x l -> x :: l) p []
-
-  let size (p : 'a partition) : int =
-    KMap.fold (fun _k _x n -> n + 1) p 0
-
-
-  let merge (f : 'a option -> 'b option -> 'c option) (p1 : 'a partition)
-      (p2 : 'b partition) : 'c partition =
-    KMap.merge (fun _k o1 o2 -> f o1 o2) p1 p2
-
-  (* Almost like Map.union of Ocaml 4.03.0 *)
-  let union (f : 'a -> 'a -> 'a) (p1 : 'a partition)
-      (p2 : 'a partition) : 'a partition =
-    let g _k o1 o2 =
-      match o1 with
-      | None -> o2
-      | Some x1 ->
-        match o2 with
-        | None -> o1
-        | Some x2 -> Some (f x1 x2)
+    (* Rebuild the flow *)
+    let add_bucket k bucket acc =
+      List.map (fun x -> k,x) bucket @ acc
     in
-    KMap.merge g p1 p2
-
-  let iter (f : 'a -> unit) (p : 'a partition) : unit =
-    KMap.iter (fun _k x -> f x) p
-
-  let filter_keys (f : key -> bool) (p : 'a partition) : 'a partition =
-    KMap.filter (fun k _x -> f k) p
+    KMap.fold add_bucket result []
 
-  let map_filter (f : key -> 'a -> 'b option) (p : 'a partition)
-    : 'b partition =
-    KMap.merge (fun k o _ -> opt_flatten (Extlib.opt_map (f k) o)) p KMap.empty
+  let iter (f : state -> unit) (p : t) : unit =
+    List.iter (fun (_k,x) -> f x) p
 end
diff --git a/src/plugins/value/engine/partition.mli b/src/plugins/value/engine/partition.mli
index ee417755012..c817ca18685 100644
--- a/src/plugins/value/engine/partition.mli
+++ b/src/plugins/value/engine/partition.mli
@@ -55,20 +55,19 @@ type branch = int
 module ExpMap = Cil_datatype.ExpStructEq.Map
 
 type key = private {
-  ration_stamp : int option;
-  transfer_stamp : int option;
+  ration_stamp : (int * int) option;
   branches : branch list;
   loops : (int * int) list;
   static_split : Integer.t ExpMap.t;
   dynamic_split : Integer.t ExpMap.t;
 }
 
+val pretty_key : Format.formatter -> key -> unit
+
+
 type 'a partition
 type 'a transfer_function = (key * 'a) list -> (key * 'a) list
 
-val stamp_after_transfer : key -> 'a list -> (key * 'a)  list
-val update_after_call : key -> 'a list -> (key * 'a)  list
-
 type unroll_limit =
   | ExpLimit of Cil_types.exp
   | IntLimit of int
@@ -79,8 +78,7 @@ type action =
   | Incr_loop
   | Branch of branch * int (* branch taken, max branches in history *)
   | Ration of int (* starting ration stamp *)
-  | Ration_merge of int option (* new ration stamp for the merge state *)
-  | Transfer_merge
+  | Ration_merge of (int * int) option (* new ration stamp for the merge state *)
   | Static_split of Cil_types.exp
   | Dynamic_split of Cil_types.exp
   | Static_merge of Cil_types.exp
@@ -90,6 +88,25 @@ type action =
 exception InvalidAction
 
 
+val empty : 'a partition
+val is_empty : 'a partition -> bool
+val size : 'a partition -> int
+
+val to_list : 'a partition -> 'a list
+
+val find : key -> 'a partition -> 'a
+val replace : key -> 'a -> 'a partition -> 'a partition
+
+val merge : ('a option -> 'b option -> 'c option) -> 'a partition ->
+  'b partition -> 'c partition
+
+val iter : ('a -> unit) -> 'a partition -> unit
+val iteri : (key -> 'a -> unit) -> 'a partition -> unit
+val filter_keys : (key -> bool) -> 'a partition -> 'a partition
+val map_states : ('a  -> 'a) -> 'a partition -> 'a partition
+val map_filter : (key -> 'a -> 'b option) -> 'a partition -> 'b partition
+
+
 module type InputDomain =
 sig
   type t
@@ -101,29 +118,27 @@ sig
   val eval_exp_to_int : t -> Cil_types.exp -> int
 end
 
-
-module Make (Domain : InputDomain) :
+module MakeFlow (Domain : InputDomain) :
 sig
-  type t = Domain.t partition
   type state = Domain.t
+  type t
 
-  val empty : 'a partition
-  val is_empty : 'a partition -> bool
-  val initial : 'a list -> 'a partition
-  val size : 'a partition -> int
-  val to_list : 'a partition -> 'a list
+  val empty : t
 
-  val find : key -> 'a partition -> 'a
-  val replace : key -> 'a -> 'a partition -> 'a partition
-  val merge : ('a option -> 'b option -> 'c option) -> 'a partition
-    -> 'b partition -> 'c partition
-  val union : ('a -> 'a -> 'a) -> 'a partition -> 'a partition -> 'a partition
+  val initial : state list -> t
+  val to_list : t -> state list
+  val of_partition : state partition -> t
+  val to_partition : t -> state partition
+
+  val is_empty : t -> bool
+  val size : t -> int
+
+  val union : t -> t -> t
 
-  val iter : ('a -> unit) -> 'a partition -> unit
   val transfer : state transfer_function -> t -> t
   val transfer_keys : t -> action -> t
-  val filter_keys : (key -> bool) -> 'a partition -> 'a partition
-  val map_states : ('a  -> 'a) -> 'a partition -> 'a partition
-  val transfer_states : ('a -> 'a list) -> 'a partition -> 'a partition
-  val map_filter : (key -> 'a -> 'b option) -> 'a partition -> 'b partition
+  val transfer_states : (state -> state list) -> t -> t
+  val legacy_transfer_states : (state list -> state list) -> t -> t
+
+  val iter : (state -> unit) -> t -> unit
 end
diff --git a/src/plugins/value/engine/partitioned_dataflow.ml b/src/plugins/value/engine/partitioned_dataflow.ml
index 14ede992285..e1b461e4c09 100644
--- a/src/plugins/value/engine/partitioned_dataflow.ml
+++ b/src/plugins/value/engine/partitioned_dataflow.ml
@@ -100,8 +100,9 @@ module Make_Dataflow
   module Partition = Trace_partitioning.Make (Domain) (Transfer) (AnalysisParam)
 
   type store = Partition.store
+  type flow = Partition.flow
+  type tank = Partition.tank
   type widening = Partition.widening
-  type propagation = Partition.propagation
 
   type edge_info = {
     mutable fireable : bool (* Does any states survive the transition ? *)
@@ -141,9 +142,10 @@ module Make_Dataflow
     | `Bottom -> Domain.top (* No analysis in this case. *)
     | `Value state -> state
 
-  let initial_propagation =
-    -1, (* dummy edge identifier *)
-    Partition.initial_propagation (States.to_list initial_states)
+  let initial_tank =
+    Partition.initial_tank (States.to_list initial_states)
+  let get_initial_flow () =
+    -1 (* Dummy edge id *), Partition.drain initial_tank
 
   let post_conditions = ref false
 
@@ -175,7 +177,7 @@ module Make_Dataflow
     VertexTable.create control_point_count
   let w_table : widening VertexTable.t =
     VertexTable.create 7
-  let e_table : (propagation * edge_info) EdgeTable.t =
+  let e_table : (tank * edge_info) EdgeTable.t =
     EdgeTable.create transition_count
 
   (* Default (Initial) stores on vertex and edges *)
@@ -183,23 +185,18 @@ module Make_Dataflow
     Partition.empty_store ~stmt:v.vertex_start_of
   let default_vertex_widening (v : vertex) () : widening =
     Partition.empty_widening ~stmt:v.vertex_start_of
-  let default_edge_propagation () : propagation * edge_info =
-    Partition.empty_propagation (), { fireable = false }
+  let default_edge_tank () : tank * edge_info =
+    Partition.empty_tank (), { fireable = false }
 
   (* Get the stores associated to a control point or edge *)
   let get_vertex_store (v : vertex) : store =
     VertexTable.find_or_add v_table v ~default:(default_vertex_store v)
   let get_vertex_widening (v : vertex) : widening =
     VertexTable.find_or_add w_table v ~default:(default_vertex_widening v)
-  let get_edge_propagation (e : vertex edge) : propagation * edge_info =
-    EdgeTable.find_or_add e_table e ~default:default_edge_propagation
-  let get_pred_propagations (v : vertex) : ('branch * propagation) list =
-    let get (_,e,_) =
-      e.edge_key, fst (get_edge_propagation e)
-    in
-    List.map get (G.pred_e graph v)
-  let get_succ_propagations (v : vertex) : propagation list =
-    List.map (fun (_,e,_) -> fst (get_edge_propagation e)) (G.succ_e graph v)
+  let get_edge_data (e : vertex edge) : tank * edge_info =
+    EdgeTable.find_or_add e_table e ~default:default_edge_tank
+  let get_succ_tanks (v : vertex) : tank list =
+    List.map (fun (_,e,_) -> fst (get_edge_data e)) (G.succ_e graph v)
 
   module StmtTable = struct
     include Cil_datatype.Stmt.Hashtbl
@@ -224,47 +221,54 @@ module Make_Dataflow
 
   type state = Domain.t
 
-  (** Join every state in the list *)
-  let smash (l : state list) : state or_bottom =
-    match l with
-    | [] -> `Bottom
-    | v1 :: l -> `Value (List.fold_left Domain.join v1 l)
+  type transfer_function = state -> state list
+
+  let id : transfer_function = fun x -> [x]
 
   (* Thse lifting function helps to uniformize the transfer functions to a
      common signature *)
 
-  let lift (f : state -> state list) : state list -> state list =
-    fun l -> List.fold_left (fun acc x -> (f x) @ acc) [] l
+  let lift (f : state -> state) : transfer_function =
+    fun x -> [f x]
+
+  let lift' (f : state -> state or_bottom) : transfer_function =
+    fun x -> Bottom.to_list (f x)
+
+  let sequence (f1 : transfer_function) (f2 : transfer_function)
+    : transfer_function =
+    fun x -> List.fold_left (fun acc y -> f2 y @ acc) [] (f1 x)
 
-  let lift' (f : state -> state or_bottom) : state list -> state list =
-    fun l -> List.fold_left (fun acc x -> Bottom.add_to_list (f x) acc) [] l
 
   (* Tries to evaluate \assigns … \from … clauses for assembly code. *)
-  let transfer_asm (stmt : stmt) (states : state list) : state list =
+  let transfer_asm (stmt : stmt) : transfer_function =
     let asm_contracts = Annotations.code_annot stmt in
     match Logic_utils.extract_contract asm_contracts with
     | [] ->
       Value_util.warning_once_current
         "assuming assembly code has no effects in function %t"
         Value_util.pretty_current_cfunction_name;
-      states
+      id
     (* There should be only one statement contract, if any. *)
     | (_, spec) :: _ ->
       let assigns = Ast_info.merge_assigns_from_spec ~warn:false spec in
-      List.map (Spec.treat_statement_assigns assigns) states
+      lift (Spec.treat_statement_assigns assigns)
 
   let transfer_assume (stmt : stmt) (exp : exp) (kind : guard_kind)
-      (states : state list) : state list =
+    : transfer_function =
     let positive = (kind = Then) in
-    lift' (fun s -> Transfer.assume s stmt exp positive) states
+    lift' (fun s -> Transfer.assume s stmt exp positive)
+
+  let transfer_assign (stmt : stmt) (dest : Cil_types.lval) (exp : exp)
+    : transfer_function =
+    lift' (fun s -> Transfer.assign s (Kstmt stmt) dest exp)
 
-  let transfer_enter (block : block) (states : state list) : state list =
+  let transfer_enter (block : block) : transfer_function =
     let vars = block_toplevel_locals block in
-    if vars = [] then states else List.map (Transfer.enter_scope kf vars) states
+    if vars = [] then id else lift (Transfer.enter_scope kf vars)
 
-  let transfer_leave (block : block) (states : state list) : state list =
+  let transfer_leave (block : block) : transfer_function =
     let vars = block.blocals in
-    if vars = [] then states else List.map (Domain.leave_scope kf vars) states
+    if vars = [] then id else lift (Domain.leave_scope kf vars)
 
   let transfer_call (stmt : stmt) (dest : lval option) (callee : exp)
       (args : exp list) (state : state) : state list =
@@ -276,8 +280,7 @@ module Make_Dataflow
       cacheable := Value_types.NoCacheCallers;
     Bottom.list_of_bot result
 
-  let transfer_instr (stmt : stmt) (instr : instr) : state list -> state list =
-    let id states = states in
+  let transfer_instr (stmt : stmt) (instr : instr) : transfer_function =
     match instr with
     | Local_init (vi, AssignInit exp, _loc) ->
       let transfer state =
@@ -294,11 +297,11 @@ module Make_Dataflow
         let state = Domain.enter_scope kf [vi] state in
         transfer_call stmt dest callee args state
       in
-      lift (Cil.treat_constructor_as_func as_func vi f args k loc)
+      Cil.treat_constructor_as_func as_func vi f args k loc
     | Set (dest, exp, _loc) ->
-      lift' (fun s -> Transfer.assign s (Kstmt stmt) dest exp)
+      transfer_assign stmt dest exp
     | Call (dest, callee, args, _loc) ->
-      lift (transfer_call stmt dest callee args)
+      transfer_call stmt dest callee args
     | Asm _ ->
       transfer_asm stmt
     | Skip _loc -> id
@@ -306,7 +309,7 @@ module Make_Dataflow
                                    from the annotation table *)
 
   let transfer_return (stmt : stmt) (return_exp : exp option)
-      (states : state list) : state list =
+    : transfer_function =
     (* Deconstruct return statement *)
     let return_var = match return_exp with
       | Some {enode = Lval (Var v, NoOffset)} -> Some v
@@ -314,62 +317,46 @@ module Make_Dataflow
       | _ -> assert false (* Cil invariant *)
     in
     (* Check postconditions *)
-    post_conditions := true;
-    let states =
+    let check_postconditions = fun state ->
+      post_conditions := true;
       if Value_util.skip_specifications kf then
-        states
+        [state]
       else match
           Logic.check_fct_postconditions kf active_behaviors Normal
-            ~pre_state:initial_state ~post_states:(States.of_list states)
+            ~pre_state:initial_state ~post_states:(States.singleton state)
             ~result:return_var
         with
         | `Bottom -> []
         | `Value v -> States.to_list v
-    in
-    (* Split strategies *)
-    let states = match Split_return.kf_strategy kf with
-      | Split_strategy.SplitEqList i ->
-        begin match return_exp with
-          | Some return_exp ->
-            let split_states = Transfer.split_final_states kf return_exp i states in
-            let states' = List.map smash split_states in
-            Bottom.all states'
-          | None ->
-            Bottom.to_list (smash states)
-        end
-      | Split_strategy.NoSplit   -> Bottom.to_list (smash states)
-      | Split_strategy.FullSplit -> states
-      (* Last case not possible : already transformed into SplitEqList *)
-      | Split_strategy.SplitAuto -> assert false
-    in
     (* Assign the return value *)
-    match return_exp with
-    | None ->
-      states
-    | Some return_exp ->
-      let vi_ret = Extlib.the (Library_functions.get_retres_vi kf) in
-      let return_lval = Var vi_ret, NoOffset in
-      let transfer state =
-        let state = Domain.enter_scope kf [vi_ret] state in
-        Transfer.assign state (Kstmt stmt) return_lval return_exp
-      in
-      lift' transfer states
+    and assign_retval =
+      match return_exp with
+      | None -> id
+      | Some return_exp ->
+        let vi_ret = Extlib.the (Library_functions.get_retres_vi kf) in
+        let return_lval = Var vi_ret, NoOffset in
+        let kstmt = Kstmt stmt in
+        fun state ->
+          let state = Domain.enter_scope kf [vi_ret] state in
+          let state' = Transfer.assign state kstmt return_lval return_exp in
+          Bottom.to_list state'
+    in
+    sequence check_postconditions assign_retval
 
-  let transfer_transition (t : vertex transition) (states : state list) : state list =
+  let transfer_transition (t : vertex transition) : transfer_function =
     match t with
-    | Skip ->                     states
-    | Return (return_exp,stmt) -> transfer_return stmt return_exp states
-    | Guard (exp,kind,stmt) ->    transfer_assume stmt exp kind states
-    | Instr (instr,stmt) ->       transfer_instr stmt instr states
-    | Enter (block) ->            transfer_enter block states
+    | Skip ->                     id
+    | Return (return_exp,stmt) -> transfer_return stmt return_exp
+    | Guard (exp,kind,stmt) ->    transfer_assume stmt exp kind
+    | Instr (instr,stmt) ->       transfer_instr stmt instr
+    | Enter (block) ->            transfer_enter block
     | Leave (block) when blocks_share_locals fundec.sbody block ->
       (* The variables from the toplevel block will be removed by the caller *)
-      states
-    | Leave (block) ->            transfer_leave block states
-    | Prop _ -> states (* Annotations are interpreted in [transfer_statement]. *)
+      id
+    | Leave (block) ->            transfer_leave block
+    | Prop _ -> id (* Annotations are interpreted in [transfer_statement]. *)
 
-  let transfer_statement_annot (stmt : stmt) ~(record : bool)
-      (states : state list) : state list =
+  let transfer_annotations (stmt : stmt) ~(record : bool) : transfer_function =
     let annots =
       (* We do not interpret annotations that come from statement contracts
          and everything previously emitted by Value (currently, alarms) *)
@@ -378,29 +365,19 @@ module Make_Dataflow
       in
       List.map fst (Annotations.code_annot_emitter ~filter stmt)
     in
-    let interp_annot states ca =
-      Logic.interp_annot
-        ~limit:(slevel stmt) ~record
-        kf active_behaviors stmt ca
-        ~initial_state states
-    in
-    States.to_list (List.fold_left interp_annot (States.of_list states) annots)
-
-  let get_cvalue = Domain.get Cvalue_domain.key
-  let gather_cvalues states =
-    match get_cvalue with
-    | Some get -> List.map get states
-    | None -> []
+    fun state ->
+      let interp_annot states ca =
+        Logic.interp_annot
+          ~limit:(slevel stmt) ~record
+          kf active_behaviors stmt ca
+          ~initial_state states
+      in
+      States.to_list
+        (List.fold_left interp_annot (States.singleton state) annots)
 
-  let transfer_statement (stmt : stmt) (states : state list) : state list =
-    current_ki := Kstmt stmt;
-    (* Apply callback *)
-    (* TODO: apply on all domains. *)
-    let cvalue_states = gather_cvalues states in
-    Db.Value.Compute_Statement_Callbacks.apply
-      (stmt, Value_util.call_stack (), cvalue_states);
+  let transfer_statement (stmt : stmt) (state : state) : state list =
     (* Interpret annotations *)
-    let states = transfer_statement_annot stmt ~record:true states in
+    let states = transfer_annotations stmt ~record:true state in
     (* Check unspecified sequences *)
     match stmt.skind with
     | UnspecifiedSequence seq when Kernel.UnspecifiedAccess.get () ->
@@ -419,24 +396,31 @@ module Make_Dataflow
       if x >= !max_displayed + slevel_display_step
       then begin
         let rounded = x / slevel_display_step * slevel_display_step in
-        Value_parameters.feedback ~once:true
+        Value_parameters.feedback ~once:true ~current:true
           "Semantic level unrolling superposing up to %d states"
           rounded;
         max_displayed := rounded;
       end
 
-  let process_loop_transitions (v1 : vertex) (v2 : vertex) (p : propagation)
-    : unit =
+  let process_partitioning_transitions (v1 : vertex) (v2 : vertex)
+      (transition : vertex transition) (f : flow) : unit =
+    (* Split return *)
+    begin match transition with
+    | Return (return_exp, _) ->
+      Partition.split_return f return_exp
+    | _ -> ()
+    end;
+    (* Loop transitions *)
     let the_stmt v = Extlib.the v.vertex_start_of in
     let enter_loop v =
-      Partition.transfer (List.map (Domain.enter_loop (the_stmt v))) p;
-      Partition.enter_loop p (the_stmt v)
+      Partition.transfer (lift (Domain.enter_loop (the_stmt v))) f;
+      Partition.enter_loop f (the_stmt v)
     and leave_loop v =
-      Partition.transfer (List.map (Domain.leave_loop (the_stmt v))) p;
-      Partition.leave_loop p (the_stmt v)
+      Partition.transfer (lift (Domain.leave_loop (the_stmt v))) f;
+      Partition.leave_loop f (the_stmt v)
     and incr_loop_counter v =
-      Partition.transfer (List.map (Domain.incr_loop_counter (the_stmt v))) p;
-      Partition.next_loop_iteration p (the_stmt v)
+      Partition.transfer (lift (Domain.incr_loop_counter (the_stmt v))) f;
+      Partition.next_loop_iteration f (the_stmt v)
     in
     let loops_left, loops_entered =
       Interpreted_automata.get_wto_index_diff kf v1 v2
@@ -448,97 +432,116 @@ module Make_Dataflow
     if loop_incr then
       incr_loop_counter v2
 
-  let process_edge (v1,e,v2 : G.edge) : unit =
+  let process_edge (v1,e,v2 : G.edge) : flow =
     let {edge_transition=transition; edge_kinstr=kinstr} = e in
-    let propagation,edge_info = get_edge_propagation e in
+    let tank,edge_info = get_edge_data e in
+    let flow = Partition.drain tank in
     !Db.progress ();
     check_signals ();
     current_ki := kinstr;
     Cil.CurrentLoc.set e.edge_loc;
-    Partition.transfer (transfer_transition transition) propagation;
-    process_loop_transitions v1 v2 propagation;
-    if not (Partition.is_empty_propagation propagation) then
-      edge_info.fireable <- true
-
-  let update_vertex ?(widening : bool = false) (v : vertex) : bool =
-    (* Set location if possible *)
-    Extlib.may
-      (fun stmt -> Cil.CurrentLoc.set (Cil_datatype.Stmt.loc stmt))
-      v.vertex_start_of;
+    Partition.transfer (transfer_transition transition) flow;
+    process_partitioning_transitions v1 v2 transition flow;
+    if not (Partition.is_empty_flow flow) then
+      edge_info.fireable <- true;
+    flow
+
+  let get_cvalue = Domain.get Cvalue_domain.key
+  let gather_cvalues states =
+    match get_cvalue with
+    | Some get -> List.map get states
+    | None -> []
+
+  let call_statement_callbacks (stmt : stmt) (f : flow) : unit =
+    (* TODO: apply on all domains. *)
+    let states = Partition.contents f in
+    let cvalue_states = gather_cvalues states in
+    Db.Value.Compute_Statement_Callbacks.apply
+      (stmt, Value_util.call_stack (), cvalue_states)
+
+  let update_vertex ?(widening : bool = false) (v : vertex)
+      (sources : ('branch * flow) list) : bool =
+    begin match v.vertex_start_of with
+      | Some stmt ->
+        (* Set location *)
+        current_ki := Kstmt stmt;
+        Cil.CurrentLoc.set (Cil_datatype.Stmt.loc stmt);
+      | None -> ()
+    end;
     (* Get vertex store *)
     let store = get_vertex_store v in
     (* Join incoming s tates *)
-    let sources = get_pred_propagations v in
-    let sources =
-      if v == automaton.entry_point
-      then initial_propagation :: sources
-      else sources
-    in
-    let p = Partition.join sources store in
+    let f = Partition.join sources store in
     (* Output slevel related things *)
     let store_size = Partition.store_size store in
+    output_slevel store_size;
     begin match v.vertex_start_of with
       | Some stmt ->
+        (* Callbacks *)
+        call_statement_callbacks stmt f;
+        (* Transfer function associated to the statement *)
+        Partition.transfer (transfer_statement stmt) f;
+        (* Debug informations *)
         Value_parameters.debug ~dkey ~current:true
           "reached statement %d with %d / %d eternal states, %d to propagate"
-          stmt.sid store_size (slevel stmt) (Partition.propagation_size p)
+          stmt.sid store_size (slevel stmt) (Partition.flow_size f);
       | _ -> ()
     end;
-    output_slevel store_size;
-    (* Transfer function associated to the statement *)
-    Extlib.may
-      (fun stmt -> Partition.transfer (transfer_statement stmt) p)
-      v.vertex_start_of;
     (* Widen if necessary *)
     let stable =
-      if Partition.is_empty_propagation p then
+      if Partition.is_empty_flow f then
         true
       else if widening then begin
-        let stable = Partition.widen store (get_vertex_widening v) p in
+        let stable = Partition.widen (get_vertex_widening v) f in
         (* Try to correct over-widenings *)
         let correct_over_widening stmt =
           (* Do *not* record the status after interpreting the annotation
              here. Possible unproven assertions have already been recorded
              when the assertion has been interpreted the first time higher
              in this function. *)
-          Partition.transfer (transfer_statement_annot stmt ~record:false) p
+          Partition.transfer (transfer_annotations stmt ~record:false) f
         in
         Extlib.may correct_over_widening v.vertex_start_of;
         stable
       end else
         false
     in
-    (* Reset sources *)
-    List.iter (fun (_,p) -> Partition.clear_propagation p) sources;
     (* Dispatch to successors *)
-    List.iter (fun p2 -> Partition.merge p ~into:p2) (get_succ_propagations v);
+    List.iter (fun into -> Partition.fill f ~into) (get_succ_tanks v);
     (* Return wether the iterator should stop or not *)
     stable
 
   let process_vertex ?(widening : bool = false) (v : vertex) : bool =
     (* Process predecessors *)
-    G.iter_pred_e process_edge graph v;
+    let process_source (_,e,_ as edge) =
+      e.edge_key, process_edge edge
+    in
+    let sources = List.map process_source (G.pred_e graph v) in
+    (* Add initial source *)
+    let sources =
+      if v <> automaton.entry_point
+      then sources
+      else get_initial_flow () :: sources
+    in
     (* Update the vertex *)
-    update_vertex ~widening v
+    update_vertex ~widening v sources
 
-  let rec simulate (v : vertex) : unit =
+  let rec simulate (v : vertex) (source : 'branch * flow) : unit =
     (* Update the current vertex *)
-    ignore (update_vertex v);
+    ignore (update_vertex v [source]);
     (* Try every possible successor *)
-    G.iter_succ_e process_edge graph v;
-    (* Find which edges were fireable *)
-    let add_if_fireable (_,e,succ) acc =
-      let p = fst (get_edge_propagation e) in
-      if Partition.is_empty_propagation p
-      then (Partition.clear_propagation p; acc)
-      else succ :: acc
+    let add_if_fireable (_,e,succ as edge) acc =
+      let f = process_edge edge in
+      if Partition.is_empty_flow f
+      then acc
+      else (e.edge_key,f,succ) :: acc
     in
     let successors = G.fold_succ_e add_if_fireable graph v [] in
     (* How many possible successors ? *)
     match successors with
     | [] -> () (* No successor - end of simulation *)
-    | [succ] -> (* One successor - continue simulation *)
-      simulate succ
+    | [b,f,succ] -> (* One successor - continue simulation *)
+      simulate succ (b,f)
     | _ -> (* Several successors - failure *)
       Value_parameters.abort "Do not know which branch to take. Stopping."
 
@@ -583,8 +586,8 @@ module Make_Dataflow
      relevant.*)
   let mark_degeneration () =
     let f stmt (v,_) =
-      let l = get_succ_propagations v in
-      if not (List.for_all Partition.is_empty_propagation l) then
+      let l = get_succ_tanks v in
+      if not (List.for_all Partition.is_empty_tank l) then
         Value_util.DegenerationPoints.replace stmt false
     in
     StmtTable.iter f automaton.stmt_table;
@@ -602,7 +605,7 @@ module Make_Dataflow
 
   let compute () : state list or_bottom =
     if interpreter_mode then
-      simulate automaton.entry_point
+      simulate automaton.entry_point (get_initial_flow ())
     else begin
       let wto = Interpreted_automata.get_wto kf in
       iterate_list wto
@@ -623,7 +626,7 @@ module Make_Dataflow
           | Then -> Db.Value.mask_then
           | Else -> Db.Value.mask_else
         in
-        let edge_info = snd (get_edge_propagation e) in
+        let edge_info = snd (get_edge_data e) in
         let old_status =
           try StmtTable.find table stmt
           with Not_found -> 0
diff --git a/src/plugins/value/engine/state_partitioning.mli b/src/plugins/value/engine/state_partitioning.mli
index 223d3db8d60..5f2508badf7 100644
--- a/src/plugins/value/engine/state_partitioning.mli
+++ b/src/plugins/value/engine/state_partitioning.mli
@@ -45,36 +45,40 @@ end
 module type Partition =
 sig
   type state       (** The states being partitioned *)
-  type store       (** The storage of a partition *)
-  type propagation (** Only contains states which needs to be propagated, i.e.
-                       states which have not been propagated yet *)
+  type store       (** The storage of all states ever met at a control point *)
+  type flow        (** A set of states which are currently propagated *)
+  type tank        (** An organized temporary accumulation of flows *)
   type widening    (** Widening informations *)
 
 
   (* --- Constructors --- *)
 
   val empty_store : stmt:Cil_types.stmt option -> store
-  val empty_propagation : unit -> propagation
+  val empty_flow : unit -> flow
+  val empty_tank : unit -> tank
   val empty_widening : stmt:Cil_types.stmt option -> widening
 
-  (** Build the initial propagation for the entry point of a function. *)
-  val initial_propagation : state list -> propagation
+  (** Build the initial tank for the entry point of a function. *)
+  val initial_tank : state list -> tank
 
 
   (* --- Pretty printing --- *)
 
   val pretty_store : Format.formatter -> store -> unit
-  val pretty_propagation : Format.formatter -> propagation -> unit
+  val pretty_flow : Format.formatter -> flow -> unit
 
 
   (* --- Accessors --- *)
 
   val expanded : store -> state list
   val smashed : store -> state or_bottom
+  val contents : flow -> state list
   val is_empty_store : store -> bool
-  val is_empty_propagation : propagation -> bool
+  val is_empty_flow : flow -> bool
+  val is_empty_tank : tank -> bool
   val store_size : store -> int
-  val propagation_size : propagation -> int
+  val flow_size : flow -> int
+  val tank_size : tank -> int
 
 
   (* --- Reset state (for hierchical convergence) --- *)
@@ -83,7 +87,8 @@ sig
      been obtained after a widening. *)
 
   val reset_store : store -> unit
-  val reset_propagation : propagation -> unit
+  val reset_flow : flow -> unit
+  val reset_tank : tank -> unit
   val reset_widening : widening -> unit
 
   (** Resets (or just delays) the widening counter. Used on nested loops, to
@@ -95,25 +100,24 @@ sig
 
   (* --- Partition transfer functions --- *)
 
-  val enter_loop : propagation -> loop -> unit
-  val leave_loop : propagation -> loop -> unit
-  val next_loop_iteration : propagation -> loop -> unit
+  val enter_loop : flow -> loop -> unit
+  val leave_loop : flow -> loop -> unit
+  val next_loop_iteration : flow -> loop -> unit
+  val split_return : flow -> Cil_types.exp option -> unit
 
 
   (* --- Operators --- *)
 
-  (** Remove all states from the propagation, leaving it empty as if it was just
-      created by [empty_propagation] *)
-  val clear_propagation : propagation -> unit
+  (** Remove all states from the tank, leaving it empty as if it was just
+      created by [empty_tank] *)
+  val drain : tank -> flow
 
-  (** Apply a transfer function to all the states of a propagation. *)
-  val transfer : (state list -> state list) -> propagation -> unit
+  (** Fill the states of the flow into the tank, modifying [into] inplace but
+      letting the flow unchanged *)
+  val fill: into:tank -> flow -> unit
 
-  (** Merge two propagations together, modifying [into] inplace. At the return
-      of the function, [into] should contain all the states of both original
-      propagations, or an overapproximation of this union: joining two states
-      together inside the propagation is allowed. *)
-  val merge : into:propagation -> propagation -> unit
+  (** Apply a transfer function to all the states of a propagation. *)
+  val transfer : (state -> state list) -> flow -> unit
 
   (** Join all incoming propagations into the given store. This function returns
       a set of states which still need to be propagated past the store.
@@ -127,17 +131,13 @@ sig
       This function also interprets partitioning annotations at the store
       vertex (slevel, splits, merges, ...) which will generally change the
       current partitioning. *)
-  val join : (branch * propagation) list -> store -> propagation
-
-  (** Widen a propagation at the position of the given store. The widening
-      object keeps track of the previous widenings to ensure termination. The
-      result is true when it is correct to end the propagation here, i.e. when
-      the current propagation is only carrying states which are included into
-      already propagated states.
+  val join : (branch * flow) list -> store -> flow
 
-      Note that the propagation given to [widen] *must* have been produced by
-      the [join] on the same store. *)
-  val widen : store -> widening -> propagation -> bool
+  (** Widen a tank. The widening object keeps track of the previous widenings to
+      ensure termination. The result is true when it is correct to end the
+      propagation here, i.e. when the current tank is only containng
+      states which are included into already propagated states. *)
+  val widen : widening -> flow -> bool
 
 end
 
diff --git a/src/plugins/value/engine/trace_partitioning.ml b/src/plugins/value/engine/trace_partitioning.ml
index 1e4e6e679ee..810f61e1a7a 100644
--- a/src/plugins/value/engine/trace_partitioning.ml
+++ b/src/plugins/value/engine/trace_partitioning.ml
@@ -44,10 +44,14 @@ struct
     let eval_exp_to_int = Transfer.eval_exp_to_int
 
     include Domain
+
+    let smash = function
+      | [] -> []
+      | v1 :: l -> [ List.fold_left join v1 l ]
   end
 
   module Index = Partitioning.Make (Domain)
-  module Partition = Partition.Make (Domain)
+  module Flow = Partition.MakeFlow (Domain)
 
   type state = Domain.t
 
@@ -61,8 +65,12 @@ struct
     mutable store_size : int;
   }
 
-  type propagation = {
-    mutable partition : state partition;
+  type flow = {
+    mutable flow_states : Flow.t;
+  }
+
+  type tank = {
+    mutable tank_states : state partition;
   }
 
   type widening_state = {
@@ -91,8 +99,11 @@ struct
       store_size = 0;
     }
 
-  let empty_propagation () : propagation =
-    { partition = Partition.empty }
+  let empty_flow () : flow =
+    { flow_states = Flow.empty }
+
+  let empty_tank () : tank =
+    { tank_states = Partition.empty }
 
   let empty_widening ~(stmt : stmt option) : widening =
     {
@@ -100,14 +111,14 @@ struct
       widening_partition = Partition.empty;
     }
 
-  let initial_propagation (states : state list) : propagation =
-    let partition = Partition.initial states in
+  let initial_tank (states : state list) : tank =
+    let propagation = Flow.initial states in
     (* Split the initial partition according to the global split seetings *)
-    let split partition lval =
-      Partition.transfer_keys partition (Dynamic_split lval)
+    let split propagation lval =
+      Flow.transfer_keys propagation (Dynamic_split lval)
     in
-    let partition = List.fold_left split partition universal_splits in
-    { partition }
+    let states = List.fold_left split propagation universal_splits in
+    { tank_states = Flow.to_partition states }
 
 
   (* Pretty printing *)
@@ -115,8 +126,8 @@ struct
   let pretty_store (fmt : Format.formatter) (s : store) : unit =
     Partition.iter (Domain.pretty fmt) s.store_partition
 
-  let pretty_propagation (fmt : Format.formatter) (p : propagation) =
-    Partition.iter (Domain.pretty fmt) p.partition
+  let pretty_flow (fmt : Format.formatter) (p : flow) =
+    Flow.iter (Domain.pretty fmt) p.flow_states
 
 
   (* Accessors *)
@@ -125,33 +136,62 @@ struct
     Partition.to_list s.store_partition
 
   let smashed (s : store) : state or_bottom =
-    match expanded s with
-    | [] -> `Bottom
-    | v1 :: l -> `Value (List.fold_left Domain.join v1 l)
+    Bottom.of_list (Domain.smash (expanded s))
+
+  let contents (f : flow) : state list =
+    Flow.to_list f.flow_states
 
   let is_empty_store (s : store) : bool =
     Partition.is_empty s.store_partition
 
-  let is_empty_propagation (p : propagation) : bool =
-    Partition.is_empty p.partition
+  let is_empty_flow (f : flow) : bool =
+    Flow.is_empty f.flow_states
+
+  let is_empty_tank (t : tank) : bool =
+    Partition.is_empty t.tank_states
 
   let store_size (s : store) : int =
     s.store_size
 
-  let propagation_size (p : propagation) : int =
-    Partition.size p.partition
+  let flow_size (f : flow) : int =
+    Flow.size f.flow_states
 
+  let tank_size (t : tank) : int =
+    Partition.size t.tank_states
 
   (* Partition transfer functions *)
 
-  let enter_loop (p : propagation) (i : loop) =
-    p.partition <- Partition.transfer_keys p.partition (Enter_loop (unroll i))
-
-  let leave_loop (p : propagation) (_i : loop) =
-    p.partition <- Partition.transfer_keys p.partition Leave_loop
-
-  let next_loop_iteration (p : propagation) (_i : loop) =
-    p.partition <- Partition.transfer_keys p.partition Incr_loop
+  let loop_transfer p action =
+    p.flow_states <- Flow.transfer_keys p.flow_states action
+
+  let enter_loop (p : flow) (i : loop) : unit =
+    loop_transfer p (Enter_loop (unroll i))
+
+  let leave_loop (p : flow) (_i : loop) : unit =
+    loop_transfer p Leave_loop
+
+  let next_loop_iteration (p : flow) (_i : loop) : unit =
+    loop_transfer p Incr_loop
+
+  let split_return (flow : flow) (return_exp : exp option) : unit =
+    (** Join every state in the list *)
+    let transfer_split states =
+      match Split_return.kf_strategy kf with
+      | Split_strategy.SplitEqList i ->
+        begin match return_exp with
+          | Some return_exp ->
+            let states = Transfer.split_final_states kf return_exp i states in
+            List.flatten (List.map Domain.smash states)
+          | None ->
+            Domain.smash states
+        end
+      | Split_strategy.NoSplit   -> Domain.smash states
+      | Split_strategy.FullSplit -> states
+      (* Last case not possible : already transformed into SplitEqList *)
+      | Split_strategy.SplitAuto -> assert false
+    in
+    flow.flow_states <-
+      Flow.legacy_transfer_states transfer_split flow.flow_states
 
 
   (* Reset state (for hierchical convergence) *)
@@ -162,8 +202,11 @@ struct
     in
     s.store_partition <- Partition.filter_keys is_eternal s.store_partition
 
-  let reset_propagation (p : propagation) : unit =
-    p.partition <- Partition.empty
+  let reset_flow (f : flow) : unit =
+    f.flow_states <- Flow.empty
+
+  let reset_tank (t : tank) : unit =
+    t.tank_states <- Partition.empty
 
   let reset_widening (w : widening) : unit =
     w.widening_partition <- Partition.empty
@@ -177,73 +220,68 @@ struct
 
   (* Operators *)
 
-  let clear_propagation (p : propagation) : unit =
-    p.partition <- Partition.empty
-
-  let transfer (f : state list -> state list) (p : propagation) : unit =
-    p.partition <- Partition.transfer_states (fun s -> f [s]) p.partition
+  let drain (t : tank) : flow =
+    let flow_states = Flow.of_partition t.tank_states in
+    t.tank_states <- Partition.empty;
+    { flow_states }
 
-  let merge ~(into : propagation) (source : propagation) : unit =
-    (* TODO: state the precondition for this to be correct *)
-    let merge_two dest src = (* Erase the destination *)
+  let fill ~(into : tank) (f : flow) : unit =
+    let erase dest src =
       if Extlib.has_some src
       then src
       else dest
     in
-    into.partition <- Partition.merge merge_two into.partition source.partition
+    let new_states = Flow.to_partition f.flow_states in
+    into.tank_states <- Partition.merge erase into.tank_states new_states
 
-  let join (sources : (branch*propagation) list) (dest : store)
-    : propagation =
+  let transfer (f : state -> state list) (p : flow) : unit =
+    p.flow_states <- Flow.transfer_states f p.flow_states
+
+  let join (sources : (branch*flow) list) (dest : store) : flow =
     let is_loop_head =
       match dest.store_stmt with
       | Some {skind=Cil_types.Loop _} -> true
       | _ -> false
     in
-    let current_ration = ref dest.store_size in
-    (* Update states counters *)
-    let count acc (_b,p) =
-      acc + Partition.size p.partition
-    in
-    dest.store_size <- List.fold_left count dest.store_size sources;
-    (* Get every source propagation *)
-    let source_partitions =
+    (* Get every source flow *)
+    let sources_states =
       match sources with
-      | [(_,p)] -> [p.partition]
+      | [(_,p)] -> [p.flow_states]
       | sources ->
         (* Several branches ; partition according to the incoming branch *)
         let get (b,p) =
-          Partition.transfer_keys p.partition (Branch (b,history_size))
+          Flow.transfer_keys p.flow_states (Branch (b,history_size))
         in
         List.map get sources
     in
+    (* Merge incomming flows *)
+    let flow_states =
+      List.fold_left Flow.union Flow.empty sources_states
+    in
     (* Handle ration stamps *)
+    let previous_store_size = dest.store_size in
+    dest.store_size <- dest.store_size + Flow.size flow_states;
     let slevel_exceeded = dest.store_size > dest.size_limit in
-    let rationing =
+    let rationing_action =
       if slevel_exceeded then
         (* No more slevel, no more ration tickets *)
-        fun p -> Partition.transfer_keys p (Ration_merge None)
+        Ration_merge None
       else if dest.merge then
         (* Merge / Merge after loop : a unique ration stamp for all *)
-        fun p -> Partition.transfer_keys p (Ration_merge (Some !current_ration))
-      else begin fun p ->
+        Ration_merge (Some (previous_store_size, 0))
+      else
         (* Attribute a ration stamp to each individual state *)
-        let p = Partition.transfer_keys p (Ration !current_ration) in
-        current_ration := !current_ration + Partition.size p;
-        p
-      end
+        Ration previous_store_size
     in
-    let source_partitions = List.map rationing source_partitions in
     (* Handle Split / Merge operations *)
-    let do_flow_actions partition =
-      let actions =
-        dest.flow_actions @ [Update_dynamic_splits ; Transfer_merge]
-      in
-      List.fold_left Partition.transfer_keys partition actions
+    let flow_actions = Update_dynamic_splits :: dest.flow_actions in
+    (* Execute actions *)
+    let actions = rationing_action :: flow_actions in
+    let flow_states =
+      List.fold_left Flow.transfer_keys flow_states actions
     in
-    let source_partitions = List.map do_flow_actions source_partitions in
-    (* Merge incomming propagations *)
-    let union = Partition.union Domain.join in
-    let partition = List.fold_left union Partition.empty source_partitions in
+    (* Join states with unique keys *)
+    let partition = Flow.to_partition flow_states in
     (* Add states to the store but filter out already propagated states *)
     let update key current_state =
       (* Inclusion test *)
@@ -272,11 +310,11 @@ struct
       (* Filter out already propagated states *)
       Extlib.opt_filter (fun s -> Index.add s dest.store_index) state
     in
-    let partition = Partition.map_filter update partition in
-    { partition }
+    let partition' = Partition.map_filter update partition in
+    { flow_states = Flow.of_partition partition' }
 
 
-  let widen (_s : store) (w : widening) (p : propagation) : bool =
+  let widen (w : widening) (f : flow) : bool =
     let stmt = w.widening_stmt in
     (* Auxiliary function to update the result *)
     let update key widening_state =
@@ -331,6 +369,8 @@ struct
           };
         Some curr
     in
-    p.partition <- Partition.map_filter widen_one p.partition;
-    Partition.is_empty p.partition
+    let p = Flow.to_partition f.flow_states in
+    let p' = Partition.map_filter widen_one p in
+    f.flow_states <- Flow.of_partition p';
+    Flow.is_empty f.flow_states
 end
-- 
GitLab