Title: Designing Classes and Programs
1CSCI 435 Compiler Design
Week 7 Class 1 Section 4.2 to Section
4.2.3.2 (290-302) Ray Schneider
2Topics of the Day
- Code Generation
- Avoiding Code Generation Altogether
- The starting Point
- Trivial code generation
- Threaded Code
- Partial Evaluation
-
3Code Generation
- the systematic replacement of nodes and subtrees
of the AST by target code segments so that the
semantics are preserved - followed by a linearization phase producing a
linear sequence of instructions from the
rewritten AST - this replacement policy is termed TREE REWRITING
and is controlled by the data flow and flow of
control requirements of the target code segments - mental image of the gradual transformation of the
AST into target code without changing the
semantics is helpful
4Intermediate Code Generation andRegister
Allocation
AST
a (b4c d 2) 9 Compiler has decided
that variables are put in registers Ra, Rc, and
Rd and that the array indexing operator
has been expanded into an addition and memory
access mem.
a, c, and d are integer variables and b is a byte
array in memory
5On the machine side
- assume we have two machine instructions
- Load_Addr MRi, C, Rd loads the address of
Ri-th element of the array at M into Rd, where
the size of the elements of M is C-bytes. - Load_Byte (MRo)Ri, C, Rd loads the byte
contents of the Ri-th element of the array at M
plus offset Ro into Rd and other parameters have
the same meaning as above. - instructions are representative of Pentium
instructions leal and movabl. - We can represent these instructions in the form
of AST's as well
6Two Sample Instructions with their ASTs
Rd
Rd
mem
M
C
Ri
M
Load_Address MRi,C,Rd
Ro
C
Ri
Load_Byte (MRo)Ri,C,Rd
7Tree Rewriting 1
We want to replace the bottom right part of the
original AST by Load_Byte(bRd)Rc,4,Rt obtained
from the second instruction by equating M with
b,Ro with Rd, Ri with Rc, C with 4, and using a
temporary register Rt as the result register.
Ra
9
2
Rt
Load_Byte(bRd)Rc,4,Rt
_at_b
Replaced subtree
Rd
Rc
4
8Tree Rewriting 2
Next we replace the top part by the instruction
Load_Addr 9Rt,2,Ra obtained from the first
instruction by equating M with 9, Ri with Rt, C
with 2, and using the register Ra which holds the
variable a as the result register
Ra
Ra
9
2
Rt
Load_Address 9Rt,2,Ra
Load_Byte(bRd)Rc,4,Rt
Load_Byte(bRd)Rc,4,Rt
_at_b
Rd
After linearization we have the object code
sequence Load_Byte (bRd)Rc,4,Rt Load_Addr
9Rt,2,Ra
Rc
4
9Several Unanswered Questions
- How did we find the subtrees to be replaced?
- Where did the register Rt come from?
- Why were the instructions linearized the way they
were? - These are the THREE MAIN ISSUES
- CODE SELECTION which part of the AST to rewrite
with what template using which substitutions for
the instruction parameters? - REGISTER ALLOCATION what to keep in which
registers, especially since there are only a
limited number? - INSTRUCTION ORDERING which part of the code is
produced first and which later?
10Code Selection and Register Allocation
- All the issues are interrelated which complicates
things, but (1) and (2) are tightly coupled - decision affect the number and types of required
registers and the available registers affect the
choice of instructions - Ordering is more forgiving any ordering
consistent with the flow-of-control and data
dependencies is acceptable, but some are better
for code generation - Exhaustive analysis is required for optimal code
generation and is impractical so we need to find
ways to constrain the code generation problem
11Three traditional ways to restrict code gen
- 1) Consider only small parts of the AST at a
time, i.e. NIBBLE don't GULP - incremental compilation in narrow compilers
- 2) Assume the target machine is simpler than it
actually is by disregarding some of its
complicating features (Model code gen on some
ideal use of the more straightforward features of
the machine) - example is decision not to use complex addressing
modes - 3) Limit the possibilities in the three issues by
adopting conventions for their use - using registers in a standard way, say R1, R2,
and R3 for parameter transfer and R4 through R7
for intermediate results in expressions
12much much more ...
- Careful application of the techniques described
in these sections will yield a reasonably
optimized compiler but not more than that. - Top Quality Code Generator see book by Muchnick
(1999) - remarkable results possible through extreme
application of the first restriction called
SUPERCOMPILATION (see Section 4.2.8) - Preprocess the intermediate code and postprocess
the generated code (the latter is called PEEPHOLE
OPTIMIZATION)
13Overall structure of a code generator
- Code Generation is performed in three phases
- Preprocessing AST node patterns are replaced
by other "better" AST node patterns - Code Generation Proper AST node patterns are
replace by target code sequences - Postprocessing code sequences are replaced by
other "better" code sequences, peephole
optimization
14Options for avoiding writing a code generator
- Occasionally faking a compiler is a valid option
- IF we have an interpreter for the source language
we can combine the AST of the source program P
and the interpreter into one executable program
file, E. - "freeze" the interpreter just before it begins
the task of interpreting the AST (if the OS
allows this), or copy and combine code segments
and data structures, then calling E causes the
interpreter to interpret the AST of P and acts
like a compiled program - Main advantage is it allow rapid prototyping and
allows incremental delivery of the compiler as
one transforms from the interpreted version to a
true compiler
15The Starting Point
- Nodes mainly of three categories 1)
administration, 2) flow of control, and 3)
expressions - Administration declarations, module structure ..
- code needed for administration nodes is minimal
and almost always trivial - Flow of Control simple skipping
(if...then...else), multi-way choice
(case,switch), computed goto's, function calls,
etc. - corresponding target instructions are usually
restricted to variants of the unconditional and
conditional jump and the stacking routine call
and return - Expressions (all paradigms) will be our main
concern
16Techniques for Code Generation
- Fall into three levels of sophistication
- TRIVIAL (Section 4.2.3)
- SIMPLE (Section 4.2.4), and
- ADVANCED (Sections 4.2.5 through 4.2.7)
- Trivial Code Generation
- Strong relationship between Iterative
Interpretation and Code Generation
Iterative Interpreter (contains) Code Segments
... that perform the actions required by the
nodes in the AST.
Compiler (generates) Code Segments
17more Trivial Code Generation
- One idea for each node of the AST, generate the
code segment that the iterative interpreter
contains for it. - This replaces the active-node pointer with a
machine instruction pointer - Need to do two things (both easy to do)
- copy data structure definitions and auxiliary
routines of the interpreter into the generated
code, and - sequence the code properly in accordance with the
flow of control
18//see page 298 fig 4.12 for include files static
AST_node Active_node_pointer static void
Trivial_code_generation(void)
printf("include \"stack.h\"\nint
main(void)\n") while (Active_node_pointer !
0) / there is only one node type,
Expression / Expression expr
Active_node-pointer switch (expr-gttype)
case 'D' printf("Push(d)\n",expr-
gtvalue) break case 'P'
printf("\n\ int e_left Pop() int
e_right Pop()\n\ switch (d)\n\
case '' Push(e_left e_right)
break\n\ case '' Push(e_left
e_right) break\n\ \n",
expr-gtoper ) break
Active_node_pointer Active_node_pointer-gtsucc
essor printf("printf(\"d\\n",Pop())
/ print the result / printf("return
0\n") void Process(AST_node icode)
Thread_AST(icode) Active_node_pointer
Thread_start Interpret_iteratively()
Each case part now consists of a single print
statement.
A trivial code generator for the demo compiler
figure 4.12
19Code for (7(15)) generated by the code generator
include "stack.h" int main(void)
Push(7) Push(1) Push(5) int e_left
Pop() int e_right Pop() switch (43)
case '' Push(e_left e_right) break case
'' Push(e_left e_right) break
int e_left Pop() int e_right Pop()
switch (42) case '' Push(e_left
e_right) break case '' Push(e_left
e_right) break printf("d\n", Pop()) /
print the result / return 0
- Several Points
- Compilation has really taken place
- Code Generator was obtained with minimal effort
- Process is easily repeated for more complicated
source languages
figure 4.13
20Threaded Code
- pack the code segments into routines possibly
with parameters - resulting code comes from a library of routines
derived directly from the interpreter, and list
of routine calls derived from the sources program - List of routine calls is called THREADED CODE
- Characteristic advantage of threaded code is that
it is SMALL, ex. the Forth Language is one of the
pioneers of threaded code
21int main(void) Expression_D(7)
Expression_D(1) Expression_D(5)
Expression_P(43) / 43 ASCII for ''/
Expression_P(42) / 42 ASCII for ''/
Print() return 0 include "stack.h" void
Expression_D(int digit) Push(digit) void
Expression_P(int oper) int e_left Pop()
int e_right Pop() switch (oper) case
'' Push(e_left e_right)break case ''
Push(e_left e_right)break void
Print(void) printf("d\n",Pop())
Possible threaded code for (7(15))
Routines for the threaded code for (7(15))
22the ultimate ...
- code size reduction can be reduce to the ultimate
by simply numbering the routines and replacing
the list of calls with an array of routine
numbers - each routine has a known number of parameters and
all parameters derive from fields in the AST and
are thus constants known to the code generator - Only need a tiny number of primitive routines (20
or so) that - load and store variables, perform arithmetic and
Boolean operations, effect jumps, etc.
23Partial Evaluation
- the process of performing part of a computation
while generating the code for the rest of the
compilation is called PARTIAL EVALUTATION - code generators can execute some of the code and
generate the rest, ex. switch statements over
constant values can be pre-executed - Many of the existing optimization techniques are
just special cases of partial optimization in the
opinion of many researchers - There is an Escher-like quality to it since it
contrasts foreground (run-now) with background
(run-later) code
24Run-now ... Run-later
case 'P' printf("\n\ int e_left
Pop() int e_right Pop()\n" ) switch
(expr-gtoper) case ''printf("Push(e_left
e_right)\n")break case ''printf("Push(e_le
ft e_right)\n")break printf("\n")
break
RUN-NOW
case 'P' printf("\n\ int e_left
Pop() int e_right Pop()\n" ) switch
(expr-gtoper) case ''printf("Push(e_left
e_right)\n")break case ''printf("Push(e_le
ft e_right)\n")break printf("\n")
break
RUN-LATER
25Next time ...
- 4.2.4 Simple Code Generation to
- 4.2.4.3 Compilation on the stack/compilation by
symbolic interpretation
26Homework for Week 9
- Bison Deeper In
- Continue with section 3.3 Practice, Part II
beginning on page 17 of Niemann's "A Compact
Guide to Lex and Yacc."
27References
- Text Modern Compiler Design Figures