5.11

Code Generation

334

In order to be able to assemble an instruction, the compiler must know its operation code and its parameter, which is a literal number or an address. These values are associated by the compiler itself with the respective identifiers. This association is performed upon processing the declaration of constants, variables, and procedures. For this purpose, the table containing the identifiers is expanded to contain the attributes associated with each identifier. If an identifier denotes a constant, its attribute is the constant value; if the identifier denotes a variable, the attribute is its address, consisting of a displacement and a level; and if the identifier denotes a procedure, then its attributes are the procedure’s entry address and its level. The corresponding extension of the declaration of the variable table is shown in Program 5.6. It is a noteworthy example of a stepwise refinement (or enrichment) of a data declaration progressing simultaneously with the refinement of the statement part.

Whereas the constant values are provided by the program text, it is the compiler’s task to determine addresses on its own. PL/0 is sufficiently simple to make sequential allocation of variables and code the obvious choice. Hence, every variable declaration is processed by incrementing a data allocation index by 1 (since each variable occupies by definition of the PL/O machine exactly one storage cell). The data allocation index dx is to be initialized upon starting the compilation of a procedure, reflecting the fact that its data segment starts empty. [Actually, dx is given the initial value 3 since each data segment contains at least the three internal variables RA, DL, SL (see preceding section).] The appropriate computations to determine the identifiers’ attributes are included in the procedure enter which is used to enter new identifiers into the table.

With this information about operands at hand, generating the actual code is a rather simple affair. Because of the convenient stack organization of the PL/O machine, there exists practically a one-to-one correspondence between operands and operators in the source language and instructions in the target code. The compiler has merely to perform a suitable resequencing into postfix form. By “postfix form” is meant that operators always follow their operands instead of being embedded between the operands as in the conventional infix form. The postfix form is sometimes also called Polish form (after its originator Lukasciewicz) or parenthesis-free form since it makes parentheses superfluous. Some correspondences between infix and postfix forms of expressions are shown in Table 5.4 (see also Sect. 4.4.2).

The very simple technique of performing this transformation is shown by the procedures expression and term in Program 5.6. It is merely a matter

335
Table 5.4 Expressions in Infix and Postfix Form.

of delaying the transmission of the arithmetic operator. At this point the reader should verify that the presented arrangement of parsing procedures also takes care of an appropriate interpretation of the conventional priority rules among the various operators.

A slightly less trivial matter is the translation of conditional and repetitive statements. In this case the generation of jump instructions is necessary, for which at times the destination address is still unknown. If one insists on a strictly sequential production of instructions in the form of an output file, then a two-pass compiler scheme is necessary. The second pass then assumes the task of supplementing the incomplete jump instructions with their destination addresses. An alternative solution adopted by the present compiler is to place the instructions into an array and essentially retaining them in directly accessible store. This method allows supplementing the missing addresses as soon as they become known. This operation is commonly called a fixup.

The only additional operation that has to be performed when issuing such a forward jump is to retain its location, i.e., its index in the program store. This address is then used to locate the incomplete instruction at the time of the fixup. The details are again evident from Program 5.6 (see routines processing if- and while statements). The patterns of code generated for the if- and while statements are as follows (L1 and L2 stand for code addresses):

display

For convenience, an auxiliary procedure called gen is introduced. Its purpose is to assemble and emit an instruction according to its three parameters. It automatically increments the code index cx which designates the location of the next instruction to be issued.

336

As an example, the code emitted by compiling the multiplication routine (5.14) is listed below in mnemonic form. The comments on the right-hand side are merely added for explanatory purposes.

display

Many tasks in compiling programming languages are considerably more complex than the ones presented in the PL/O compiler for the PL/O machine [5-4]. Most of them are much more resistant to being neatly organized. The reader trying to extend the presented compiler in either direction toward a more powerful Janguage or a more conventional computer will soon realize the truth of this statement. Nevertheless, the basic approach toward designing a complex program presented here retains its validity, and even increases its value when the task grows more complicated and more sophisticated. It has, in fact, been successfully used in the construction of large compilers [5-1 and 5-9].

337
Program 5.6 PL/0 Compiler.
338
Program 5.6 (Continued)
339
Program 5.6 (Continued)
340
display
341
Program 5.6 (Continued)
342
Program 5.6 (Continued)
343
display
344
display
345
Program 5.6 (Continued)
346
Program 5.6 (Continued)
347
Program 5.6 (Continued)
↑ Up