You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1064 lines
39 KiB
1064 lines
39 KiB
(*
|
|
* Copyright (c) 2017-present, Facebook, Inc.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*)
|
|
|
|
open! IStd
|
|
module F = Format
|
|
module L = Logging
|
|
module BasicCost = CostDomain.BasicCost
|
|
module NodesBasicCostDomain = CostDomain.NodeInstructionToCostMap
|
|
|
|
module Payload = SummaryPayload.Make (struct
|
|
type t = CostDomain.summary
|
|
|
|
let update_payloads sum (payloads: Payloads.t) = {payloads with cost= Some sum}
|
|
|
|
let of_payloads (payloads: Payloads.t) = payloads.cost
|
|
end)
|
|
|
|
(* We use this treshold to give error if the cost is above it.
|
|
Currently it's set randomly to 200. *)
|
|
let expensive_threshold = BasicCost.of_int_exn 200
|
|
|
|
let solver = `ConstraintSolver
|
|
|
|
(* CFG modules used in several other modules *)
|
|
module InstrCFG = ProcCfg.NormalOneInstrPerNode
|
|
module NodeCFG = ProcCfg.Normal
|
|
module InstrCFGScheduler = Scheduler.ReversePostorder (InstrCFG)
|
|
module Node = ProcCfg.DefaultNode
|
|
|
|
(* Compute a map (node,instruction) -> basic_cost, where basic_cost is the
|
|
cost known for a certain operation. For example for basic operation we
|
|
set it to 1 and for function call we take it from the spec of the function.
|
|
The nodes in the domain of the map are those in the path reaching the current node.
|
|
*)
|
|
module TransferFunctionsNodesBasicCost = struct
|
|
module CFG = InstrCFG
|
|
module Domain = NodesBasicCostDomain
|
|
|
|
type extras = BufferOverrunChecker.invariant_map
|
|
|
|
let cost_atomic_instruction = BasicCost.one
|
|
|
|
let instantiate_cost ~tenv ~caller_pdesc ~inferbo_caller_mem ~callee_pname ~params ~callee_cost =
|
|
match Ondemand.get_proc_desc callee_pname with
|
|
| None ->
|
|
L.(die InternalError)
|
|
"Can't instantiate symbolic cost %a from call to %a (can't get procdesc)" BasicCost.pp
|
|
callee_cost Typ.Procname.pp callee_pname
|
|
| Some callee_pdesc ->
|
|
match BufferOverrunChecker.Payload.read caller_pdesc callee_pname with
|
|
| None ->
|
|
L.(die InternalError)
|
|
"Can't instantiate symbolic cost %a from call to %a (can't get summary)" BasicCost.pp
|
|
callee_cost Typ.Procname.pp callee_pname
|
|
| Some inferbo_summary ->
|
|
let inferbo_caller_mem = Option.value_exn inferbo_caller_mem in
|
|
let callee_entry_mem = BufferOverrunDomain.Summary.get_input inferbo_summary in
|
|
let callee_exit_mem = BufferOverrunDomain.Summary.get_output inferbo_summary in
|
|
let callee_ret_alias = BufferOverrunDomain.Mem.find_ret_alias callee_exit_mem in
|
|
let (subst_map, _), _ =
|
|
BufferOverrunSemantics.get_subst_map tenv callee_pdesc params inferbo_caller_mem
|
|
callee_entry_mem ~callee_ret_alias
|
|
in
|
|
BasicCost.subst callee_cost subst_map
|
|
|
|
|
|
let exec_instr_cost inferbo_mem (astate: CostDomain.NodeInstructionToCostMap.astate)
|
|
{ProcData.pdesc; tenv} (node: CFG.Node.t) instr : CostDomain.NodeInstructionToCostMap.astate =
|
|
let key = CFG.Node.id node in
|
|
let astate' =
|
|
match instr with
|
|
| Sil.Call (_, Exp.Const (Const.Cfun callee_pname), params, _, _) ->
|
|
let callee_cost =
|
|
match Payload.read pdesc callee_pname with
|
|
| Some {post= callee_cost} ->
|
|
if BasicCost.is_symbolic callee_cost then
|
|
instantiate_cost ~tenv ~caller_pdesc:pdesc ~inferbo_caller_mem:inferbo_mem
|
|
~callee_pname ~params ~callee_cost
|
|
else callee_cost
|
|
| None ->
|
|
cost_atomic_instruction
|
|
in
|
|
CostDomain.NodeInstructionToCostMap.add key callee_cost astate
|
|
| Sil.Load _ | Sil.Store _ | Sil.Call _ | Sil.Prune _ ->
|
|
CostDomain.NodeInstructionToCostMap.add key cost_atomic_instruction astate
|
|
| _ ->
|
|
astate
|
|
in
|
|
L.(debug Analysis Medium)
|
|
"@\n>>>Instr: %a Cost: %a@\n" (Sil.pp_instr Pp.text) instr
|
|
CostDomain.NodeInstructionToCostMap.pp astate' ;
|
|
astate'
|
|
|
|
|
|
let exec_instr costmap ({ProcData.extras= inferbo_invariant_map} as pdata) node instr =
|
|
let inferbo_mem = BufferOverrunChecker.extract_pre (CFG.Node.id node) inferbo_invariant_map in
|
|
let costmap = exec_instr_cost inferbo_mem costmap pdata node instr in
|
|
costmap
|
|
|
|
|
|
let pp_session_name node fmt = F.fprintf fmt "cost(basic) %a" CFG.Node.pp_id (CFG.Node.id node)
|
|
end
|
|
|
|
module AnalyzerNodesBasicCost =
|
|
AbstractInterpreter.MakeNoCFG (InstrCFGScheduler) (TransferFunctionsNodesBasicCost)
|
|
|
|
(* Map associating to each node a bound on the number of times it can be executed.
|
|
This bound is computed using environments (map: val -> values), using the following
|
|
observation: the number of environments associated with a program point is an upperbound
|
|
of the number of times the program point can be executed in any execution.
|
|
The size of an environment env is computed as:
|
|
|env| = |env(v1)| * ... * |env(n_k)|
|
|
|
|
where |env(v)| is the size of the interval associated to v by env.
|
|
|
|
Reference: see Stefan Bygde PhD thesis, 2010
|
|
|
|
*)
|
|
module BoundMap = struct
|
|
type t = BasicCost.astate Node.IdMap.t
|
|
|
|
let print_upper_bound_map bound_map =
|
|
L.(debug Analysis Medium) "@\n\n******* Bound Map ITV **** @\n" ;
|
|
Node.IdMap.iter
|
|
(fun nid b ->
|
|
L.(debug Analysis Medium) "@\n node: %a --> bound = %a @\n" Node.pp_id nid BasicCost.pp b
|
|
)
|
|
bound_map ;
|
|
L.(debug Analysis Medium) "@\n******* END Bound Map ITV **** @\n\n"
|
|
|
|
|
|
let filter_loc formal_pvars vars_to_keep = function
|
|
| AbsLoc.Loc.Var (Var.LogicalVar _) ->
|
|
false
|
|
| AbsLoc.Loc.Var (Var.ProgramVar pvar) when List.mem formal_pvars pvar ~equal:Pvar.equal ->
|
|
false
|
|
| AbsLoc.Loc.Var var when Control.VarSet.mem var vars_to_keep ->
|
|
true
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let compute_upperbound_map node_cfg inferbo_invariant_map control_invariant_map =
|
|
let pname = Procdesc.get_proc_name node_cfg in
|
|
let formal_pvars =
|
|
Procdesc.get_formals node_cfg |> List.map ~f:(fun (m, _) -> Pvar.mk m pname)
|
|
in
|
|
let compute_node_upper_bound bound_map node =
|
|
let node_id = NodeCFG.Node.id node in
|
|
match Procdesc.Node.get_kind node with
|
|
| Procdesc.Node.Exit_node _ ->
|
|
Node.IdMap.add node_id BasicCost.one bound_map
|
|
| _ ->
|
|
let exit_state_opt =
|
|
let instr_node_id = InstrCFG.last_of_underlying_node node |> InstrCFG.Node.id in
|
|
BufferOverrunChecker.extract_post instr_node_id inferbo_invariant_map
|
|
in
|
|
match exit_state_opt with
|
|
| Some entry_mem ->
|
|
(* compute control vars, i.e. set of variables that affect the execution count *)
|
|
let control_vars = Control.compute_control_vars control_invariant_map node in
|
|
L.(debug Analysis Medium)
|
|
"@\n>>> All dependencies for node = %a : %a @\n\n" Procdesc.Node.pp node
|
|
Control.VarSet.pp control_vars ;
|
|
(* bound = env(v1) *... * env(vn) *)
|
|
let bound =
|
|
match entry_mem with
|
|
| Bottom ->
|
|
L.internal_error
|
|
"@\n\
|
|
[COST ANALYSIS INTERNAL WARNING:] No 'env' found. This location is \
|
|
unreachable returning cost 0 \n" ;
|
|
BasicCost.zero
|
|
| NonBottom mem ->
|
|
BufferOverrunDomain.MemReach.heap_range
|
|
~filter_loc:(filter_loc formal_pvars control_vars)
|
|
mem
|
|
in
|
|
L.(debug Analysis Medium)
|
|
"@\n>>>Setting bound for node = %a to %a@\n\n" Node.pp_id node_id BasicCost.pp
|
|
bound ;
|
|
Node.IdMap.add node_id bound bound_map
|
|
| _ ->
|
|
Node.IdMap.add node_id BasicCost.zero bound_map
|
|
in
|
|
let bound_map =
|
|
NodeCFG.fold_nodes node_cfg ~f:compute_node_upper_bound ~init:Node.IdMap.empty
|
|
in
|
|
print_upper_bound_map bound_map ; bound_map
|
|
|
|
|
|
let upperbound bound_map nid =
|
|
match Node.IdMap.find_opt nid bound_map with
|
|
| Some bound ->
|
|
bound
|
|
| None ->
|
|
L.(debug Analysis Medium)
|
|
"@\n\n[WARNING] Bound not found for node %a, returning Top @\n" Node.pp_id nid ;
|
|
BasicCost.top
|
|
end
|
|
|
|
module ControlFlowCost = struct
|
|
(* A Control-flow cost represents the number of times the flow of control can go through a certain CFG item (a node or an edge),
|
|
or a sum of such things *)
|
|
|
|
module Item = struct
|
|
type t = [`Node of Node.id | `Edge of Node.id * Node.id]
|
|
|
|
let compare : t -> t -> int =
|
|
fun x y ->
|
|
match (x, y) with
|
|
| `Node id1, `Node id2 ->
|
|
Node.compare_id id1 id2
|
|
| `Node _, `Edge _ ->
|
|
-1
|
|
| `Edge _, `Node _ ->
|
|
1
|
|
| `Edge (f1, t1), `Edge (f2, t2) ->
|
|
[%compare : Node.id * Node.id] (f1, t1) (f2, t2)
|
|
|
|
|
|
let equal = [%compare.equal : t]
|
|
|
|
let pp : F.formatter -> t -> unit =
|
|
fun fmt -> function
|
|
| `Node id ->
|
|
F.fprintf fmt "Node(%a)" Node.pp_id id
|
|
| `Edge (f, t) ->
|
|
F.fprintf fmt "Edge(%a -> %a)" Node.pp_id f Node.pp_id t
|
|
|
|
|
|
let normalize ~(normalizer: t -> [> t]) (x: t) : t =
|
|
match normalizer x with #t as x -> x | _ -> assert false
|
|
|
|
|
|
let of_node i = `Node i
|
|
end
|
|
|
|
module Sum = struct
|
|
type 'a set = (* non-empty sorted list *) 'a list
|
|
|
|
type t = [`Sum of int * Item.t set]
|
|
|
|
let of_list l =
|
|
let length = List.length l in
|
|
let set = List.sort ~compare:Item.compare l in
|
|
`Sum (length, set)
|
|
|
|
|
|
let compare : t -> t -> int =
|
|
fun (`Sum (l1, s1)) (`Sum (l2, s2)) -> [%compare : int * Item.t list] (l1, s1) (l2, s2)
|
|
|
|
|
|
let pp : F.formatter -> t -> unit =
|
|
fun fmt (`Sum (_, set)) -> Pp.seq ~sep:" + " Item.pp fmt set
|
|
|
|
|
|
let items (`Sum (_, l)) = l
|
|
|
|
let normalized_items ~normalizer (`Sum (_, l)) =
|
|
let normalizer = (normalizer :> Item.t -> [> Item.t]) in
|
|
l |> List.rev_map ~f:(Item.normalize ~normalizer)
|
|
|
|
|
|
let normalize ~normalizer sum = sum |> normalized_items ~normalizer |> of_list
|
|
|
|
(* Given a sum and an item, remove one occurence of the item in the sum. Returns [None] if the item is not present in the sum.
|
|
[remove_one_item ~item:A (A + B)] = B
|
|
[remove_one_item ~item:A (A + B + C)] = B + C
|
|
[remove_one_item ~item:A (A + A + B)] = A + B
|
|
[remove_one_item ~item:A (B + C)] = None
|
|
*)
|
|
let remove_one_item ~item (`Sum (len, l)) =
|
|
match IList.remove_first l ~f:(Item.equal item) with
|
|
| None ->
|
|
None
|
|
| Some [e] ->
|
|
Some (e :> [Item.t | t])
|
|
| Some l ->
|
|
Some (`Sum (len - 1, l))
|
|
|
|
|
|
let cost ~of_item (`Sum (_, l)) =
|
|
List.fold l ~init:BasicCost.zero ~f:(fun cost item -> BasicCost.plus cost (of_item item))
|
|
end
|
|
|
|
type t = [Item.t | Sum.t]
|
|
|
|
let is_node = function `Node nid -> Some nid | _ -> None
|
|
|
|
let assert_node = function `Node nid -> nid | _ -> assert false
|
|
|
|
let compare : t -> t -> int =
|
|
fun x y ->
|
|
match (x, y) with
|
|
| (#Item.t as x), (#Item.t as y) ->
|
|
Item.compare x y
|
|
| #Item.t, #Sum.t ->
|
|
-1
|
|
| #Sum.t, #Item.t ->
|
|
1
|
|
| (#Sum.t as x), (#Sum.t as y) ->
|
|
Sum.compare x y
|
|
|
|
|
|
let make_node node = `Node node
|
|
|
|
let make_pred_edge succ pred = `Edge (pred, succ)
|
|
|
|
let make_succ_edge pred succ = `Edge (pred, succ)
|
|
|
|
let pp : F.formatter -> t -> unit =
|
|
fun fmt -> function #Item.t as item -> Item.pp fmt item | #Sum.t as sum -> Sum.pp fmt sum
|
|
|
|
|
|
let sum : Item.t list -> t = function [] -> assert false | [e] -> (e :> t) | l -> Sum.of_list l
|
|
|
|
module Set = struct
|
|
type elt = t [@@deriving compare]
|
|
|
|
type t =
|
|
{ mutable size: int
|
|
; mutable items: Item.t ARList.t
|
|
; mutable sums: Sum.t ARList.t
|
|
; mutable cost: BasicCost.astate }
|
|
|
|
let create e =
|
|
let items, sums =
|
|
match e with
|
|
| #Item.t as item ->
|
|
(ARList.singleton item, ARList.empty)
|
|
| #Sum.t as sum ->
|
|
(ARList.empty, ARList.singleton sum)
|
|
in
|
|
{size= 1; items; sums; cost= BasicCost.top}
|
|
|
|
|
|
let compare_size {size= size1} {size= size2} = Int.compare size1 size2
|
|
|
|
(* Invalidation is just a sanity check, union-find already takes care of it. *)
|
|
let is_valid {size} = size >= 1
|
|
|
|
let cost {cost} = cost
|
|
|
|
(* move semantics, should not be called with aliases *)
|
|
let merge ~from ~to_ =
|
|
assert (not (phys_equal from to_)) ;
|
|
assert (is_valid from) ;
|
|
assert (is_valid to_) ;
|
|
to_.size <- to_.size + from.size ;
|
|
to_.items <- ARList.append to_.items from.items ;
|
|
to_.sums <- ARList.append to_.sums from.sums ;
|
|
from.size <- 0
|
|
|
|
|
|
let pp_equalities fmt t =
|
|
ARList.append (t.items :> elt ARList.t) (t.sums :> elt ARList.t)
|
|
|> IContainer.to_rev_list ~fold:ARList.fold_unordered |> List.sort ~compare
|
|
|> Pp.seq ~sep:" = " pp fmt
|
|
|
|
|
|
let normalize_sums : normalizer:(elt -> elt) -> t -> unit =
|
|
fun ~normalizer t ->
|
|
t.sums
|
|
<- t.sums
|
|
|> IContainer.rev_map_to_list ~fold:ARList.fold_unordered ~f:(Sum.normalize ~normalizer)
|
|
|> List.dedup_and_sort ~compare:Sum.compare |> ARList.of_list
|
|
|
|
|
|
let infer_equalities_by_removing_item ~on_infer t item =
|
|
t.sums
|
|
|> IContainer.rev_filter_map_to_list ~fold:ARList.fold_unordered
|
|
~f:(Sum.remove_one_item ~item)
|
|
|> IContainer.iter_consecutive ~fold:List.fold ~f:on_infer
|
|
|
|
|
|
let sum_items t =
|
|
t.sums
|
|
|> ARList.fold_unordered ~init:ARList.empty ~f:(fun acc sum ->
|
|
sum |> Sum.items |> ARList.of_list |> ARList.append acc )
|
|
|> IContainer.to_rev_list ~fold:ARList.fold_unordered
|
|
|> List.dedup_and_sort ~compare:Item.compare
|
|
|
|
|
|
let infer_equalities_from_sums
|
|
: on_infer:(elt -> elt -> unit) -> normalizer:(elt -> elt) -> t -> unit =
|
|
fun ~on_infer ~normalizer t ->
|
|
normalize_sums ~normalizer t ;
|
|
(* Keep in mind that [on_infer] can modify [t].
|
|
It happens only if we merge a node while infering equalities from it, i.e. in the case an item appears in an equality class both alone and in two sums, i.e. X = A + X = A + B.
|
|
This is not a problem here (we could stop if it happens but it is not necessary as existing equalities still remain true after merges) *)
|
|
(* Also keep in mind that the current version, in the worst-case scenario, is quadratic-ish in the size of the CFG *)
|
|
sum_items t |> List.iter ~f:(fun item -> infer_equalities_by_removing_item ~on_infer t item)
|
|
|
|
|
|
let init_cost : of_node:(Node.id -> BasicCost.astate) -> t -> unit =
|
|
fun ~of_node t ->
|
|
let min_if_node cost item =
|
|
match item with `Node node -> BasicCost.min_default_left cost (of_node node) | _ -> cost
|
|
in
|
|
t.cost <- ARList.fold_unordered t.items ~init:t.cost ~f:min_if_node
|
|
|
|
|
|
let improve_cost_from_sums
|
|
: on_improve:(Sum.t -> BasicCost.astate -> BasicCost.astate -> unit)
|
|
-> of_item:(Item.t -> BasicCost.astate) -> t -> unit =
|
|
fun ~on_improve ~of_item t ->
|
|
let f sum =
|
|
let cost_of_sum = Sum.cost ~of_item sum in
|
|
let new_cost = BasicCost.min_default_left t.cost cost_of_sum in
|
|
if not (BasicCost.( <= ) ~lhs:t.cost ~rhs:new_cost) then (
|
|
on_improve sum cost_of_sum new_cost ;
|
|
t.cost <- new_cost )
|
|
in
|
|
Container.iter t.sums ~fold:ARList.fold_unordered ~f
|
|
|
|
|
|
let improve_cost_with t cost' =
|
|
let old_cost = t.cost in
|
|
let new_cost = BasicCost.min_default_left old_cost cost' in
|
|
if not (BasicCost.( <= ) ~lhs:old_cost ~rhs:new_cost) then (
|
|
t.cost <- new_cost ;
|
|
Some old_cost )
|
|
else None
|
|
end
|
|
end
|
|
|
|
module ConstraintSolver = struct
|
|
module Equalities = struct
|
|
include ImperativeUnionFind.Make (ControlFlowCost.Set)
|
|
|
|
let normalizer equalities e = (find equalities e :> ControlFlowCost.t)
|
|
|
|
let pp_repr fmt (repr: Repr.t) = ControlFlowCost.pp fmt (repr :> ControlFlowCost.t)
|
|
|
|
let pp_equalities fmt equalities =
|
|
let pp_item fmt (repr, set) =
|
|
F.fprintf fmt "%a --> %a" pp_repr repr ControlFlowCost.Set.pp_equalities set
|
|
in
|
|
IContainer.pp_collection ~fold:fold_sets ~pp_item fmt equalities
|
|
|
|
|
|
let pp_costs fmt equalities =
|
|
let pp_item fmt (repr, set) =
|
|
F.fprintf fmt "%a --> %a" pp_repr repr BasicCost.pp (ControlFlowCost.Set.cost set)
|
|
in
|
|
IContainer.pp_collection ~fold:fold_sets ~pp_item fmt equalities
|
|
|
|
|
|
let log_union equalities e1 e2 =
|
|
match union equalities e1 e2 with
|
|
| None ->
|
|
L.(debug Analysis Verbose)
|
|
"[UF] Preexisting %a = %a@\n" ControlFlowCost.pp e1 ControlFlowCost.pp e2 ;
|
|
false
|
|
| Some (e1, e2) ->
|
|
L.(debug Analysis Verbose)
|
|
"[UF] Union %a into %a@\n" ControlFlowCost.pp e1 ControlFlowCost.pp e2 ;
|
|
true
|
|
|
|
|
|
let try_to_improve ~on_improve ~f equalities ~max =
|
|
let f did_improve repr_set =
|
|
if did_improve then (
|
|
f ~did_improve:(fun () -> ()) repr_set ;
|
|
true )
|
|
else
|
|
let did_improve = ref false in
|
|
f ~did_improve:(fun () -> did_improve := true) repr_set ;
|
|
!did_improve
|
|
in
|
|
let rec loop max =
|
|
if fold_sets equalities ~init:false ~f then (
|
|
on_improve () ;
|
|
if max > 0 then loop (max - 1)
|
|
else
|
|
L.(debug Analysis Verbose) "[ConstraintSolver] Maximum number of iterations reached@\n" )
|
|
in
|
|
loop max
|
|
|
|
|
|
(**
|
|
Infer equalities from sums, like this:
|
|
(1) A + sum1 = A + sum2 => sum1 = sum2
|
|
|
|
It does not try to saturate
|
|
(2) A = B + C /\ B = D + E => A = C + D + E
|
|
Nor combine more than 2 equations
|
|
(3) A = B + C /\ B = D + E /\ F = C + D + E => A = F
|
|
((3) is implied by (1) /\ (2))
|
|
|
|
Its complexity is unknown but I think it is bounded by nbNodes x nbEdges x max.
|
|
*)
|
|
let infer_equalities_from_sums equalities ~max =
|
|
let normalizer = normalizer equalities in
|
|
let f ~did_improve (_repr, set) =
|
|
let on_infer e1 e2 = if log_union equalities e1 e2 then did_improve () in
|
|
ControlFlowCost.Set.infer_equalities_from_sums ~on_infer ~normalizer set
|
|
in
|
|
let on_improve () =
|
|
L.(debug Analysis Verbose) "[ConstraintSolver][EInfe] %a@\n" pp_equalities equalities
|
|
in
|
|
try_to_improve ~on_improve ~f equalities ~max
|
|
|
|
|
|
let normalize_sums equalities =
|
|
let normalizer = normalizer equalities in
|
|
Container.iter ~fold:fold_sets equalities ~f:(fun (_repr, set) ->
|
|
ControlFlowCost.Set.normalize_sums ~normalizer set )
|
|
|
|
|
|
let union equalities e1 e2 =
|
|
let _ : bool = log_union equalities e1 e2 in
|
|
()
|
|
|
|
|
|
let init_costs bound_map equalities =
|
|
let of_node node_id = BoundMap.upperbound bound_map node_id in
|
|
Container.iter equalities ~fold:fold_sets ~f:(fun (_repr, set) ->
|
|
ControlFlowCost.Set.init_cost ~of_node set )
|
|
|
|
|
|
(**
|
|
From sums: if A = B + C, do cost(A) = min(cost(A), cost(B) + cost(C))
|
|
From inequalities: if A = B + C, then B <= A, do cost(B) = min(cost(B), cost(A))
|
|
*)
|
|
let improve_costs equalities ~max =
|
|
let of_item (item: ControlFlowCost.Item.t) =
|
|
(item :> ControlFlowCost.t) |> find equalities |> find_set equalities
|
|
|> Option.value_map ~f:ControlFlowCost.Set.cost ~default:BasicCost.top
|
|
in
|
|
let f ~did_improve (repr, set) =
|
|
let on_improve sum cost_of_sum new_cost =
|
|
L.(debug Analysis Verbose)
|
|
"[ConstraintSolver][CImpr] Improved cost of %a using %a (cost: %a), from %a to %a@\n"
|
|
pp_repr repr ControlFlowCost.Sum.pp sum BasicCost.pp cost_of_sum BasicCost.pp
|
|
(ControlFlowCost.Set.cost set) BasicCost.pp new_cost ;
|
|
did_improve ()
|
|
in
|
|
ControlFlowCost.Set.improve_cost_from_sums ~on_improve ~of_item set ;
|
|
let try_from_inequality (sum_item: ControlFlowCost.Item.t) =
|
|
let sum_item_set =
|
|
(sum_item :> ControlFlowCost.t) |> find equalities |> find_create_set equalities
|
|
in
|
|
match
|
|
ControlFlowCost.Set.improve_cost_with sum_item_set (ControlFlowCost.Set.cost set)
|
|
with
|
|
| Some previous_cost ->
|
|
L.(debug Analysis Verbose)
|
|
"[ConstraintSolver][CImpr] Improved cost of %a <= %a (cost: %a), from %a to %a@\n"
|
|
ControlFlowCost.Item.pp sum_item pp_repr repr BasicCost.pp
|
|
(ControlFlowCost.Set.cost set) BasicCost.pp previous_cost BasicCost.pp
|
|
(ControlFlowCost.Set.cost sum_item_set) ;
|
|
did_improve ()
|
|
| None ->
|
|
()
|
|
in
|
|
ControlFlowCost.Set.sum_items set |> List.iter ~f:try_from_inequality
|
|
in
|
|
let on_improve () =
|
|
L.(debug Analysis Verbose) "[ConstraintSolver][CImpr] %a@\n" pp_costs equalities
|
|
in
|
|
try_to_improve ~on_improve ~f equalities ~max
|
|
end
|
|
|
|
let add_constraints equalities node get_nodes make =
|
|
match get_nodes node with
|
|
| [] ->
|
|
(* either start/exit node or dead node (broken CFG) *)
|
|
()
|
|
| nodes ->
|
|
let node_id = Node.id node in
|
|
let edges = List.rev_map nodes ~f:(fun other -> make node_id (Node.id other)) in
|
|
let sum = ControlFlowCost.sum edges in
|
|
Equalities.union equalities (ControlFlowCost.make_node node_id) sum
|
|
|
|
|
|
let collect_on_node equalities node =
|
|
add_constraints equalities node Procdesc.Node.get_preds ControlFlowCost.make_pred_edge ;
|
|
add_constraints equalities node Procdesc.Node.get_succs ControlFlowCost.make_succ_edge
|
|
|
|
|
|
let collect_constraints node_cfg =
|
|
let equalities = Equalities.create () in
|
|
Container.iter node_cfg ~fold:NodeCFG.fold_nodes ~f:(collect_on_node equalities) ;
|
|
L.(debug Analysis Verbose)
|
|
"[ConstraintSolver] Procedure %a @@ %a@\n" Typ.Procname.pp (Procdesc.get_proc_name node_cfg)
|
|
Location.pp_file_pos (Procdesc.get_loc node_cfg) ;
|
|
L.(debug Analysis Verbose)
|
|
"[ConstraintSolver][EInit] %a@\n" Equalities.pp_equalities equalities ;
|
|
Equalities.normalize_sums equalities ;
|
|
L.(debug Analysis Verbose)
|
|
"[ConstraintSolver][ENorm] %a@\n" Equalities.pp_equalities equalities ;
|
|
Equalities.infer_equalities_from_sums equalities ~max:10 ;
|
|
L.(debug Analysis Verbose)
|
|
"[ConstraintSolver][EInfe] %a@\n" Equalities.pp_equalities equalities ;
|
|
equalities
|
|
|
|
|
|
let compute_costs bound_map equalities =
|
|
Equalities.init_costs bound_map equalities ;
|
|
L.(debug Analysis Verbose) "[ConstraintSolver][CInit] %a@\n" Equalities.pp_costs equalities ;
|
|
Equalities.improve_costs equalities ~max:10 ;
|
|
L.(debug Analysis Verbose) "[ConstraintSolver][CImpr] %a@\n" Equalities.pp_costs equalities
|
|
|
|
|
|
let get_node_nb_exec equalities node_id =
|
|
let set =
|
|
node_id |> ControlFlowCost.make_node |> Equalities.find equalities
|
|
|> Equalities.find_set equalities
|
|
in
|
|
Option.value_exn set |> ControlFlowCost.Set.cost
|
|
end
|
|
|
|
(* Structural Constraints are expressions of the kind:
|
|
n <= n1 +...+ nk
|
|
|
|
The informal meaning is: the number of times node n can be executed is less or
|
|
equal to the sum of the number of times nodes n1,..., nk can be executed.
|
|
*)
|
|
module StructuralConstraints = struct
|
|
type t = {single: Node.id list; sum: Node.IdSet.t list}
|
|
|
|
(*
|
|
Finds subset of constraints of node k.
|
|
It returns a pair (single_constraints, sum_constraints) where single constraints are
|
|
of the form 'x_k <= x_j' and sum constraints are of the form 'x_k <= x_j1 +...+ x_jn'.
|
|
*)
|
|
let get_constraints_of_node constraints k =
|
|
let c = Node.IdMap.find_opt k constraints in
|
|
match c with Some c -> c | _ -> {single= []; sum= []}
|
|
|
|
|
|
let print_constraints_map constraints =
|
|
let pp_nidset fmt nidset = Pp.seq ~sep:" + " Node.pp_id fmt (Node.IdSet.elements nidset) in
|
|
L.(debug Analysis Medium)
|
|
"@\n\n******* Structural Constraints size = %i **** @\n" (Node.IdMap.cardinal constraints) ;
|
|
Node.IdMap.iter
|
|
(fun n {single; sum} ->
|
|
List.iter
|
|
~f:(fun s -> L.(debug Analysis Medium) "@\n %a <= %a @\n" Node.pp_id n Node.pp_id s)
|
|
single ;
|
|
List.iter
|
|
~f:(fun s -> L.(debug Analysis Medium) "@\n %a <= %a @\n" Node.pp_id n pp_nidset s)
|
|
sum )
|
|
constraints ;
|
|
L.(debug Analysis Medium) "@\n******* END Structural Constraints **** @\n\n"
|
|
|
|
|
|
(* for each program point return a set of contraints of the kind
|
|
|
|
i<=Sum_{j \in Predecessors(i) } j
|
|
i<=Sum_{j \in Successors(i)} j
|
|
*)
|
|
let compute_structural_constraints node_cfg =
|
|
let compute_node_constraints acc node =
|
|
let constraints_add node get_nodes =
|
|
match get_nodes node with
|
|
| [] ->
|
|
{single= []; sum= []}
|
|
| [single] ->
|
|
{single= [NodeCFG.Node.id single]; sum= []}
|
|
| nodes ->
|
|
let sum =
|
|
List.fold nodes ~init:Node.IdSet.empty ~f:(fun idset node ->
|
|
Node.IdSet.add (NodeCFG.Node.id node) idset )
|
|
in
|
|
{single= []; sum= [sum]}
|
|
in
|
|
let preds = constraints_add node Procdesc.Node.get_preds in
|
|
let succs = constraints_add node Procdesc.Node.get_succs in
|
|
Node.IdMap.add (NodeCFG.Node.id node)
|
|
{single= List.append preds.single succs.single; sum= List.append preds.sum succs.sum} acc
|
|
in
|
|
let constraints =
|
|
NodeCFG.fold_nodes node_cfg ~f:compute_node_constraints ~init:Node.IdMap.empty
|
|
in
|
|
print_constraints_map constraints ; constraints
|
|
end
|
|
|
|
(* MinTree is used to compute:
|
|
|
|
\max (\Sum_{n \in Nodes} c_n * x_n )
|
|
|
|
given a set of contraints on x_n. The constraints involve the contro flow
|
|
of the program.
|
|
|
|
*)
|
|
module MinTree = struct
|
|
type mt_node =
|
|
| Leaf of (Node.id * BasicCost.astate)
|
|
| Min of mt_node list
|
|
| Plus of mt_node list
|
|
|
|
let add_leaf node nid leaf =
|
|
let leaf' = Leaf (nid, leaf) in
|
|
match node with Min l -> Min (leaf' :: l) | Plus l -> Plus (leaf' :: l) | _ -> assert false
|
|
|
|
|
|
let plus_seq pp f l = Pp.seq ~sep:" + " pp f l
|
|
|
|
let rec pp fmt node =
|
|
match node with
|
|
| Leaf (nid, c) ->
|
|
F.fprintf fmt "%a:%a" Node.pp_id nid BasicCost.pp c
|
|
| Min l ->
|
|
F.fprintf fmt "Min(%a)" (Pp.comma_seq pp) l
|
|
| Plus l ->
|
|
F.fprintf fmt "(%a)" (plus_seq pp) l
|
|
|
|
|
|
let add_child node child =
|
|
match child with
|
|
| Plus [] | Min [] ->
|
|
node (* if it's a dummy child, don't add it *)
|
|
| _ ->
|
|
match node with Plus l -> Plus (child :: l) | Min l -> Min (child :: l) | _ -> assert false
|
|
|
|
|
|
let rec evaluate_tree t =
|
|
match t with
|
|
| Leaf (_, c) ->
|
|
c
|
|
| Min l ->
|
|
evaluate_operator BasicCost.min_default_left l
|
|
| Plus l ->
|
|
evaluate_operator BasicCost.plus l
|
|
|
|
|
|
and evaluate_operator op l =
|
|
match l with
|
|
| [] ->
|
|
assert false
|
|
| [c] ->
|
|
evaluate_tree c
|
|
| c :: l' ->
|
|
let res_c = evaluate_tree c in
|
|
let res_l' = evaluate_operator op l' in
|
|
op res_c res_l'
|
|
|
|
|
|
(* a plus node is well formed if has at least two addends *)
|
|
let is_well_formed_plus_node plus_node =
|
|
match plus_node with Plus (_ :: _ :: _) -> true | _ -> false
|
|
|
|
|
|
module SetOfSetsOfNodes = Caml.Set.Make (Node.IdSet)
|
|
|
|
module BuiltTreeMap = Caml.Map.Make (struct
|
|
type t = Node.id * Node.IdSet.t [@@deriving compare]
|
|
end)
|
|
|
|
let minimum_propagation (bound_map: BoundMap.t)
|
|
(constraints: StructuralConstraints.t Node.IdMap.t) self
|
|
((q, visited): Node.id * Node.IdSet.t) =
|
|
let rec build_min node branch visited_acc worklist =
|
|
match worklist with
|
|
| [] ->
|
|
(node, branch, visited_acc)
|
|
| k :: rest ->
|
|
if Node.IdSet.mem k visited_acc then build_min node branch visited_acc rest
|
|
else
|
|
let visited_acc' = Node.IdSet.add k visited_acc in
|
|
let node = add_leaf node k (BoundMap.upperbound bound_map k) in
|
|
let k_constraints = StructuralConstraints.get_constraints_of_node constraints k in
|
|
let worklist' =
|
|
List.fold k_constraints.single ~init:rest ~f:(fun acc ub_id ->
|
|
if Node.IdSet.mem ub_id visited_acc' then acc else ub_id :: acc )
|
|
in
|
|
let branch =
|
|
List.fold_left
|
|
~f:(fun branch set_addend ->
|
|
assert (Node.IdSet.cardinal set_addend >= 2) ;
|
|
if Node.IdSet.is_empty (Node.IdSet.inter set_addend visited_acc') then
|
|
SetOfSetsOfNodes.add set_addend branch
|
|
else branch )
|
|
~init:branch k_constraints.sum
|
|
in
|
|
build_min node branch visited_acc' worklist'
|
|
in
|
|
let node, branch, visited_res = build_min (Min []) SetOfSetsOfNodes.empty visited [q] in
|
|
SetOfSetsOfNodes.fold
|
|
(fun addend i_node ->
|
|
L.(debug Analysis Medium) "@\n\n|Set addends| = %i {" (Node.IdSet.cardinal addend) ;
|
|
Node.IdSet.iter (fun e -> L.(debug Analysis Medium) " %a, " Node.pp_id e) addend ;
|
|
L.(debug Analysis Medium) " }@\n " ;
|
|
let plus_node =
|
|
Node.IdSet.fold
|
|
(fun n acc ->
|
|
let child = self (n, visited_res) in
|
|
add_child acc child )
|
|
addend (Plus [])
|
|
in
|
|
(* without this check it would add plus node with just one child, and give wrong results *)
|
|
if is_well_formed_plus_node plus_node then add_child i_node plus_node else i_node )
|
|
branch node
|
|
|
|
|
|
let with_cache f =
|
|
(* a map used for bookkeeping of the min trees that we have already built *)
|
|
let global_built_tree_map : mt_node BuiltTreeMap.t ref = ref BuiltTreeMap.empty in
|
|
let rec f_with_cache x =
|
|
match BuiltTreeMap.find_opt x !global_built_tree_map with
|
|
| Some v ->
|
|
v
|
|
| None ->
|
|
let v = f f_with_cache x in
|
|
global_built_tree_map := BuiltTreeMap.add x v !global_built_tree_map ;
|
|
v
|
|
in
|
|
Staged.stage f_with_cache
|
|
|
|
|
|
let find_tree_eq_rel n trees equalities =
|
|
let elt = ControlFlowCost.make_node n in
|
|
let repr = ConstraintSolver.Equalities.find equalities elt in
|
|
let repr_node = ControlFlowCost.assert_node (repr :> ControlFlowCost.t) in
|
|
match Node.IdMap.find_opt repr_node trees with
|
|
| Some t ->
|
|
t
|
|
| _ ->
|
|
L.(die InternalError) "@\n Equivalent tree not found. Stop.@\n"
|
|
|
|
|
|
let compute_trees_from_contraints bound_map node_cfg eqs constraints =
|
|
let start_node = Node.id (NodeCFG.start_node node_cfg) in
|
|
let start_node_item = ControlFlowCost.Item.of_node start_node in
|
|
let start_node_repr = ConstraintSolver.Equalities.find eqs start_node_item in
|
|
L.(debug Analysis Verbose) "@\n =========== Computed Equalities ==========@\n" ;
|
|
L.(debug Analysis Verbose) "[Equalities] %a@\n" ConstraintSolver.Equalities.pp_equalities eqs ;
|
|
let minimum_propagation =
|
|
with_cache (minimum_propagation bound_map constraints) |> Staged.unstage
|
|
in
|
|
let min_trees =
|
|
ConstraintSolver.Equalities.fold_sets eqs ~init:Node.IdMap.empty ~f:
|
|
(fun acc_trees (rep, _eq_cl) ->
|
|
match ControlFlowCost.is_node (rep :> ControlFlowCost.t) with
|
|
| None ->
|
|
acc_trees
|
|
| Some rep_id ->
|
|
let tree =
|
|
if ConstraintSolver.Equalities.Repr.equal start_node_repr rep then
|
|
(* for any node in the same equivalence class as the start node we give the trivial MinTree:
|
|
min(1)
|
|
*)
|
|
add_leaf (Min []) rep_id (BoundMap.upperbound bound_map rep_id)
|
|
else minimum_propagation (rep_id, Node.IdSet.empty)
|
|
in
|
|
Node.IdMap.add rep_id tree acc_trees )
|
|
in
|
|
Node.IdMap.iter
|
|
(fun nid t -> L.(debug Analysis Medium) "@\n node %a = %a @\n" Node.pp_id nid pp t)
|
|
min_trees ;
|
|
min_trees
|
|
end
|
|
|
|
module ReportedOnNodes = AbstractDomain.FiniteSetOfPPSet (Node.IdSet)
|
|
|
|
type extras_TransferFunctionsWCET =
|
|
{ basic_cost_map: AnalyzerNodesBasicCost.invariant_map
|
|
; get_node_nb_exec: Node.id -> BasicCost.astate
|
|
; summary: Summary.t }
|
|
|
|
(* Calculate the final Worst Case Execution Time predicted for each node.
|
|
It uses the basic cost of the nodes (computed previously by AnalyzerNodesBasicCost)
|
|
and MinTrees which give an upperbound on the number of times a node can be executed
|
|
*)
|
|
module TransferFunctionsWCET = struct
|
|
module CFG = InstrCFG
|
|
module Domain = AbstractDomain.Pair (BasicCost) (ReportedOnNodes)
|
|
|
|
type extras = extras_TransferFunctionsWCET
|
|
|
|
let should_report_on_instr = function
|
|
| Sil.Call _ | Sil.Load _ | Sil.Prune _ | Sil.Store _ ->
|
|
true
|
|
| Sil.Abstract _ | Sil.Declare_locals _ | Sil.Nullify _ | Sil.Remove_temps _ ->
|
|
false
|
|
|
|
|
|
(* We don't report when the cost is Top as it corresponds to subsequent 'don't know's.
|
|
Instead, we report Top cost only at the top level per function when `report_infinity` is set to true *)
|
|
let should_report_cost cost =
|
|
not (BasicCost.is_top cost) && not (BasicCost.( <= ) ~lhs:cost ~rhs:expensive_threshold)
|
|
|
|
|
|
let do_report summary loc cost =
|
|
let ltr =
|
|
let cost_desc = F.asprintf "with estimated cost %a" BasicCost.pp cost in
|
|
[Errlog.make_trace_element 0 loc cost_desc []]
|
|
in
|
|
let exn =
|
|
let message =
|
|
F.asprintf
|
|
"The execution time from the beginning of the function up to this program point is \
|
|
likely above the acceptable threshold of %a (estimated cost %a)"
|
|
BasicCost.pp expensive_threshold BasicCost.pp cost
|
|
in
|
|
Exceptions.Checkers (IssueType.expensive_execution_time_call, Localise.verbatim_desc message)
|
|
in
|
|
Reporting.log_error summary ~loc ~ltr exn
|
|
|
|
|
|
(* get a list of nodes and check if we have already reported for at
|
|
least one of them. In that case no need to report again. *)
|
|
let should_report_on_node preds reported_so_far =
|
|
List.for_all
|
|
~f:(fun node ->
|
|
let nid = Procdesc.Node.get_id node in
|
|
not (ReportedOnNodes.mem nid reported_so_far) )
|
|
preds
|
|
|
|
|
|
let map_cost get_node_nb_exec m : BasicCost.astate =
|
|
CostDomain.NodeInstructionToCostMap.fold
|
|
(fun ((node_id, _) as instr_node_id) c acc ->
|
|
let t = get_node_nb_exec node_id in
|
|
let c_node = BasicCost.mult c t in
|
|
let c_node' = BasicCost.plus acc c_node in
|
|
L.(debug Analysis Medium)
|
|
"@\n [AnalyzerWCET] Adding cost: (%a) --> c =%a t = %a @\n" InstrCFG.Node.pp_id
|
|
instr_node_id BasicCost.pp c BasicCost.pp t ;
|
|
L.(debug Analysis Medium)
|
|
"@\n [AnalyzerWCET] Adding cost: (%a) --> c_node=%a cost = %a @\n" InstrCFG.Node.pp_id
|
|
instr_node_id BasicCost.pp c_node BasicCost.pp c_node' ;
|
|
c_node' )
|
|
m BasicCost.zero
|
|
|
|
|
|
let exec_instr ((_, reported_so_far): Domain.astate) {ProcData.extras} (node: CFG.Node.t) instr
|
|
: Domain.astate =
|
|
let {basic_cost_map= invariant_map_cost; get_node_nb_exec; summary} = extras in
|
|
let cost_node =
|
|
let instr_node_id = CFG.Node.id node in
|
|
match AnalyzerNodesBasicCost.extract_post instr_node_id invariant_map_cost with
|
|
| Some node_map ->
|
|
L.(debug Analysis Medium)
|
|
"@\n [AnalyzerWCET] Final map for node: %a @\n" CFG.Node.pp_id instr_node_id ;
|
|
map_cost get_node_nb_exec node_map
|
|
| _ ->
|
|
assert false
|
|
in
|
|
L.(debug Analysis Medium)
|
|
"@\n[>>>AnalyzerWCET] Instr: %a Cost: %a@\n" (Sil.pp_instr Pp.text) instr BasicCost.pp
|
|
cost_node ;
|
|
let astate' =
|
|
let und_node = CFG.Node.underlying_node node in
|
|
let preds = Procdesc.Node.get_preds und_node in
|
|
let reported_so_far =
|
|
if
|
|
should_report_on_instr instr && should_report_on_node (und_node :: preds) reported_so_far
|
|
&& should_report_cost cost_node
|
|
then (
|
|
do_report summary (Sil.instr_get_loc instr) cost_node ;
|
|
let nid = Procdesc.Node.get_id und_node in
|
|
ReportedOnNodes.add nid reported_so_far )
|
|
else reported_so_far
|
|
in
|
|
(cost_node, reported_so_far)
|
|
in
|
|
astate'
|
|
|
|
|
|
let pp_session_name _node fmt = F.pp_print_string fmt "cost(wcet)"
|
|
end
|
|
|
|
module AnalyzerWCET = AbstractInterpreter.MakeNoCFG (InstrCFGScheduler) (TransferFunctionsWCET)
|
|
|
|
let check_and_report_infinity cost proc_desc summary =
|
|
if BasicCost.is_top cost then
|
|
let loc = Procdesc.get_start_node proc_desc |> Procdesc.Node.get_loc in
|
|
let message =
|
|
F.asprintf "The execution time of the function %a cannot be computed" Typ.Procname.pp
|
|
(Procdesc.get_proc_name proc_desc)
|
|
in
|
|
let exn =
|
|
Exceptions.Checkers (IssueType.infinite_execution_time_call, Localise.verbatim_desc message)
|
|
in
|
|
Reporting.log_error ~loc summary exn
|
|
|
|
|
|
let checker ({Callbacks.tenv; proc_desc} as callback_args) : Summary.t =
|
|
let inferbo_invariant_map, summary =
|
|
BufferOverrunChecker.compute_invariant_map_and_check callback_args
|
|
in
|
|
let node_cfg = NodeCFG.from_pdesc proc_desc in
|
|
(* collect all prune nodes that occur in loop guards, needed for ControlDepAnalyzer *)
|
|
let control_maps = Loop_control.get_control_maps node_cfg in
|
|
(* computes the control dependencies: node -> var set *)
|
|
let control_dep_invariant_map =
|
|
let proc_data = ProcData.make proc_desc tenv control_maps in
|
|
Control.ControlDepAnalyzer.exec_cfg node_cfg proc_data ~initial:Control.ControlDepSet.empty
|
|
~debug:true
|
|
in
|
|
let instr_cfg = InstrCFG.from_pdesc proc_desc in
|
|
let invariant_map_NodesBasicCost =
|
|
let proc_data = ProcData.make proc_desc tenv inferbo_invariant_map in
|
|
(*compute_WCET cfg invariant_map min_trees in *)
|
|
AnalyzerNodesBasicCost.exec_cfg instr_cfg proc_data ~initial:NodesBasicCostDomain.empty
|
|
~debug:true
|
|
in
|
|
(* given the semantics computes the upper bound on the number of times a node could be executed *)
|
|
let bound_map =
|
|
BoundMap.compute_upperbound_map node_cfg inferbo_invariant_map control_dep_invariant_map
|
|
in
|
|
let pp_extra, get_node_nb_exec =
|
|
let equalities = ConstraintSolver.collect_constraints node_cfg in
|
|
match solver with
|
|
| `MinTree ->
|
|
let min_trees =
|
|
let constraints = StructuralConstraints.compute_structural_constraints node_cfg in
|
|
MinTree.compute_trees_from_contraints bound_map node_cfg equalities constraints
|
|
in
|
|
let trees_valuation =
|
|
Node.IdMap.fold
|
|
(fun nid t acc ->
|
|
let res = MinTree.evaluate_tree t in
|
|
L.(debug Analysis Medium)
|
|
"@\n Tree %a eval to %a @\n" Node.pp_id nid BasicCost.pp res ;
|
|
Node.IdMap.add nid res acc )
|
|
min_trees Node.IdMap.empty
|
|
in
|
|
let pp_extra fmt = F.fprintf fmt "|Trees|=%i" (Node.IdMap.cardinal min_trees) in
|
|
let get_node_nb_exec node_id =
|
|
match Node.IdMap.find_opt node_id trees_valuation with
|
|
| Some t ->
|
|
t
|
|
| None ->
|
|
MinTree.find_tree_eq_rel node_id trees_valuation equalities
|
|
in
|
|
(pp_extra, get_node_nb_exec)
|
|
| `ConstraintSolver ->
|
|
let () = ConstraintSolver.compute_costs bound_map equalities in
|
|
let pp_extra _ = () in
|
|
(pp_extra, ConstraintSolver.get_node_nb_exec equalities)
|
|
in
|
|
let initWCET = (BasicCost.zero, ReportedOnNodes.empty) in
|
|
match
|
|
AnalyzerWCET.compute_post
|
|
(ProcData.make proc_desc tenv
|
|
{basic_cost_map= invariant_map_NodesBasicCost; get_node_nb_exec; summary})
|
|
~debug:true ~initial:initWCET
|
|
with
|
|
| Some (exit_cost, _) ->
|
|
L.internal_error
|
|
"@\n\
|
|
[COST ANALYSIS] PROCESSING MIN_TREE for PROCEDURE '%a' |CFG| = %i %t FINAL COST = %a @\n"
|
|
Typ.Procname.pp
|
|
(Procdesc.get_proc_name proc_desc)
|
|
(Container.length ~fold:NodeCFG.fold_nodes node_cfg)
|
|
pp_extra BasicCost.pp exit_cost ;
|
|
check_and_report_infinity exit_cost proc_desc summary ;
|
|
Payload.update_summary {post= exit_cost} summary
|
|
| None ->
|
|
if Procdesc.Node.get_succs (Procdesc.get_start_node proc_desc) <> [] then (
|
|
L.internal_error "Failed to compute final cost for function %a" Typ.Procname.pp
|
|
(Procdesc.get_proc_name proc_desc) ;
|
|
summary )
|
|
else summary
|