From bfe2caf92d7bfd87d7ef2c5100628b865b97bb87 Mon Sep 17 00:00:00 2001 From: Qianyi Shu Date: Wed, 1 Jul 2020 02:06:32 -0700 Subject: [PATCH] [cost] log top cost function Summary: Following from previous diff. **Idea** - 80% of functions with Top cost are caused by calling top-costed callees, i.e. callee's Top cost is simply propagated to its transitive callers, so the aim is to investigate such root callees along with the number of their transitive callers. Consider the following code ``` void bar1() { // top cost function } void bar2() { // another top cost function } void baz(){ // baz have top cost because of bar bar1(); } void foo() { // goo have top cost because of baz baz(); bar2() } ``` Clearly, the root cause of the foo being top cost is `bar1` and `bar2`. 1. When we are analyzing `baz`, we know that it calls `bar1`, which is top cost, so we record that `baz = { T, bar1 } `. 2. Now, say we are analyzing foo. When we analyze the call to `baz`, we found out that the top cost of `baz` is caused by `bar1`, so we record `foo = { T, bar1 }`. When we analyze the call to `bar2`, we know that `bar2` is top cost, but since at this stage we only want to deal with the first top cost function we met, so we ignore it. Since we are keeping track of top cost function by examining the `Call` instruction, we would expect to see two log of `bar1` in the result. The test plan confirms it. Reviewed By: ezgicicek Differential Revision: D22231457 fbshipit-source-id: 45d48e4a7 --- infer/src/cost/cost.ml | 28 ++++++++++++++++++++++------ infer/src/cost/costDomain.ml | 8 ++++++++ infer/src/cost/costDomain.mli | 2 ++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/infer/src/cost/cost.ml b/infer/src/cost/cost.ml index a24ccbc82..517a27a96 100644 --- a/infer/src/cost/cost.ml +++ b/infer/src/cost/cost.ml @@ -84,10 +84,18 @@ module InstrBasicCostWithReason = struct CostDomain.of_operation_cost (model model_env ~ret inferbo_mem) | None -> ( match Tenv.get_summary_formals tenv ~get_summary ~get_formals callee_pname with - | `Found ({CostDomain.post= callee_cost_record}, callee_formals) -> - CostDomain.map callee_cost_record ~f:(fun callee_cost -> - instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem - ~callee_pname ~callee_formals ~params ~callee_cost ~loc ) + | `Found ({CostDomain.post= callee_cost_record}, callee_formals) -> ( + let instantiated_cost = + CostDomain.map callee_cost_record ~f:(fun callee_cost -> + instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem + ~callee_pname ~callee_formals ~params ~callee_cost ~loc ) + in + match CostDomain.get_operation_cost callee_cost_record with + | {cost; top_pname_opt= None} when BasicCost.is_top cost -> + CostDomain.add_top_pname_opt CostKind.OperationCost instantiated_cost + (Some callee_pname) + | _ -> + instantiated_cost ) | `FoundFromSubclass (callee_pname, {CostDomain.post= callee_cost_record}, callee_formals) -> (* Note: It ignores top cost of subclass to avoid its propagations. *) @@ -178,8 +186,16 @@ module WorstCaseCost = struct let compute tenv extras cfg = let init = CostDomain.zero_record in - InstrCFG.fold_nodes cfg ~init ~f:(fun acc pair -> - exec_node tenv extras pair |> CostDomain.plus acc ) + let cost = + InstrCFG.fold_nodes cfg ~init ~f:(fun acc pair -> + exec_node tenv extras pair |> CostDomain.plus acc ) + in + Option.iter (CostDomain.get_operation_cost cost).top_pname_opt ~f:(fun top_pname -> + ScubaLogging.log_message ~label:"unmodeled_function_top_cost" + ~message:(F.asprintf "Unmodeled Function[Top Cost] : %a" Procname.pp top_pname) ; + Logging.(debug Analysis Verbose) + "@ Unmodeled Function[Top Cost]: %a@\n" Procname.pp top_pname ) ; + cost end let is_report_suppressed pname = diff --git a/infer/src/cost/costDomain.ml b/infer/src/cost/costDomain.ml index 6c8e9efdc..02fcee964 100644 --- a/infer/src/cost/costDomain.ml +++ b/infer/src/cost/costDomain.ml @@ -31,6 +31,8 @@ module BasicCostWithReason = struct {record with cost= BasicCost.subst callee_pname location record.cost eval_sym} + (* When we fold the nodes while traversing the cfg, + make sure we only keep the first top cost callee we see *) let plus record1 record2 = { cost= BasicCost.plus record1.cost record2.cost ; top_pname_opt= Option.first_some record1.top_pname_opt record2.top_pname_opt } @@ -78,6 +80,12 @@ let pp_summary fmt {post} = F.fprintf fmt "@\n Post: %a @\n" VariantCostMap.pp p let get_cost_kind kind cost_record = VariantCostMap.get kind cost_record +let add_top_pname_opt kind cost_record top_pname_opt = + VariantCostMap.update kind + (function Some cost_with_reason -> Some {cost_with_reason with top_pname_opt} | _ -> None) + cost_record + + let get_operation_cost cost_record = get_cost_kind CostKind.OperationCost cost_record let map ~f cost_record = VariantCostMap.map f cost_record diff --git a/infer/src/cost/costDomain.mli b/infer/src/cost/costDomain.mli index f0b7658b8..6b0095b8d 100644 --- a/infer/src/cost/costDomain.mli +++ b/infer/src/cost/costDomain.mli @@ -58,6 +58,8 @@ val pp_summary : F.formatter -> summary -> unit val get_cost_kind : CostKind.t -> t -> BasicCostWithReason.t +val add_top_pname_opt : CostKind.t -> t -> Procname.t option -> t + val get_operation_cost : t -> BasicCostWithReason.t val map : f:(BasicCostWithReason.t -> BasicCostWithReason.t) -> t -> t