Widening and Narrowing in Variable Range Analysis
Interval analysis domain, for each program point, maps each program variable to an interval.
Analysis domain has elements where denotes the set of such intervals.
Height of lattice for unbounded integers: infinite.
Height of lattice of one interval for 64-bit integers: around
Moreover, if we have variables in program and program points, height of lattice for the analysis domain is times larger.
How to guarantee (reasonably fast) termination?
Widening technique: if the iteration does not seem to be converging, take a “jump” and make the interval much wider (larger).
Finite set of jump points (e.g. set of all integer constants in the program)
In fixpoint computation, compose with function
We require the condition:
for all .
The condition holds for the example above.
Approaches:
- always apply widening (we will assume this)
- iterate a few times with only (without using ), if we are not at a fixpoint at this program point, then widen.
- this is not monotonic: if you start at fixpoint, it converges, if start below, can jump over fixpoint!
Standard iteration:
Widening:
Example where widening works nicely
Consider program:
x = 0; while (x < 1000) { x = x + 1; }
Interval analysis without widening will need around 1000 iterations to converge to interval for at the end of the program. This may be too slow.
Let us derive the set by taking all constants that appear in the program, as well as and :
After a few iterations, widening maps interval into . This gives for at loop entry and again for at the end of the program, but in many fewer iterations.
Example showing problems with widening
Consider program:
x = 0; y = 1; while (x < 1000) { x = x + 1; y = 2*x; y = y + 1; print(y); }
Interval analysis without widening will need around 1000 iterations to converge to
This may be too slow.
Now apply widening with the same as before. When within loop we obtain , applying widening function to the interval for results in . We obtain at the end of the program:
Narrowing
Observation
Consider a monotonic function, such as on the set of real numbers. If we consider a sequence this sequence is monotonically increasing iff (e.g. for ) and it is monotonically decreasing iff (e.g. for ). Informally, the sequence continues of the direction in which it starts in the first step.
This is because implies by monotonicity of that etc., whereas implies .
The Idea
Let such that .
After finding fixpoint of , apply to improve precision.
Widen and Narrow
Lemma: Let and be monotonic functions on a partial order such that for all . Define the following:
where we also assume that the two and one exist. Then
- is the least fixpoint of and , is the least fixpoint of (by Tarski's Fixpoint Theorem), and
- .
Proof: By induction, for each we have
Thus by Comparing Fixpoints of Sequences, we have .
Next, we have that
Thus, . From there by induction and monotonicity of we obtain
i.e. the sequence is decreasing. Therefore, is its upper bound and therefore .
On the other hand, we have by monotonicity of , the fact that is fixpoint, and that:
Thus, is the lower bound on , so .
End of proof.
Note: even if does not exist, we can simply compute for any chosen value of , it is still a sound over-approximation, because it approximates , which approximates the concrete value:
so
Being able to stop at any point gives us an anytime algorithm.
Example
Example showing how narrowing may improve result after widening
In the above example for the program, the results obtained using widening are:
x = 0; y = 1; // x -> [0,0], y -> [1,1] // (merge point) // x -> [0,1000], y -> [1,+infty) while (x < 1000) { // x -> [0,999], y -> [1,+infty) x = x + 1; // x -> [0,1000], y -> [1,+infty) y = 2*x; // x -> [0,1000], y -> [0,+infty) y = y + 1; // x -> [0,1000], y -> [1,+infty) print(y); } // x -> [1000,1000], y -> [1,+infty)
Let us now apply one ordinary iteration, without widening. We obtain:
x = 0; y = 1; // x -> [0,0], y -> [1,1] // (merge point) // x -> [0,1000], y -> [1,2001] while (x < 1000) { // x -> [0,999], y -> [1,+infty) x = x + 1; // x -> [0,1000], y -> [1,+infty) y = 2*x; // x -> [0,1000], y -> [0,2000] y = y + 1; // x -> [0,1000], y -> [1,2001] print(y); } // x -> [1000,1000], y -> [1,2001]
Thus, we obtained a good first approximation by a few iterations with widening and then improved it with a single iteration without widening.