Programming Languages: Lecture #5
Page 1 of 13
Programming Languages Semantics /Noam Rinetzky Mooly Sagiv
Lecture #5, 22 April 2004: Chaotic iterations for constant propagation
Notes by: Dotan Patrich
Introduction
In this lecture, we will go over the process of chaotic iteration as an algorithm for constant
propagation and generalize it to a broader class of problems. Continuing from last lesson,
we will go into details about the algorithm's mathematical properties and its behavior.
In the second half of the lecture we, define the Galois connection between the abstract
semantics as it is defined in the algorithm's domain and a concrete semantics, in our case a
collecting variation of structural operation semantics. In the last part we will build ground
for proving the soundness of the algorithm.
Chaotic Iterations Algorithm
In the last lecture, we saw a general chaotic iteration algorithm over a control flow graph
(CFG) for a given program, this lesson we will go over a specialized version of the
algorithm for the problem of constant propagation. We will go over the details of the
algorithm in this section in several steps:
Show examples of the algorithm's behavior on sample programs
Formulate it's behavior using a system of equations and see it’s solutions properties
Prove the algorithm correctness in finding a least fixed point
Analyze the complexity of the algorithm.
For the moment, we assume that our programming language does not include procedures
and that the control flow of the program is given.
For simplicity, we initially ignore exceptions and other constructs which complicates the
task of defining control flow graph. Under these restrictions, it is easier to construct the
CFG for the algorithm statically.
We also assume, without loss of generality, that there is a designated control flow node s
which is the start node and that this node has no incoming edges.
Programming Languages: Lecture #5
Page 2 of 13
The algorithm
Chaotic(G(V, E): Graph, s: Node, τ: L, f: E (L L) ) {
for each v in V do dfentry[v] :=
dfentry[s] = τ
WL = {s}
while (WL ) do
select and remove an element u WL
for each v, such that e=(u, v) E do
temp = f(e)(dfentry[u])
new := temp dfentry[v]
if (new dfentry[v]) then
dfentry[v] := new;
WL := WL {v}
It is an iterative algorithm that given - a monotonic transfer function in each edge of the
control flow graph - f, initial values for the variables at the start of the program – τ and a
starting node in the CFG – s, computes the least fixed point of a system of equations
defined by the CFG of the program.
The iterative algorithm goes over each node v in the worklist, and computes the changes in
the states of nodes that are the successors of v in the CFG, by applying function f on the
state of v and performing meet operation with the state in v ‘s successors. When a change is
detected in an abstract state of a node we add it to the worklist. This way the change of a
state is propagating to its descendent nodes. The order of going through the nodes in the
worklist is not defined and we will see that it only affects the number of iterations.
At node v in the control flow graph the program state is represented by the (algoritm)
variable dfentry[v]. The value of these variables are taken from some lattice. In the constant
propagation, the lattice is a finite map from the program variable names to another
complete lattice L'. Where the lattice L` represents the possible values of a single variable
and it is Z{,}. An example of an element in the lattice L which can occur at a CFG
node is [x1, y, z0], where 1, and 0 in this example are the values from L` and the
state of all variables is a value in L.
We compute the lattice element at each node by computing dfentry. dfentry[v] holds the
possible state of the program variables at a node's statement. .
As we have seen last lesson, the transfer function in each edge of the control flow graph is
monotonic. The algorithm works in a complete lattice given (L,,,,,) and in each
iteration a larger value is generated for some CFG nodes, or the worklist shrinks.
Also, we will see in this section that the algorithm computes the least fixed point (lfp) of a
monotone function, which is the minimal solution for each node's state in the control flow
graph.
Programming Languages: Lecture #5
Page 3 of 13
Examples of the algorithm run
We begin with a simple example than was shown last lesson on how to create the control
flow graph and transfer function of it:
WL
dfentry[v]
{1}
{2}
df[2]:=[x0, y0,z3]
{3}
df[3]:=[x1, y0,z3]
{4}
df[4]:=[x1, y0,z3]
{5}
df[5]:=[x1, y0,z3]
{7}
df[7]:=[x1, y7,z3]
{8}
df[8]:=[x3, y7,z3]
{3}
df[3]:=[x, y,z3]
{4}
df[4]:=[x, y,z3]
{5,6} df[5]:=[x1, y,z3]
{6,7} df[6]:=[x, y,z3]
{7}
df[7]:=[x, y7,z3]
In the example above, the left shows the control flow graph of a given program with the
transfer function f on each edge of the control graph. On the right, there is a table that
details the changes in the work-list and the node's states after each iteration of the
algorithm.
Order of traversal example
The order of traversing the nodes of the control flow graph is not defined in the algorithm,
and it affects the number of iterations of the algorithm. Next, is an example of in which the
in which the algorithm was applied to the same program above, but the nodes were
traversed in a different order. We can see how the different order affect the number of
iterations. It is important to note that regardless of the traversal order the algorithm is
guaranteed to produce the least fix point.
Programming Languages: Lecture #5
Page 4 of 13
WL
dfentry[v]
{1}
{2}
df[2]:=[x0, y0,z3]
{3}
df[3]:=[x1, y0,z3]
{4}
df[4]:=[x1, y0,z3]
{5}
df[5]:=[x1, y0,z3]
{7}
df[7]:=[x1, y7,z3]
{8}
df[8]:=[x3, y7,z3]
{3}
df[3]:=[x, y,z3]
{4}
df[4]:=[x, y,z3]
{5,6} df[6]:=[x, y,z3]
{5,7} df[7]:=[x, y7,z3]
{5,8} df[8]:=[x3, y7,z3]
{5}
df[5]:=[x1, y,z3]
{7}
df[7]:=[x, y7,z3]
As we can see in this example we need 13 iterations of the algorithm before we reach a fixpoint, compared to 11 iterations in the previous example. While it does not seem such a big
difference, remember that this depends on the number of variables and the length of the
program.
One heuristic of traversing the nodes is a DFS walk on the graph or post-order on the worklist. However, we are guarantied to end the execution of the algorithm in a limited number
of iterations in the worst case, whatever traversal method we choose, as we will prove in the
section about the complexity of the algorithm.
Yet another example
Here is another example that should give you a better understanding of the role of the
variable value lattice and how the chaotic iteration differ from the actual run of the
program.
The program we will use is:
y:=x
z:=1
while (y>1)
z:=z*y
y:=y-1
y:=0
Programming Languages: Lecture #5
Page 5 of 13
Again for this program we will build a control flow graph and transition function on the
edges. In the table bellow on the left is the CFG and on the right are the changes in states
and the worklist:
WL
dfentry[v]
{1}
{2}
df[2]:=[x3, y3,z5]
{3}
df[3]:=[x3, y3,z1]
{4,6} df[4]:=[x3, y3,z1]
{5,6} df[5]:=[x3, y3,z3]
{3,6} df[3]:=[x3, y,z]
{4,6} df[4]:=[x3, y,z]
{5,6} df[5]:=[x3, y,z]
{3,6} df[3]:=[x3, y,z]
{6}
df[6]:=[x3, y,z]
The interesting thing about this algorithm is that the number of iterations over the while
loop is not related to the number of iterations in the actual runtime, but to the lattice
changes. This example shows clearly the difference of this static analysis algorithm to the
actual execution of the program.
Another thing to notice in this example is the change in the lattice value for the y and z
variables. Although, the variables change their value during run time many times they will
change their value in the static analysis only twice, as this is the height of the lattice L`
used.
Mathematical behavior of the algorithm
We will write a system of equation that describes the algorithm behavior and deduce by its
properties that the chaotic iterations algorithm computes the least fixed point of the lattice.
For every node v in the control flow graph an equation will be generated that take into
account all the predecessor nodes of v and define the requirements on that node's state
accordingly.
{
S=
dfentry[s]=τ
dfentry[v]= {f(u,v) (dfentry[u]) | (u,v)E}
}
The chaotic iterations algorithm applies this system of equations and by starting with value
at each node (except for the starting node s which gets some initial value τ) it iteratively
computes a minimal solution for this system of equations.
A solution for the system of equation is also referred to as a fixed point, as after applying
the functions of the algorithm on the states in the solution, they will not change. There may
be more than one fixed point to this system of equation, but first let us show that at least
one exists.
Programming Languages: Lecture #5
Page 6 of 13
We will show that a fixed point for the system of equations exists and the lfp (least fixed
point) of it is well-defined by using Tarski's theorem. To do so, we need to convert the
system of equations above to an equivalent monotone function FS:LnLn:
FS (X)[s] = τ
FS (X)[v] ={f(u,v) (X[u]) | (u,v)E}
The function FS "looks" at all the nodes in the CFG at once (FS operates on a vector of
variables) and computes their new values according to the function at each edge of the CFG
simultaneously. The functions at each edge of the CFG are monotone and by the definition
of FS we get the FS is monotone as well, as it is composed of monotone functions at each
component of the vector.
By Tarski's theorem on FS we conclude that the least fixed point is well-defined. Also, by
the equality between the system of equations and FS we get that lfp(S)=lfp(FS).
To show the properties of such a fixed point, and what ``solution’’ is not a fixed point for
the system of equations above, we show the examples bellow.
Node #
Non-Solution 1
1
df[1]:=[x0,y0,z0]
2
df[2]:=[x0,y0,z3]
3
df[3]:=[x, y7,z3]
4
df[4]:=[x, y7,z3]
5
df[5]:=[x1, y7,z3]
6
df[6]:=[x, y7,z3]
7
df[7]:=[x, y7,z3]
8
df[8]:=[x3, y7,z3]
Node #
Solution 2
1
df[1]:=[x0,y0,z0]
2
df[2]:=[x0,y0,z3]
3
df[3]:=[x,
y,z3]
4
df[4]:=[x,
y,z3]
5
df[5]:=[x1, y,z3]
6
df[6]:=[x,
y,z3]
7
df[7]:=[x, y7,z3]
8
df[8]:=[x3, y7,z3]
Programming Languages: Lecture #5
Page 7 of 13
The first example is not a solution to the system of equations as in node 3 the value of y
should be . Note that when the program runs, the first time we reach this node the value
of y is 0, afterwards the value of of y is always 7. Thus, the value of y is not a constant in
this point. In the static analysis, we merge the abstract state of the after the assignment in
node 2 (in which y is found to be always 0) and print statement in node 8 (in which y is
found to have the constant value 7) and we get that the (abstract) value of y is in node 3.
Any more iterations over the loop do not change this value as it is the maximal in the
lattice. Also note that this solution is not sound as it report y to be constant in node 3,
whereas it is not. We return to the issue of soundness in the second part of the lecture.
The second example is a solution to the system of equations. We can see that applying any
of the monotone transfer functions from the CFG on a value at any node, does not change
the states of its successor. In this sense, the solution is a fixed point.
As we start with a value in each node and each step apply a monotone function on the
states, the state of each node must not "decrease" at each iteration, thus the solution we get
must be the least fixed point of the equations. In the next section, we will prove that the
algorithm stops.
Complexity of the algorithm
The algorithm is based on the complete lattice (L, ,,,,) given and at each step apply a
monotone function f, taken from an edge of the control flow graph, on a node state from
this lattice. In the case of constant propagation, the lattice is actually a finite map of
variables name to variable values represented by lattice L`. Where lattice L` is composed of
3 states: ; a constant number state; and . The height of the lattice L` is 2, so after
applying a monotone function on L` twice at most we will reach the maximum value in
L`. From this, the height of the lattice L is 2|V|, since the abstract value of any variable
can change at most 2 time. In particular, the abstract value of a node in L, cannot after
applying a monotone function on L at most 2|V| times at most on a node we will reach the
maximum value - in L.
At each of the algorithm iterations, we remove one node from the worklist. We place other
nodes in the worklist, only if the value of their abstract state. Each time the abstract state
changes, it increases in the lattice (note, that in the computation of new, that the old value is
merged with the propagated information). Since the lattice has a bounded size, as we have
just seen, the value of the abstract state at a node can be changed at most 2|V| times. Thus,
we get that each node can appear in the worklist at most 2|V| times. We conclude, that
after a limited number of iterations the worklist will be empty and the algorithm will stop.
We prove in this section that the algorithm's runtime complexity is actually O(nhck)
where n is the number of CFG nodes, k is the maximum out degree of edges, h is the height
of the lattice, c is the maximum cost of applying f, calculating , and performing L
comparisons.
In each iteration, a computation of cost c needs to be done in order to apply the transfer
function f, compute meet and compare lattices. These computations are performed on each
child of the node in the CFG, so define k to be the maximum number of child in the CFG
for a node (in the example above – k=2), and we get that the computations at each iteration
Programming Languages: Lecture #5
Page 8 of 13
cost at most ck. Since each node (out of n) is processed at most |V| times, we get that the
time complexity of the chaotic iteration algorithm is O(nhck).
Note that although k is determined by the programming language, e.g., in the while
language it is 2, the number of variables affects the height of the lattice and cost of applying
operations on it. Thus, the algorithm complexity is not linear in the number of lines of code.
As a side note, in many compilers we see that there is a big change when we compile small
piece of code in comparison to many lines of code, not in proportion to the difference in
code length.
Soundness
In this section we will lay the grounds for proving soundness of the chaotic iterations
algorithm for the problem of constant propagation. We will start by defining the semantics
used and it's relation to the algorithm steps, then we will formulate a theorem for local and
global correctness of soundness. We will lay the ground for the proof of the local soundness
in this lesson and leave the proof for next lesson.
Recall that in the case of constant propagation, in order for the algorithm to be sound it is
required that every detected constant is indeed a constant, or in general, the least fixed point
represents all occurring runtime states. Another desirable property of static analysis is
completeness. In constant propagation, a complete algorithm detects every constant in the
program. Or in general, every state represented by the least fixed point is reachable for
some input.
Next lesson we will see how usually completeness can not be reached by a static analysis
tool, e.g., it is very hard not to miss constants, or in an tool that looks for possible run time
errors, not to output any warnings at all when there are no actual errors.
Soundness in constant propagation
We regard soundness in constant propagation using these goals to be achieved by the
algorithm:
Every detected constant is indeed such
May include fewer constants
May miss
Applying this on the chaotic iterations algorithm we arrive at these requirements:
At every control flow graph node v
o all constants in dfentry[v] are indeed constants
o dfentry[v] "represents" all the possible concrete states arising when the
structural operational semantics reaches v
Note, that the requirements above allow us to represent states for a node that never occur in
runtime. However, every state that arises in actual run of the program has to be represented
in the possible states we gathered for the node.
Programming Languages: Lecture #5
Page 9 of 13
We will demonstrate the technique to proving soundness for the problem of constant
propagation and our definition of the helping structures and the semantics will be
according, however, this technique is general and can be applied to other problems.
Programming Languages: Lecture #5
Page 10 of 13
The abstract interpretation technique
This technique is the foundation of program analysis. It relates each step in the algorithm to
a step in a structural operational semantics and establishes global correctness of some
property using a general theorem proved in CC1976.
The aim in defining such a technique is to establish soundness of a given program analysis
algorithm and to design new program analysis algorithms.
Later we will define the mathematical relation (a Galois connection) between the program
concrete states, as computed by the collecting semantics, and the abstract states computed
by the chaotic iterations algorithm. This will prove that the algorithm is sound. Galois
connections can be constructed in a systematic way and can be used to induce one
specification of an analysis from another.
Proof of soundness
The method we devise bellow to prove soundness in constant propagation is to first define
the semantics of our programming language. Then, according to it, we create a "collecting"
semantics that is better suited for the soundness proof. The relation between the constant
propagation domain and the collecting semantics is defined by a Galois connection. If we
manage to prove the local soundness on the atomic statements of the semantic we can
conclude by a theorem of CC1976 global correctness of soundness.
Bellow, are the steps in proving the soundness in constant propagation:
Define an "appropriate" structural operation semantics
Define "collecting" structural operation semantics
Establish a Galois connection between collecting states and the abstract states used
in the constant propagation algoritm
(Local correctness) Show that the abstract interpretation of every atomic statement
is sound w.r.t. the collecting semantics
(Global correctness) Conclude that the analysis is sound by CC1976:
(lfp(S)[v, entry]) REACH[v, entry]
(lfp(S)[v, exit] REACH[v, exit]
The REACH notion written above means all the possible states that the program can come
with at a certain statement.
The "collecting" semantic is similar to the structural operation semantic only it calculates a
collection of states. It is equivalent to running the program on all possible states and for
each program point, it gets the set of all of the possible states that could arise at that point.
Over the next sub-sections we will detail each of the steps
Step 1: Structural Operation Semantics
We choose to use the structural operation semantics because it allows us to reason about
intermediate stages in a program execution and it also allows us to deal with nonterminating programs. The rules of the semantics are written bellow:
Programming Languages: Lecture #5
axioms
[asssos]
<x := a,s> s[xAas]
[skipsos]
<skip,s> s
[comp1sos]
<S1,s> <S’1,s’>
Page 11 of 13
<S1;S2,s> <S’1;S2,s’>
[comp
2
sos]
rules
<S1,s> s’
<S1;S2,s> <S2,s’>
tt
[if
sos]
<if b then S1 else S2,s> <S1,s>
if Bbs = tt
[ifffsos]
<if b then S1 else S2,s> <S2,s>
if Bbs = ff
[whilesos]
<while b do S,s><if b then (S;while b do S) else skip,s>
Another reason for choosing the structural operation semantic over the natural semantic is
because the natural semantic has a problem dealing with parallel statements, a case that
arises often in program analysis.
In the case of constant propagation, the “appropriate” operational semantics is the standard
one. Next lesson we shell see examples when different (instrumented) semantics are used.
Step 2: Collecting Semantics
The collecting semantic is used to define the collection of states that can rise at any
program point according to the structural operational semantics. One way to look at it is to
say that since we do not know on which input the program will run, we have to apply the
semantics on every possible input, thus generating a collection of states. The collecting
semantics states are a complete lattice as well; the lattice is the power set of the structural
operational semantic states.
Since we are interested in safety properties, i.e., properties that hold at all the states that
reach a program point, there is no lost of precision in writing the set of all possible states.
Note, that we do not “merge” different states, but write a separate state in the collection for
each possible state. An example of a state in the collecting semantic where the possible
values for variable x are 1 or 3 is {[x1], [x3]}, where as, in constant propagation we will
describe this state (an others too!) as [x].
The collecting semantic is used to define the set of all possible program states that can arise
at any program point. We use it to deduce soundness on the chaotic iterations algorithm by
showing that the abstract states computed by this algorithm are over approximations of the
states computes by the collecting semantics, i.e., they may represent more states than can
actually occur.
In the next sub-sections we will formulate this definition, show an example of it on a while
program and write the mathematical behavior for it in a system of equations, similar to what
we have shown for the chaotic iterations algorithm.
Programming Languages: Lecture #5
Page 12 of 13
The Collecting Semantics – an iterative definition
We provide a definition for the collecting semantics in a similar way to the way we defined
the chaotic iteration algorithm. For each edge in the CFG we define a monotone transfer
function that describes the relations between the set of states at each program node. A
solution for the resulting system of equation defines the set of state that can occur at a node
according to the collecting semantics. We will show than that the least solution for the
system of equations is well-defined, although it may not be computable.
We will define CSentry(l) and CSexit(l) as the collecting semantic states (set of states of the
operational semantics) that apply before and after the statement l. The monotone function
for changing the collecting state for each rule is defined as:
[skip]
CSexit(l) = CSentry(l)
[b]
CSexit(l) = { | CSentry(l), b=tt }
[x:=a]
CSexit(l) = { s[xAas] | sCSentry(l) }
CSentry(l) = CSexit(l`)
CSentry(l) = { s0 | s0Var*Z }
(where l` immediately precedes l in the control flow graph)
(an equation for the initial states)
We wrote the equations for the collecting interpretation only for the atomic statements in
the semantic and the boolean evaluation, other rules in the semantic will be constructed
from it. For example the transfer function for the node if(x>0) will be
CSexit(l)={|CSentry(l),x>0=tt} on the true edge and
CSexit(l)={|CSentry(l),x>0=ff} on the false edge.
As can be seen, these transfer functions are monotone in the sense that when there are more
states in CSentry there will also be more states in CSexit.
The join operation is defined as a union for this lattice, as can be seen in the 4th function.
A simple example
To make the definition more clear, we will apply it on the example program we used for the
chaotic iterations algorithm in the first section. In the example bellow we can see that there
is a collection of states in each node of the CFG, and not a single state as was the result of
calculating the constant propagation algorithm.
Programming Languages: Lecture #5
Page 13 of 13
Mathematical system of equations
As we have seen for the constant propagation chaotic iteration algorithm, we can describe
the collecting semantics behavior using a system of equations:
{
S=
CSentry [s] ={ 0}
CSentry [v] = { f(e)(CSentry[u]) | e=(u,v) E}
}
Where f(e) = X. {st(e)() | X} for atomic statements and a similar function for
Boolean evaluation rules. For example, the transfer function on the true edge of an if(b)
node will be f(e) = X. {st(e)() | X, b=tt }
From this system of equations we can come with the following equivalent monotone
function FS:LnLn:
FS (X)[v] = { f(e)(X[u] | (u,v) E}
Note that in the collecting semantics the collection of states can be a non-finite set, however
we do not compute this set. The collecting semantics is used only for the correctness proof
and does not need be compute them. We actually do, is to compute the states of the lattice
defined for the constant propagation. We construct Galois connection between the states of
the collecting semantics and the abstract states computed by the constant propagation
algorithm, and use it prove the correctness of the algorithm. We will go over this Galois
connection in the next lesson.
As seen in the beginning of this lesson, using Tarski's theorem we get that the least fixed
point of the function FS, used to define the collecting semantics, is well-defined, and from
the equivalent between to the system of equation and the function FS we get that exists a
least fixed point for the system of equation and it is well-defined as well, i.e.,
lfp(S)=lfp(FS).
© Copyright 2026 Paperzz