Skip to content

Commit fd03dcc

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

File tree

2 files changed

+184
-5
lines changed

2 files changed

+184
-5
lines changed
Lines changed: 182 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,183 @@
1-
# Decreases Clause
1+
# Decreases Clauses
22

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

5151
### Semantics
5252

53-
The loop invariant clause expands to several assumptions and assertions:
53+
A loop invariant clause expands to several assumptions and assertions:
5454
1. The invariant is _asserted_ just before the first iteration.
5555
2. The invariant is _assumed_ on a non-deterministic state to model a non-deterministic iteration.
5656
3. The invariant is finally _asserted_ again to establish its inductiveness.
@@ -77,7 +77,6 @@ int binary_search(int val, int *buf, int size)
7777
while(lb <= ub)
7878
__CPROVER_loop_invariant(0L <= lb && lb - 1L <= ub && ub < size)
7979
__CPROVER_loop_invariant(mid == (lb + ub) / 2L)
80-
__CPROVER_decreases(ub - lb)
8180
{
8281
if(buf[mid] == val) break;
8382
if(buf[mid] < val)

0 commit comments

Comments
 (0)