Skip to content

Commit ac982ca

Browse files
SaswatPadhiLong Pham
and
Long Pham
committed
Documentation for decreases clauses
Co-authored-by: Long Pham <[email protected]>
1 parent 578791e commit ac982ca

File tree

3 files changed

+195
-24
lines changed

3 files changed

+195
-24
lines changed
Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,172 @@
1-
# Decreases Clause
1+
# Decreases Clauses
22

3-
TODO: Document `__CPROVER_decreases`
3+
A _decreases_ clause specifies a measure that must strictly decrease at every iteration of a loop.
4+
By demonstrating that the measure
5+
6+
1. is bounded from below, and
7+
2. strictly decreases at each iteration
8+
9+
we can prove termination of loops.
10+
This is because the measure must eventually hit the lower bound
11+
at which point the loop must terminate,
12+
since the measure cannot strictly decrease further.
13+
This technique for proving termination was proposed by Robert Floyd,
14+
and interested readers may refer to his seminal paper
15+
"[_Assigning Meaning to Programs_](https://people.eecs.berkeley.edu/~necula/Papers/FloydMeaning.pdf)".
16+
17+
### Syntax
18+
19+
A one-dimensional (1D) decreases clause for a loop is an arithmetic expression `e`
20+
over the variables visible at the same scope as the loop,
21+
specified as `__CPROVER_decreases(e)`.
22+
23+
Like invariant clauses, decreases clauses may be specified just after the loop guard.
24+
An example of a 1D decreases clause is shown below.
25+
26+
```c
27+
for(int i = 0; i < n; i += 2)
28+
__CPROVER_loop_invariant(0 <= i && i <= n)
29+
__CPROVER_decreases(n - i)
30+
{ ... }
31+
```
32+
33+
Please see the [invariant clauses](contracts-invariants.md) page
34+
for more examples on `for` and `do...while` loops.
35+
36+
To help prove termination of more complex loops,
37+
CBMC also supports multi-dimensional decreases clauses.
38+
A multi-dimensional decreases clause is an [ordered tuple](https://en.wikipedia.org/wiki/Tuple)
39+
of arithmetic expressions, specified as `__CPROVER_decreases(e_1, e_2, ..., e_n)`.
40+
An example of a multi-dimensional decreases clause is given below.
41+
42+
```c
43+
while(i < n)
44+
__CPROVER_loop_invariant(0 <= i && i <= n)
45+
__CPROVER_loop_invariant(0 <= j && j <= n)
46+
__CPROVER_decreases(n - i, n - j)
47+
{
48+
if (j < n)
49+
j++;
50+
else
51+
{
52+
i++;
53+
j = 0;
54+
}
55+
}
56+
```
57+
58+
We extend the strict arithmetic comparison for 1D decreases clauses
59+
to a strict [lexicographic comparison](https://en.wikipedia.org/wiki/Lexicographic_order)
60+
for multi-dimensional decreases clauses.
61+
62+
**Important.**
63+
Like invariant clauses, decreases clauses must be free of side effects,
64+
for example, mutation of local or global variables.
65+
Otherwise, CBMC raises an error message during compilation:
66+
```
67+
Decreases clause is not side-effect free. (at: file main.c line 4 function main)
68+
```
69+
70+
### Semantics
71+
72+
A decreases clause extends the loop abstraction introduced in the [invariants clause](contracts-invariants.md) manual.
73+
In addition to the inductiveness check asserted at the end of a single arbitrary iteration,
74+
CBMC would also assert the strict decrement of the measure specified in the decreases clause.
75+
At a high level, in addition to the assumptions and assertions introduced by the invariant clause,
76+
a decreases clause expands to three key steps:
77+
1. At the beginning of the loop body, record the initial value of the measure specified in the decreases clause.
78+
2. At the end of the loop body, record the final value of the measure specified in the decreases clause.
79+
3. After the loop iteration, assert that the final value is strictly smaller than the initial one.
80+
81+
For a 1D decreases clause, we use the strict arithmetic comparison (i.e., `<`).
82+
For a multi-dimensional decreases clause, say `(e_1, ..., e_n)`,
83+
we extend the strict arithmetic comparison to a strict lexicographic comparison.
84+
85+
As an example, consider our binary search implementation again,
86+
this time with a decreases clause annotation to prove its termination:
87+
88+
```c
89+
int binary_search(int val, int *buf, int size)
90+
{
91+
if(size <= 0 || buf == NULL) return NOT_FOUND;
92+
93+
long long lb = 0, ub = size - 1;
94+
long long mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
95+
96+
while(lb <= ub)
97+
__CPROVER_loop_invariant(0L <= lb && lb - 1L <= ub && ub < size)
98+
__CPROVER_loop_invariant(mid == ((unsigned int) lb + (unsigned int) ub) >> 1)
99+
__CPROVER_decreases(ub - lb)
100+
{
101+
if(buf[mid] == val) break;
102+
if(buf[mid] < val)
103+
lb = mid + 1;
104+
else
105+
ub = mid - 1;
106+
mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
107+
}
108+
return lb > ub ? NOT_FOUND : mid;
109+
}
110+
```
111+
112+
The instrumented GOTO program is conceptually similar to the following high-level C program:
113+
114+
```c
115+
int binary_search(int val, int *buf, int size)
116+
{
117+
if(size <= 0 || buf == NULL) return NOT_FOUND;
118+
119+
long long lb = 0, ub = size - 1;
120+
long long mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
121+
122+
/* 1. assert invariant at loop entry */
123+
assert(0L <= lb && lb - 1L <= ub && ub < size);
124+
assert(mid == ((unsigned int) lb + (unsigned int) ub) >> 1);
125+
126+
/* 2. create a non-deterministic state for modified variables */
127+
havoc(lb, ub, mid);
128+
129+
/* 3. establish invariant to model state at an arbitrary iteration */
130+
__CPROVER_assume(0L <= lb && lb - 1L <= ub && ub < size);
131+
__CPROVER_assume(mid == ((unsigned int) lb + (unsigned int) ub) >> 1);
132+
133+
/* 4. perform a single arbitrary iteration (or exit the loop) */
134+
if(lb <= ub)
135+
{
136+
/* 5. declare variables for tracking the loop variant */
137+
int old_measure, new_measure;
138+
139+
/* 6. evaluate the variant at the start of the loop body */
140+
old_measure = ub - lb;
141+
142+
if(buf[mid] == val) break;
143+
if(buf[mid] < val)
144+
lb = mid + 1;
145+
else
146+
ub = mid - 1;
147+
mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
148+
149+
/* 7. assert the invariant to establish inductiveness */
150+
assert(0L <= lb && lb - 1L <= ub && ub < size);
151+
assert(mid == ((unsigned int) lb + (unsigned int) ub) >> 1);
152+
153+
/* 8. evaluate the variant at the end of the loop body */
154+
new_measure = ub - lb;
155+
156+
/* 9. assert the decreases clause */
157+
assert(new_measure < old_measure);
158+
159+
/* 10. terminate this symbolic execution path; similar to "exit" */
160+
__CPROVER_assume(false);
161+
}
162+
return lb > ub ? NOT_FOUND : mid;
163+
}
164+
```
165+
166+
The instrumented code points (5), (6), (8), and (9) are specific to the decreases clause.
167+
168+
**Important.**
169+
Decreases clauses work in conjunction with [loop invariants](contract-invariants.md),
170+
which model an arbitrary loop iteration at which the decreases clause is checked.
171+
If a decreases clause is annotated on a loop without an invariant clause,
172+
then the weakest possible invariant (i.e, `true`) is used to model an arbitrary iteration.

doc/cprover-manual/contracts-invariant.md renamed to doc/cprover-manual/contracts-invariants.md

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Invariant Clause
1+
# Invariant Clauses
22

33
An _invariant_ clause specifies a property that must be preserved
44
by every iteration of a loop.
@@ -46,11 +46,14 @@ __CPROVER_loop_invariant(i <= n);
4646

4747
**Important.** Invariant clauses must be free of _side effects_,
4848
for example, mutation of local or global variables.
49-
49+
Otherwise, CBMC raises an error message during compilation:
50+
```
51+
Loop invariant is not side-effect free. (at: file main.c line 4 function main)
52+
```
5053

5154
### Semantics
5255

53-
The loop invariant clause expands to several assumptions and assertions:
56+
A loop invariant clause expands to several assumptions and assertions:
5457
1. The invariant is _asserted_ just before the first iteration.
5558
2. The invariant is _assumed_ on a non-deterministic state to model a non-deterministic iteration.
5659
3. The invariant is finally _asserted_ again to establish its inductiveness.
@@ -71,20 +74,19 @@ int binary_search(int val, int *buf, int size)
7174
{
7275
if(size <= 0 || buf == NULL) return NOT_FOUND;
7376

74-
long lb = 0, ub = size - 1;
75-
long mid = (lb + ub) / 2;
77+
long long lb = 0, ub = size - 1;
78+
long long mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
7679

7780
while(lb <= ub)
7881
__CPROVER_loop_invariant(0L <= lb && lb - 1L <= ub && ub < size)
79-
__CPROVER_loop_invariant(mid == (lb + ub) / 2L)
80-
__CPROVER_decreases(ub - lb)
82+
__CPROVER_loop_invariant(mid == ((unsigned int) lb + (unsigned int) ub) >> 1)
8183
{
8284
if(buf[mid] == val) break;
8385
if(buf[mid] < val)
8486
lb = mid + 1;
8587
else
8688
ub = mid - 1;
87-
mid = (lb + ub) / 2;
89+
mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
8890
}
8991
return lb > ub ? NOT_FOUND : mid;
9092
}
@@ -97,19 +99,19 @@ int binary_search(int val, int *buf, int size)
9799
{
98100
if(size <= 0 || buf == NULL) return NOT_FOUND;
99101
100-
long lb = 0, ub = size - 1;
101-
long mid = (lb + ub) / 2;
102+
long long lb = 0, ub = size - 1;
103+
long long mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
102104
103105
/* 1. assert invariant at loop entry */
104106
assert(0L <= lb && lb - 1L <= ub && ub < size);
105-
assert(mid == (lb + ub) / 2L);
107+
assert(mid == ((unsigned int) lb + (unsigned int) ub) >> 1);
106108
107109
/* 2. create a non-deterministic state for modified variables */
108110
havoc(lb, ub, mid);
109111
110112
/* 3. establish invariant to model state at an arbitrary iteration */
111-
__CPROVER_assume(0L <= lb && lb - 1L <= ub && ub < size)
112-
__CPROVER_assume(mid == (lb + ub) / 2L)
113+
__CPROVER_assume(0L <= lb && lb - 1L <= ub && ub < size);
114+
__CPROVER_assume(mid == ((unsigned int) lb + (unsigned int) ub) >> 1);
113115
114116
/* 4. perform a single arbitrary iteration (or exit the loop) */
115117
if(lb <= ub)
@@ -119,11 +121,11 @@ int binary_search(int val, int *buf, int size)
119121
lb = mid + 1;
120122
else
121123
ub = mid - 1;
122-
mid = (lb + ub) / 2;
124+
mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
123125
124126
/* 5. assert the invariant to establish inductiveness */
125127
assert(0L <= lb && lb - 1L <= ub && ub < size);
126-
assert(mid == (lb + ub) / 2L);
128+
assert(mid == ((unsigned int) lb + (unsigned int) ub) >> 1);
127129
128130
/* 6. terminate this symbolic execution path; similar to "exit" */
129131
__CPROVER_assume(false);

doc/cprover-manual/contracts-loops.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ int binary_search(int val, int *buf, int size)
2424
{
2525
if(size <= 0 || buf == NULL) return NOT_FOUND;
2626

27-
long lb = 0, ub = size - 1;
28-
long mid = (lb + ub) / 2;
27+
int lb = 0, ub = size - 1;
28+
int mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
2929

3030
while(lb <= ub)
3131
{
@@ -34,7 +34,7 @@ int binary_search(int val, int *buf, int size)
3434
lb = mid + 1;
3535
else
3636
ub = mid - 1;
37-
mid = (lb + ub) / 2;
37+
mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
3838
}
3939
return lb > ub ? NOT_FOUND : mid;
4040
}
@@ -96,20 +96,20 @@ int binary_search(int val, int *buf, int size)
9696
{
9797
if(size <= 0 || buf == NULL) return NOT_FOUND;
9898

99-
long lb = 0, ub = size - 1;
100-
long mid = (lb + ub) / 2;
99+
int lb = 0, ub = size - 1;
100+
int mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
101101

102102
while(lb <= ub)
103103
__CPROVER_loop_invariant(0L <= lb && lb - 1L <= ub && ub < size)
104-
__CPROVER_loop_invariant(mid == (lb + ub) / 2L)
104+
__CPROVER_loop_invariant(mid == ((unsigned int) lb + (unsigned int) ub) >> 1)
105105
__CPROVER_decreases(ub - lb)
106106
{
107107
if(buf[mid] == val) break;
108108
if(buf[mid] < val)
109109
lb = mid + 1;
110110
else
111111
ub = mid - 1;
112-
mid = (lb + ub) / 2;
112+
mid = ((unsigned int) lb + (unsigned int) ub) >> 1;
113113
}
114114
return lb > ub ? NOT_FOUND : mid;
115115
}

0 commit comments

Comments
 (0)