Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loop Unrolling for the first exp.unrolling-factor Iterations #563

Merged
merged 26 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
acb0b7e
loop unrolling option implemented
mikcp Jan 21, 2022
44def90
Merge branch 'master' into unrolling
mikcp Jan 21, 2022
84d94f2
Comment explaining reason behind replicated stmts
mikcp Jan 25, 2022
7456571
add_label_to_remainder_loop does not remove previous labels of st_loo…
mikcp Jan 25, 2022
29bc829
is_remainder_loop recurses all labels of the stmt
mikcp Jan 25, 2022
26b97ab
redundant check removed
mikcp Jan 25, 2022
e37c29d
by calling the unrolling visitor after preparedCFG, breaks and contin…
mikcp Jan 25, 2022
8a7fc15
basic tests added
mikcp Jan 26, 2022
bdbb726
test to add continue support failed
mikcp Jan 28, 2022
6643236
support for continue instructions added
mikcp Jan 31, 2022
db5da87
Revert "by calling the unrolling visitor after preparedCFG, breaks an…
mikcp Jan 31, 2022
b6f3fd7
Merge branch 'old-version-unrolling' into unrolling
mikcp Jan 31, 2022
99b382e
extra tests deleted
mikcp Jan 31, 2022
5e03d86
Merge branch 'master' into unrolling
michael-schwarz Feb 16, 2022
62a161d
Merge branch 'master' into unrolling
michael-schwarz Mar 12, 2022
72fab58
Add failing example
michael-schwarz Mar 12, 2022
bf5a3fa
Some clenaup
michael-schwarz Mar 12, 2022
10f7e8c
fix example
michael-schwarz Mar 12, 2022
930fae8
Alternative loop unrolling
michael-schwarz Mar 12, 2022
ac9ed15
fix comments
michael-schwarz Mar 12, 2022
4975cf1
fix comments
michael-schwarz Mar 12, 2022
44b162f
add failing test
michael-schwarz Mar 14, 2022
29b1714
One last(?) rewrite
michael-schwarz Mar 14, 2022
cdc512c
typo
michael-schwarz Mar 14, 2022
3acce11
Make Hashtable for patching gotos less fragile
michael-schwarz Mar 15, 2022
3f6e3b8
add test with unrolled array
michael-schwarz Mar 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions src/util/cilfacade.ml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,118 @@ let end_basic_blocks f =
let thisVisitor = new allBBVisitor in
visitCilFileSameGlobals thisVisitor f

(* replaces goto s with goto s' when newtarget s = Some s' IN PLACE *)
class patchLabelsGotosVisitor(newtarget) = object
inherit nopCilVisitor

method! vstmt s =
match s.skind with
| Goto (target,loc) ->
(match newtarget !target with
| None -> SkipChildren
| Some nt -> s.skind <- Goto (ref nt, loc); DoChildren)
| _ -> DoChildren
end

(* Hashtable used to patch gotos later *)
module StatementHashTable = Hashtbl.Make(struct
type t = stmt
(* Identity by physical equality. *)
let equal = (==)
let hash = Hashtbl.hash
end)

(*
Makes a copy, replacing top-level breaks with goto loopEnd and top-level continues with
goto currentIterationEnd
Also assigns fresh names to all labels and patches gotos for labels appearing in the current
fragment to their new name
*)
class copyandPatchLabelsVisitor(loopEnd,currentIterationEnd) = object
inherit nopCilVisitor

val mutable depth = 0
val mutable loopNestingDepth = 0

val gotos = StatementHashTable.create 20

