Skip to content

Commit f46d60a

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

File tree

3 files changed

+197
-14
lines changed

3 files changed

+197
-14
lines changed
Lines changed: 183 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,184 @@
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+
Formally, given two evaluations `(a_1, ..., a_n)` and `(b_1, ..., b_n)`,
85+
a strict lexicographic comparison, denoted by `(a_1, ..., a_n) ≺ (b_1, ..., b_n)`,
86+
is defined as the predicate:
87+
88+
```c
89+
(a_1 < b_1)
90+
|| (a_1 == b_1 && a_2 < b_2)
91+
|| ...
92+
|| (a_1 == b_1 && ... && a_n < b_n)
93+
```
94+
95+
where `||` and `&&` are Boolean disjunction and conjunction respectively.
96+
97+
As an example, consider our binary search implementation again,
98+
this time with a decreases clause annotation to prove its termination:
99+
100+
```c
101+
int binary_search(int val, int *buf, int size)
102+
{
103+
if(size <= 0 || buf == NULL) return NOT_FOUND;
104+
105+
long long lb = 0, ub = size - 1;
106+
long long mid = (lb + ub) / 2;
107+
108+
while(lb <= ub)
109+
__CPROVER_loop_invariant(0L <= lb && lb - 1L <= ub && ub < size)
110+
__CPROVER_loop_invariant(mid == (lb + ub) / 2L)
111+
__CPROVER_decreases(ub - lb)
112+
{
113+
if(buf[mid] == val) break;
114+
if(buf[mid] < val)
115+
lb = mid + 1;
116+
else
117+
ub = mid - 1;
118+
mid = (lb + ub) / 2;
119+
}
120+
return lb > ub ? NOT_FOUND : mid;
121+
}
122+
```
123+
124+
The instrumented GOTO program is conceptually similar to the following high-level C program:
125+
126+
```c
127+
int binary_search(int val, int *buf, int size)
128+
{
129+
if(size <= 0 || buf == NULL) return NOT_FOUND;
130+
131+
long long lb = 0, ub = size - 1;
132+
long long mid = (lb + ub) / 2;
133+
134+
/* 1. assert invariant at loop entry */
135+
assert(0L <= lb && lb - 1L <= ub && ub < size);
136+
assert(mid == (lb + ub) / 2L);
137+
138+
/* 2. create a non-deterministic state for modified variables */
139+
havoc(lb, ub, mid);
140+
141+
/* 3. establish invariant to model state at an arbitrary iteration */
142+
__CPROVER_assume(0L <= lb && lb - 1L <= ub && ub < size)
143+
__CPROVER_assume(mid == (lb + ub) / 2L)
144+
145+
/* 4. perform a single arbitrary iteration (or exit the loop) */
146+
if(lb <= ub)
147+
{
148+
/* 5. declare variables for tracking the loop variant */
149+
int old_measure, new_measure;
150+
151+
/* 6. evaluate the variant at the start of the loop body */
152+
old_measure = ub - lb;
153+
154+
if(buf[mid] == val) break;
155+
if(buf[mid] < val)
156+
lb = mid + 1;
157+
else
158+
ub = mid - 1;
159+
mid = (lb + ub) / 2;
160+
161+
/* 7. assert the invariant to establish inductiveness */
162+
assert(0L <= lb && lb - 1L <= ub && ub < size);
163+
assert(mid == (lb + ub) / 2L);
164+
165+
/* 8. evaluate the variant at the end of the loop body */
166+
new_measure = ub - lb;
167+
168+
/* 9. assert the decreases clause */
169+
assert(new_measure < old_measure);
170+
171+
/* 10. terminate this symbolic execution path; similar to "exit" */
172+
__CPROVER_assume(false);
173+
}
174+
return lb > ub ? NOT_FOUND : mid;
175+
}
176+
```
177+
178+
The instrumented code points (5), (6), (8), and (9) are specific to the decreases clause.
179+
180+
**Important.**
181+
Decreases clauses work in conjunction with [loop invariants](contract-invariants.md),
182+
which model an arbitrary loop iteration at which the decreases clause is checked.
183+
If a decreases clause is annotated on a loop without an invariant clause,
184+
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: 10 additions & 8 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,13 +74,12 @@ 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 = (lb + ub) / 2;
7679

7780
while(lb <= ub)
7881
__CPROVER_loop_invariant(0L <= lb && lb - 1L <= ub && ub < size)
7982
__CPROVER_loop_invariant(mid == (lb + ub) / 2L)
80-
__CPROVER_decreases(ub - lb)
8183
{
8284
if(buf[mid] == val) break;
8385
if(buf[mid] < val)
@@ -97,8 +99,8 @@ 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 = (lb + ub) / 2;
102104
103105
/* 1. assert invariant at loop entry */
104106
assert(0L <= lb && lb - 1L <= ub && ub < size);

doc/cprover-manual/contracts-loops.md

Lines changed: 4 additions & 4 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+
long long long long lb = 0, ub = size - 1;
28+
long long long long mid = (lb + ub) / 2;
2929

3030
while(lb <= ub)
3131
{
@@ -96,8 +96,8 @@ 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+
long long long long lb = 0, ub = size - 1;
100+
long long long long mid = (lb + ub) / 2;
101101

102102
while(lb <= ub)
103103
__CPROVER_loop_invariant(0L <= lb && lb - 1L <= ub && ub < size)

0 commit comments

Comments
 (0)