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 21 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
46 changes: 46 additions & 0 deletions src/util/cilfacade.ml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,51 @@ let end_basic_blocks f =
let thisVisitor = new allBBVisitor in
visitCilFileSameGlobals thisVisitor f

class removeLabelsVisitor = object
inherit nopCilVisitor

val nestingDepth = ref 0
michael-schwarz marked this conversation as resolved.
Show resolved Hide resolved

method! vstmt s =
let new_labels = List.filter_map (function Label(str,loc,b) -> None | x -> Some x) s.labels in
match s.skind with
| Loop _ -> nestingDepth := !nestingDepth+1; ChangeDoChildrenPost({s with labels = new_labels}, fun x -> nestingDepth := !nestingDepth-1; x)
| Continue loc -> if !nestingDepth = 0 then
ChangeTo({s with labels = new_labels; skind = Break loc})
michael-schwarz marked this conversation as resolved.
Show resolved Hide resolved
else
ChangeTo({s with labels = new_labels})
| _ -> ChangeDoChildrenPost({s with labels = new_labels}, Fun.id)
end

class loopUnrollingVisitor = object
(* Labels are simply handled by removing them. It is always correct to simply jump into the non-unrolled part of the loop! *)
(* The price we need to pay for this simpler handling of labels and still wanting to benefit from unrolling nested loops is that *)
(* we need to put our unrolled code into fake while(1) loops such that breaks have something to break out of, add artificial breaks *)
(* at the end of loops, and replacing (top level!) continues with breaks. *)
(* This is still a lot more intuitive than complicated handling of gotos as would be required when doing it after the removal of breaks *)
inherit nopCilVisitor

method! vstmt s =
let labelsRemover = new removeLabelsVisitor in
michael-schwarz marked this conversation as resolved.
Show resolved Hide resolved
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) ->
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
let bstmts = b.bstmts @ [mkStmt @@ Break loc] in
let copies = List.init (factor) (fun _ -> visitCilStmt labelsRemover (mkStmt (Loop (mkBlock bstmts,loc,loc2,None,None)))) in
michael-schwarz marked this conversation as resolved.
Show resolved Hide resolved
mkStmt (Block (mkBlock (copies@[s])))
| _ -> failwith "invariant broken"
in
ChangeDoChildrenPost({s with sid = s.sid},duplicate_and_rem_labels)
| _ -> DoChildren
end

let loop_unrolling f =
let thisVisitor = new loopUnrollingVisitor in
visitCilFileSameGlobals thisVisitor f

let visitors = ref []
let register_preprocess name visitor_fun =
Expand All @@ -94,6 +139,7 @@ let do_preprocess ast =
iterGlobals ast (function GFun (fd,_) -> List.iter (f fd) !visitors | _ -> ())

let createCFG (fileAST: file) =
if (get_int "exp.unrolling-factor")>0 then loop_unrolling fileAST;
(* The analyzer keeps values only for blocks. So if you want a value for every program point, each instruction *)
(* needs to be in its own block. end_basic_blocks does that. *)
(* After adding support for VLAs, there are new VarDecl instructions at the point where a variable was declared and *)
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;
}
13 changes: 13 additions & 0 deletions tests/regression/55-loop-unrolling/02-break.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
int main(void) {
int r=5;
for (int i = 0; i < 2; ++i) {
switch (i) {
case 0:
break;
case 1:
r = 8;
}
}

return 0;
}