Compiler Overview


Contents

Introduction

What is a compiler?

Here's a simple pictorial view:
	source program --> COMPILER --> object program
                              |
			      --> error messages
A compiler is itself a program, written in some host language. (In cs536, students will implement a compiler for a simple source language using Java as the host language.)

A compiler operates in phases; each phase translates the source program from one representation to another. Different compilers may include different phases, and/or may order them somewhat differently. A typical organization is shown below.

Below, we look at each phase of the compiler.

The Scanner

The scanner is called by the parser; here's how it works:

The definitions of what is a lexeme, token, or bad character all depend on the source language.

Example

Here are some Java lexemes and the corresponding tokens:

Note that multiple lexemes can correspond to the same token (e.g., there are many identifiers).

Given the source code:

a Java scanner would return the following sequence of tokens: Erroneous characters for Java source include # and control-a.

The Parser

The parser:

Example

Notes:

The Semantic Analyzer

The semantic analyzer checks for (more) "static semantic" errors, e.g., type errors. It may also annotate and/or change the abstract syntax tree (e.g., it might annotate each node that represents an expression with its type). Example:

        Abstract syntax tree before semantic analysis

                           =
                          / \
                         /   \
                 position     +
                             / \
                            /   \
                     initial     *
                                / \
                               /   \
                           rate     60


        Abstract syntax tree after semantic analysis


                           = (float)
                          / \
                         /   \
                 position     + (float)
                 (float)     / \
                            /   \
                     initial     * (float)
                     (float)    / \
                               /   \
                           rate     intToFloat (float)
                                        |
                                        |
                                        60 (int)

The Intermediate Code Generator

The intermediate code generator translates from abstract-syntax tree to intermediate code. One possibility is 3-address code (code in which each instruction involves at most 3 operands). Below is an example of 3-address code for the abstract-syntax tree shown above. Note that in this example, the first three instructions each have exactly three operands (the location where the result of the operation is stored, and two operators); the fourth instruction has just two operands ("position" and "temp3").

		temp1 = inttofloat(60)
		temp2 = rate * temp1
		temp3 = initial + temp2
		position = temp3

The Optimizer

The optimizer tries to improve code generated by the intermediate code generator. The goal is usually to make code run faster, but the optimizer may also try to make the code smaller. In the example above, an optimizer might first discover that the conversion of the integer 60 to a floating-point number can be done at compile time instead of at run time. Then it might discover that there is no need for "temp1" or "temp3". Here's the optimized code:

		temp2 = rate * 60.0
		position = initial + temp2

The Code Generator

The code generator generates object code from (optimized) intermediate code. For example, the following code might be generated for our running example:


		LOADF	rate,R1
		MULF	#60.0,R1
		LOADF	initial,R2
		ADDF	R2,R1
		STOREF	R1,position