method! vstmt s =
let after x =
depth <- depth-1;
if depth = 0 then
(* the labels can only be patched once the entire part of the AST we want has been transformed, and *)
(* we know all lables appear in the hash table *)
let patchLabelsVisitor = new patchLabelsGotosVisitor(StatementHashTable.find_opt gotos) in
let x = visitCilStmt patchLabelsVisitor x in
StatementHashTable.clear gotos;
x
else
x
in
let rename_labels sn =
let new_labels = List.map (function Label(str,loc,b) -> Label (Cil.freshLabel str,loc,b) | x -> x) sn.labels in
(* this makes new physical copy*)
let new_s = {sn with labels = new_labels} in
if new_s.labels <> [] then
(* Use original s, ns might be temporay e.g. if the type of statement changed *)
(* record that goto s; appearing in the current fragment should later be patched to goto new_s *)
StatementHashTable.add gotos s new_s;
new_s
in
depth <- depth+1;
match s.skind with
| Continue loc ->
if loopNestingDepth = 0 then
(* turn top-level continues into gotos to end of current unrolling *)
ChangeDoChildrenPost(rename_labels {s with skind = Goto (!currentIterationEnd, loc)}, after)
else
ChangeDoChildrenPost(rename_labels s, after)
| Break loc ->
if loopNestingDepth = 0 then
(* turn top-level breaks into gotos to end of current unrolling *)
ChangeDoChildrenPost(rename_labels {s with skind = Goto (loopEnd,loc)}, after)
else
ChangeDoChildrenPost(rename_labels s, after)
| Loop _ -> loopNestingDepth <- loopNestingDepth+1;
ChangeDoChildrenPost(rename_labels s, fun x -> loopNestingDepth <- loopNestingDepth-1; after x)
| _ -> ChangeDoChildrenPost(rename_labels s, after)
end

class loopUnrollingVisitor = object
(* Labels are simply handled by giving them a fresh name. Jumps coming from outside will still always go to the original label! *)
inherit nopCilVisitor

method! vstmt s =
match s.skind with
| Loop (b,loc, loc2, break , continue) ->
let duplicate_and_rem_labels s =
match s.skind with
| Loop (b,loc, loc2, break , continue) ->
(* We copy the statement to later be able to modify it without worrying about invariants *)
let s = { s with sid = s.sid } in
michael-schwarz marked this conversation as resolved.
Show resolved Hide resolved
let factor = GobConfig.get_int "exp.unrolling-factor" in
(* top-level breaks should immediately go to the end of the loop, and not just break out of the current iteration *)
let break_target = { (Cil.mkEmptyStmt ()) with labels = [Label (Cil.freshLabel "loop_end",loc, true)]} in
(* continues should go to the next unrolling *)
let continue_target i = { (Cil.mkEmptyStmt ()) with labels = [Label (Cil.freshLabel ("loop_continue_" ^ (string_of_int i)),loc, true)]} in
(* passed as a reference so we can reuse the patcher for all unrollings of the current loop *)
let current_continue_target = ref dummyStmt in
let patcher = new copyandPatchLabelsVisitor (ref break_target, ref current_continue_target) in
let one_copy () = visitCilStmt patcher (mkStmt (Block (mkBlock b.bstmts))) in
let copies = List.init (factor) (fun i ->
current_continue_target := continue_target i;
mkStmt (Block (mkBlock [one_copy (); !current_continue_target])))
in
mkStmt (Block (mkBlock (copies@[s]@[break_target])))
| _ -> failwith "invariant broken"
in
ChangeDoChildrenPost({s with sid = s.sid},duplicate_and_rem_labels)
| _ -> DoChildren
end

let loop_unrolling fd =
let thisVisitor = new loopUnrollingVisitor in
ignore (visitCilFunction thisVisitor fd)

let visitors = ref []
let register_preprocess name visitor_fun =
Expand Down Expand Up @@ -110,6 +222,8 @@ let createCFG (fileAST: file) =
iterGlobals fileAST (fun glob ->
match glob with
| GFun(fd,_) ->
(* before prepareCfg so continues still appear as such *)
if (get_int "exp.unrolling-factor")>0 then loop_unrolling fd;
prepareCFG fd;
computeCFGInfo fd true
| _ -> ()
Expand Down
7 changes: 7 additions & 0 deletions src/util/options.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,13 @@
"Path to C preprocessor (cpp) to use. If empty, then automatically searched.",
"type": "string",
"default": ""
},
"unrolling-factor": {
"title": "exp.unrolling-factor",
"description":
"Sets the unrolling factor for the loopUnrollingVisitor.",
"type": "integer",
"default": 0
}
},
"additionalProperties": false
Expand Down
194 changes: 194 additions & 0 deletions tests/regression/55-loop-unrolling/01-simple-cases.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// PARAM: --set solver td3 --enable ana.int.interval --set exp.unrolling-factor 5
int global;

int main(void)
{
example1();
example2();
example3();
example4();
example5();
example6();
example7();
example8();
example9();
example10();
return 0;
}

