The Intermediate Language
\(dgUNIX is a Trademark of Bell Laboratories.
Communication between the two phases of the compiler proper
is carried out by means of a pair of intermediate files.
These files are treated as having identical structure,
although the second file contains only the code generated for strings.
It is convenient to write strings out separately to reduce the
need for multiple location counters in a later assembly
The intermediate language is not machine-independent;
its structure in a number of ways reflects
the fact that C was originally a one-pass compiler
chopped in two to reduce the maximum memory
In fact, only the latest version
of the compiler has a complete
intermediate language at all.
Until recently, the first phase of the compiler generated
assembly code for those constructions it could deal with,
and passed expression parse trees, in absolute binary
to the second phase for code generation.
Now, at least, all inter-phase information
is passed in a describable form, and there are
no absolute pointers involved, so the coupling between
the phases is not so strong.
The areas in which the machine
(and system) dependencies are most noticeable are
Storage allocation for automatic variables and arguments
has already been performed,
and nodes for such variables refer to them by offset
(for example, from integer to pointer)
has already occurred using the assumption of
byte addressing and 2-byte words.
Data representations suitable to the PDP-11 are assumed;
in particular, floating point constants are passed as
four words in the machine representation.
As it happens, each intermediate file is represented as a sequence
of binary numbers without any explicit demarcations.
It consists of a sequence of
conceptual lines, each headed by an operator, and possibly containing
The operators are small numbers;
to assist in recognizing failure in synchronization,
the high-order byte of each operator word is always the
either 16-bit binary numbers or strings of characters representing names.
Each name is terminated by a null character.
There is no alignment requirement for numerical
operands and so there is no padding
The binary representation was chosen to avoid the necessity
of converting to and from character form
and to minimize the size of the files.
It would be very easy to make
each operator-operand `line' in the file be
a genuine, printable line, with the numbers in octal or decimal;
this in fact was the representation originally used.
The operators fall naturally into two classes:
those which represent part of an expression, and all others.
Expressions are transmitted in a reverse-Polish notation;
as they are being read, a tree is built which is isomorphic
to the tree constructed in the first phase.
Expressions are passed as a whole, with no non-expression operators
The reader maintains a stack; each leaf of the expression tree (name, constant)
each unary operator replaces the top of the stack by a node whose
operand is the old top-of-stack;
each binary operator replaces the top pair on the stack with
When the expression is complete there is exactly one item on the
Following each expression
is a special operator which passes the unique previous expression
to the `optimizer' described below and then to the code
Here is the list of operators not themselves part of expressions.
marks the end of an input file.
.Op BDATA "flag data ..."
specifies a sequence of bytes to be assembled
It is followed by pairs of words; the first member
of the pair is non-zero to indicate that the data continue;
a zero flag is not followed by data and terminates
The data bytes occupy the low-order part of a word.
.Op WDATA "flag data ..."
specifies a sequence of words to be assembled as
static data; it is identical to the BDATA operator
except that entire words, not just bytes, are passed.
means that subsequent information is to be compiled as program text.
means that subsequent information is to be compiled as static data.
means that subsequent information is to be compiled as unitialized
is an external name defined in the current program.
It is produced for each external data or function definition.
indicates that the name refers to a data area whose size is the
specified number of bytes.
It is produced for external data definitions without explicit initialization.
bytes should be set aside for data storage.
It is used to pad out short initializations of external data
and to reserve space for static (internal) data.
It will be preceded by an appropriate label.
external data definition whose size is not
an integral number of words.
It is not produced after strings except when they initialize
is produced just before a BDATA or WDATA initializing
external data, and serves as a label for the data.
is produced just before each function definition,
and labels its entry point.
is produced at the start of each function for each static variable
Subsequent uses of the variable will be in terms of the given number.
The code generator uses this only to produce a debugging symbol table.
Likewise, each automatic variable's name and stack offset
is specified by this operator.
Arguments count as automatics.
Each register variable is similarly named, with its register number.
produces a register-save sequence at the start of each function,
just after its label (RLABEL).
is used to indicate the number of registers used
It actually gives the register number of the lowest
free register; it is redundant because the RNAME operators could be
is produced before the save sequence for functions
when the profile option is turned on.
It produces code to count the number
of times the function is called.
.Op SWIT "deflab line label value ..."
is produced for switches.
When control flows into it,
the value being switched on is in the register
forced by RFORCE (below).
The switch statement occurred on the indicated line
of the source, and the label number of the default location
Then the operator is followed by a sequence of label-number and value pairs;
the list is terminated by a 0 label.
generates an internal label.
It is referred to elsewhere using the given number.
indicates an unconditional transfer to the internal label number
produces the return sequence for a function.
It occurs only once, at the end of each function.
causes the expression just preceding to be compiled.
The argument is the line number in the source where the
.Op NAME "class type name"
.Op NAME "class type number"
indicates a name occurring in an expression.
The first form is used when the name is external;
the second when the name is automatic, static, or a register.
Then the number indicates the stack offset, the label number,
or the register number as appropriate.
Class and type encoding is described elsewhere.
transmits an integer constant.
This and the next two operators occur as part of expressions.
.Op FCON "type 4-word-value"
transmits a floating constant as
four words in PDP-11 notation.
transmits a floating-point constant
whose value is correctly represented by its high-order word
indicates a null argument list of a function call in an expression;
call is a binary operator whose second operand is the argument list.
produces a conditional branch.
It is an expression operator, and will be followed
The branch to the label number takes place if the expression's
truth value is the same as that of
and the expression evaluates to true, the branch is taken.
There are binary operators corresponding
to each such source-language operator;
the type of the result of each is passed as well.
Some perhaps-unexpected ones are:
COMMA, which is a right-associative operator designed
to simplify right-to-left evaluation
prefix and postfix ++ and \-\-, whose second operand
is the increment amount, as a CON;
QUEST and COLON, to express the conditional
and a sequence of special operators for expressing
relations between pointers, in case pointer comparison
is different from integer comparison
There are also numerous unary operators.
These include ITOF, FTOI, FTOL, LTOF, ITOL, LTOI
which convert among floating, long, and integer;
JUMP which branches indirectly through a label expression;
INIT, which compiles the value of a constant expression
RFORCE, which is used before a return sequence or
a switch to place a value in an agreed-upon register.