Reviewed By: mbouaziz Differential Revision: D9460455 fbshipit-source-id: 7488c1a69master
parent
66ef5f5c15
commit
49e582fa49
@ -0,0 +1,33 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2018-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
|
||||||
|
open Graph
|
||||||
|
module L = Logging
|
||||||
|
module CFG = ProcCfg.Normal
|
||||||
|
(* Use ocamlgraph's dominators functor to get the dominators *)
|
||||||
|
module GDoms = Dominator.Make (ProcCfg.MakeOcamlGraph (CFG))
|
||||||
|
|
||||||
|
let print_dominators cfg idom =
|
||||||
|
L.(debug Analysis Medium) "@\n ----- **** Dominators TABLE **** -------@\n" ;
|
||||||
|
Procdesc.get_nodes cfg
|
||||||
|
|> List.iter ~f:(fun n ->
|
||||||
|
L.(debug Analysis Medium) "@\n Node: %a Dominators:\n" Procdesc.Node.pp n ;
|
||||||
|
List.iter
|
||||||
|
~f:(L.(debug Analysis Medium) "%a;" Procdesc.Node.pp)
|
||||||
|
(GDoms.idom_to_dominators idom n) )
|
||||||
|
|
||||||
|
|
||||||
|
(* Computes the dominator tree, using ocamlgraph's Lengauer-Tarjan algorithm.
|
||||||
|
[compute_idom cfg node] returns a function [idom : Procdesc.Node.t -> Procdesc.Node.t] s.t.
|
||||||
|
[idom n] returns the immediate dominator of [n]. *)
|
||||||
|
let get_idoms pdesc =
|
||||||
|
let idom = GDoms.compute_idom pdesc (ProcCfg.Normal.start_node pdesc) in
|
||||||
|
print_dominators pdesc idom ; idom
|
||||||
|
|
||||||
|
|
||||||
|
(* make each node to be dominated by itself, i.e reflexive, unlike ocamlgraph *)
|
||||||
|
let dominates idom x y = GDoms.idom_to_dom idom x y || Procdesc.Node.equal x y
|
@ -0,0 +1,104 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2018-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 InstrCFG = ProcCfg.NormalOneInstrPerNode
|
||||||
|
|
||||||
|
module Call = struct
|
||||||
|
type t = {pname: Typ.Procname.t; loc: Location.t} [@@deriving compare]
|
||||||
|
|
||||||
|
let pp fmt {pname; loc} =
|
||||||
|
F.fprintf fmt "loop-invariant call to %a, at %a " Typ.Procname.pp pname Location.pp loc
|
||||||
|
end
|
||||||
|
|
||||||
|
module LoopNodes = AbstractDomain.FiniteSet (Procdesc.Node)
|
||||||
|
module HoistCalls = AbstractDomain.FiniteSet (Call)
|
||||||
|
|
||||||
|
(** Map loop_header -> instrs that can be hoisted out of the loop *)
|
||||||
|
module LoopHeadToHoistInstrs =
|
||||||
|
Procdesc.NodeMap
|
||||||
|
|
||||||
|
(* A loop-invariant function call C(args) at node N can be hoisted out of the loop if
|
||||||
|
*
|
||||||
|
* 1. C is guaranteed to execute, i.e. N dominates all loop sources
|
||||||
|
* 2. args are loop invariant *)
|
||||||
|
|
||||||
|
let add_if_hoistable inv_vars instr node source_nodes idom hoistable_calls =
|
||||||
|
match instr with
|
||||||
|
| Sil.Call ((ret_id, _), Exp.Const (Const.Cfun pname), _, loc, _)
|
||||||
|
when (* Check condition (1); N dominates all loop sources *)
|
||||||
|
List.for_all ~f:(fun source -> Dominators.dominates idom node source) source_nodes
|
||||||
|
&& (* Check condition (2); id should be invariant already *)
|
||||||
|
LoopInvariant.InvariantVars.mem (Var.of_id ret_id) inv_vars ->
|
||||||
|
HoistCalls.add {pname; loc} hoistable_calls
|
||||||
|
| _ ->
|
||||||
|
hoistable_calls
|
||||||
|
|
||||||
|
|
||||||
|
let get_hoistable_calls inv_vars loop_nodes source_nodes idom =
|
||||||
|
LoopNodes.fold
|
||||||
|
(fun node hoist_calls ->
|
||||||
|
let instr_in_node = Procdesc.Node.get_instrs node in
|
||||||
|
Instrs.fold ~init:hoist_calls
|
||||||
|
~f:(fun acc instr -> add_if_hoistable inv_vars instr node source_nodes idom acc)
|
||||||
|
instr_in_node )
|
||||||
|
loop_nodes HoistCalls.empty
|
||||||
|
|
||||||
|
|
||||||
|
let get_hoist_inv_map tenv reaching_defs_invariant_map loop_head_to_source_nodes idom =
|
||||||
|
Procdesc.NodeMap.fold
|
||||||
|
(fun loop_head source_nodes inv_map ->
|
||||||
|
(* get all the nodes in the loop *)
|
||||||
|
let loop_nodes = Loop_control.get_all_nodes_upwards_until loop_head source_nodes in
|
||||||
|
let inv_vars_in_loop =
|
||||||
|
LoopInvariant.get_inv_vars_in_loop tenv reaching_defs_invariant_map loop_head loop_nodes
|
||||||
|
~is_inv_by_default:true
|
||||||
|
in
|
||||||
|
let hoist_instrs = get_hoistable_calls inv_vars_in_loop loop_nodes source_nodes idom in
|
||||||
|
LoopHeadToHoistInstrs.add loop_head hoist_instrs inv_map )
|
||||||
|
loop_head_to_source_nodes LoopHeadToHoistInstrs.empty
|
||||||
|
|
||||||
|
|
||||||
|
let do_report summary Call.({pname; loc}) loop_head_loc =
|
||||||
|
let exp_desc =
|
||||||
|
F.asprintf "Loop-invariant call to %a at %a" Typ.Procname.pp pname Location.pp loc
|
||||||
|
in
|
||||||
|
let ltr = [Errlog.make_trace_element 0 loc exp_desc []] in
|
||||||
|
let exn =
|
||||||
|
let message =
|
||||||
|
F.asprintf "%s can be moved out of the loop at %a." exp_desc Location.pp loop_head_loc
|
||||||
|
in
|
||||||
|
Exceptions.Checkers (IssueType.invariant_call, Localise.verbatim_desc message)
|
||||||
|
in
|
||||||
|
Reporting.log_error summary ~loc ~ltr exn
|
||||||
|
|
||||||
|
|
||||||
|
let checker {Callbacks.tenv; summary; proc_desc} : Summary.t =
|
||||||
|
let cfg = InstrCFG.from_pdesc proc_desc in
|
||||||
|
let proc_data = ProcData.make_default proc_desc tenv in
|
||||||
|
(* computes reaching defs: node -> (var -> node set) *)
|
||||||
|
let reaching_defs_invariant_map =
|
||||||
|
ReachingDefs.Analyzer.exec_cfg cfg proc_data
|
||||||
|
~initial:(ReachingDefs.init_reaching_defs_with_formals proc_desc)
|
||||||
|
~debug:false
|
||||||
|
in
|
||||||
|
(* get dominators *)
|
||||||
|
let idom = Dominators.get_idoms proc_desc in
|
||||||
|
let loop_head_to_source_nodes = Loop_control.get_loop_head_to_source_nodes cfg in
|
||||||
|
(* get a map, loop head -> instrs that can be hoisted out of the loop *)
|
||||||
|
let loop_head_to_inv_instrs =
|
||||||
|
get_hoist_inv_map tenv reaching_defs_invariant_map loop_head_to_source_nodes idom
|
||||||
|
in
|
||||||
|
(* report function calls to hoist (per loop) *)
|
||||||
|
(* Note: we report the innermost loop for hoisting out. TODO: Future
|
||||||
|
optimization, take out as further up as possible.*)
|
||||||
|
LoopHeadToHoistInstrs.iter
|
||||||
|
(fun loop_head inv_instrs ->
|
||||||
|
let loop_head_loc = Procdesc.Node.get_loc loop_head in
|
||||||
|
HoistCalls.iter (fun call -> do_report summary call loop_head_loc) inv_instrs )
|
||||||
|
loop_head_to_inv_instrs ;
|
||||||
|
summary
|
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-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.
|
||||||
|
*/
|
||||||
|
class Hoist {
|
||||||
|
|
||||||
|
int foo(int x, int y) {
|
||||||
|
return x + y;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bar(int x) {
|
||||||
|
return 3 * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// will only report one even though both calls are invariant, due to
|
||||||
|
// location clashing (no column information)
|
||||||
|
void clash_function_calls_hoist(int size) {
|
||||||
|
int x = 10;
|
||||||
|
int y = 5;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
foo(x, foo(y, x + y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// will report both
|
||||||
|
void two_function_call_hoist(int size) {
|
||||||
|
int x = 10;
|
||||||
|
int y = 5;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
foo(x, bar(y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// it is ok to move fun call to a temp. var
|
||||||
|
void reassigned_temp_hoist(int size) {
|
||||||
|
int x = 10;
|
||||||
|
int y = 5;
|
||||||
|
int d = 0;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
d = foo(x, y);
|
||||||
|
d = 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// it is ok to just hoist function call into a temp var.
|
||||||
|
void used_in_loop_body_before_def_temp_hoist(int size, int[] M) {
|
||||||
|
int x = 10;
|
||||||
|
int y = 5;
|
||||||
|
int d = 20;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
M[i] = d;
|
||||||
|
d = foo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop_guard_hoist(int size, int[] M) {
|
||||||
|
int x = 10;
|
||||||
|
int d = 0;
|
||||||
|
int y = 5;
|
||||||
|
for (int i = 0; i < foo(d, size); i++) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
void legit_hoist(int size, int[] M) {
|
||||||
|
int x = 10;
|
||||||
|
int y = 5;
|
||||||
|
int d = 0;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
d = foo(x, x + y);
|
||||||
|
M[i] = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// x is not invariant, hence d won't be invariant as well
|
||||||
|
void dep_not_invariant_dont_hoist(int size, int[] M) {
|
||||||
|
int x = 10;
|
||||||
|
int y = 5;
|
||||||
|
int d = 20;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
if (i > 3) {
|
||||||
|
x = 3;
|
||||||
|
}
|
||||||
|
d = foo(x, y); // shouldn't be hoisted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// foo(x) is not guaranteed to execute
|
||||||
|
void not_guaranteed_to_execute_dont_hoist(int size, int x, int y) {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (i < size) {
|
||||||
|
if (i > 3) {
|
||||||
|
foo(x, y);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void x_not_invariant_dont_hoist(int size, int x, int y) {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (i < size) {
|
||||||
|
x = foo(x, y) + y;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void array_store_hoist(int size, int[] M) {
|
||||||
|
int x = 10;
|
||||||
|
int y = 5;
|
||||||
|
int d = 0;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
M[i] = foo(x, x + y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// y = ... can be taken out of the inner loop
|
||||||
|
void nested_loop_hoist(int size, int x, int y) {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (i < size) {
|
||||||
|
for (int j = 0; i < j; j++) {
|
||||||
|
y = foo(i, x);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// j is not invariant, y can't be taken out
|
||||||
|
void nested_loop_dont_hoist(int size, int x, int y) {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (i < size) {
|
||||||
|
for (int j = 0; j < i; j++) {
|
||||||
|
y = foo(j, x);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
# Copyright (c) 2018-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.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
ANALYZER = checkers
|
||||||
|
INFER_OPTIONS = --loop-hoisting-only --debug-exceptions
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests
|
||||||
|
SOURCES = $(wildcard *.java)
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/javac.make
|
@ -0,0 +1,9 @@
|
|||||||
|
codetoanalyze/java/hoisting/Hoist.java, Hoist.array_store_hoist(int,int[]):void, 5, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 115]
|
||||||
|
codetoanalyze/java/hoisting/Hoist.java, Hoist.clash_function_calls_hoist(int):void, 4, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 23]
|
||||||
|
codetoanalyze/java/hoisting/Hoist.java, Hoist.legit_hoist(int,int[]):void, 5, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 71]
|
||||||
|
codetoanalyze/java/hoisting/Hoist.java, Hoist.loop_guard_hoist(int,int[]):void, 4, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 63]
|
||||||
|
codetoanalyze/java/hoisting/Hoist.java, Hoist.nested_loop_hoist(int,int,int):void, 5, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 126]
|
||||||
|
codetoanalyze/java/hoisting/Hoist.java, Hoist.reassigned_temp_hoist(int):void, 5, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 43]
|
||||||
|
codetoanalyze/java/hoisting/Hoist.java, Hoist.two_function_call_hoist(int):void, 4, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 32]
|
||||||
|
codetoanalyze/java/hoisting/Hoist.java, Hoist.two_function_call_hoist(int):void, 4, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.bar(int) at line 32]
|
||||||
|
codetoanalyze/java/hoisting/Hoist.java, Hoist.used_in_loop_body_before_def_temp_hoist(int,int[]):void, 6, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 55]
|
Loading…
Reference in new issue