// Simple example
void example1(void)
{
int a[5];
int i = 0;

while (i < 5) {
a[i] = i;
i++;
}

assert(a[0] == 0); // UNKNOWN
assert(a[3] == 3); // UNKNOWN
}

// Do-while loop simple example
void example2(void)
{
int a[5];
int i = 0;

do {
a[i] = i;
i++;
} while (i<=5);

assert(a[0] == 0); // UNKNOWN
assert(a[3] == 3); // UNKNOWN
}

// Initialization not completed, yet the array representation is not precise
void example3(void)
{
int a[10];
int i = 0;

while (i < 5) {
a[i] = i;
i++;
}

assert(a[0] == 0); // UNKNOWN
assert(a[3] == 0); // UNKNOWN
assert(a[7] == 0); // UNKNOWN
}

// Example with increased precision. Goblint detects in which iteration it is during the unrolled part.
void example4(void)
{
int a[10];
int i = 0;
int first_iteration = 1;

while (i < 10) {
if (first_iteration == 1) assert(i==0);
else if (i > 5) assert(i == 6); // UNKNOWN
first_iteration = 0;
a[i] = 0;
i++;
}

assert(a[0] == 0);
assert(first_iteration == 0);
}


// Example where the precision increase can be appreciated by a variable that
// is modified in the loop other than the ones used in the loop head
void example5(void)
{
int a[4];
int i = 0;
int top = 0;

while (i < 4) {
a[i] = 0;
top += i;
if(i==2){
assert(top == 3);
}
else{
assert(top == 3); // FAIL
}
i++;
}

assert(a[0] == 0);
assert(a[3] == 0);
assert(top == 6);
}

// Loop has less iterations than the unrolling factor
void example6(void)
{
int a[5];
int i = 0;
int top = 0;

while (i < 3) {
a[i] = 0;
assert(a[0]==0);
i++;
}

assert(a[0] == 0);
assert(a[3] == 0);
assert(top == 6); // FAIL
}

// There is a call on the loop's condition
int update(int i) {
if (i>5){
return 0;
}
else{
return 1;
}
}
void example7(void)
{
int a[10];
int i = 0;
while(update(i)){
a[i] = i;
++i;
}
assert(a[0] == 0); //UNKNOWN
assert(a[6] == 0); //UNKNOWN
}

// nested loops
void example8(void)
{
int a[5];
int b[] = {0,0,0,0,0};
int i = 0;
while(i < 5){
a[i] = i;
int j = 0;
while(j < 5){
b[j] += a[i];
++j;
}
++i;
}
return 0;
}

// example with loop like the ones CIL does internally (while(1) + break)
void example9(void)
{
int a[5];
int i = 0;
while(1){
a[i] = i;
++i;
if (i == 5) break;
}
return 0;
}

// example with loop containing a "continue" instruction
void example10(void)
{
int a[5];
int i = 0;
while(i<5){
if (i == 3) {
i++;
continue;
}
a[i] = i;
++i;
}
return 0;
}
17 changes: 17 additions & 0 deletions tests/regression/55-loop-unrolling/02-break.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// PARAM: --set exp.unrolling-factor 5
int main(void) {
int r=5;
for (int i = 0; i < 2; ++i) {
switch (i) {
case 0:
break;
case 1:
r = 8;
}
r = 17;
break;
}

assert(r==17);
return 0;
}
18 changes: 18 additions & 0 deletions tests/regression/55-loop-unrolling/03-break-right-place.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// PARAM: --set exp.unrolling-factor 5
#include<assert.h>

int main(void) {
int i = 0;
int j = 0;

while(i< 17) {
if (j==0) {
j = 1;
// the break is not just out of this unrolling, but out of the entire loop!
break;
}
i++;
}

assert(i == 0);
}
15 changes: 15 additions & 0 deletions tests/regression/55-loop-unrolling/04-simple.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// PARAM: --set solver td3 --enable ana.int.interval --set exp.unrolling-factor 5 --set ana.base.arrays.domain unroll --set ana.base.arrays.unrolling-factor 5
// Simple example
void main(void)
{
int a[5];
int i = 0;

while (i < 5) {
a[i] = i;
i++;
}

assert(a[0] == 0);
assert(a[3] == 3);
}
Loading