time is, in the worst case, linear in the number of blocks in the free list, which is
proportional to the number of calls to free() that the program has made. Freeing
a block is done in constant time.
Another problem with this implementation is fragmentat
10: g := p+ f
11: M[g] := e
12: i := i+1
14: IF i < a THEN loop ELSE end
15: LABEL end
Figure 11.5: The program in figure 11.2 after common subexpression elimination.
the expression p+ f in instruction 10 even though it has the same value as the
The condition cond is a simple comparison between variables (which may or may
not be callee-saves).
A normal callee-saves strategy will in the prologue save (and in the epilogue
restore) all callee-saves registers used in t
f1g; f1; 2; 4g; f1; 3; 9gg = f1g.
When a big operator is used in combination with a set builder, a special abbreviated
notation can be used:
fe j pg and
fe j pgcan be written, respectively,
fxn j n 2 f0; 1; 2gg j x 2
a QAD compiler will be easier to write than an interpreter, and for other languages
an interpreter is easier. The relative ease/difficulty may also depend on the language
used to implement the QAD interpreter/compiler.
It is also
6 5 6 6;7
7 6 7 7;8
9 8 9 9;10
10 9 10 10
11 10 7
12 11 1;5;9;12
13 12 2 2
Figure 11.3: pred, gen and kill for the program in figure 11.2
For the fixed-point iteration we initialise the in set of instruction 1 to the empty
set and all o
is described in detail in ,  and .
In a weakly-typed low-level language like C, there is no way to distinguish
pointers and integers. Also, pointers can point into the middle of blocks. So automatic
memory management with the restrictions we ha
Most operating systems allow a program to allocate and free (deallocate) chunks
of memory while the program runs. These chunks are, typically, fairly large and
operating-system calls to allocate and free memory can be slow. So, it is normal
260 CHAPTER 12
Write a program that has jumps to jumps and perform jump-to-jump optimisation
of it as described in section 11.3. Try to make the program cover all the three
optimisation cases described at the end of section 11.3.
a) As described in the beg
Write pseudo-code for non-recursive versions of scan and sweep using these
In section 12.8.2, the space between scan and next works like a queue, so copying
live nodes to to-space is done as a breadth-first traversal of the live nodes
assignments to uses.
The liveness analysis presented in chapter 9 consisted of four things:
1. Information about which instructions can follow others, i.e., the successors
of each instruction.
2. For each instruction gen and kill sets that describe how da
When we want to represent an unspecified program (which can be a compiler,
an interpreter or something else entirely) written in language D, we write it as
These figures can be combined to represent executions of programs. For example,
running a program
run slowly. The reason is that it has been compiled by using the QAD compiler (in
combination with the ML compiler). A better result can be obtained by letting the
generated compiler compile itself:
This yields a co
where x 6= y
x := x binop k /0 assg(x)
x := M[y] fx := M[y]g assg(x)
where x 6= y
x := M[x] /0 assg(x)
x := M[k] fx := M[k]g assg(x)
M[x] := y /0 loads
M[k] := y /0 loads
GOTO l /0 /0
IF x relop y THEN lt ELSE l f /0 /0
x := CALL f (args) /0 assg(x)
M[FP] := returnaddress
We also see an unfortunate problem: Just before the call to g, we overwrite fs
return address in M[FP] by gs return address, so we will not return correctly to fs
252 CHAPTER 11. ANALYSIS AND OP
return next - n (* old value of next *)
If garbage collection is not required, allocation is done in constant time. After
returning from a garbage collection, it is assumed that there is sufficient memory
for the allocation. We will later, briefly, return
example, in a doubly-linked list, the backwards pointers are weak pointers while
the forward pointers are normal pointers.
It is not always easy for the compiler to determine which pointer fields should
be weak pointers, so reference counting is rarely us
FP! SL (null)
FP! SL (to f)
Figure 10.14: Activation records for f and g from figure 10.11
If there are more than two nested scopes, pointers to all outer scopes need to be
passed as parameters t
12.5.3 Sorting by block size
To reduce the time used for searching for a sufficiently large block when calling
malloc(), we can keep free blocks sorted by size. A common strategy for this
is limiting block sizes (including size fields) to powers of two an
If we have a call power(x,5), we can replace this by a call power5(x) to a specialised
function. We now need to add a definition of this specialised function to
the program. The most obvious idea would be to take the above code for the power
A.2. SET-BUILDER NOTATION 293
A[A = A union is idempotent
A\A = A intersection is idempotent
A[B = B[A union is commutative
A\B = B\A intersection is commutative
A[(B[C) = (A[B)[C union is associative
A\(B\C) = (A\B)\C intersection is associati
_ If fs frame holds no useful information at the time we call g (or we can be
sure that g does not overwrite any useful information in the frame), we can
reuse the frame for f as the frame for g.
We will look at this in more detail belo
In this diagram, the ML-to-x86 compiler written in ARM has two roles: It is the
output of the first compilation and the compiler that runs the second compilation.
Such combinations can, however, be a bit confusing: The compiler that is the input
to the se
that occur in the program. This makes things slightly simpler later on. We call this
analysis available assignments
234 CHAPTER 11. ANALYSIS AND OPTIMISATION
It is clear that information flows from assignment forwards, so for each instruction
in the progr
in generation g+1, so blocks in the older generations are not copied very often.
Also, collection of a small generation is very fast, so pauses (though more common)
are typically much shorter. When all generations need to be collected, though, the
next := first word of to-space
last := last word of to-space
scan := next
for all heap-pointer variables p in the root set do
p := forward(p)
while scan<next do
scan := scan + the size of the node at scan
By the invariant, nodes between the s
12.7. REFERENCE COUNTING 267
size field. When a block is allocated, its counter field is set to 1 (representing the
pointer that is returned by malloc(). When the program adds or removes pointers
to the block, the counter is incremented and decremented. I
Proof: It is trivially true that /0_F( /0). Since F is monotonic, this implies F( /0)_
F(F( /0). This again implies F(F( /0) _ F(F(F( /0) and, by induction, Fi( /0) _
Fi+1( /0). So we have a chain
/0 _ F( /0) _ F(F( /0) _ F(F(F( /0) _ _ _ _
Since the univ
b) Show, using Bratman diagrams, the steps required to optimise P to P0 and
then execute P0.
290 CHAPTER 13. BOOTSTRAPPING A COMPILER
Set notation and concepts
A.1 Basic concepts and notation
A set is a collection of items. You can write a set
translate this into x86 code afterwards, but this introduces several problems:
_ Adding an extra pass makes the compilation process take longer.
_ Some efficiency will be lost in the translation.
_ We still need to make the ARM-to-x86 compiler run on the
If all Fi are distributive in all arguments, we can use a work-set algorithm similar
to the algorithm for a single distributive function.
What set is built by the set builder
fx2+y2 j x 2 f1; 2; 3; 4g; y 2 f1; 2; 3; 4g; x < y2g?
on Information Theory, IT-2(3):113124, 1956.
 J. Earley and H. Sturgis. A formalism for translator interactions. Communications
of the ACM, 13:607617, 1970.
 Peter Naur (ed.). Revised report on the algorithmic language Algol 60. Communications