diff --git a/Makefile b/Makefile index 5d51fd0d81a053bf22fff6c62c7071d0c83a2a08..411ebb702e9700c539ebd8b0abf82366a00a6e93 100644 --- a/Makefile +++ b/Makefile @@ -877,7 +877,7 @@ PLUGIN_DISTRIB_EXTERNAL+= \ PLUGIN_CMO:= slevel/split_strategy value_parameters \ utils/value_perf utils/value_util utils/red_statuses \ utils/mark_noresults \ - utils/widen_hints_ext utils/widen utils/unroll_annots \ + utils/widen_hints_ext utils/widen utils/partitioning_annots \ engine/split_return \ slevel/per_stmt_slevel \ utils/library_functions \ @@ -918,8 +918,8 @@ PLUGIN_CMO:= slevel/split_strategy value_parameters \ engine/subdivided_evaluation engine/evaluation engine/abstractions \ engine/recursion engine/transfer_stmt engine/transfer_specification \ engine/partitioning engine/mem_exec \ - engine/legacy_partitioning engine/basic_partitioning \ - engine/loop_partitioning engine/partitioned_dataflow \ + engine/partition engine/partitioning_parameters engine/trace_partitioning \ + engine/partitioned_dataflow \ engine/initialization \ engine/compute_functions engine/analysis register PLUGIN_CMI:= values/abstract_value values/abstract_location \ diff --git a/headers/header_spec.txt b/headers/header_spec.txt index 7a59f56fa0af6966a004d87d2a7ee43768bc23cb..e94bb4bef29fba47a4d4011839b7e546957bd5b1 100644 --- a/headers/header_spec.txt +++ b/headers/header_spec.txt @@ -1170,24 +1170,22 @@ src/plugins/value/engine/abstractions.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/abstractions.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/analysis.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/analysis.mli: CEA_LGPL_OR_PROPRIETARY -src/plugins/value/engine/basic_partitioning.ml: CEA_LGPL_OR_PROPRIETARY -src/plugins/value/engine/basic_partitioning.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/compute_functions.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/compute_functions.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/evaluation.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/evaluation.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/initialization.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/initialization.mli: CEA_LGPL_OR_PROPRIETARY -src/plugins/value/engine/legacy_partitioning.ml: CEA_LGPL_OR_PROPRIETARY -src/plugins/value/engine/legacy_partitioning.mli: CEA_LGPL_OR_PROPRIETARY -src/plugins/value/engine/loop_partitioning.ml: CEA_LGPL_OR_PROPRIETARY -src/plugins/value/engine/loop_partitioning.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/mem_exec.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/mem_exec.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/partitioned_dataflow.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/partitioned_dataflow.mli: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/engine/partition.ml: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/engine/partition.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/partitioning.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/partitioning.mli: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/engine/partitioning_parameters.ml: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/engine/partitioning_parameters.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/recursion.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/recursion.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/split_return.ml: CEA_LGPL_OR_PROPRIETARY @@ -1195,6 +1193,8 @@ src/plugins/value/engine/split_return.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/state_partitioning.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/subdivided_evaluation.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/subdivided_evaluation.mli: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/engine/trace_partitioning.ml: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/engine/trace_partitioning.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/transfer_logic.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/transfer_logic.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/engine/transfer_specification.ml: CEA_LGPL_OR_PROPRIETARY @@ -1239,9 +1239,9 @@ src/plugins/value/utils/red_statuses.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/red_statuses.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/library_functions.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/library_functions.mli: CEA_LGPL_OR_PROPRIETARY -src/plugins/value/utils/unroll_annots.ml: CEA_LGPL_OR_PROPRIETARY -src/plugins/value/utils/unroll_annots.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/mark_noresults.ml: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/utils/partitioning_annots.ml: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/utils/partitioning_annots.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/state_import.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/state_import.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/utils/structure.ml: CEA_LGPL_OR_PROPRIETARY diff --git a/src/plugins/value/engine/basic_partitioning.ml b/src/plugins/value/engine/basic_partitioning.ml deleted file mode 100644 index 4a790f63214cb4110f506432f5b5a23262b78576..0000000000000000000000000000000000000000 --- a/src/plugins/value/engine/basic_partitioning.ml +++ /dev/null @@ -1,313 +0,0 @@ -(**************************************************************************) -(* *) -(* This file is part of Frama-C. *) -(* *) -(* Copyright (C) 2007-2019 *) -(* CEA (Commissariat à l'énergie atomique et aux énergies *) -(* alternatives) *) -(* *) -(* you can redistribute it and/or modify it under the terms of the GNU *) -(* Lesser General Public License as published by the Free Software *) -(* Foundation, version 2.1. *) -(* *) -(* It is distributed in the hope that it will be useful, *) -(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) -(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) -(* GNU Lesser General Public License for more details. *) -(* *) -(* See the GNU Lesser General Public License version 2.1 *) -(* for more details (enclosed in the file licenses/LGPLv2.1). *) -(* *) -(**************************************************************************) - -open Cil_types -open Bottom.Type -open State_partitioning - - -module Make (Domain : Domain) (Param : Param) = -struct - include Param - module Partition = Partitioning.Make (Domain) - - type state = Domain.t - - (** Stores contains what have been already computed at a fixed control - point. - Propagation contrains what have changed since last iteration and needs - to be propagated. - Widening records previous states used in widening operations and keep - tracks of the number of iterations remaining before next widening. - - How states are stored: - - Eternal states are states which, once propagated, we are sure they - will stay until the end of the analysis. This means they have not - been widened, and thus cannot descend or disapear. - - When states have been widened and/or joined due to the lack of - slevel, they are put in the ultimate state. - - Eternal states that can't be propagated due to the lack of slevel - are doomed to wander into the limbo state. It is a join of all those - states. - With these rules, the ultimate state at one point is the join of the - limbo state and the ultimate states of the predecessors. - The field [size] store a value compatible with the legacy way of - counting the number of eternal states in the store. *) - type store = { - size_limit : int; - merge : bool; - mutable eternal_states : Partition.t; - mutable ultimate_state : state or_bottom; - mutable limbo_state : state or_bottom; - mutable size : int; - } - - type propagation = { - mutable eternal_propagation : state list; - mutable ultimate_propagation : state or_bottom option; - mutable widening_depth : int; - mutable current_depth : int; - } - - type shadow = { - mutable transfered_eternals : int; - mutable shadowed_ultimate : state or_bottom; - } - - type widening = { - stmt : stmt; - mutable widened_state : state or_bottom; - mutable previous_state : state or_bottom; - mutable widening_counter : int; - } - - (* Constructors *) - - let empty_store ~(stmt : stmt option) : store = - let size_limit, merge = match stmt with - | None -> max_int, false - | Some stmt -> slevel stmt, merge stmt - in - { - size_limit; merge; - eternal_states = Partition.empty (); - ultimate_state = `Bottom; - limbo_state = `Bottom; - size = 0; - } - - let empty_propagation () : propagation = - { - eternal_propagation = []; - ultimate_propagation = None; - widening_depth = max_int; - current_depth = 0; - } - - let empty_shadow () : shadow = - { - transfered_eternals = 0; - shadowed_ultimate = `Bottom; - } - - let empty_widening ~(stmt : stmt option) : widening = - { - stmt = Extlib.opt_conv Cil.invalidStmt stmt; - widened_state = `Bottom; - previous_state = `Bottom; - widening_counter = widening_delay; - } - - let initial_propagation (states : state list) = - let empty = empty_propagation () in - { empty with eternal_propagation = states } - - (* Pretty printing *) - - let pretty_eternal (fmt : Format.formatter) (v : Domain.t) : unit = - Format.fprintf fmt "eternal state %a@\n" Domain.pretty v - - let pretty_ultimate (fmt : Format.formatter) (v : Domain.t) : unit = - Format.fprintf fmt "ultimate state %a@\n" Domain.pretty v - - let pretty_store (fmt : Format.formatter) (s : store) : unit = - List.iter (pretty_eternal fmt) (Partition.to_list s.eternal_states); - if not (Bottom.is_bottom s.ultimate_state) then - pretty_ultimate fmt (Bottom.non_bottom s.ultimate_state) - - let pretty_propagation (fmt : Format.formatter) (p : propagation) = - List.iter (pretty_eternal fmt) p.eternal_propagation; - Extlib.may (Bottom.pretty pretty_ultimate fmt) p.ultimate_propagation - - (* Accessors *) - - let expanded (s : store) : state list = - Bottom.add_to_list s.ultimate_state (Partition.to_list s.eternal_states) - - let smashed (s : store) : state or_bottom = - let l = expanded s in - Domain.join_list l - - let is_empty_store (s : store) : bool = - s.size = 0 && Bottom.is_bottom s.ultimate_state - - let is_empty_propagation (p : propagation) : bool = - p.eternal_propagation = [] && not (Extlib.has_some p.ultimate_propagation) - - let is_empty_shadow (s : shadow) : bool = - s.transfered_eternals = 0 && Bottom.is_bottom s.shadowed_ultimate - - let store_size (s : store) : int = - s.size - - let propagation_size (p : propagation) : int = - List.length p.eternal_propagation + - match p.ultimate_propagation with - | None | Some `Bottom -> 0 - | Some (`Value _) -> 1 - - (* Reset state (for hierchical convergence) *) - - let reset_store (s : store) : unit = - s.ultimate_state <- `Bottom - - let reset_propagation (p : propagation) : unit = - p.ultimate_propagation <- None; - p.widening_depth <- max_int - - let reset_shadow (s : shadow) : unit = - s.shadowed_ultimate <- `Bottom - - let reset_widening (w : widening) : unit = - w.widened_state <- `Bottom; - w.previous_state <- `Bottom; - w.widening_counter <- widening_delay - - let reset_widening_counter (w : widening) : unit = - w.widening_counter <- max w.widening_counter (widening_period - 1) - - (* Operators *) - - let clear_propagation (p : propagation) : unit = - p.eternal_propagation <- []; - p.ultimate_propagation <- None; - p.widening_depth <- max_int; - p.current_depth <- 0 - - let transfer (f : state list -> state list) (p : propagation) : unit = - let transfer_ultimate state' = - state' >>- fun x -> Domain.join_list (f [x]) - in - if p.eternal_propagation <> [] then - p.eternal_propagation <- f p.eternal_propagation; - p.ultimate_propagation <- - Extlib.opt_map transfer_ultimate p.ultimate_propagation - - let merge ~(into : propagation) (source : propagation) : unit = - if Extlib.has_some source.ultimate_propagation then - into.ultimate_propagation <- source.ultimate_propagation; - into.eternal_propagation <- - source.eternal_propagation @ into.eternal_propagation; - into.widening_depth <- source.widening_depth; - into.current_depth <- source.current_depth - - let join (sources : (propagation*shadow) list) (dest : store): propagation = - let ultimates_changed = ref false in - (* Update source shadow with source propagation *) - let update (eternals,ultimates) (p,s) = - begin match p.ultimate_propagation with - | None -> () - | Some state' -> - if not (Bottom.equal Domain.equal state' s.shadowed_ultimate) then - ultimates_changed := true; - s.shadowed_ultimate <- state' - end; - s.transfered_eternals <- s.transfered_eternals + - List.length p.eternal_propagation; - p.eternal_propagation @ eternals, - Bottom.add_to_list s.shadowed_ultimate ultimates - in - let eternals, ultimates = List.fold_left update ([],[]) sources in - (* Create a new propagation *) - let p = empty_propagation () in - let current_depth acc (p,_s) = max p.current_depth acc - and widening_depth acc (p,_s) = min p.widening_depth acc in - p.current_depth <- List.fold_left current_depth 0 sources; - p.widening_depth <- List.fold_left widening_depth max_int sources; - (* Add all eternal states *) - dest.size <- dest.size + List.length eternals; - let states = Partition.merge_set_return_new eternals dest.eternal_states in - (* Merge / Merge after loop : join eternal states being propagated *) - let states = - if dest.merge - then Bottom.to_list (Domain.join_list states) - else states - in - (* Do we have too many eternal states ? *) - if dest.size > dest.size_limit then - begin - (* Send excess states into limbo *) - dest.limbo_state <- Domain.join_list ~into:dest.limbo_state states; - ultimates_changed := true - end else - p.eternal_propagation <- states; - (* Join ultimate states *) - if !ultimates_changed then - begin - let inputs = Bottom.add_to_list dest.limbo_state ultimates in - let state' = Domain.join_list inputs in - if not (Bottom.equal Domain.equal state' dest.ultimate_state) then - p.ultimate_propagation <- Some state'; - dest.ultimate_state <- state' - end; - p - - let widen (_s : store) (w : widening) (p : propagation) : bool = - let ultimate_stable = - match p.ultimate_propagation with - | None -> true - | Some current_state -> - let previous_state = w.previous_state in - w.previous_state <- current_state; - w.widening_counter <- w.widening_counter - 1; - match previous_state, current_state with - | _, `Bottom -> true - | `Bottom, `Value _ -> false - | `Value prev, `Value curr -> - if Domain.is_included curr prev then - true - else if w.widening_counter >= 0 then - false - else begin - Value_parameters.feedback ~level:1 ~once:true ~current:true - ~dkey:Value_parameters.dkey_widening - "applying a widening at this point"; - let prev = match w.widened_state with - | `Value v -> Domain.join prev v - | `Bottom -> prev - in - let next = Domain.widen kf w.stmt prev (Domain.join prev curr) in - p.ultimate_propagation <- Some (`Value next); - p.widening_depth <- min p.widening_depth p.current_depth; - w.previous_state <- `Value next; - w.widened_state <- `Value next; - w.widening_counter <- widening_period - 1; - false - end - in - ultimate_stable && p.eternal_propagation = [] - - let enter_loop (p : propagation) (_ : loop) = - p.current_depth <- p.current_depth + 1 - - let leave_loop (p : propagation) (_ : loop) = - p.current_depth <- p.current_depth - 1; - match p.ultimate_propagation with - (* We leave the loop where the ultimate have been widened. - It becomes eternal again. *) - | Some state' when p.current_depth < p.widening_depth -> - p.ultimate_propagation <- None; - p.eternal_propagation <- Bottom.add_to_list state' p.eternal_propagation - | _ -> () - - let next_loop_iteration (_p : propagation) (_ : loop) = () -end diff --git a/src/plugins/value/engine/legacy_partitioning.ml b/src/plugins/value/engine/legacy_partitioning.ml deleted file mode 100644 index bb2b4660552bdd4cb95ac52621f595d414375e5b..0000000000000000000000000000000000000000 --- a/src/plugins/value/engine/legacy_partitioning.ml +++ /dev/null @@ -1,217 +0,0 @@ -(**************************************************************************) -(* *) -(* This file is part of Frama-C. *) -(* *) -(* Copyright (C) 2007-2019 *) -(* CEA (Commissariat à l'énergie atomique et aux énergies *) -(* alternatives) *) -(* *) -(* you can redistribute it and/or modify it under the terms of the GNU *) -(* Lesser General Public License as published by the Free Software *) -(* Foundation, version 2.1. *) -(* *) -(* It is distributed in the hope that it will be useful, *) -(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) -(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) -(* GNU Lesser General Public License for more details. *) -(* *) -(* See the GNU Lesser General Public License version 2.1 *) -(* for more details (enclosed in the file licenses/LGPLv2.1). *) -(* *) -(**************************************************************************) - - -open Cil_types -open Bottom.Type -open State_partitioning - -module Make (Domain : Domain) (Param : Param) = -struct - include Param - module Partition = Partitioning.Make (Domain) - - type state = Domain.t - - type store = { - size_limit : int; - merge : bool; - mutable eternal_states : Partition.t; - mutable ultimate_state : state or_bottom; - mutable size : int; - } - - type propagation = { - mutable states : state list; - } - - type shadow = { - mutable transfered_states : int; - } - - type widening = { - stmt : stmt; - mutable widened_state : state or_bottom; - mutable previous_state : state or_bottom; - mutable widening_counter : int; - } - - (* Constructors *) - - let empty_store ~(stmt : stmt option) : store = - let size_limit, merge = match stmt with - | None -> max_int, false - | Some stmt -> slevel stmt, merge stmt - in - { - size_limit; merge; - eternal_states = Partition.empty (); - ultimate_state = `Bottom; - size = 0; - } - - let empty_propagation () : propagation = - { states = [] } - - let empty_shadow () : shadow = - { transfered_states = 0 } - - let empty_widening ~(stmt : stmt option) : widening = - { - stmt = Extlib.opt_conv Cil.invalidStmt stmt; - widened_state = `Bottom; - previous_state = `Bottom; - widening_counter = widening_delay; - } - - let initial_propagation (states : state list) : propagation = - { states } - - (* Pretty printing *) - - let pretty_eternal (fmt : Format.formatter) (v : Domain.t) : unit = - Format.fprintf fmt "eternal state %a@\n" Domain.pretty v - - let pretty_ultimate (fmt : Format.formatter) (v : Domain.t) : unit = - Format.fprintf fmt "ultimate state %a@\n" Domain.pretty v - - let pretty_store (fmt : Format.formatter) (s : store) : unit = - List.iter (pretty_eternal fmt) (Partition.to_list s.eternal_states); - if not (Bottom.is_bottom s.ultimate_state) then - pretty_ultimate fmt (Bottom.non_bottom s.ultimate_state) - - let pretty_propagation (fmt : Format.formatter) (p : propagation) = - List.iter (pretty_eternal fmt) p.states - - (* Accessors *) - - let expanded (s : store) : state list = - Bottom.add_to_list s.ultimate_state (Partition.to_list s.eternal_states) - - let smashed (s : store) : state or_bottom = - let l = expanded s in - Domain.join_list l - - let is_empty_store (s : store) : bool = - s.size = 0 && Bottom.is_bottom s.ultimate_state - - let is_empty_propagation (p : propagation) : bool = - p.states = [] - - let is_empty_shadow (s : shadow) : bool = - s.transfered_states = 0 - - let store_size (s : store) : int = - s.size - - let propagation_size (p : propagation) : int = - List.length p.states - - (* Reset state (for hierchical convergence) *) - - let reset_store (s : store) : unit = - s.ultimate_state <- `Bottom - - let reset_propagation (p : propagation) : unit = - p.states <- [] - - let reset_shadow (_s : shadow) : unit = () - - let reset_widening (w : widening) : unit = - w.widened_state <- `Bottom; - w.previous_state <- `Bottom; - w.widening_counter <- widening_delay - - let reset_widening_counter w = - w.widening_counter <- max w.widening_counter (widening_period - 1) - - (* Operators *) - - let clear_propagation (p : propagation) : unit = - p.states <- [] - - let transfer (f : state list -> state list) (p : propagation) : unit = - if p.states <> [] then - p.states <- f p.states - - let merge ~(into : propagation) (source : propagation) : unit = - into.states <- source.states @ into.states - - let join (sources : (propagation*shadow) list) (dest : store) : propagation = - (* Update source stores with source propagation *) - let update acc (p,s) = - let size = List.length p.states in - s.transfered_states <- s.transfered_states + size; - dest.size <- dest.size + size; - Partition.merge_set_return_new p.states dest.eternal_states @ acc - in - let new_states = List.fold_left update [] sources in - (* Create a new propagation *) - let p = { states = new_states } in - (* Merge / Merge after loop : join eternal states being propagated *) - if dest.merge then - p.states <- Bottom.to_list (Domain.join_list p.states); - (* Do we have too many eternal states ? *) - if dest.size > dest.size_limit then - begin - let state' = Domain.join_list ~into:dest.ultimate_state p.states in - if Bottom.is_included Domain.is_included state' dest.ultimate_state then - p.states <- [] - else begin - dest.ultimate_state <- state'; - p.states <- Bottom.to_list state' - end - end; - p - - let widen (s : store) (w : widening) (p : propagation) : bool = - let current_state = s.ultimate_state - and previous_state = w.previous_state in - if not (Bottom.is_bottom current_state) then begin - w.previous_state <- current_state; - w.widening_counter <- w.widening_counter - 1; - match previous_state, current_state with - | _, `Bottom | `Bottom, _ -> () - | `Value prev, `Value curr -> - if Domain.is_included curr prev then - p.states <- [] - else if w.widening_counter < 0 then begin - Value_parameters.feedback ~level:1 ~once:true ~current:true - ~dkey:Value_parameters.dkey_widening - "applying a widening at this point"; - let prev = match w.widened_state with - | `Value v -> Domain.join prev v - | `Bottom -> prev - in - let next = Domain.widen kf w.stmt prev (Domain.join prev curr) in - p.states <- [next]; - w.previous_state <- `Value next; - w.widened_state <- `Value next; - w.widening_counter <- widening_period - 1 - end - end; - p.states = [] - - let enter_loop (_p : propagation) (_ : loop) = () - let leave_loop (_p : propagation) (_ : loop) = () - let next_loop_iteration (_p : propagation) (_ : loop) = () -end diff --git a/src/plugins/value/engine/partition.ml b/src/plugins/value/engine/partition.ml new file mode 100644 index 0000000000000000000000000000000000000000..44642d2a166d24d00e1823f992ae18f9ce5aa221 --- /dev/null +++ b/src/plugins/value/engine/partition.ml @@ -0,0 +1,325 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2019 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +(* Utility function on options *) +let opt_flatten (type a) (o : a option option) : a option = + Extlib.opt_conv None o + +module LvalMap = Cil_datatype.LvalStructEq.Map +module IList = Datatype.List (Datatype.Int) + +type branch = int + +type key = { + ration_stamp : int option; + transfer_stamp : int option; + branches : branch list; + loops : int list; + static_split : Integer.t LvalMap.t; + dynamic_split : Integer.t LvalMap.t; +} + +module Key = +struct + type t = key + + let compare k1 k2 = + 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) + <?> (IList.compare, k1.loops, k2.loops) + <?> (LvalMap.compare Integer.compare, k1.static_split, k2.static_split) + <?> (LvalMap.compare Integer.compare, k1.dynamic_split, k2.dynamic_split) + <?> (IList.compare, k1.branches, k2.branches) +end + +module KMap = Map.Make (Key) + + +type 'a partition = 'a KMap.t + +type action = + | Enter_loop + | Leave_loop + | Incr_loop of int + | Branch of branch * int + | Ration of int + | Ration_merge of int option + | Transfer_merge + | Static_split of Cil_types.lval + | Dynamic_split of Cil_types.lval + | Static_merge of Cil_types.lval + | Dynamic_merge of Cil_types.lval + | Update_dynamic_splits + +exception InvalidAction + + +module type InputDomain = +sig + type t + + exception Cant_split + + val join : t -> t -> t + val split : t -> Cil_types.lval -> (Integer.t * t) list +end + + +module Make (Domain : InputDomain) = +struct + type t = Domain.t partition + type state = Domain.t + + let empty : 'a partition = + KMap.empty + + let empty_key : key = { + ration_stamp = None; + transfer_stamp = None; + branches = []; + loops = []; + static_split = LvalMap.empty; + dynamic_split = LvalMap.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 + in + KMap.add k x 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 split_state ~(static : bool) (lval : Cil_types.lval) + (key : key) (state : state) : (key * state) list = + try + let update_key (v,x) = + let k = + if static then + { key with static_split = LvalMap.add lval v key.static_split } + else + { key with dynamic_split = LvalMap.add lval v key.dynamic_split } + in + (k,x) + in + List.map update_key (Domain.split state lval) + with Domain.Cant_split -> + [(key,state)] + + let split ~(static : bool) (p : t) (lval : Cil_types.lval) = + let add_split key state p = + add_list p (split_state ~static lval key state) + in + KMap.fold add_split p KMap.empty + + let update_dynamic_splits p = + (* Update one state *) + let update_state key state p = + (* Split the states in the list l for the given lval *) + let update_lval lval _ l = + let static = false in + List.fold_left (fun l (k,s) -> split_state ~static lval k s @ l) [] l + in + (* Foreach lval in original state: split *) + let l = LvalMap.fold update_lval key.dynamic_split [(key,state)] in + add_list p l + in + KMap.fold update_state p KMap.empty + + let map_keys (f : key -> key) (p : t) = + KMap.fold (fun k x acc -> add acc (f k) x) p empty + + let transfer_keys p = function + | Static_split lval -> + split ~static:true p lval + + | Dynamic_split lval -> + split ~static:false p lval + + | Update_dynamic_splits -> + update_dynamic_splits p + + | action -> (* Simple map transfer functions *) + let transfer = match action with + | Static_split _ | Dynamic_split _ | Update_dynamic_splits -> + assert false (* Handled above *) + + | Enter_loop -> fun k -> + { k with loops = 0 :: k.loops } + + | Leave_loop -> fun k -> + begin match k.loops with + | [] -> raise InvalidAction + | _ :: tl -> { k with loops = tl } + end + + | Incr_loop limit -> fun k -> + begin match k.loops with + | [] -> raise InvalidAction + | h :: tl -> + if h >= limit then + k + else + { k with loops = h + 1 :: tl } + end + + | Branch (b,max) -> fun k -> + let list_start l i = + let rec aux acc i = function + | [] -> acc + | _ when i <= 0 -> List.rev acc + | x :: l -> aux (x :: acc) (i - 1) l + in + aux [] i l + in + if max > 0 then + { k with branches = b :: list_start k.branches (max - 1) } + else if k.branches <> [] then + { k with branches = [] } + else + k + + | Ration (min) -> + let r = ref min in + fun k -> + let ration_stamp = Some !r in + incr r; + { k with ration_stamp } + + | Ration_merge ration_stamp -> fun k -> + { k with ration_stamp } + + | Transfer_merge -> fun k -> + { k with transfer_stamp = None } + + | Static_merge lval -> fun k -> + { k with static_split = LvalMap.remove lval k.static_split } + + | Dynamic_merge lval -> fun k -> + { k with dynamic_split = LvalMap.remove lval k.dynamic_split } + 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 t = ref 0 in + let add p y = + let k' = { k with transfer_stamp = Some !t } in + incr t; + KMap.add k' y p + in + match f x with + | [y] -> KMap.add k y p + | l -> List.fold_left add p l + in + KMap.fold transfer_one p KMap.empty + + (* + let legacy_transfer_states (f : 'a list -> 'a list) (p : 'a partition) + : 'a partition = + (* 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 = + (* Ignore the ration stamp *) + let k = { k with ration_stamp = None } in + (* Find the bucket *) + let contents = + try KMap.find k buckets + with Not_found -> [] + in + (* Add the state to the bucket *) + KMap.add k (x :: contents) buckets + in + let buckets = KMap.fold fill_buckets p KMap.empty 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) + 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 + + 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 +end diff --git a/src/plugins/value/engine/partition.mli b/src/plugins/value/engine/partition.mli new file mode 100644 index 0000000000000000000000000000000000000000..161b95ca6ba604b2f9ce9d1ef315a319bb2f6eea --- /dev/null +++ b/src/plugins/value/engine/partition.mli @@ -0,0 +1,119 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2019 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +(* A state partition is a collection of states, each of which is identified + by a unique key. The key identifies the reason for which we want to keep + the state separate from the others. The partitioning method will involve + updating the key. If at some point two states share the same key, it means + that the partitioning method decided to not consider those states separately + anymore and that they should be joined together. + + The key have several fields, one for each kind of partitioning. + + - Ration stamps: These modelize the legacy slevel. Each state is given + a ration stamp (represented by an integer) until there is no slevel left. + It is an option type, when there is no more ration stamp, this field is + set to None; each new state will not be distinguished by this field. + - Branches: This field enumerate the last branches taken to reach this + state. The partitioning may chose how the branches are identified, but it + is a First-In-First-Out set. + - Loops: This field stores the loop iterations needed to reach this state + for each loop we are currently in. It is stored in reverse order + (innermost loop first) + - Static/Dynamic splits: + + Note on implementation. These partitions are implemented as map from keys + to states. We chose to have the same partition for stores, propagation and + widenings so the combination of propagation + store or propagation + + widening can be done as a map2 operation. However, this involve some tricks + to make keys be always distinguished in propagation, like giving them new + ration stamps. It may have been more natural to consider that propagations + are lists, allowing states to have the same key. +*) + +type branch = int + +module LvalMap = Cil_datatype.LvalStructEq.Map + +type key = private { + ration_stamp : int option; + transfer_stamp : int option; + branches : branch list; + loops : int list; + static_split : Integer.t LvalMap.t; + dynamic_split : Integer.t LvalMap.t; +} + +type 'a partition + +type action = + | Enter_loop + | Leave_loop + | Incr_loop of int (* the parameter is the unroll limit *) + | 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 + | Static_split of Cil_types.lval + | Dynamic_split of Cil_types.lval + | Static_merge of Cil_types.lval + | Dynamic_merge of Cil_types.lval + | Update_dynamic_splits + +exception InvalidAction + + +module type InputDomain = +sig + type t + + exception Cant_split + + val join : t -> t -> t + val split : t -> Cil_types.lval -> (Integer.t * t) list +end + + +module Make (Domain : InputDomain) : +sig + type t = Domain.t partition + type state = Domain.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 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 iter : ('a -> unit) -> 'a partition -> unit + 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 +end diff --git a/src/plugins/value/engine/partitioned_dataflow.ml b/src/plugins/value/engine/partitioned_dataflow.ml index 8621a85292a90e77ee6d6e6620d39659ec65aca6..e3d842a247fe0282e8e30d59a7a9b84d902d3133 100644 --- a/src/plugins/value/engine/partitioned_dataflow.ml +++ b/src/plugins/value/engine/partitioned_dataflow.ml @@ -39,9 +39,6 @@ let check_signals, signal_abort = let dkey = Value_parameters.dkey_iterator let dkey_callbacks = Value_parameters.dkey_callbacks -let is_return s = match s.skind with Return _ -> true | _ -> false -let is_loop s = match s.skind with Loop _ -> true | _ -> false - let blocks_share_locals b1 b2 = match b1.blocals, b2.blocals with | [], [] -> true @@ -88,108 +85,27 @@ module Make_Dataflow let interpreter_mode = Value_parameters.InterpreterMode.get () - let slevel (stmt : stmt) : int = - if is_return stmt || interpreter_mode then - max_int - else match Per_stmt_slevel.local kf with - | Per_stmt_slevel.Global i -> i - | Per_stmt_slevel.PerStmt f -> f stmt - - let merge_after_loop : bool = - Kernel_function.Set.mem kf - (Value_parameters.SlevelMergeAfterLoop.get ()) - - let merge (stmt : stmt) : bool = - is_loop stmt && merge_after_loop - || - match Per_stmt_slevel.merge kf with - | Per_stmt_slevel.NoMerge -> false - | Per_stmt_slevel.Merge f -> f stmt - - let default_loop_unroll : int = Value_parameters.MinLoopUnroll.get () - - let unroll (stmt : stmt) : int = - let local_unroll = match Unroll_annots.get_unroll_terms stmt with - | [] -> - let is_attribute a = Cil.hasAttribute a stmt.sattr in - begin - match List.filter is_attribute ["for" ; "while" ; "dowhile"] with - | [] -> () - | loop_kind :: _ -> - let wkey = - if loop_kind = "for" - then Value_parameters.wkey_missing_loop_unroll_for - else Value_parameters.wkey_missing_loop_unroll - in - Value_parameters.warning - ~wkey ~source:(fst (Cil_datatype.Stmt.loc stmt)) ~once:true - "%s loop without unroll annotation" loop_kind - end; - None - | [t] -> - (* Inlines the value of const variables in [t]. *) - let global_init vi = - try (Globals.Vars.find vi).init with Not_found -> None - in - let t = - Cil.visitCilTerm (new Logic_utils.simplify_const_lval global_init) t - in - begin match Logic_utils.constFoldTermToInt t with - | Some n -> Some (Integer.to_int n) - | None -> - Kernel.warning ~once:true ~current:true - "invalid term, not integer: %a" - Printer.pp_term t; - None - end - | _ -> - Kernel.warning ~once:true ~current:true - "ignoring invalid unroll annotation"; - None - in match local_unroll with - | Some n -> n - | None -> default_loop_unroll - let slevel_display_step : int = Value_parameters.ShowSlevel.get () + (* Ideally, the slevel parameter should not be used anymore in this file + but it is still required for logic interpretation *) + let slevel = + let module P = Partitioning_parameters.Make (AnalysisParam) in + P.slevel - (* --- Abstract values storage --- *) - - module Domain = struct - include Domain - let join_list ?(into : t or_bottom = `Bottom) (l : t list) : t or_bottom = - List.fold_left - (fun acc v -> Bottom.join join acc (`Value v)) - into l - end - - module PartitioningParam = struct - type loop = stmt - let kf = kf - let widening_delay = Value_parameters.WideningDelay.get () - let widening_period = Value_parameters.WideningPeriod.get () - let slevel = slevel - let merge = merge - let unroll = unroll - end - - module type P = - State_partitioning.Partition with type state = Domain.t - and type loop = PartitioningParam.loop - - let partition_module = - if descending_iteration = NoIteration - then (module Loop_partitioning.Make (Domain) (PartitioningParam) : P) - else (module Basic_partitioning.Make (Domain) (PartitioningParam) : P) + (* --- Abstract values storage --- *) - module Partition = (val partition_module: P) + module Partition = Trace_partitioning.Make (Domain) (AnalysisParam) type store = Partition.store type widening = Partition.widening type propagation = Partition.propagation - type shadow = Partition.shadow + + type edge_info = { + mutable fireable : bool (* Does any states survive the transition ? *) + } (* --- Interpreted automata --- *) @@ -226,8 +142,8 @@ module Make_Dataflow | `Value state -> state let initial_propagation = - Partition.initial_propagation (States.to_list initial_states), - Partition.empty_shadow () + -1, (* dummy edge identifier *) + Partition.initial_propagation (States.to_list initial_states) let post_conditions = ref false @@ -259,7 +175,7 @@ module Make_Dataflow VertexTable.create control_point_count let w_table : widening VertexTable.t = VertexTable.create 7 - let e_table : (propagation * shadow) EdgeTable.t = + let e_table : (propagation * edge_info) EdgeTable.t = EdgeTable.create transition_count (* Default (Initial) stores on vertex and edges *) @@ -267,18 +183,21 @@ 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_store () : propagation * shadow = - Partition.empty_propagation (), Partition.empty_shadow () + let default_edge_propagation () : propagation * edge_info = + Partition.empty_propagation (), { 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 * shadow = - EdgeTable.find_or_add e_table e ~default:default_edge_store - let get_pred_propagations (v : vertex) : (propagation * shadow) list = - List.map (fun (_,e,_) -> get_edge_propagation e) (G.pred_e graph 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) @@ -305,6 +224,12 @@ 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) + (* Thse lifting function helps to uniformize the transfer functions to a common signature *) @@ -382,10 +307,6 @@ module Make_Dataflow let transfer_return (stmt : stmt) (return_exp : exp option) (states : state list) : state list = - (** Join every state in the list and put the result in a singleton. *) - let smash (l : state list) : state list = - Bottom.to_list (Domain.join_list l) - in (* Deconstruct return statement *) let return_var = match return_exp with | Some {enode = Lval (Var v, NoOffset)} -> Some v @@ -411,12 +332,12 @@ module Make_Dataflow 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 Domain.join_list split_states in + let states' = List.map smash split_states in Bottom.all states' | None -> - smash states + Bottom.to_list (smash states) end - | Split_strategy.NoSplit -> smash states + | 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 @@ -529,13 +450,15 @@ module Make_Dataflow let process_edge (v1,e,v2 : G.edge) : unit = let {edge_transition=transition; edge_kinstr=kinstr} = e in - let propagation,_shadow = get_edge_propagation e in + let propagation,edge_info = get_edge_propagation e 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 + 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 *) @@ -586,7 +509,7 @@ module Make_Dataflow false in (* Reset sources *) - List.iter (fun (p,_) -> Partition.clear_propagation p) 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); (* Return wether the iterator should stop or not *) @@ -619,21 +542,6 @@ module Make_Dataflow | _ -> (* Several successors - failure *) Value_parameters.abort "Do not know which branch to take. Stopping." - let reset_component (vertex_list : vertex list) : unit = - let reset_edge (_,e,_) = - let p,s = get_edge_propagation e in - Partition.reset_propagation p; - Partition.reset_shadow s; - in - let reset_vertex v = - let s = get_vertex_store v - and w = get_vertex_widening v in - Partition.reset_store s; - Partition.reset_widening w; - List.iter reset_edge (G.succ_e graph v) - in - List.iter reset_vertex vertex_list - let rec iterate_list (l : wto) = List.iter iterate_element l and iterate_element = function @@ -644,7 +552,7 @@ module Make_Dataflow Otherwise, only resets the widening counter for this component. This is especially useful for nested loops. *) if hierachical_convergence - then reset_component (v :: Wto.flatten w) + then () (* reset_component (v :: Wto.flatten w) *) else Partition.reset_widening_counter (get_vertex_widening v); (* Iterate until convergence *) let iteration_count = ref 0 in @@ -715,12 +623,12 @@ module Make_Dataflow | Then -> Db.Value.mask_then | Else -> Db.Value.mask_else in - let shadow = snd (get_edge_propagation e) in + let edge_info = snd (get_edge_propagation e) in let old_status = try StmtTable.find table stmt with Not_found -> 0 and status = - if Partition.is_empty_shadow shadow then 0 else mask + if edge_info.fireable then mask else 0 in let new_status = old_status lor status in StmtTable.replace table stmt new_status; diff --git a/src/plugins/value/engine/partitioning.ml b/src/plugins/value/engine/partitioning.ml index 3b6f292b4f6f572cb609f4d6054c7c9b8dd33c84..c40be57a5bc0189d25c7352849313ecfa5ec33ee 100644 --- a/src/plugins/value/engine/partitioning.ml +++ b/src/plugins/value/engine/partitioning.ml @@ -33,6 +33,7 @@ module type S = sig type t val empty: unit -> t + val add : state -> t -> bool val merge_set_return_new: state list -> t -> state list val join: t -> state or_bottom val to_list: t -> state list diff --git a/src/plugins/value/engine/partitioning.mli b/src/plugins/value/engine/partitioning.mli index 40bb50a1f25a6f772a4f8980cec36c87c0f883a3..4ecc2ee21ff9444a5913bbe3277499e428ca95c0 100644 --- a/src/plugins/value/engine/partitioning.mli +++ b/src/plugins/value/engine/partitioning.mli @@ -34,6 +34,7 @@ module type S = sig val empty: unit -> t + val add : state -> t -> bool val merge_set_return_new: state list -> t -> state list val join: t -> state or_bottom diff --git a/src/plugins/value/engine/partitioning_parameters.ml b/src/plugins/value/engine/partitioning_parameters.ml new file mode 100644 index 0000000000000000000000000000000000000000..d625a9ef865e0d2bc4f1627a990d0117199734fb --- /dev/null +++ b/src/plugins/value/engine/partitioning_parameters.ml @@ -0,0 +1,129 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2019 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +open State_partitioning +open Value_parameters +open Partitioning_annots +open Cil_types + +let is_return s = match s.skind with Return _ -> true | _ -> false +let is_loop s = match s.skind with Loop _ -> true | _ -> false + +let warn ?(current = true) = Kernel.warning ~once:true ~current + +module Make (Kf : Kf) : Parameters = +struct + let kf = Kf.kf + + let widening_delay = WideningDelay.get () + let widening_period = WideningPeriod.get () + + let interpreter_mode = InterpreterMode.get () + + let slevel stmt = + if is_return stmt || interpreter_mode then + max_int + else match Per_stmt_slevel.local kf with + | Per_stmt_slevel.Global i -> i + | Per_stmt_slevel.PerStmt f -> f stmt + + let merge_after_loop = SlevelMergeAfterLoop.mem kf + + let merge stmt = + is_loop stmt && merge_after_loop + || + match Per_stmt_slevel.merge kf with + | Per_stmt_slevel.NoMerge -> false + | Per_stmt_slevel.Merge f -> f stmt + + let default_loop_unroll = MinLoopUnroll.get () + + let unroll stmt = + let local_unroll = match get_unroll_annot stmt with + | [] -> + let is_attribute a = Cil.hasAttribute a stmt.sattr in + begin + match List.filter is_attribute ["for" ; "while" ; "dowhile"] with + | [] -> () + | loop_kind :: _ -> + let wkey = + if loop_kind = "for" + then Value_parameters.wkey_missing_loop_unroll_for + else Value_parameters.wkey_missing_loop_unroll + in + Value_parameters.warning + ~wkey ~source:(fst (Cil_datatype.Stmt.loc stmt)) ~once:true + "%s loop without unroll annotation" loop_kind + end; + None + | [t] -> + (* Inlines the value of const variables in [t]. *) + let global_init vi = + try (Globals.Vars.find vi).init with Not_found -> None + in + let t = + Cil.visitCilTerm (new Logic_utils.simplify_const_lval global_init) t + in + begin match Logic_utils.constFoldTermToInt t with + | Some n -> Some (Integer.to_int n) + | None -> + warn "invalid term, not integer: %a" Printer.pp_term t; + None + end + | _ -> + warn "ignoring invalid unroll annotation"; + None + in match local_unroll with + | Some n -> n + | None -> default_loop_unroll + + let history_size = HistoryPartitioning.get () + + let universal_splits = + let add name l = + try + let vi = Globals.Vars.find_from_astinfo name VGlobal in + Cil.var vi :: l + with Not_found -> + warn ~current:false "cannot find the global variable %s for value \ + partitioning" name; + l + in + ValuePartitioning.fold add [] + + let flow_actions stmt = + let term_to_lval = function + | {term_node = TLval tlv} -> + !Db.Properties.Interp.term_lval_to_lval ~result:None tlv + | _ -> + warn "split/merge expressions must be lvalues"; + raise Exit + in + let map_annot acc t = + try + match t with + | FlowSplit t -> Partition.Static_split (term_to_lval t) :: acc + | FlowMerge t -> Partition.Static_merge (term_to_lval t) :: acc + with Exit -> acc (* Impossible to convert term to lval *) + in + List.fold_left map_annot [] (get_flow_annot stmt) +end diff --git a/src/plugins/value/engine/legacy_partitioning.mli b/src/plugins/value/engine/partitioning_parameters.mli similarity index 95% rename from src/plugins/value/engine/legacy_partitioning.mli rename to src/plugins/value/engine/partitioning_parameters.mli index 9006e977b10eb7d8b3f6c62af203180b23055a08..177cb8752a1eb4fa0d01a2140217c15fd045dbe1 100644 --- a/src/plugins/value/engine/legacy_partitioning.mli +++ b/src/plugins/value/engine/partitioning_parameters.mli @@ -20,4 +20,5 @@ (* *) (**************************************************************************) -module Make : State_partitioning.Partitioning +module Make (Kf : State_partitioning.Kf) : State_partitioning.Parameters + diff --git a/src/plugins/value/engine/state_partitioning.mli b/src/plugins/value/engine/state_partitioning.mli index b9958356da1a916830c3c1d4bcce6a9164e61f50..7395634a2c1c848b59d1beb9f2fccdf51bccfd72 100644 --- a/src/plugins/value/engine/state_partitioning.mli +++ b/src/plugins/value/engine/state_partitioning.mli @@ -22,34 +22,32 @@ open Bottom.Type -module type Param = +type branch = Partition.branch +type loop = Cil_types.stmt + +module type Kf = +sig + val kf : Cil_types.kernel_function +end + +module type Parameters = sig - type loop - val kf : Cil_types.kernel_function val widening_delay : int val widening_period : int val slevel : Cil_types.stmt -> int val merge : Cil_types.stmt -> bool val unroll : loop -> int -end - -module type Domain = -sig - include Partitioning.Domain - val join_list : ?into:t or_bottom -> t list -> t or_bottom + val history_size : int + val universal_splits : Cil_types.lval list + val flow_actions : Cil_types.stmt -> Partition.action list end module type Partition = sig - type loop (** Loops identifiers *) 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 shadow (** The shadow of a propagation remembers all the previous - propagations ; shadows are useful before joins during - descending sequences or to find if a transition is - fireable *) type widening (** Widening informations *) @@ -57,7 +55,6 @@ sig val empty_store : stmt:Cil_types.stmt option -> store val empty_propagation : unit -> propagation - val empty_shadow : unit -> shadow val empty_widening : stmt:Cil_types.stmt option -> widening (** Build the initial propagation for the entry point of a function. *) @@ -76,7 +73,6 @@ sig val smashed : store -> state or_bottom val is_empty_store : store -> bool val is_empty_propagation : propagation -> bool - val is_empty_shadow : shadow -> bool val store_size : store -> int val propagation_size : propagation -> int @@ -88,7 +84,6 @@ sig val reset_store : store -> unit val reset_propagation : propagation -> unit - val reset_shadow : shadow -> unit val reset_widening : widening -> unit (** Resets (or just delays) the widening counter. Used on nested loops, to @@ -120,10 +115,8 @@ sig together inside the propagation is allowed. *) val merge : into:propagation -> propagation -> unit - (** Join all incoming propagations into the given store. Each propagation is - paired with a shadow of the previous propagations on the same edge. This - function returns a set of states which still need to be propagated past - the store. + (** Join all incoming propagations into the given store. This function returns + a set of states which still need to be propagated past the store. If a state from the propagations is included in another state which has already been propagated, it may be removed from the output propagation. @@ -134,7 +127,7 @@ sig This function also interprets partitioning annotations at the store vertex (slevel, splits, merges, ...) which will generally change the current partitioning. *) - val join : (propagation * shadow) list -> store -> propagation + 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 @@ -148,7 +141,8 @@ sig end +module type Domain = Partitioning.Domain + module type Partitioning = - functor (Domain : Domain) (Param : Param) -> + functor (Domain : Domain) (Kf : Kf) -> Partition with type state = Domain.t - and type loop = Param.loop diff --git a/src/plugins/value/engine/trace_partitioning.ml b/src/plugins/value/engine/trace_partitioning.ml new file mode 100644 index 0000000000000000000000000000000000000000..081851332d43db8f067848956479c918bed8c98f --- /dev/null +++ b/src/plugins/value/engine/trace_partitioning.ml @@ -0,0 +1,386 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2019 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +open Cil_types +open Bottom.Type +open State_partitioning +open Partition + + +module Make (Domain : Domain) (Kf : Kf) = +struct + module Parameters = Partitioning_parameters.Make (Kf) + + open Kf + open Parameters + + (* Add the split function to the domain *) + module Domain = + struct + include Domain + + module Val = struct + include Main_values.CVal + include Structure.Open (Structure.Key_Value) (Main_values.CVal) + let reduce t = t + end + + module Eva = + Evaluation.Make + (Val) + (Main_locations.PLoc) + (Cvalue_domain.State) + + exception Cant_split + + (* TODO: size of split limit *) + let split state lval = + (* Whenever the split fail, warn the user and exit with an exception *) + let fail message = + Value_parameters.warning ~once:true message; + raise Cant_split + in + (* Get the cvalue *) + let cvalue = match get Cvalue_domain.key with + | Some get_cvalue -> get_cvalue state + | None -> fail "cannot partition by value when the CValue domain is not\ + active" + in + (* Retrieve the location of the lval *) + let cstate = Cvalue_domain.inject cvalue in + let location = match Eva.lvaluate ~for_writing:true cstate lval with + | `Value (_valuation, loc, _typ), _alarmset -> + Precise_locs.imprecise_location loc + | `Bottom, _alarmset -> + fail "cannot partition by value on an imprecise lvalue" + in + (* Extract the ival *) + let ival = + try + let v = Cvalue.Model.find cvalue location in + Cvalue.V.project_ival v + with Cvalue.V.Not_based_on_null -> + fail "cannot partition by value on pointers" + in + (* Build a state with the lvalue set to a singleton *) + let build i acc = + let v = Cvalue.V.inject_int i in + let cvalue = Cvalue.Model.add_binding ~exact:true cvalue location v in + let new_state = set Cvalue_domain.key cvalue state in + (i,new_state) :: acc + in + (* For each integer of the ival, build a new state *) + try + Ival.fold_int build ival [] + with Abstract_interp.Error_Top -> + fail "too many values to partition by value on" + end + + module Index = Partitioning.Make (Domain) + module Partition = Partition.Make (Domain) + + type state = Domain.t + + type store = { + size_limit : int; + merge : bool; + flow_actions : action list; + store_stmt : stmt option; + store_index : Index.t; + mutable store_partition : state partition; + mutable store_size : int; + } + + type propagation = { + mutable partition : state partition; + } + + type widening_state = { + widened_state : state option; + previous_state : state; + widening_counter : int; + } + + type widening = { + widening_stmt : stmt; + mutable widening_partition : widening_state partition; + } + + (* Constructors *) + + let empty_store ~(stmt : stmt option) : store = + let size_limit, merge, flow_actions = match stmt with + | None -> max_int, false, [] + | Some stmt -> slevel stmt, merge stmt, flow_actions stmt + in + { + size_limit; merge; flow_actions; + store_stmt = stmt; + store_index = Index.empty (); + store_partition = Partition.empty; + store_size = 0; + } + + let empty_propagation () : propagation = + { partition = Partition.empty } + + let empty_widening ~(stmt : stmt option) : widening = + { + widening_stmt = Extlib.opt_conv Cil.invalidStmt stmt; + widening_partition = Partition.empty; + } + + let initial_propagation (states : state list) : propagation = + let partition = Partition.initial states in + (* Split the initial partition according to the global split seetings *) + let split partition lval = + Partition.transfer_keys partition (Dynamic_split lval) + in + let partition = List.fold_left split partition universal_splits in + { partition } + + + (* Pretty printing *) + + 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 + + + (* Accessors *) + + let expanded (s : store) : state list = + 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) + + 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 store_size (s : store) : int = + s.store_size + + let propagation_size (p : propagation) : int = + Partition.size p.partition + + + (* Partition transfer functions *) + + let enter_loop (p : propagation) (_i : loop) = + p.partition <- Partition.transfer_keys p.partition Enter_loop + + let leave_loop (p : propagation) (_i : loop) = + p.partition <- Partition.transfer_keys p.partition Leave_loop + + let next_loop_iteration (p : propagation) (i : loop) = + let limit = unroll i in + p.partition <- Partition.transfer_keys p.partition (Incr_loop limit) + + + (* Reset state (for hierchical convergence) *) + + let reset_store (s : store) : unit = + let is_eternal key = + key.ration_stamp <> None + in + s.store_partition <- Partition.filter_keys is_eternal s.store_partition + + let reset_propagation (p : propagation) : unit = + p.partition <- Partition.empty + + let reset_widening (w : widening) : unit = + w.widening_partition <- Partition.empty + + let reset_widening_counter (w : widening) : unit = + let reset w = + { w with widening_counter = max w.widening_counter (widening_period - 1) } + in + w.widening_partition <- Partition.map_states reset w.widening_partition + + + (* 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 merge ~(into : propagation) (source : propagation) : unit = + (* TODO: state the precondition for this to be correct *) + let merge_two dest src = (* Erase the destination *) + if Extlib.has_some src + then src + else dest + in + into.partition <- Partition.merge merge_two into.partition source.partition + + let join (sources : (branch*propagation) list) (dest : store) + : propagation = + 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 = + match sources with + | [(_,p)] -> [p.partition] + | sources -> + (* Several branches ; partition according to the incoming branch *) + let get (b,p) = + Partition.transfer_keys p.partition (Branch (b,history_size)) + in + List.map get sources + in + (* Handle ration stamps *) + let slevel_exceeded = dest.store_size > dest.size_limit in + let rationing = + if slevel_exceeded then + (* No more slevel, no more ration tickets *) + fun p -> Partition.transfer_keys p (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 -> + (* 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 + 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 + 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 + (* Add states to the store but filter out already propagated states *) + let update key current_state = + (* Inclusion test *) + let state = + try + let previous_state = Partition.find key dest.store_partition in + if Domain.is_included current_state previous_state then + (* The current state is included in the previous; stop *) + None + else begin + (* Propagate the join of the two states *) + if is_loop_head then + Value_parameters.feedback ~level:1 ~once:true ~current:true + "starting to merge loop iterations"; + Some (Domain.join previous_state current_state) + end + with + (* There is no previous state, propagate normally *) + Not_found -> Some current_state + in + (* Add the propagated state to the store *) + let add s = + dest.store_partition <- Partition.replace key s dest.store_partition; + in + Extlib.may add state; + (* 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 widen (_s : store) (w : widening) (p : propagation) : bool = + let stmt = w.widening_stmt in + (* Auxiliary function to update the result *) + let update key widening_state = + w.widening_partition <- + Partition.replace key widening_state w.widening_partition + in + (* Apply widening to each leaf *) + let widen_one key curr = + try + (* Search for an already existing widening state *) + let wstate = Partition.find key w.widening_partition in + (* Update the widening state *) + update key { + wstate with + previous_state = curr; + widening_counter = wstate.widening_counter - 1 + }; + (* Propagated state decreases, stop to propagate *) + if Domain.is_included curr wstate.previous_state then + None + (* Widening is delayed *) + else if wstate.widening_counter > 0 then begin + Some curr + (* Apply widening *) + end else begin + Value_parameters.feedback ~level:1 ~once:true ~current:true + ~dkey:Value_parameters.dkey_widening + "applying a widening at this point"; + (* We join the previous widening state with the previous iteration + state so as to allow the intermediate(s) iteration(s) (between + two widenings) to stabilize at least a part of the state. *) + let prev = match wstate.widened_state with + | Some v -> Domain.join wstate.previous_state v + | None -> wstate.previous_state + in + let next = Domain.widen kf stmt prev (Domain.join prev curr) in + update key { + previous_state = next; + widened_state = Some next; + widening_counter = widening_period - 1; + }; + Some next + end + with Not_found -> + (* The key is not in the widening state; add it if slevel is not + exceeded *) + if key.ration_stamp = None then + update key { + widened_state = None; + previous_state = curr; + widening_counter = widening_delay - 1; + }; + Some curr + in + p.partition <- Partition.map_filter widen_one p.partition; + Partition.is_empty p.partition +end diff --git a/src/plugins/value/engine/basic_partitioning.mli b/src/plugins/value/engine/trace_partitioning.mli similarity index 99% rename from src/plugins/value/engine/basic_partitioning.mli rename to src/plugins/value/engine/trace_partitioning.mli index 9006e977b10eb7d8b3f6c62af203180b23055a08..07d509e4b5c203ebba676300dda6930ee5f22c12 100644 --- a/src/plugins/value/engine/basic_partitioning.mli +++ b/src/plugins/value/engine/trace_partitioning.mli @@ -21,3 +21,4 @@ (**************************************************************************) module Make : State_partitioning.Partitioning + diff --git a/src/plugins/value/slevel/per_stmt_slevel.ml b/src/plugins/value/slevel/per_stmt_slevel.ml index 4c9b5b4fe09d3e4dcc77cf0f66ada2aecad65cda..ca504df3baeb37c32d6aa5048117373d27e3afb6 100644 --- a/src/plugins/value/slevel/per_stmt_slevel.ml +++ b/src/plugins/value/slevel/per_stmt_slevel.ml @@ -21,6 +21,7 @@ (**************************************************************************) open Cil_types +open Partitioning_annots module G = struct type t = kernel_function @@ -39,54 +40,6 @@ end module Dfs = Graph.Traverse.Dfs(G) -(* We use the following encoding to store the directives in the AST: *) -type local_slevel = - | LMerge (* encoded as '"merge"' *) - | LDefault (* encoded as '"default"' *) - | LLocal of int (* encoded as 'Const i' *) - -let retrieve_annot lt = - match lt with - | [{term_node = TConst (Integer (i, _))}] -> - LLocal (Integer.to_int i) - | [{term_node = TConst (LStr "default")}] -> LDefault - | [{term_node = TConst (LStr "merge")}] -> LMerge - | _ -> LDefault (* be kind. Someone is bound to write a visitor that will - simplify our term into something unrecognizable... *) - -let () = Logic_typing.register_code_annot_next_stmt_extension "slevel" false - (fun ~typing_context:_ ~loc args -> - let abort () = - Value_parameters.abort ~source:(fst loc) "Invalid slevel directive" - in - let open Logic_ptree in - let p = match args with - | [{lexpr_node = PLvar ("default" | "merge" as s)}] -> - Logic_const.tstring s - | [{lexpr_node = PLconstant (IntConstant i)}] -> - begin - try - let i = int_of_string i in - if i < 0 then abort (); - Logic_const.tinteger i - with Failure _ -> abort () - end - | _ -> abort () - in - Ext_terms [p] - ) - -let () = Cil_printer.register_code_annot_extension "slevel" - (fun _pp fmt lp -> - match lp with - | Ext_id _ | Ext_preds _ -> assert false - | Ext_terms lt -> - match retrieve_annot lt with - | LDefault -> Format.pp_print_string fmt "default" - | LMerge -> Format.pp_print_string fmt "merge" - | LLocal i -> Format.pp_print_int fmt i - ) - type slevel = | Global of int | PerStmt of (stmt -> int) @@ -111,19 +64,9 @@ module DatatypeMerge = Datatype.Make(struct let mem_project = Datatype.never_any_project end) -let extract_slevel_directive s = - let rec find_one l = - match l with - | [] -> None - | {annot_content = AExtended(_,_,(_,"slevel", _, _,Ext_terms lp))} :: _ -> - Some (retrieve_annot lp) - | _ :: q -> find_one q - in - find_one (Annotations.code_annot s) - let kf_contains_slevel_directive kf = List.exists - (fun stmt -> extract_slevel_directive stmt <> None) + (fun stmt -> get_slevel_annot stmt <> None) (Kernel_function.get_definition kf).sallstmts let compute kf = @@ -139,15 +82,15 @@ let compute kf = (* Before visiting the successors of the statement: push or pop according to directive *) let pre s = - match extract_slevel_directive s with - | None | Some LMerge as d -> + match get_slevel_annot s with + | None | Some SlevelMerge as d -> Cil_datatype.Stmt.Hashtbl.add h_local s (Stack.top local_slevel); if d <> None then Cil_datatype.Stmt.Hashtbl.add h_merge s (); - | Some (LLocal i) -> + | Some (SlevelLocal i) -> if debug then Format.printf "Vising split %d, pushing %d@." s.sid i; Cil_datatype.Stmt.Hashtbl.add h_local s i; Stack.push i local_slevel; - | Some LDefault -> + | Some SlevelDefault -> let top = Stack.pop local_slevel in if debug then Format.printf "Visiting merge %d, poping (prev %d)@." s.sid top; @@ -157,12 +100,12 @@ let compute kf = (* after the visit of a statement and its successors. Do the converse operation of pre *) and post s = - match extract_slevel_directive s with - | None | Some LMerge -> () - | Some (LLocal _) -> + match get_slevel_annot s with + | None | Some SlevelMerge -> () + | Some (SlevelLocal _) -> if debug then Format.printf "Leaving split %d, poping@." s.sid; ignore (Stack.pop local_slevel); - | Some LDefault -> + | Some SlevelDefault -> (* slevel on nodes above s *) let above = Cil_datatype.Stmt.Hashtbl.find h_local s in (* slevel on s and on the nodes below *) diff --git a/src/plugins/value/utils/partitioning_annots.ml b/src/plugins/value/utils/partitioning_annots.ml new file mode 100644 index 0000000000000000000000000000000000000000..55908660a051c0b6b1d48ed6fd073f040d643f21 --- /dev/null +++ b/src/plugins/value/utils/partitioning_annots.ml @@ -0,0 +1,175 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2019 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +open Cil_types +open Logic_ptree + +[@@@ warning "-42"] + +type slevel_annotation = + | SlevelMerge + | SlevelDefault + | SlevelLocal of int + +type unroll_annotation = term + +type flow_annotation = + | FlowSplit of term + | FlowMerge of term + + +exception Parse_error + +module type Annotation = +sig + type t + + val name : string + val is_loop_annot : bool + val parse : typing_context:Logic_typing.typing_context -> lexpr list -> t + val export : t -> acsl_extension_kind + val import : acsl_extension_kind -> t + val print : Format.formatter -> t -> unit +end + +module Register (M : Annotation) = +struct + include M + + let typing_ext ~typing_context ~loc args = + try export (parse ~typing_context args) + with Parse_error -> + typing_context.Logic_typing.error loc "Invalid %s directive" name + + let printer_ext _pp fmt lp = + print fmt (import lp) + + let () = + if is_loop_annot then begin + Logic_typing.register_code_annot_next_loop_extension name false typing_ext; + Cil_printer.register_loop_annot_extension name printer_ext + end else begin + Logic_typing.register_code_annot_next_stmt_extension name false typing_ext; + Cil_printer.register_code_annot_extension name printer_ext + end + + let get stmt = + let filter_add _emitter annot acc = + match annot.annot_content with + | Cil_types.AExtended (_, is_loop_annot', (_,name',_,_,data)) + when name' = name && is_loop_annot' = is_loop_annot -> + import data :: acc + | _ -> acc + in + List.rev (Annotations.fold_code_annot filter_add stmt []) +end + + +module Slevel = Register (struct + type t = slevel_annotation + + let name = "slevel" + let is_loop_annot = false + + let parse ~typing_context:_ = function + | [{lexpr_node = PLvar "default"}] -> SlevelDefault + | [{lexpr_node = PLvar "merge"}] -> SlevelMerge + | [{lexpr_node = PLconstant (IntConstant i)}] -> + let i = + try int_of_string i + with Failure _ -> raise Parse_error + in + if i < 0 then raise Parse_error; + SlevelLocal i + | _ -> raise Parse_error + + let export = function + | SlevelDefault -> Ext_terms [Logic_const.tstring "default"] + | SlevelMerge -> Ext_terms [Logic_const.tstring "merge"] + | SlevelLocal i -> Ext_terms [Logic_const.tinteger i] + + let import = function + | Ext_terms [{term_node}] -> + begin match term_node with + | TConst (LStr "default") -> SlevelDefault + | TConst (LStr "merge") -> SlevelMerge + | TConst (Integer (i, _)) -> SlevelLocal (Integer.to_int i) + | _ -> SlevelDefault (* be kind. Someone is bound to write a visitor that + will simplify our term into something + unrecognizable... *) + end + | _ -> assert false + + let print fmt = function + | SlevelDefault -> Format.pp_print_string fmt "default" + | SlevelMerge -> Format.pp_print_string fmt "merge" + | SlevelLocal i -> Format.pp_print_int fmt i + end) + +module SimpleTermAnnotation = +struct + type t = term + + let parse ~typing_context = function + | [t] -> + let open Logic_typing in + typing_context.type_term typing_context typing_context.pre_state t + | _ -> raise Parse_error + + let export t = + Ext_terms [t] + + let import = function + | Ext_terms [t] -> t + | _ -> assert false + + let print = Printer.pp_term +end + +module Unroll = Register (struct + include SimpleTermAnnotation + let name = "unroll" + let is_loop_annot = true + end) + +module Split = Register (struct + include SimpleTermAnnotation + let name = "split" + let is_loop_annot = false + end) + +module Merge = Register (struct + include SimpleTermAnnotation + let name = "merge" + let is_loop_annot = false + end) + + +let get_slevel_annot stmt = + try Some (List.hd (Slevel.get stmt)) + with Failure _ -> None + +let get_unroll_annot stmt = Unroll.get stmt + +let get_flow_annot stmt = + List.map (fun a -> FlowSplit a) (Split.get stmt) @ + List.map (fun a -> FlowMerge a) (Merge.get stmt) diff --git a/src/plugins/value/utils/unroll_annots.mli b/src/plugins/value/utils/partitioning_annots.mli similarity index 80% rename from src/plugins/value/utils/unroll_annots.mli rename to src/plugins/value/utils/partitioning_annots.mli index f47d4f9ea36ca3fc749f09f85f500c0e6db00255..264cd94a5f0f25e3f3e82dfed7108f8bc89cd2b7 100644 --- a/src/plugins/value/utils/unroll_annots.mli +++ b/src/plugins/value/utils/partitioning_annots.mli @@ -20,8 +20,17 @@ (* *) (**************************************************************************) -(** Syntax extension for unrolling annotations, used by Eva. *) +type slevel_annotation = + | SlevelMerge + | SlevelDefault + | SlevelLocal of int -open Cil_types +type unroll_annotation = Cil_types.term -val get_unroll_terms : stmt -> term list +type flow_annotation = + | FlowSplit of Cil_types.term + | FlowMerge of Cil_types.term + +val get_slevel_annot : Cil_types.stmt -> slevel_annotation option +val get_unroll_annot : Cil_types.stmt -> unroll_annotation list +val get_flow_annot : Cil_types.stmt -> flow_annotation list diff --git a/src/plugins/value/utils/unroll_annots.ml b/src/plugins/value/utils/unroll_annots.ml deleted file mode 100644 index 3c2ae1c955ca2d81332b6c8ccd7ae388f5dd7b2e..0000000000000000000000000000000000000000 --- a/src/plugins/value/utils/unroll_annots.ml +++ /dev/null @@ -1,54 +0,0 @@ -(**************************************************************************) -(* *) -(* This file is part of Frama-C. *) -(* *) -(* Copyright (C) 2007-2019 *) -(* CEA (Commissariat à l'énergie atomique et aux énergies *) -(* alternatives) *) -(* *) -(* you can redistribute it and/or modify it under the terms of the GNU *) -(* Lesser General Public License as published by the Free Software *) -(* Foundation, version 2.1. *) -(* *) -(* It is distributed in the hope that it will be useful, *) -(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) -(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) -(* GNU Lesser General Public License for more details. *) -(* *) -(* See the GNU Lesser General Public License version 2.1 *) -(* for more details (enclosed in the file licenses/LGPLv2.1). *) -(* *) -(**************************************************************************) - -open Cil_types - -exception Parse_error of string option - -let parse_error ?msg () = raise (Parse_error msg) - -let () = Logic_typing.register_code_annot_next_loop_extension "unroll" false - begin fun ~typing_context ~loc:_ args -> - match args with - | [arg] -> - let open Logic_typing in - Ext_terms - [typing_context.type_term typing_context typing_context.pre_state arg] - | _ -> parse_error ~msg:"must be a single term" () - end - -let () = Cil_printer.register_behavior_extension "unroll" - begin fun _pp fmt lp -> - match lp with - | Ext_terms [t] -> Printer.pp_term fmt t - | Ext_id _ | Ext_preds _ | Ext_terms _ -> assert false - end - -let get_unroll_terms stmt = - Annotations.fold_code_annot - (fun _emitter annot acc -> - match annot with - | {annot_content = - AExtended (_, true, (_, "unroll", _,_,Ext_terms [term]))} -> - term :: acc - | _ -> acc - ) stmt [] diff --git a/src/plugins/value/value_parameters.ml b/src/plugins/value/value_parameters.ml index 7b0254eb4854e6efd6d47df74d0e7ef0ed107de7..051104b6512fc80a9bc51ba83d09febea2a4d41c 100644 --- a/src/plugins/value/value_parameters.ml +++ b/src/plugins/value/value_parameters.ml @@ -589,6 +589,8 @@ let () = InitializationPaddingGlobals.add_aliases ["-val-initialization-padding- (* --- Tuning --- *) (* ------------------------------------------------------------------------- *) +(* --- Iteration strategy --- *) + let () = Parameter_customize.set_group precision_tuning let () = Parameter_customize.is_invisible () module DescendingIteration = @@ -645,22 +647,7 @@ module WideningPeriod = let () = WideningDelay.set_range ~min:1 ~max:max_int let () = add_precision_dep WideningPeriod.parameter -let () = Parameter_customize.set_group precision_tuning -module ILevel = - Int - (struct - let option_name = "-eva-ilevel" - let default = 8 - let arg_name = "n" - let help = - "Sets of integers are represented as sets up to <n> elements. \ - Above, intervals with congruence information are used \ - (defaults to 8, must be between 4 and 128)" - end) -let () = add_precision_dep ILevel.parameter -let () = ILevel.add_aliases ["-val-ilevel"] -let () = ILevel.add_update_hook (fun _ i -> Ival.set_small_cardinal i) -let () = ILevel.set_range 4 256 +(* --- Partitioning --- *) let () = Parameter_customize.set_group precision_tuning module SemanticUnrollingLevel = @@ -727,6 +714,31 @@ module MinLoopUnroll = let () = add_precision_dep MinLoopUnroll.parameter let () = MinLoopUnroll.set_range 0 max_int +let () = Parameter_customize.set_group precision_tuning +module HistoryPartitioning = + Int + (struct + let option_name = "-eva-partition-history" + let arg_name = "n" + let default = 0 + let help = + "keep states distincts as long as the <n> last branching in their\ + traces are also distinct. (A value of 0 deactivate this feature)" + end) +let () = add_precision_dep HistoryPartitioning.parameter +let () = HistoryPartitioning.set_range 0 max_int + +let () = Parameter_customize.set_group precision_tuning +module ValuePartitioning = + String_set + (struct + let option_name = "-eva-partition-value" + let help = "partition the space of reachable states according to the \ + possible values of the global(s) variable(s) V." + let arg_name = "V" + end) +let () = add_precision_dep ValuePartitioning.parameter + let () = Parameter_customize.set_group precision_tuning let () = Parameter_customize.argument_may_be_fundecl () module SplitReturnFunction = @@ -780,6 +792,25 @@ let () = let () = add_precision_dep SplitReturn.parameter let () = SplitReturn.add_aliases ["-val-split-return"] +(* --- Misc --- *) + +let () = Parameter_customize.set_group precision_tuning +module ILevel = + Int + (struct + let option_name = "-eva-ilevel" + let default = 8 + let arg_name = "n" + let help = + "Sets of integers are represented as sets up to <n> elements. \ + Above, intervals with congruence information are used \ + (defaults to 8, must be between 4 and 128)" + end) +let () = add_precision_dep ILevel.parameter +let () = ILevel.add_aliases ["-val-ilevel"] +let () = ILevel.add_update_hook (fun _ i -> Ival.set_small_cardinal i) +let () = ILevel.set_range 4 256 + let () = Parameter_customize.set_group precision_tuning let () = Parameter_customize.argument_may_be_fundecl () module BuiltinsOverrides = diff --git a/src/plugins/value/value_parameters.mli b/src/plugins/value/value_parameters.mli index c6a81aa6e935db912cd109fdec2d1c5f6ea3c6cf..c6891422da333e93705ee8f140834f7398e232db 100644 --- a/src/plugins/value/value_parameters.mli +++ b/src/plugins/value/value_parameters.mli @@ -72,6 +72,11 @@ module WarnCopyIndeterminate: Parameter_sig.Kernel_function_set module IgnoreRecursiveCalls: Parameter_sig.Bool +module DescendingIteration: Parameter_sig.String +module HierarchicalConvergence: Parameter_sig.Bool +module WideningDelay: Parameter_sig.Int +module WideningPeriod: Parameter_sig.Int + module SemanticUnrollingLevel: Parameter_sig.Int module SlevelFunction: Parameter_sig.Map with type key = Cil_types.kernel_function @@ -80,11 +85,9 @@ module SlevelFunction: module SlevelMergeAfterLoop: Parameter_sig.Kernel_function_set module MinLoopUnroll : Parameter_sig.Int +module HistoryPartitioning : Parameter_sig.Int +module ValuePartitioning : Parameter_sig.String_set -module DescendingIteration: Parameter_sig.String -module HierarchicalConvergence: Parameter_sig.Bool -module WideningDelay: Parameter_sig.Int -module WideningPeriod: Parameter_sig.Int module ArrayPrecisionLevel: Parameter_sig.Int module AllocatedContextValid: Parameter_sig.Bool diff --git a/tests/misc/unroll_annots.c b/tests/misc/unroll_annots.c deleted file mode 100644 index 9154b42da50951e4ded83202de0eacb935147d7f..0000000000000000000000000000000000000000 --- a/tests/misc/unroll_annots.c +++ /dev/null @@ -1,22 +0,0 @@ -#define N 10 -int a[N], b[N]; - -int main() { - //@ loop unroll N; - for (int i = 0; i < N; i++) { - //@ loop unroll 1; - for (int j = 0; j < N; j++) { - a[i] = 42; - } - } - - //@ loop unroll 1; - for (int i = 0; i < N; i++) { - //@ loop unroll N; - for (int j = 0; j < N; j++) { - b[j] = 42; - } - } - - return 0; -} diff --git a/tests/value/oracle/partitioning-annots.0.res.oracle b/tests/value/oracle/partitioning-annots.0.res.oracle new file mode 100644 index 0000000000000000000000000000000000000000..75f64a73b09ab27c502e61cbf524958617be1403 --- /dev/null +++ b/tests/value/oracle/partitioning-annots.0.res.oracle @@ -0,0 +1,26 @@ +[kernel] Parsing tests/value/partitioning-annots.c (with preprocessing) +[eva] Analyzing a complete application starting at test_unroll +[eva] Computing initial state +[eva] Initial state computed +[eva:initial-state] Values of globals at initialization + k ∈ {0} +[eva] tests/value/partitioning-annots.c:25: starting to merge loop iterations +[eva] tests/value/partitioning-annots.c:33: starting to merge loop iterations +[eva] tests/value/partitioning-annots.c:35: starting to merge loop iterations +[eva] Recording results for test_unroll +[eva] done for function test_unroll +[eva] ====== VALUES COMPUTED ====== +[eva:final-states] Values at end of function test_unroll: + a[0..9] ∈ {42} + b[0..9] ∈ {42} +[from] Computing for function test_unroll +[from] Done for function test_unroll +[from] ====== DEPENDENCIES COMPUTED ====== + These dependencies hold at termination for the executions that terminate: +[from] Function test_unroll: + NO EFFECTS +[from] ====== END OF DEPENDENCIES ====== +[inout] Out (internal) for function test_unroll: + a[0..9]; b[0..9]; i; j; i_0; j_0 +[inout] Inputs for function test_unroll: + \nothing diff --git a/tests/value/oracle/partitioning-annots.1.res.oracle b/tests/value/oracle/partitioning-annots.1.res.oracle new file mode 100644 index 0000000000000000000000000000000000000000..8778196f4add115ff01d5d80a512da5ab26ba11f --- /dev/null +++ b/tests/value/oracle/partitioning-annots.1.res.oracle @@ -0,0 +1,68 @@ +[kernel] Parsing tests/value/partitioning-annots.c (with preprocessing) +[eva] Analyzing a complete application starting at test_split +[eva] Computing initial state +[eva] Initial state computed +[eva:initial-state] Values of globals at initialization + k ∈ {0} +[eva] computing for function Frama_C_interval <- test_split. + Called from tests/value/partitioning-annots.c:47. +[eva] using specification for function Frama_C_interval +[eva] tests/value/partitioning-annots.c:47: + function Frama_C_interval: precondition 'order' got status valid. +[eva] Done for function Frama_C_interval +[eva] computing for function Frama_C_interval <- test_split. + Called from tests/value/partitioning-annots.c:48. +[eva] tests/value/partitioning-annots.c:48: + function Frama_C_interval: precondition 'order' got status valid. +[eva] Done for function Frama_C_interval +[eva] tests/value/partitioning-annots.c:54: + Frama_C_show_each_before_first_split: {0; 1}, {0; 1; 2}, {0} +[eva] tests/value/partitioning-annots.c:57: + Frama_C_show_each_before_second_split: {0}, {0; 1; 2}, {0} +[eva] tests/value/partitioning-annots.c:57: + Frama_C_show_each_before_second_split: {1}, {0; 1; 2}, {1} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {0}, {0}, {0} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {0}, {1}, {0} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {0}, {2}, {0} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {1}, {0}, {1} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {1}, {1}, {1} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {1}, {2}, {1} +[eva] tests/value/partitioning-annots.c:61: + Frama_C_show_each_before_second_merge: {0; 1}, {0}, {0; 1} +[eva] tests/value/partitioning-annots.c:61: + Frama_C_show_each_before_second_merge: {0; 1}, {1}, {0; 1} +[eva] tests/value/partitioning-annots.c:61: + Frama_C_show_each_before_second_merge: {0; 1}, {2}, {0; 1} +[eva] tests/value/partitioning-annots.c:63: + Frama_C_show_each_end: {0; 1}, {0; 1; 2}, {0; 1} +[eva] Recording results for test_split +[eva] done for function test_split +[eva] ====== VALUES COMPUTED ====== +[eva:final-states] Values at end of function test_split: + Frama_C_entropy_source ∈ [--..--] + k ∈ {0; 1} + i ∈ {0; 1} + j ∈ {0; 1; 2} +[from] Computing for function test_split +[from] Computing for function Frama_C_interval <-test_split +[from] Done for function Frama_C_interval +[from] Done for function test_split +[from] ====== DEPENDENCIES COMPUTED ====== + These dependencies hold at termination for the executions that terminate: +[from] Function Frama_C_interval: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) + \result FROM Frama_C_entropy_source; min; max +[from] Function test_split: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) + k FROM Frama_C_entropy_source +[from] ====== END OF DEPENDENCIES ====== +[inout] Out (internal) for function test_split: + Frama_C_entropy_source; k; i; j +[inout] Inputs for function test_split: + Frama_C_entropy_source; k diff --git a/tests/value/oracle/partitioning-annots.2.res.oracle b/tests/value/oracle/partitioning-annots.2.res.oracle new file mode 100644 index 0000000000000000000000000000000000000000..c41cff4e89944fac83d636b9c12be7c77acab2cf --- /dev/null +++ b/tests/value/oracle/partitioning-annots.2.res.oracle @@ -0,0 +1,76 @@ +[kernel] Parsing tests/value/partitioning-annots.c (with preprocessing) +[eva] Analyzing a complete application starting at test_split +[eva] Computing initial state +[eva] Initial state computed +[eva:initial-state] Values of globals at initialization + k ∈ {0} +[eva] computing for function Frama_C_interval <- test_split. + Called from tests/value/partitioning-annots.c:47. +[eva] using specification for function Frama_C_interval +[eva] tests/value/partitioning-annots.c:47: + function Frama_C_interval: precondition 'order' got status valid. +[eva] Done for function Frama_C_interval +[eva] computing for function Frama_C_interval <- test_split. + Called from tests/value/partitioning-annots.c:48. +[eva] tests/value/partitioning-annots.c:48: + function Frama_C_interval: precondition 'order' got status valid. +[eva] Done for function Frama_C_interval +[eva] tests/value/partitioning-annots.c:54: + Frama_C_show_each_before_first_split: {0; 1}, {0; 1; 2}, {0} +[eva] tests/value/partitioning-annots.c:57: + Frama_C_show_each_before_second_split: {0}, {0; 1; 2}, {0} +[eva] tests/value/partitioning-annots.c:57: + Frama_C_show_each_before_second_split: {1}, {0; 1; 2}, {1} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {0}, {0}, {0} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {0}, {1}, {0} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {0}, {2}, {0} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {1}, {0}, {1} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {1}, {1}, {1} +[eva] tests/value/partitioning-annots.c:59: + Frama_C_show_each_before_first_merge: {1}, {2}, {1} +[eva] tests/value/partitioning-annots.c:61: + Frama_C_show_each_before_second_merge: {0}, {0}, {0} +[eva] tests/value/partitioning-annots.c:61: + Frama_C_show_each_before_second_merge: {1}, {0}, {1} +[eva] tests/value/partitioning-annots.c:61: + Frama_C_show_each_before_second_merge: {0}, {1}, {0} +[eva] tests/value/partitioning-annots.c:61: + Frama_C_show_each_before_second_merge: {1}, {1}, {1} +[eva] tests/value/partitioning-annots.c:61: + Frama_C_show_each_before_second_merge: {0}, {2}, {0} +[eva] tests/value/partitioning-annots.c:61: + Frama_C_show_each_before_second_merge: {1}, {2}, {1} +[eva] tests/value/partitioning-annots.c:63: + Frama_C_show_each_end: {0}, {0; 1; 2}, {0} +[eva] tests/value/partitioning-annots.c:63: + Frama_C_show_each_end: {1}, {0; 1; 2}, {1} +[eva] Recording results for test_split +[eva] done for function test_split +[eva] ====== VALUES COMPUTED ====== +[eva:final-states] Values at end of function test_split: + Frama_C_entropy_source ∈ [--..--] + k ∈ {0; 1} + i ∈ {0; 1} + j ∈ {0; 1; 2} +[from] Computing for function test_split +[from] Computing for function Frama_C_interval <-test_split +[from] Done for function Frama_C_interval +[from] Done for function test_split +[from] ====== DEPENDENCIES COMPUTED ====== + These dependencies hold at termination for the executions that terminate: +[from] Function Frama_C_interval: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) + \result FROM Frama_C_entropy_source; min; max +[from] Function test_split: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) + k FROM Frama_C_entropy_source +[from] ====== END OF DEPENDENCIES ====== +[inout] Out (internal) for function test_split: + Frama_C_entropy_source; k; i; j +[inout] Inputs for function test_split: + Frama_C_entropy_source; k diff --git a/tests/value/oracle/partitioning-annots.3.res.oracle b/tests/value/oracle/partitioning-annots.3.res.oracle new file mode 100644 index 0000000000000000000000000000000000000000..276bbc7d39325158489c457f78d399d100539768 --- /dev/null +++ b/tests/value/oracle/partitioning-annots.3.res.oracle @@ -0,0 +1,67 @@ +[kernel] Parsing tests/value/partitioning-annots.c (with preprocessing) +[eva] Analyzing a complete application starting at test_loop_split +[eva] Computing initial state +[eva] Initial state computed +[eva:initial-state] Values of globals at initialization + k ∈ {0} +[eva] computing for function Frama_C_interval <- test_loop_split. + Called from tests/value/partitioning-annots.c:81. +[eva] using specification for function Frama_C_interval +[eva] tests/value/partitioning-annots.c:81: + function Frama_C_interval: precondition 'order' got status valid. +[eva] Done for function Frama_C_interval +[eva] computing for function Frama_C_interval <- test_loop_split. + Called from tests/value/partitioning-annots.c:81. +[eva] Done for function Frama_C_interval +[eva] tests/value/partitioning-annots.c:79: starting to merge loop iterations +[eva] computing for function Frama_C_interval <- test_loop_split. + Called from tests/value/partitioning-annots.c:81. +[eva] Done for function Frama_C_interval +[eva] computing for function Frama_C_interval <- test_loop_split. + Called from tests/value/partitioning-annots.c:81. +[eva] Done for function Frama_C_interval +[eva] computing for function Frama_C_interval <- test_loop_split. + Called from tests/value/partitioning-annots.c:81. +[eva] Done for function Frama_C_interval +[eva] computing for function Frama_C_interval <- test_loop_split. + Called from tests/value/partitioning-annots.c:81. +[eva] Done for function Frama_C_interval +[eva:alarm] tests/value/partitioning-annots.c:88: Warning: + accessing uninitialized left-value. assert \initialized(&A[i]); +[eva] tests/value/partitioning-annots.c:93: Frama_C_show_each: {0}, {42} +[eva] tests/value/partitioning-annots.c:93: Frama_C_show_each: {1}, {42} +[eva] tests/value/partitioning-annots.c:93: Frama_C_show_each: {2}, {42} +[eva] tests/value/partitioning-annots.c:93: Frama_C_show_each: {3}, {42} +[eva] tests/value/partitioning-annots.c:93: Frama_C_show_each: {4}, {42} +[eva] tests/value/partitioning-annots.c:93: Frama_C_show_each: {5}, {42} +[eva] tests/value/partitioning-annots.c:93: Frama_C_show_each: {6}, {42} +[eva] tests/value/partitioning-annots.c:93: Frama_C_show_each: {7}, {42} +[eva] tests/value/partitioning-annots.c:93: Frama_C_show_each: {8}, {42} +[eva] tests/value/partitioning-annots.c:93: Frama_C_show_each: {9}, {42} +[eva] tests/value/partitioning-annots.c:94: assertion got status valid. +[eva] tests/value/partitioning-annots.c:97: + Frama_C_show_each: {{ "Value 42 not found" }} +[eva] Recording results for test_loop_split +[eva] done for function test_loop_split +[eva] ====== VALUES COMPUTED ====== +[eva:final-states] Values at end of function test_loop_split: + Frama_C_entropy_source ∈ [--..--] + A[0] ∈ [0..100] + [1..9] ∈ [0..100] or UNINITIALIZED + i ∈ [0..10] +[from] Computing for function test_loop_split +[from] Computing for function Frama_C_interval <-test_loop_split +[from] Done for function Frama_C_interval +[from] Done for function test_loop_split +[from] ====== DEPENDENCIES COMPUTED ====== + These dependencies hold at termination for the executions that terminate: +[from] Function Frama_C_interval: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) + \result FROM Frama_C_entropy_source; min; max +[from] Function test_loop_split: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) +[from] ====== END OF DEPENDENCIES ====== +[inout] Out (internal) for function test_loop_split: + Frama_C_entropy_source; A[0..9]; i +[inout] Inputs for function test_loop_split: + Frama_C_entropy_source diff --git a/tests/value/oracle/partitioning-annots.4.res.oracle b/tests/value/oracle/partitioning-annots.4.res.oracle new file mode 100644 index 0000000000000000000000000000000000000000..a2c98fb07a7bc198ce0425de2a4edbfd9082503c --- /dev/null +++ b/tests/value/oracle/partitioning-annots.4.res.oracle @@ -0,0 +1,39 @@ +[kernel] Parsing tests/value/partitioning-annots.c (with preprocessing) +[eva] Analyzing a complete application starting at test_history +[eva] Computing initial state +[eva] Initial state computed +[eva:initial-state] Values of globals at initialization + k ∈ {0} +[eva] computing for function Frama_C_interval <- test_history. + Called from tests/value/partitioning-annots.c:103. +[eva] using specification for function Frama_C_interval +[eva] tests/value/partitioning-annots.c:103: + function Frama_C_interval: precondition 'order' got status valid. +[eva] Done for function Frama_C_interval +[eva] tests/value/partitioning-annots.c:109: Frama_C_show_each: {0; 1}, {0; 1} +[eva:alarm] tests/value/partitioning-annots.c:112: Warning: + division by zero. assert j ≢ 0; +[eva] Recording results for test_history +[eva] done for function test_history +[eva] ====== VALUES COMPUTED ====== +[eva:final-states] Values at end of function test_history: + Frama_C_entropy_source ∈ [--..--] + i ∈ {0; 1} + j ∈ {0; 1} + k_0 ∈ {1} +[from] Computing for function test_history +[from] Computing for function Frama_C_interval <-test_history +[from] Done for function Frama_C_interval +[from] Done for function test_history +[from] ====== DEPENDENCIES COMPUTED ====== + These dependencies hold at termination for the executions that terminate: +[from] Function Frama_C_interval: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) + \result FROM Frama_C_entropy_source; min; max +[from] Function test_history: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) +[from] ====== END OF DEPENDENCIES ====== +[inout] Out (internal) for function test_history: + Frama_C_entropy_source; i; j; k_0 +[inout] Inputs for function test_history: + Frama_C_entropy_source diff --git a/tests/value/oracle/partitioning-annots.5.res.oracle b/tests/value/oracle/partitioning-annots.5.res.oracle new file mode 100644 index 0000000000000000000000000000000000000000..d0d38833a469d604f5c41b04875eba90472e49ad --- /dev/null +++ b/tests/value/oracle/partitioning-annots.5.res.oracle @@ -0,0 +1,38 @@ +[kernel] Parsing tests/value/partitioning-annots.c (with preprocessing) +[eva] Analyzing a complete application starting at test_history +[eva] Computing initial state +[eva] Initial state computed +[eva:initial-state] Values of globals at initialization + k ∈ {0} +[eva] computing for function Frama_C_interval <- test_history. + Called from tests/value/partitioning-annots.c:103. +[eva] using specification for function Frama_C_interval +[eva] tests/value/partitioning-annots.c:103: + function Frama_C_interval: precondition 'order' got status valid. +[eva] Done for function Frama_C_interval +[eva] tests/value/partitioning-annots.c:109: Frama_C_show_each: {1}, {1} +[eva] tests/value/partitioning-annots.c:109: Frama_C_show_each: {0}, {0} +[eva] Recording results for test_history +[eva] done for function test_history +[eva] ====== VALUES COMPUTED ====== +[eva:final-states] Values at end of function test_history: + Frama_C_entropy_source ∈ [--..--] + i ∈ {0; 1} + j ∈ {0; 1} + k_0 ∈ {1} +[from] Computing for function test_history +[from] Computing for function Frama_C_interval <-test_history +[from] Done for function Frama_C_interval +[from] Done for function test_history +[from] ====== DEPENDENCIES COMPUTED ====== + These dependencies hold at termination for the executions that terminate: +[from] Function Frama_C_interval: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) + \result FROM Frama_C_entropy_source; min; max +[from] Function test_history: + Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF) +[from] ====== END OF DEPENDENCIES ====== +[inout] Out (internal) for function test_history: + Frama_C_entropy_source; i; j; k_0 +[inout] Inputs for function test_history: + Frama_C_entropy_source diff --git a/tests/value/partitioning-annots.c b/tests/value/partitioning-annots.c new file mode 100644 index 0000000000000000000000000000000000000000..0e22c9b32f025b5a053945e9c9b2653e407b15a9 --- /dev/null +++ b/tests/value/partitioning-annots.c @@ -0,0 +1,121 @@ +/* run.config* + GCC: + STDOPT: #"-main test_unroll" + STDOPT: #"-main test_split" + STDOPT: +"-main test_split -eva-partition-value k" + STDOPT: #"-main test_loop_split -eva-partition-history 1" + STDOPT: #"-main test_history -eva-partition-history 0" + STDOPT: #"-main test_history -eva-partition-history 1" + */ + +#include "__fc_builtin.h" + +#define N 10 + +void test_unroll() +{ + int a[N], b[N]; + + // The inner loop needs to be unrolled to allow strong updates + // The outer loops doesn't need to be unrolled + + //@ loop unroll N; + for (int i = 0; i < N; i++) { + //@ loop unroll 1; + for (int j = 0; j < N; j++) { + a[i] = 42; + } + } + + // This time the outer loop needs unrolling but not the inner loop + + //@ loop unroll 1; + for (int i = 0; i < N; i++) { + //@ loop unroll N; + for (int j = 0; j < N; j++) { + b[j] = 42; + } + } + + // At the end, we must have both arrays a and b to be fully initialized at 42 +} + +int k; + +void test_split() +{ + int i = Frama_C_interval(0,1); + int j = Frama_C_interval(0,2); + + // The splits are done on i and j and undone in the same order + // If global dynamic split is done on k, since it is equaly to i, merge i will + // have no effects. + + Frama_C_show_each_before_first_split(i,j,k); + //@ split i; + k = i; + Frama_C_show_each_before_second_split(i,j,k); + //@ split j; + Frama_C_show_each_before_first_merge(i,j,k); + //@ merge i; + Frama_C_show_each_before_second_merge(i,j,k); + //@ merge j; + Frama_C_show_each_end(i,j,k); +} + +void test_loop_split() +{ + int A[N]; + int i; + + // In this example we can split on the value of the loop index in order to + // keep the relation between i and the value A[i] found in the array to be + // equal to 42. + // However, since the split is not dynamic, an history partitioning must be + // added to distinguish between the two states that share i = 9 : those who + // left the loop at the break point and those who left after the loop test. + + // Init a random array + for (i = 0 ; i < N ; i ++) + { + A[i] = Frama_C_interval(0,100); + } + + // Search for some value + for (i = 0 ; i < N ; i++) + { + //@ split i; + if (A[i] == 42) + break; + } + + if (i < N) { + Frama_C_show_each(i, A[i]); + //@ assert A[i] == 42; + } + else { + Frama_C_show_each("Value 42 not found"); + } +} + +void test_history() +{ + int i = Frama_C_interval(0,1); + int j = 0, k = 1; + + if (i) + j = 1; + + Frama_C_show_each(i, j); + + if (i) + k = k / j; +} + +void main(void) +{ + test_unroll(); + test_split(); + test_loop_split(); +} +