The design of the GIER ALGOL compiler

The design of the GIER ALGOL compiler

The Design of the GIER ALGOL Compiler PETER Regnecentralen, NAUR Copenhagen AssraAcT-The report gives a full description of the design of an ALGOL ...

3MB Sizes 54 Downloads 124 Views

The Design of the GIER ALGOL

Compiler PETER Regnecentralen,

NAUR Copenhagen

AssraAcT-The report gives a full description of the design of an ALGOL 60 system for the GIER, a machine having 1024 words of core store and 12,800 words on drum. An introductory section gives the historical perspective of the design and the principal characteristics of the machine. The second section discusses the problems of the running ALGOL-program: storage allocation and addressing of variables, program references, procedure calls, and the automatic administration of transfers of program segments from the drum to the core store. The third section discusses the storage problem of the translator and the resulting multipass design. The fourth section describes some of the methods which have been found useful in writing the translator. The last section gives the size of the final system and some notes on its performance. 1.

INTRODUCTION

GIER ALGOL is an ALGOL compiler written for the medium size computer GIER manufactured by Regnecentralen, Copenhagen. The compiler was first distributed in a slightly restricted form in September 1962. A final version was distributed in February 1963. By this time it had become the dominating programming language for the machine in most of the installations. The language of GIER ALGOL is ALGOL 60 except for the omission of integers as labels, arrays called by value and own arrays. Input and output (including transfers of variables between the magnetic drum and the core store) are controlled by means of standard procedures. A full description of the language is given in the published manual.2 In the following the design of the system and compiler is discussed in some detail. Some information on the performance is also given. Since the purpose is to give a clear picture of the over-all design the discussion is not confined to the points where the system deviates from designs described elsewhere in the literature. 4

49

50

Peter Naur

The GIER ALGOL design has been influenced by a variety of other projects. To put the description in its proper perspective it is helpful to give a brief historical account of the development of our ideas during the years 1959-1961. Our first exposure to compiler techniques came from Professors F, L. Bauer and K. Samelson of the University of Mains, Germany, the leaders of the ALCOR group. In June 1959 P. Mondrup and W. Heise of Regnecentralen spent a few days at Mainz and had the opportunity of studying the sequential techniques developed there.g These techniques were adapted to our machine DASK by P. Mondrup and form the main frame of the DASK ALGOL compiler compfeted in late 1961 a In the meantime we became increasingly aware of the problems inherent in the ALGOL procedure concept and of the incompleteness of the description of procedures given in the Zurich ALGOL report (ALGOL 58). We raised this problem within the ALCOR group in December 1959, but were d~sappo~ted to find that the other members of this group did not seem prepared to take a common stand in the problem. Fortunately the problem was cleared in ALGOL 60 and during the time which followed we developed the scheme for handling the ALGOL 60 name concept which was used in DASK ALGOL3 In March 1960 we opened an active personal contact with Professor A, van Wijngaarden, Dr. E. W. Dijkstra, and Mr. J. A. Zonneveld of the Stichting Mathematical Center of Amsterdam, Netherlands, who came to spend a few days of informal discussions with us. These discussions showed that both groups independently had arrived at the same conclusion, viz. that the design of the system which administers the run-time requirements of the ALGOL program (storage allocation aad procedure calls) must be considered the primary problem while the translator is a secondary problem. The Dutch group impressed us greatly by their very general approach. However, although they were prepared to put their soiution of the problem of recursive procedures at our disposal we decided to stick to the more modest approach which we had already developed to some extent. The reasons for this reluctance were practical. First of all we felt the size of the problem to be already rather frightening, in particular because we still had to face the problem of the two levels of store in DASK, and also because we wished to include far more error detecting capability in our compiler than the Dutch. Also at that time we feared the loss of running

Besign of the Gier

Algol G~~iler

51

speed of a system which included recursive procedures (a fear which we now know was unfounded). The first news of the success of the Dutch project, in June 1960, fell like a bomb in our group. However, we quickly regained spirits and during July to September completed the design and coding of the DASK ALGOL running system, including run-time facilities for drum transfers of program and variables and the set of standard functions and input and output procedures.5 This system was wired into DASK as a new fixed store and was completed in this form in January 1961. In the meantime the coding of the DASK ALGOL compiler itself had proceded, although at a much slower speed than anticipated. As already mentioned it is based on the Mainz principles, but the adaptation to DASK had required important modifications. Essentially it is a three pass compiler, using three tape units, one of them holding the compiler, the two others being used to store the partially translated program. The first pass performs a rather modest preprocessing, mainly taking care of the peculiarities of the paper tape hardware representation. The second pass performs almost all the translation, keeping all its tables on the drum. The third pass completes the addressing, which is a very quick process, This compiler was not completed until about September 1961. Already during the Iater phases of the coding of DASK ALGOL other approaches were considered, as a preliminary to coming attempts. When the idea of the syntax-directed-compiler by Irons6 was published, some of us got very excited about it. From about December 1960 to June 1961 we had Mr. B. Mayoh working on using this for a compiler for the GIER which was then in its last phases of development. However, after this trial we decided that, as far as we were concerned, the approach was a mistake. Other contacts included a visit of the present writer to Amsterdam in April 1961 during which Edsger Dijkstra supplied me with all desired details of their method of addressing at run time and of scanning the source program.’ Another decisive influence came from the Storage Allocation Symposium in Princeton in June 1961 where Jorn Jensen learned about the dynamic storage allocation scheme planned for the Ferranti Atlas.8 Finally, during a stay at the University of North Carolina, Chapel Hill, during the later half of 1961 the present writer had the chance to reconsider the complete problem of translating ALGOL 60. The most important result of this was an approach to the problem of analysing and checking the source text, which although related to wellknown methods, in particular Grau’s version of the Bauer-Samelson method, l* yet has some merit of its own (see the Turing machine approach

Peter Naur

52 below). A further used in the U.S. expressions during This takes us ALGOL compiler

result of this visit was the exposure to the idea widely of using a Polish notation as an intermediate form of translation. *3 to January 1962 when it was decided that a GIER should be written.

2.2. Characteristicsof GIER The GIER is a machine manufactured by Regnecentralen. It has also been marketed as DISADEC. The following figures refer to the minimum configuration for which the ALGOL compiler was primarily designed. For further details see Ref. 1. Stores. Core store: 1024 words of 42 bits, access time 8 ~8 microseconds. Magnetic drum: 320 tracks of 40 words each. Transfer time of complete track: 20 milliseconds. During drum transfers other operations may take place in the machine. Word structure: Of the 42 bits two are regarded as marks and are not processed in parallel with the rest of the word. Floating point operations divide the word into a 10 bit exponent and a 30 bit mantissa while the marks are irrelevant. Order structure: The marks in each word select the order interpretation modes: (a) One instruction of 40 bits or two instructions of 20 bits in the word, (b) fixed or floating operation. Addressing facilities : There is one normal index register, the pregister, and an index register which is also coupled to the subroutine return mechanism, the s-register. In addition the address may be relative to the order counter, r-relative addressing. Indirect addressing is provided. Instructions of 40 bits may also include an incrementing of the address. Operation times: Fixed point operations range from 36 to 50 psec. 170 psec, Floating-point operations : addition 100 psec, multiplication division 220 psec. Input: 8-hole paper tape, reading at 500 characters per second. Output: 8-hole paper tape, punched at 150 characters per second. 2.3. Aims and methods The aims of the project were roughly as follows: 1. GIER ALGOL should be a practical working system, taking full advantage of the machine as far as this is compatible with (a) the generality of ALGOL 60, and (b) a dead line on the completion of a workable compiler of 1 September 1962. 2. It should include virtually complete error detection of the source

ofthe

Design program,

and should be capable

compilation

Gier Algol Compiler of finding

53

any number

of errors in one

run.

The methods adopted for achieving

these aims are as follows:

1. The design centres around a dynamic storage allocation in a stack, basically similar to the design of Dijkstra.‘, lo 2. The storage of program of transfers integrated

of program

is handled

by an automatic

of variables

administration

tracks to the core store at run time, completely

with the stack administration.

3. The design of the translator again is based on storage allocation considerations. These indicate that to achieve speed many internal passes should be used, employing

the drum for storing the partially

program, while the program and tables of each enough to be stored completely in the core store. 4. During

translation,

error

handling

translated

pass should

is integrated

be small

with the trans-

lation, i.e. the occurrence of an error is not considered the exception but will be handled by the same kind of logic as is used for any regular language

feature.

All these methods are discussed in greater detail in the remaining

part

of the present report.

3. THE

RUNNING

The running translated

SYSTEM

system is the fixed administration

ALGOL

program

while the program

which is used by any is executed.

It occupies

the last 200 words of the core store and its most important

tasks are the

handling

of procedure

of the dynamic storage allocation

and the execution

calls. The running

system must be defined before the translator

since it is an integral previous experience is : storage allocation, 3.1.

part of the definition

is written

of the target language.

Our

shows that the proper order to decide on the solution addressing,

procedure

call.

The non-homogeneous store In a machine

with a core store of 1024 words of 42 bits and a backing

drum of 12,800 words some method of transferring

the translated

ALGOL

program and its variables between the two media must be found. In DASK ALGOL a system based on explicit information supplied by the programmer was used for both program and data.* In GIER ALGOL the transfers of variables are again fully under the control of the programmer, although the tools placed at his disposal, standard procedures, are very different from those incorporated in DASK ALGOL. Transfers of

54

Peter Naur

program in GIER ALGOL, on the other hand, are done automatically by the system. The decision to provide automatic transfers of program, but not of variables, is based on the following considerations: 1. The programmer will be fully aware of the storage demands made by the variables of his program, but will have only a very inaccurate knowledge of the length of the code needed to represent the algorithms. Therefore the programmer’s task of specifying the transfers of variables is much easier than the specification of transfers of program. 2. Transfers of variables to and from drum may be regarded as input/ output operations and will therefore be analogous to operations which the programmer will have to learn and use anyway. Any conventions for transfers of program sections will constitute a unique addition to the conventions which the programmer will have to learn. 3. In a sensible system the machine instructions will be constant throughout the run of the program. For the implementor the transfers of program are simpler because no question of saving a piece of program which has been copied from drum to cores ever arises. 3.2. Storage and addressing of uariables Since all variables which can be referenced directly by the ALGOL program will be stored in the core store, the familiar stack arrangement can be usedm7plo This is inherently a very economical storage principle. However, to make full use of the economy of the arrangement, the scheme chosen for utilizing the storage space left unused by the stack must be able to follow the variations of the size of the stack during the run of the This was a decisive factor in the design of the system for program. allocation of program described below. In the addressing system developed by Dijkstra7p lo two kinds of variables are distinguished: (a) Named variables, addressed through their block number and block relative address; (6) Anonymous variables, addressed relative to the current top of the stack. In Dijkstra’s system and in DASK ALGOL4 anonymous variables are used wherever the identity of a variable is given completely through the structure of the ALGOL text. In a machine having built-in floating point operations, but no special facility for working at the top of a stack, like GIER, references to named variables can usually be performed much faster than references to anonymous variables (see below). For this reason the GIER ALGOL translator has eliminated references to anonymous variables as far as possible. This is achieved by replacing anonymous variables by internally named local quantities. This will cause a certain slight waste of working locations,

Design of the Gier Algol Compiler

55

since each block will have to reserve as many locations as are used at any one point of it. The only case where this replacement of anonymous variables by local ones is not possible is that in which an anonymous quantity is handed over from one block to another and therefore must be addressed relative to the universal stack top pointer. The simplest example of this is the return information which is generated whenever the program transfers control to a procedure or a formal parameter called by name The return information is used by which is equivalent to a procedure. that procedure to transfer the control back again (see the section on program points below). The basis of the addressing of named variables is the recognition that at any moment during the execution of a program the variables which may be referenced directly are exactly those which are local to the youngest incarnations of the lexicographically enclosing blocks. The relative addresses of the variables within a block are finally calculated during translation. In order to obtain the absolute address of a variable we only have to add what we call the “stack reference” of the corresponding activation of the block to the relative address. The absolute addresses of all accessible variables may therefore be calculated if the program has available the block numbers and relative addresses of the variables on the one hand, and the “stack reference” of the youngest incarnations of the enclosing blocks on the other. These latter are held in the table called the DISPLAY, lo In an obvious notation we have for a variable described by its block number and relative address: stack reference = DISPLAY

[block number]

absolute address = relative address + stack reference

In the final machine code it is more convenient to replace the block number by the so-called “DISPLAY reference” which is the absolute address of the location which holds the stack reference. Therefore we have stack reference = store [DISPLAY

reference]

This scheme requires that the DISPLAY be up-dated whenever the control changes to a different block environment. The basis for doing this is given below in the section on program points. In a machine having built-in floating point operations, like GIER, the address calculations according the above scheme must be done by some very direct method if the use of an undesirably high proportion of the running time for this purpose shall be avoided. The solution used in GIER ALGOL consists in using the two index registers as follows : (a) Variables

in the outermost

block.

For these variables

the stack

56

Peter h’aur

reference will be constant throughout the program and the compiler will be able to calculate the final absolute machine addresses, (b) Variables in the currently local block. The stack reference of the local block is at all times available in the index register p, as well as in the appropriate element of the DISPLAY. References to local variables can therefore be made by means of p-relative addresses. (c) Variables in intermediate blocks. These are addressed relative to the index register s. The s-register is used for other purposes as well. However, the compiler is able to keep track of these uses and will be in a position to insert explicit machine instructions of the form s : = store[DISPLAY reference] where necessary. The decision to use the index registers in this way is based on the observation that in practical programs, in particular published procedures, the overwhelming majority of identifiers refer to local quantities. 3.3. Program aoin ts No part of the translated

program can be executed unless the DISPLAY is up-dated so as to contain the stack references of the youngest incarnations of the lexicographically enclosing blocks. The up-dating is performed on the basis of the chains of stack references which form a part of the block information stored in the stack at all bIock relative addresses 0 and 1. In fact, each time a block is entered the DISPLAY reference corresponding to its block number and the stack reference of the youngest incarnation of its innermost enclosing block are placed at relative address 0 of the newly reserved section of the stack. By these means an up-dating of the DISPLAY to make it correspond to the newly established environment may be performed at any time while this incarnation of the bIock is still active if only the value of the stack reference is supplied. The algorithm is as follows (I owe this improved form of the arrangement, which only uses one input parameter, to a personal communication from E. W. Dijkstra in February 1962): j:=

DISPLAY

for

sr:=

ref erence Dart (store[.stack reference]) ;

stack reference, stack reference part (store[sr]) while j < DISPLAY reference of block 0 do

begin storefj]:=

An alternative description ALGOL is the following:

sr;j:=j

+

1 end;

which lies closer to the realization

switch UPDATE:

= updO, updl, upd2, . . . ;

in GIER

57

Design of the Gier Algol Compiler sr : = stack reference; go to UPDATE[ DISPLAY . upd2 :

updl: upd0 :

.

I + DISPLAY

reference part (store[sr])]

reference of block 0 ;

.

DISPLAY [2] : = sr, sr : = stack reference part(store[sr]) DISPLAY[l] := sr; sr: = stack reference part(store[sr]) DISPI;AY [0] : = sr;

; ;

The switch UPDATE must have as many elements as there are lexicographically enclosing blocks in the program. This number is determined by the compiler. When the control is transferred from one point in the translated ALGOL program to another it is in general necessary to up-date the DISPLAY to correspond to the new environment. Consequently, in order to specify a transfer of control we must in general supply both the static description of the destination (segment track number and relative address, see the description of program allocation below) and a dynamic description of its environment, the stack reference. This set of three numbers, track number, track relative address, and stack reference, together define what we call a program point. Transfer of control within the same block need not use the general program point specification since the DISPLAY remains unchanged. This state of affairs may easily be recognized by the translator in the practically most important cases, jumps created by if and for clauses. In GIER ALGOL extensive use is made of this possibility of simplifying the execution. 3.4. The local declaration Every explicitly Program points are treated as a kind of variables. named point of a block (labelled points and entries into the bodies of local procedures) will have a location in the stack allocated to it, like the location where the value of a variable is kept. When the block is entered all the locations of program points are initialized. This is done by a subroutine call, the so-called local declaration, placed at the beginning of each block. The local declaration contains information on how may locations must be reserved in the stack and gives the static description of each named program point. Program points which are essentially similar to the explicitly named ones are generated in procedure calls, as described below. Switch declarations are also taken care of in the local declaration, but are not described in detail here. The complete machine formats of the local declaration are given in Ref. 2, appendix 2.

58

Peter Naur

3.5. Descri’tions

of quantities

The use of locations program

in the stack for holding the descriptions

points is one case of the more general

quantities

of named

use of descriptions

which extends to array and switch identifiers.

The

description

of an array

identifier

comprises

(1)

an

absolute

address which points to the point in the stack where the so-called vector”

of

“dope

is stored, and (2) another absolute address which is a base address

of the elements.

All array identifiers

of the same array segment share the

same dope vector. Array identifier descriptions are placed in the stack by the explicit code representing the array declaration. A switch

identifier

is described

by a word giving

(1) the absolute

address of the first word of a list of the elements kept in the stack, and (2) the number of switch elements. descriptions

of the associated

The switch identifier switch elements

descriptions

are placed in the stack by

the local declaration. An important factor in the choice of the formats of identifier tions is the requirements

of the procedure

lead to the introduction

of descriptions

and the

call.

descrip-

These requirements

of simple variables,

also

as explained

more fully below. 3.6. Procedure calls The central procedure meters.

problem

of the procedure

call is to communicate

body a sufficient amount of information This information

each parameter,

is transmitted

the so-called

via locations

formal locations.

proceeds as follows : The translator has transformed

in the stack, one for

In detail GIER

each procedure

to the

about the actual paraALGOL

call into a call of a sub-

routine followed by words describing the actual parameters and in addition giving the static description of the point following the complete reference and call, the return point. A last word gives the DISPLAY block relative address of the location in the stack which holds the description of the entry point of the procedure being called. The possible forms of words used as parameters (see also Ref. 2, appendix ment in the call are the following: 1. Constant. The value of the constant

2) and their treat-

is given and is transmitted

location. 2. Described in stack. The call supplies the DISPLAY location in the stack. This location

to the formal

reference and relative address of a will hold a description of a quantity

(see above). In the call the description is copied into the formal location. This form is used for array, switch and procedure identifiers, and for formal parameters. 3. Static program point. The call supplies a program segment track number and a track relative address. In the call the full program point is formed and put into the formal location. This is used for actual parameters which are compound expressions and for the return point. The codes representing compound expressions (thunks, see Ref. 11) have been placed at the end of the call proper by the translator. 4. Simple variable. The call supplies the DISPLAY reference and relative address of the variable. The call calculates the absolute address and puts it in the formal location. The procedure call subroutine will transform the descriptions of the actual parameters and transmit them to the stack one by one. In doing so it need not refer to the procedure to be called. The procedure body will be able to identify each formal location by using the top of the stack as reference point. In fact, since our stack starts at the high address end of the store the description of the return point will be found at the address “last used in stack” while the description of actual parameter no. p is found at the address “last used in stack” + p. 3.7. Referrirlg to formal parameters The outstanding advantage of using explicit stored descriptions of the quantities of the program, including the program points, is that the translated ALGOL program need not distinguish between formal and non-formal versions of the quantities. Indeed, once a quantity description has been placed in a formal location the procedure body may refer to it exactly as though it had been non-formal. The only formal parameters which receive a special treatment when referred to from the procedure body are those which are specified as integer, real, Boolean or label. It may be noted that the reason for this anomaly is that we do not wish to treat simple non-formal variables through a general administratioI1 based on descriptions-t~s would entail an undue loss of storage space and execution speed. When referring to a simple formal parameter called by name a routine in the running system will examine the description of the actual parameter given in the formal location in the stack. Depending on the description the actual parameter is one of three things: (1) a constant, (2) a simple variable, or (3) an

Peter Naur

60

expression. The contents of the formal location in the three cases is: (1) the value of the constant, (2) the absolute address of the variable, and (3) the description ofthe program point where the actual parameter expression code (thunk) starts. The routine is expected to place the address of the actual parameter in the so-called universal address. This is simple in cases (1) and (2). I n case (3) the routine must form the complete program point description of the point following the reference to the formal and pIace it at the top of the stack and transfer control to the point described in the formal location, the thunk. The code representing the thunk will then perform its task and will place the appropriate address in the universal address and will finally return to the point described at the top of the stack. 3.8. Arithmetic expressioons and subscripts The running system is rather simple-minded about arithmetic expressions. All arithmetic variables are represented as floating point numbers within the machine. The difference between integer and real types only makes itself felt in round-off operations. Likewise subscripting is treated by the most straightforward method : the machine code evaluates the subscripts one by one and forms the product sum of these values and the coefficients kept in the dope vector, using floating point operations throughout. A g-word subroutine in the running system then calculates the final machine address and checks that it lies within the proper bounds. 3.9. Storage of program

In trying to automize the transfers of program segments from the drum to the core store it is tempting to make use of the segme~ta~on of the program which is defined in the block structure. The block structure was in fact used in this manner in DASK ALG0L.4 However, we have since then realized that this approach has serious disadvantages. The point is that a block by definition is a unit with respect to the scope of its identifiers, However, what we need for segmentation is the dynamic units of the program. These are rather dificult to detect at translate time. In simple cases the most important dynamic units are small loops comprising much less than a block. In fact, the first part of a block is usually just initialization which is devoid of interest as soon as the real work begins, A more complicated situation is shown in the following fragment of a program which will be a dynamic unit while the solution of the equation Lf2

+ (1 -

(C + 1) xln((s

+ X)/z>x_JJ = 0

Design of the Gier Algol Compiler

61

is being found by bisections: .

.

.

procedure

Bisection(F,

begin.

. .

for q: = q/2 while x:=

x, . . ) ; real F, x; . . .

(ifF

&s(q)

> 0 then

> eps do q else -q)

+ x

end; BiJCCtio,2(2+2 .

.

+

(1 -

(Z + 1) Xh((Z

+

1)/Z) Xy, Z, . . .)

.

For each cycle of the for statement the control will pass from Bisection to the expression in the call, from there to the In-routine, then back to the call and finally back to Bisection. This means that the most important dynamic unit is composed of three pieces of program taken from widely separated places in the text. On the basis of considerations of this kind and inspired by the scheme adopted for the Ferranti Atlas8 we decided on the following scheme for the storage of the programs within GIER ALGOL: 1. No part of the instructions representing the algorithms of the program will be held permanently in the core store. 2. The program of instructions is held on the drum and is segmented by the compiler into the drum tracks so as to waste no space on the tracks. 3. The program segment stored on a drum track is arranged in such a way that while it is executed it may be stored in any place in the core store and will make no assumptions as to other program segments being present in the core store. 4. At any time the part of the core store which is not reserved for variables will be available for as many program segments as it will hold. The number of segment places will be allowed to vary between 2 and 20. 5. Program segments are transferred from the drum to the core store when the execution of the program requires them. 6. The running system keeps a table of the program segments currently held in the core store. When the program calls for a transfer of control to another segment the running system will try to avoid transferring the segment from the drum by first searching through this table. 7. When the program calls for a transfer of control to a segment which is not in the core store and there are no more unused segment places left in the core store, the segment will be overwritten on that segment held in the core store which for the longest time has been left unused.

Peter Naur This scheme could be realized cular

the availability

be used for references

within

for jump instructions, compiled

tables

which

relative

in GIER.

into

a table

common

In fact, as one of a program

to the program,

for each segment.

could

This is used, not only

to constants.

of this scheme the literal constants

are separate

In parti-

to the order counter

the same segment.

but also for references

unusual consequence never

quite conveniently

of addressing

are

but only into

In this way the constants

used from a segment will follow this segment and not make any permanent demands on the core store.

This requires that constants

all stages of the translation

as unique class of objects.

Further

details of this scheme are given in the following

the machine require

are taken through

code of the running

sections.

system the corresponding

In

algorithms

about 80 words.

3.10. Program segment priorities The

rule of point

7 above is applied

with the aid of a priority

as-

sociated with each available segment. The running system keeps a current priority, which is the priority of the segment which is currently in action. Each

time the control

increased

is transferred

In this way the difference associated transfers

to a segment

the current

by one and is also assigned as the new priority with a segment

between

the current

priority

having

the lowest priority

left unused for the longest time. Additional complications arise

program

segments.

However,

segments should be somewhat to only a transient

cancellation

segment

In particular

will be the one which has been

because

in general

available segment places in the core store will vary. should in general be prepared to use all the available it is desirable

system in the case of an increase

is

and the priority

in the core store shows how many

have been made since the segment was last used.

the segment

priority

of the segment.

that

of the amount

the number

Clearly the system core store space for the reaction

of storage

cautious because the increase of reservations

of

in the stack.

of the

available

for

might be due For this reason

the following procedure has been adopted. Irrespective of the amount of free store, new segment places are only put into circulation one at a time and only at the time when an actual transfer of a segment from the drum is taking place. Further, this new segment place is not used for the segment actually being transferred, but is left empty with the priority one less than the new current priority. An additional advantage of this method is that the actual process of initializing a new segment place may use the drum transfer time which would otherwise be wasted.

Design 3.11.

ofthe

Gier Algol Compiler

63

Example of segment allocation

As an illustration, let a program cycle indefinitely in a simple loop consisting of the segments stored in the tracks 91, 92, 93, and 94. The following table describes the development of the situation from the initial state, set by the translator, until all four segments are present in the core store. Each line corresponds to a situation. The columns give, for each segment place, the drum track number of the segment stored in that place and the priority of that segment. If no track number and priority is given it means that the segment place is not regarded as available. The current priority is given in an additional column. Segment 1 Initial state After entry into 9 1 - 92 - 93 _ - 94 _ - 91 - 92

-1 91 1 91 1 91 1 91 1 91 5 91 5

3

2 _ 93 93 93 93

place

0 0 0 3 3 3 3

92 92 92 92 92

4

0 2 2 2 2 6

94 94 94

5

1 1 4 4 4

_ -

6

2 2 2 2

-

Current priority

3 3 3

If the loop had comprised 5 segments the 5th would have been put into segment place 1, thus overwriting segment 9 1. This indicates the tendency of the scheme to keep the program at the lower segment places. This tendency is further enhanced by the final process involved in the program allocation, the cancellation of segment places on priority overflow described in the following section. 3.12.

Program storage clean-up

Priorities will always be kept in the range O-5 11. Whenever the priority counter reaches 512 the opportunity is taken to perform the following clean-up of the situation: priorities from 0 to 255 are replaced by 0, those from 256 to 511 by 1, while the track which is just being entered will be assigned the priority 2. Moreover, the track places are checked in sequence, beginning with the one closest to the stack of variables, and all those of new priority 0 are removed from the list until the first one having the priority 1 or 2 is found or there are only two segment places left. The principal aim of this clean-up is to cancel those segment places which are never used. This may be of importance in short programs, because it will reduce the number of unsuccessful tests for coincidence of the new track number and the track numbers kept in the segment table performed at each transfer of control to a segment.

Peter Naur

64 4. MULTIPASS

TRANSLATION

The importance of the scheme used for storage allocation during translation forced itself upon our attention through our work with the DASK ALGOL compiler. This compiler was based on principles which ignored the problems attendant on using a machine with a non-homogeneous store, The ill effects of this approach showed themselves in two ways. First, the writing of the translator became a continual struggle (and a very time-consuming one) to fit the translator programs and tables into the machine. Second, the frequent transfers of programs and tables to and from the drum limit the speed of the compiler to a level with is entirely out of harmony with the internal speed of the machine. 4.2. The translation problem in machines with a. non-homogeneousstore The translation of an ALGOL program consists basically in the combination of two bodies of information, viz. the information supplied by the ALGOL source program and that of the translation program (the translator). In a machine with a non-homogeneous store we wiI1 at any one time during translation have only a small fraction of each of these two bodies at our disposal in the fast store. The principal aim in designing the storage allocation scheme is to avoid that the part of the bodies being in the focus changes rapidly in a random manner. Part of this aim may be achieved by using sequential scanning of the program text. By this we will understand that each basic translation process treats all or nearly all the symbols of the text by taking them in order from one end of the program to the other, producing at the same time a transformed text as output. This method of processing obviously avoids random references to the source program. Where the translator program is concerned the answer to the basic storage allocation requirement is to divide the translation process into a number of separate passes. In each pass we will then need only a fraction of the total translator program. This still leaves to decide, what is the best number of translator passes ? The conflicting factors are, on the one hand, that if we use few passes then the translator program of each pass will be long and at a certain point we are again back in the situation where we have to divide the translator program between the fast and the backing store, and on the other, that if we use a large number of passes then the total cost of transferring the program from the backing store to the fast store once for each pass will become comparable to the time of making a very few references to each part of the program, but in a random order.

Design

ofthe

65

Gier Algol Compiler

The actual proportions will depend strictly on the data transfer characteristics of the machine. In GIER ALGOL the drum is the only backing store available. If, as a first very rough guide, we consider the times required to refer to a word placed on the drum we get:

Random Sequential

Access time

Drum time

Total time

O-05 msec 0.05 msec

20 msec 0.5 msec

20 msec 0.55 msec

Thus under these circumstances we may perform more than 35 sequential references to all words of a text during the time it takes to refer once to each word of the same text in a random manner. If, on the other hand, we try to make a guess as to the number of passes which would be necessary in order to avoid drum transfers of translator program during each pass we can proceed as follows: The total size of the translator ought not to exceed 5000 words (DASK ALGOL has about 5500 40-bit words). If we remember that each pass will require space for tables the average size of the program of a pass ought to be about 500 words. This then indicates that about 10 translator passes might be expected. Gomparing these preliminary estimates we arrive at the following conclusion : The translator should be designed on the basis of the principle that each pass, including all necessary tables, must be small enough to be held in the core store. The exact number of passes, estimated to be about 10, should be adjusted to fit the most logical division of the translation process consistent with this principle and need not for its own sake be kept small. 4.3. Intermediate languages In a multiscan translator the choice of intermediate languages becomes of prime importance. The following conflicting factors have to be considered: From the point of view of the individual scans (or translator “passes”) great flexibility and a great multitude of structures of varying length is desirable. From the point of view of the programs which have to perform the packing and unpacking of information, which is necessary during each scan, uniformity of structure is desirable. Uniformity is also desirable for translator checking purposes. In view of these considerations the following compromise was adopted : All intermediate languages are expressed in terms of basic units of information each of 10 bits. This is called a “byte”. Thus the input to each pass 5

66

Peter Naur

and the output from it will always consist of a uniform string of bytes. Within each intermediate language any amount of structure within this byte string may be employed in order to communicate more intricate structures. Thus, for example, a number in the original ALGOL program will in most of the intermediate languages be represented by 5 consecutive bytes, the first being a distinct “number mark” while the remaining four supply the actual value of the number. However, from the point of view of the general packing and unpacking program the various intermediate languages are undistinguishable, since they all consist of just a uniform byte string. By this method the programs which will perform the packing and unpacking of the intermediate versions of the program and which will take them from the drum and store them on the drum will be the same for all passes. In addition since the output from each pass is always of the same form the same test output program can be used for all passes and can be coupled to the common pass administration.

4.4. Reverse scans In a multipass translator it is highly advantageous to let some of the scans be reverse scans, i.e. scans which start at the end of the program and move towards the begin. First of all the problem of forward references clearly is solved completely in this manner. In addition it becomes easy to eliminate syntactically incorrect sections of the program during the translation. This is important because it is highly desirable that an error which has been detected does not prevent the translation process from continuing to check the rest of the program. Two of the 9 passes of GIER ALGOL are reverse scans.

4.5. The general pass administration According to the description given above all passes use the same general pass administration. As far as the individual pass programs are concerned the general pass administration is a subroutine with two entries, one for input and one for output. In fact, these two entries are almost completely independent since they use two independent buffers in their communication with the drum. The general pass administration uses 4 sections of 40 words each in the core store. Two of these are used as buffers for the input to the pass programs, the other two for the output from the pass program. Normally one of the input buffers holds the bytes of that section of the program which is presently being processed by the

pass program whiIe the other input buffer holds the next input drum trackWhen all the bytes on the active buffer have been used the transfer from drum of the next following drum track is initiated, At the same time the pass program can proceed to process the input bytes waiting in the other input buffer. A similar buffering technique is used on the output side. The bytes are packed into the GIER words with 4 bytes in a word. Thus one drum track holds 160 bytes. The unpacking (on the input sick) and packing (on the output side) of the bytes into the words are performed by the general pass administration. This turns out to be a more timeconsuming process than the corresponding drum transfers. In fact the average unpacking time per byte is about 220 psec, which means that the unpacking of the 160 bytes on a track will take about 35 msec. This again means that except for collisions between drum transfers called from the input and output side the time for drum transfers will be negligible, owing to the parallel operation during drum transfers in GIER, The time for packing on the output side being about 260 psecjbyte, the total time for the administration of a pass becomes about 77 msec per drum track plus the time wasted due to collisions of drum transfers, about 5 msec on the average, The processing time per byte of course varies greatly, the minimum being about 80 ysec (direct copying from input to output). At an early stage of the development we guessed that an average byte processing time of 500 psec corresponding to about 10 instruction execution times, might be expected. On this assumption the pass administration and processing times are comparable and the total time for 10 passes will bc about 2 set/drum track, This proved to correspond closely to the final performance. On the drum the tracks holding the partially translated program are used in a cyclic manner. About half the drum is available for this, while the rest of the drum holds the translator and the running system programs. Suppose that the available tracks are numbered from 1 to N. The first pass wifl then place its output in tracks no. 1 to M, say, where dearly M I N. The second pass will take its input from tracks no. 1, 2, etc,, and will place its output in tracks M+ 1, Mf2, etc. When output to track no. N has been made the administration will continue to output into track no. I, 2, etc., which presumable have now been released by the input side administration. This process is continued smoothly from one pass to the next, except for the case that the direction of scanning is reversed. This does not cause any difficulty, however, since the cyclic use of the drum tracks may with equal ease take place in either direction. Only in the programs which perform the packing and unpacking a few changes are necessary,

Peter Naur

68 8.6. Distribution

of tasks among the tradation

passes

Once the great advantages of a multipass compiler are established the problem remains to distribute the translation tasks among the passes. As will be shown below the solution of this problem follows in a straightforward manner from a consideration of the basic characteristics of ALGOL 60, of our basic demands on the compiler, and of the multipass scheme itself_ These characteristics are : 60 characteristics: identifiers of arbitrary length; the characteristics of quantities are given by explicit declarations which may appear scattered among the program text (labels, procedure bodies referring to each other). 2. Demand on the compiler : it should detect virtually any number of No kind of error should formal errors of a program in a single translation. cause the compiler to be thrown off the track, 3. Multipass scheme: the program, tables, and working space of each pass must be accommodated entirely within the core store. 1. ALGOL

The proper sequencing of passes may now be established as follows: First, it follows from points 1 and 2 that at least two major checking processes must be distinguished. The first must snalyse and check the delimiter structure, without regard to the types and kinds of identifiers. When this has been completed another process will be able to check and analyse the operand structure. In addition to these two processes we must have a first pass which will convert the hardware representation used for input into a form which gives the ALGOL 60 symbols directly. As our first rough picture we may therefore distinguish the following 3 phases, numbered in anticipation of the following discussion : Pass 1. Analysis of hardware representation (microscopic context] + Pass 3, Analysis of delimiter structure ~i~terme~ate fevel context). Pass 6. Anatysis of types and kinds of quantities (global context). Now, in view of our basic requirement on the size of each pass it is clear that tables of descriptions of the identifiers of the program can only be accommodated in translation passes which do not also require elaborate programs There are essentially two such tables to consider : a table which holds the originai form of the identifier {the actual letters and digits) to be used in matching the identifiers, and a table of descriptions of the kinds, types, and storage assignment, of the identifiers, to be used in generating the final code realizing the active parts of the program. Identifier matching can in principle be made as soon as the microSCO& structure has been established, but need not be done until the identifier description table is formed. However, in order to reduce the

length of the intermediate language string representing the program it is advantageous in practise to do it early, i.e. before the analysis of the delimiter structure. Identifier matching is therefore done in pass 2. The fact that by this method the same identifier will be represented alike whether it denotes the same quantity, or, through the block structure, different quantities, does not cause any difficulty since in any case the association of identifiers with blocks is best postponed until the table of identifier descriptions is formed. The table of identifier descriptions can only be formed after the analysis of the delimiter structure but must precede the processing and check of expressions. Because of the fact that ALGOL 60 effectively allows identifiers to be declared anywhere in the block in which they are local (labels) the establishment of the table of identifier descriptions af a block requires- a scan of the complete block {Pass 4). Again, since the pass which performs the processing of expressions cannot be expected to hold the complete table, we must insert an additional pass, which serves only to distribute the information of the identifier description table to each place in the program where an identifier occurs (Pass 5). In order to take best advantage of the block structure of ALGOL 60 programs these two last mentioned passes are best arranged as a reverse pass followed by a forward pass. In this way the table of identifier descriptions need not be able to hold all quantities of the program simultaneously, but only the largest number of quantities which is available at any one point of the program. We do not take full advantage of this po~sibili~ in GIER ALGOL but actually proceed as follows: During the reverse pass 4 the descriptions of identifiers are collected in a stack, Each time a block begin is encountered the top section of the stack is emptied into the program. During the forward pass 5 the identifier description table consists of a normal table having one position for each distinct identifier of the program plus a stack used to hold the descriptions of identifiers which have been declared to have a different meaning. Each time a block begin is encountered the declarations of local quantities within the block are copied from the input into the table of descriptions. If the table already holds a description at a position which is about to be filled the previous description is placed in the stack. Throughout the program the identifiers are now replaced by their full descriptions, such as they may be found in the table. When a block end is encountered the descriptions of quantities having the current block number are deleted from the table and previous descriptions restored from the stack. It will be noted that the use of a table having one position for each distinct identifier during the second of these passes actually will waste space. On the other

70

Peter Naur

hand, we gain a great advantage in speed since the description of an identifier, given its number, may be found directly in the table. If a stack had been used for all currently active descriptions the retrieval of a description would need a search through the stack. The above discussion covers the analysing and checking stages of the compiler. The remaining part of the compiler generates the final machine code and places it in the correct locations on the drum tracks. In order to reduce the bulk of the logic which generates the one-address-instruction form of the program, pass 6, which performs the checking of types and kinds, also transforms the expressions into reverse Polish strings. ‘* Pass 7 transforms this to a form which specifies the final machine instructions for arithmetic instructions while the final storage and internal references are still open. Pass 8 arranges the final machine instructions on the drum tracks and calculates the internal references. Finally, pass 9 rearranges the segments on the drum. The tasks of the passes may be summarized as follows: Conversion to Pass 1. Analysis and check of hardware representation. Strings are assembled. reference language. In the output each distinct identifier is Pass 2. Identifier matching. associated with an integer between 5 12 and 1022. Pass 3. Analysis and check of delimiter structure. Delimiters of multiple meaning are replaced by distinctive characters. Extra delimiters are inserted to facilitate the later scanning. Pass 4. Collection of declarations and specifications at the begin of blocks and in procedure headings. Rearrangement of procedure calls. Pass 5. Distribution of identifier descriptions. Storage allocation of variables. Pass 6. Check of types and kinds of identifiers and other operands. Conversion to Reverse Polish notation. Pass 7. Generation of machine instructions for expressions. Allocation of working variables. Pass 8. Final addressing of program. Segmentation into drum tracks. Production of final machine code. Pass 9. Rearrangement of the program tracks on the drum. 4.7. Examples of translator action The practical implications of the above division of the translation process into 9 passes may be explained by describing the fate of some of the language features through the process. These descriptions will give some

71 examples of the contents of the actual byte strings produced by the passes. The individual bytes will be identified by words in bold type, like delimiters in ALGOL 60. It may be noted that the complete alphabets of the output languages of all the passes are given in the appendix 2 of ref. 2. The treatment of literal constants (strings, numbers, logical values) is dictated primarily by the run-time storage allocation of program. AS already mentioned any constant used from one particular program segment track must be stored on that same track. Therefore constants must be carried through the translation process up to pass 8 at the place in the program where they occur. In other words, no table of constants for the complete program is ever formed. This treatment offers the advantage that during pass 7 constant operators may receive special treatment. In particular it is possible to evaluate any arithmetic expressions or subexpressions which deal entirely with constant operands. The individual classes of constants are processed as follows : Strings are assembled in pass 1. Those which are short enough to be stored in a single word are output as five bytes of which the four give the value of the string when concatenated. Longer strings are placed in a list held on the topmost drum tracks and the drum track number and relative address replace the actual string in the five bytes communicated as output. Numbers are assembled in pass 3 and again are represented by five bytes. Similarly the logical values are output from pass 3 as five bytes. Constants pass unchanged through passes 4 and 5. In pass 6 the three types of constants are checked for compatibility with context, In pass 7 arithmetic expressions with constant operands are evaluated, but otherwise the constants pass through in the same form. Finally, in pass 8 a constant table is built up separately for each program track. This table will also hold the program point descriptions necessary for local jumps from one segment to another. Identifiers are assembled in pass 2. In the output from pass 2 each distinct identifier will be represented by an integer in the range 5 12 to 1022, irrespective of the block structure of the program. These integers pass unchanged through pass 3. In pass 4 the declarations of identifiers are collected whereas identifiers appearing in any other way pass through unchanged. In the output from pass 5 they are replaced by descriptions of four bytes each, giving the kind and type of the quantity, the block number, the block relative address and, where relevant, an additional number specifying the number of subscripts, of parameters, or of switch elements, in the declaration of the quantity. In pass 6 the information on kind and type and on the number of parameters is used for checking and at the same time removed. The output from pass 6 describes the quantities only by three bytes, giving the block number and block relative address. In the

Peter Naur

72

output from pass 7 these operands can only appear in association with bytes describing the operation parts of machine instructions. In pass 8 the operand descriptions are finally converted to machine addresses with the appropriate modifications (indirect, relative, indexed, etc.). Simple expressions are structurally unchanged through the passes 1 to 5. In pass 6 the parenthesized expressions are converted to the Reverse Polish form in which the operands are quoted in exactly the same order as in the source text while the operators appear in the order in which they must act.‘* It may be noted that although this use of a Polish notation as an intermediate language is inspired by the techniques used by the Michigan school*3 we do not see any advantages in the use of the quadruples suggested by Kantorovichr5 because this notation introduces explicit names for working quantities which in many cases will have to be efiminated later. Our pass 6 operates in the manner described by Dijkstra’ and produces the Reverse Polish form, as defined by Hamblin.14 In pass 7 the Reverse Polish form is converted into a form which makes explicit reference to the machine instructions and working locations. During this pass the order of references to operands is changed so as to ~~rnize the use of working locations in so far as this is compatible with the possible side effects induced by references to procedures or formal parameters called by name. Thus, if a, 6, and c, are simple variables the expression a x (b + c) will require the following instructions : Take b Add c Multiply

by a.

On the other hand, iffis a formal parameter called by name belonging to such a block level that it may affect the simple variable a the expression a x (f-t 4 will give rise to the following instructions : Take a Place in working location Take value off Add c Multiply by contents of working location. Conditional expressions are modified slightly during pass 3 so as to make the following processing more independent of context. Thus for example the following expression : ifb then ielsej

-

k

73

Design of the Gier Algol Compiler will appear in the output from pass 3 as: X--expression j -

b then-expression

i else--expression

k end-else-expression

Here the bold words stand for specific bytes in the output from pass 3. Thus while the input consisted of 8 bytes the output has 9 bytes. ambiguous

input symbols have been replaced

by unique ones.

Also the A further

differentiation

of conditional

expressions is produced by pass 6 where types

are recognized

and checked.

In the output from pass 6 the input symbol

else -expression

by either else-RF-expression,

is replaced

R+xpression

or else-address-expression

the expression (arithmetic,

Boolean or designational).

tion is made for end-else-expression.

distinctions,

and consequently

elseto the type of

A similar differentia-

In pass 7 these distinctions

taken properly into account in generating sub-expressions

according

the instructions

terminating

are the

the output from pass 7 does not make the

but only produces

else and end-else.

bytes representing

Finally in pass 8 these bytes are used to form the proper jump instructions as follows. Pass 8 in its reverse scan first encounter two copies of the current

program

program

points.

changed

to point to the current

unconditional

When subsequently

jump instruction

the stack is output.

Similar

if (an if immediately this

technique

the

point

end-else.

to be recorded

else is encountered

This causes in a stack of

one of these is

point, to be used by then, and then an to the point recorded

actions

following

are performed

in the other item of at then and else-

an else in the source

use of conventional

symbolic

program).

addresses

By

becomes

unnecessary. Assignment statements are first processed in passes 3 and 4 which make the structure more explicit. As an example the following assignment statement: a := b := c := d - e will appear in the output from pass 4 as a first - :=

b :=

c :=

prepare-assign

d -

e end-assign

where the four bold combinations represent unique output bytes from pass 4. By this transformation the type checking during pass 6 becomes greatly

simplified.

like an operator

In the output from pass 6 the assignment in the inverse Polish string.

is treated

If in the above example

a,

b, and c, are of type integer while d and e are of type real the output will be a b c d e - round-prepare-assign := : = := where of course each of the operands is represented by the proper description in terms of block number and block relative address.

74

Peter Naur

Procedure statements and function designators are made explicit by pass 3 like assignment statements, In pass 4 they are transformed in such a manner that they appear as a call which includes a listing of each actual parameter, followed by the code representing any actual parameters which are compound expressions. This is quite close to their final form except in the case of calls of standard functions having one parameter called by value (sin, cos, etc.). These are converted to appear like operators in the final program by pass 6. An example of the transformation of procedure calls is given below. In generating internal program points the translator nowhere employs the customary symbolic address technique. Instead the known structure of the text, in combination with stacks, is used to transmit the static descriptions of program points from the place in the program where the points appear to the place where the description is needed. This may be illustrated by the following example of a procedure call:

P(a+b, 5 d-J) In the output from pass 3 this appears as 14 bytes: P begin-call

a + 6 call-parameter

call-parameter

Pass 4 transforms reverse order) : pression

c expression

a + b end-expression

expression

e

f end-call

this to the following string of 18 bytes (generated P f expression

begin-call

d -

c call-parameter

end-call

begin-expression

in the

begin-exd-e

end-

bypasslabel

At this stage the list of actual parameters has been formed. This list reverses the order of the actual parameters. This is convenient for pass 8 which has to replace each occurrence of the symbol expression by the static description of the entry point of the corresponding expression. In fact, the reverse pass 8 need only record the program point description of each begin-expression in a stack and then use the top element of this stack to replace each occurrence of the symbol expression. In a similar way the symbol bypasslabel serves to record the return point of the call during pass 8. Since this will be used to replace the symbol end-call it is clear from the above structure that a second stack must be used during pass 8. 5.

TRANSLATOR

METHODOLOGY

As will be clear from the discussion of the background of the project the development of new methods of translation had a low priority. Even so we have in the following notes tried to describe the most important

Design of the Gier Algal ~ornp~~eT

75

tools employed in the project, Where this description differs from similar descriptions given elsewhere it will probably be found that the difference is more one of the way we think about a tool than in the way we use it. However, in view of the present rudimentary state of the discussion of translation methods this may still be of interest. 5.1. The stack as a transmission device The well-known principle of a stack (or push-down list, or cellar, or ~rst-in-last-out-list) is used extensively in the GIER ALGOL compiler. We like to regard the stack as a device for transmitting information from one point in a text to another. It may be used wherever the sections of the text within which a piece of information to be transmitted exists form a This means that within one communication nested parenthesis structure. process it is necessary to have as many stacks as there are independent parenthesis structures to be taken care of. A further distinction may usefully be made between the case where a piece of information transmitted through the stack will be used just once, and the case where one such piece may be used any number of times. We tend to denote the former type of stack as a true push-down list, the designation “stack” being used for the latter type. The following notes discuss briefly the stacks used throughout the compiler, Pass 3. A push-down list holds those delimiters having left-parenthesis character which have not yet been matched by a corresponding right-hand delimiter. Example : While scanning the following text : begin a := if 6 then c[i] else d;

the stack is used to communicate Transmitting I=!!%

symbol

as follows: Receiving

symbol

f

:=1: if then [ else

;hen else

1

. >

The information transmitted through the stack is used to create explicit right-hand delimiters corresponding to those left-hand symbols which do not already have them, and to replace those delimiters which in ALGOL 60 are used for more than one purpose by more explicit ones. This is reflected

76

Peter Naur

in the following text which is the output from pass 3 of the above input text : begin a : = i&expression sion d end-else-expression

b then-expression

c[LJelse-expres-

end-assign

Pass 4. This reverse pass uses a push-down list to transmit declarations of quantities from the place in the program where the declaration is written to the begin of the corresponding block, and also to transmit the the information about each actual parameter to the left parenthesis of the procedure call. Pass 5. A push-down list is used to transmit the descriptions of identifiers across any such sections of the program where the identifier is declared to have another meaning. The push-down list is used side by side with a normal list having one position for each distinct identifier of the program holding the current description of that identifier. Pass 6. This uses two push-down lists. An operator list transmits the operators from the place where they occur in the parenthesized input string to the place where they are needed to form the corresponding Polish string. An operand push-down list transmits the descriptions of the kinds and types of operands (identifiers, numbers, variables, etc.) to the place where they are operated upon, Pass 7. A stack transmits the description of the storage of each operand from the place in the Polish input string where it appears to the place where it is finally operated upon, usually forming the address of an instruction. Pass 8. Two push-down lists are used, both for transmitting references to points in the program from the place where the point is to the place where it is referred to. Running program. A stack is used to communicate all variable parts of the program. These include declared and internal working variables, storage allocation coefficients of arrays, dynamic program point descriptions and block information.

In GIER ALGOL syntactic analysis of texts is mostly carried out by a process which has some similarity to the working of a Turing machine. The crucial feature of this method is the insistence on an explicit enumeration of the states in which the scanner may find itself at any time. These states are regarded as equivalent and each is characterized by its number. It is understood that the current state number must give a description of the situation which is sufficient to be used for a complete determination

of the action to be taken on the folIowing symbol in the input, including the assignment of a new current state. Indeed, this requirement may be regarded as the criterion that a proposed set of states is in fact adequate for the analysis at hand. This approach may conveniently be illustrated by the logic necessary to analyse and check a text which purports to contain a number in the sense of ALGOL 60. Let the text be composed of digits, points, exponent tens, plus and minus signs, and a further class of symbols which we shall denote terminators. Further, let us set out to scan a piece of text which contains any number of terminators folfowed by one ALGOL 60 number followed by one terminator. In order to anaIyse and check such a text our scanner will need 8 states. The logic may conveniently be described by the following table which for each combination of a state and an input symbol gives the new state. When an error has been found the new state A (alarm) is indicated. State 1: Only terminators yet 2: After first sign 3: Among digits before point 4: Following point 5: Among digits following point 6: Following ten 7 : After exponent sign 8: Among exponent digits

The sequence Input States Input States

ofstates

symbols: : :

;

2

‘-.

6

0 3 4

2 -j-

r0 f

terminator 1 A 6 6 integer A A 6 decimal number A A A number with exponent

ten

of the scanner in a few examples is given beIow :

11

symboh:

+ - digit point 2 346 A 3 4 A 3 4 A 5 A A 5 A 7 8 A A8AA A8AA

7

;

3 3 8

integer ; If

number withexponent

The above description has stressed the checking aspect of the Turing machine approach. However, an equally important aspect is the case with which arbitrary actions may be specified. In general each point in the tabte with arguments state and input symbot wifl correspond to exactly one unconditional action, By using this approach it is usually possible to avoid tests in the individual actions to a surprisingly high degree, This in our experience is a very effective way of reducing the bulk and execution time of the translator algorithms,

78

Peter Naur The

most extensive

Surprisingly correct

application

ALGOL

text in the manner

up to the next following delimiter source program 5.3.

of this approach

is made in pass 3.

we have found that only 32 states exist while suggested by Dijkstra,’

in each process.

errors we need another

scanning

a

i.e. scanning

In order to take care of

4 states.

The use of tables and switches A large part of the logic of the GIER

in tables. each.

Usually

Typically

the basic translation

central administration unpacks

ALGOL

translator

the words in the tables contain

is described

4 parameters

of 10 bits

cycle of a pass is controlled

by a

which takes an input byte, uses it to look up a table,

the word found in the table,

and jumps

address is given in one of the parameters

to the action

of the word.

whose

The remaining

parameters may then be used freely by the action program values or the like.

3

as output byte

This method is partly dictated by space economy considerations. However, it has several positive advantages. First, it contributes to the clarity of the logic.

But the most important

use of a switch for controlling alternative

to expressing

the

the logic, translator

feature is the insistence

on the

We see this use of a switch as an logic

as an algorithm

includes numerous tests of relations and Booleans for controlling We feel that the switch method is superior in all respects:

which

the action.

clarity,

speed,

storage economy. 5.4. Pseudo-evaluation The

basic

evaluation.

of exfiressions

method

used in the processing

of expressions

is pseudo-

This method is, in fact, used twice: for type checking

(pass 6)

and for the generation of final machine instructions (pass 7). By pseudoevaluation we mean that the compiler scans the expressions while keeping at all times a complete this account combined

to combine

account

of all currently

active operands

the operands in the same manner

during the actual evaluation

tion differs from an actual evaluation

at run time.

and using

as they will be

The pseudo-evalua-

in the kind of information about .the operands which is being processed. During actual evaluation we operate performed during on the values of the operands. The pseudo-evaluation pass 6 operates on descriptions of the kinds and types of the operands. During pass 7 we operate on the descriptions of the storage of the operands. The pseudo-evaluation is carried out at a stage when the expressions are written in the Reverse Polish form. In this form there are two kinds of operands: explicitly named variables and anonymous variables in the stack. The appearance of a named variable always means that this

Design

of the Gier

Algol Combiner

79

variable should be transferred to the top of the stack. The operators always operate on anonymous variables. The use of this method results in a very transparent logic. At the same time it allows a great flexibility in the kind of operators which may be handled. This is particularly important in the generation of machine code for the more intricate structures of ALGOL 60 such as the array declaration. Likewise the individual treatment of a great variety of operands is possible. This is important if a complete checking of types is aimed at. 5.5. operand descriptions In using the method of pseudo-evaluation the form of the operand descriptions is of some importance. Typically, during type and kind checking the action program of each operator will have to check that the operand, or operands, described by the top items of the operand stack conform to proper usage. In ALGOL 60 we have to distinguish between 25 classes of operands while the number of operators is rather larger. A simple way to handle this large number of combinations is to describe each class of operands as a Boolean vector, kept as a bit pattern in one machine word. Each element of this Boolean vector is designed to characterize that division of the total class of operands which is of interest in one particular operator action program. In this way the action program of a particular operator will only have to test one single bit of the operand description found at the top of the operand stack in order to check whether the operand is proper. In using this method we found that we needed 35 bits in the operand description, which could therefore easily be kept in one machine word. 5.6. Formal errors in the source program One of the principal aims of the design was to let the compiler be an effective checker of source programs by being able to continue to process a program even after errors in it had been found. The clue to this problem was found to lie in a complete integration of the logic of translation and that of treating errors. This approach could be applied most simply to errors of compatibility of the declarations of an identifier and its uses (missing declarations, incompatible kinds or types). When such an error is found the description of the identifier is changed to denote a special internal quantity, “undeclared”, and a suitable alarm message given. In all letter occurrences the identifiers of this description are accepted without further alarm messages. This method suppresses the avalanche of error messages

80

Peter Naur

which will often occur in compilers with a more primitive technique of error handling. Errors of the delimiter structure cannot be handled as effectively as this. The problem is to re-establish the synchronism between the text given in the input string and the variables describing the state of the translator. In general this problem has no unique solution and it is necessary to resort to some ad hoc convention. The solution we have adopted consists in the complete removal from the input text of the surroundings of the sore spot. This surrounding will usually comprise a basic statement. In particular, care has been taken never to remove any begin or end from the input string since this would usually cause a very great amount of additional difficulty. This removal process can be accomplished very easily during the forward pass 3, which detects the errors of the delimiter structure, and the reverse pass 4. Every error message produced by the translator gives the number of the line in which the error occurs, in addition to a characterization of the error. In this way only the carriage return characters of the source program need be kept through the translation passes, while the original identifiers can be eliminated completely during pass 2. In order to facilitate the identification of errors pass 1 will optionally produce an output of a copy of every 10th line of the source program with the line number attached.

During the planning and development of the logic of the translator ALGOL itself was used extensively, However, no attempt was made to use a boot-strapping method, for two reasons: first, the size of the available store would prohibit such an approach, Second, in our experience the manual transcription of the algorithms into machine language is an effort which pays off very well in the improved speed and storage economy of the final product. The second of these statements may be illustrated by the fact that pass 6, which had required one to two man months for the development of its logic, could be transcribed into machine code in about one man week, During the development of the translation passes some pains were taken to make all the members of the group familiar with all the logic. Indeed, we believe that only by distributing the knowledge of the over-all design of the translator as widely as possible among the participants in the work it is possible to derive the maximum of benefit from the freedom to choose the most suitable pass to perform each translation process. One of the ways to achieve this was to insist on a very thorough manual testing

81

Design of the Gier Algol Compiler of all parts of the logic, before each part of the translator the machine

for the first time.

This manual

checking

was loaded into would always be

done by another person than the one who had done the actual ming,

and would therefore

program-

force at least one other person to familiarize

himself with all details. At all stages of the design great stress was placed on the problem of The goal is to obtain the maximum of conchecking the translator. venience in obtaining information on the performance passes and in specifying the test input. The solution following:

The

produced

test output

by each pass.

consisted

entirely

of the translator adopted was the

of the values of the bytes

These values were printed neatly as ten integers

per line by a short output program

which is a part of the general

pass

administration.

Only in the case of pass 8, which produces machine code, another test output program had to be used. The problem of test input, on the other hand, was solved simply by checking

the passes strictly in their

natural sequence, in other words by checking first pass 1, then pass 2, etc. Therefore the test input to any pass is written in ALGOL and the checkout problem

becomes

one of writing

test programs

in ALGOL

specially

adapted to each pass to make sure that all parts of the logic of the pass are properly put to work. This method proved to be highly successful. programs were needed to check the complete

About 160 short ALGOL translator while no other

diagnostic

or the like)

facilities

necessary,

(memory

or even desirable.

system, including

standard

dumps,

traces,

Even so the passes 6, 7,8,9, procedures,

were

found

and the running

were all loaded into the machine

for the first time and checked during a period of 20 days.

Although

there

were still errors left after this period the result was good enough

to be

distributed

6.

6.1.

SIZE

and widely used for several months in most GIER

AND

installations.

PERFORMANCE

The size of the translator and running system During

translation

the system occupies

138 out of the 320 tracks on

the drum, the remaining tracks being available for the partially translated The number of words used for the programs and permanent program. tables

and the use of the remaining'

part of the core store during each

phase of the translation and execution process are as given below: The size of the intermediate forms of average programs varies by a factor of about 2, the output from pass 2 being the shortest and the final machine code the longest. This means that if the final program can be G

Peter Naur

82 Phase General

pass administration

Words for Program Tables 371 0 501

132

89

62

-3

264

268

-4

216

43

-5

172

29

Pass 1 -

2

-

6

348

299

-

7

529

179

-8

336

140

-9

185

16

‘Running system

280

0

Standard

740

0

procedures

The total system

4031 +

Use of the remaining part of the core store E&h pass has 769 words at its disposal. Buffer for long text strings: 40 words. Identifier table : max. 618 words. Delimiter push-down list: max. 237 words. Declaration collection pushdown list: max. 5 10 words. Identifier description lists: max. 570 words. Operator push-down : max. 50 words, operand push-down: max 72 words. Operand stack: max. 61 words. Program reference pushdown lists: max. 293 words. Track table : 182 words. Program segments variables : max. words.

and 810

1168 = 5199 words.

accommodated in the machine then the same will hold for all the intermediate versions. The capacity of the working areas of the passes is adequate. In fact, although the compiler is used extensively for large production programs in several installations, we know of’no realistic program which could not be compiled because of overflow of push-down lists or stacks. During program execution only the running system and standard procedures are needed and the pass programs and tables may be overwritten by variables. However, if the translator is not destroyed in this

way the translation of a program may start immediately upon completion of execution of the preceding program.

As already mentioned the compile time is about 2 seconds per fmal segment of 40 words, corresponding to about 60 one-address machine instructions. For very short programs a basic time of about 4 seconds becomes prominent. This is about 4 times as slow as input from binary tape, but &tasterthan input of symbolic machine code. At this speed the compile-and-go mode is very attractive, and in fact no program for producing an output of the compiled program in binary form has so far been written. More than one third of the translate time is spent in pass 1. This pass is in fact input limited in speed. Each of the remaining passes takes between 5 and 14 per cent ofthe total translate time offair-sized programs, pass 2 ~iden~~er matching) leading with pass 7 (generation of machine instructions for expressions) a close second. The error detecting capabilities of the translator fully satisfy the design goal and have proved very convenient in practise. In fact, the translator may be used as an extremely effective proof-reading device. When the performance of the translated program is being discussed the normal reaction is to ask for a comparison of the execution speed of a compiled and a hand-coded program. There are several reasons why we do not wish to try to answer this type of question: 1. The question is undefined as long as it is not specified what the kind ofthe problem is, who does the machine coding, and what the rules for transcribing the algorithmic constituents between ALGOL 60 and the machine coding are supposed to be. In particular on the last score there is room firr a large factor of uncertainty in a system like GIER ALGOL which includes powerful mechanisms for storage allocation not normally considered in hand coding. 2. Even if a well-defined comparisan can be made the outcome of it is of no particular interest because it does not point to constructive improvements of the design. For these reasons we wish to base our assessment of the performance of the system on analyses of the time spent on the various language functions during the execution of realistic, practical algorithms, By this approach we will be able to detect the bottlenecks of the execution. Analyses of this kind have been made for algorithms for inverting matrices, for finding eigenvalues of symmetric matrices, and for definite integration by Simpson’s rule, These analyses very definitely point to two bottlenecks:

84

Peter Naur

(1) subscripting, and (2) transfer of control to a segment which is already present in the core store. The relative importance of these two items varies greatly, not only with the program, but also with the manner in which the program happens to be segmented. However, as a rough average it appears that these two items together account for well over half the execution time of many realistic programs. As a further conclusion it may be stated that such programs might be speeded up by a factor of two or more if two or three special machine-instructions designed to take care of these two problems were included in the machine. ACKNOWLEDGEMENTS

The design of GIER ALGOL which goes beyond the sources already quoted in the historical notes is due to a day-by-day pleasurable and inspiring collaboration of Jorn Jensen and the present writer. Where the large-scale design is concerned it is impossible to disentangle our contributions. The machine coding was done almost entirely by Jorn Jensen. While the project was in progress we were joined by Peter Kraft, Henning Christensen, Paul Lindgreen, Knut-Sivert Skog, and Peter Villemoes, who did a large amount of the practical and clerical work while at the same time learning the techniques. The project was also supported in various ways by several other members of the staff of Regnecentralen, in particular Agnes Michaelsen and Kirsten Andersen. Finally the debt to Niels Ivar Beth, director of Regnecentralen, should be recorded. In fact, his continued and enthusiastic support was an indispensable condition for the success of the project. REFERENCES 1. KRARUP, T. and SVEJGAARQB., GIER, Logical Orguni&ion. Ingenioren, International edition, vol. 5, no. 4 (Dec. 1961). 2. NAUR, P. (ed.), A Manual ofGIER ALGOL. Regnecentralen, Copenhagen (1963). 3. JENSEN, J. and NAUR,P., An Implementation ofALGOL 60 Procedures, BIT 1,38 (1961). 4. JENSEN, J., MONDRUP,P. and NAUR, P., A Storage Allocution Schemefor ALGOL 60. BIT 1,89 (1961); Comm. ACM 4,441-445 (Oct. 1961). 5. JENSEN, J., JENSEN, T., MONDRUP,P. and NAUR, P., A Manual of the DASK ALGOL Language. Regnecentralen, Copenhagen (1961). 6. IRONS,EDGART., A Syntax Directed Compiler for ALGOL 60. Comm. ACM 4 51-55 (Jan. 1961) 7. DIJKSTRA,E. W., ALGOL-60 Translation. ALGOL Bulletin Supplement no. 10, Math. Centrum Amsterdam (Nov. 1961) ; Annual Review of Automatic Programming Vol. III, 327-356. Pergamon Press, London (1963). 8. FOTHERINGHAM, JOHN,Dynamic Storage Allocation in the Atlas Computer, including an Automatic Use of a Backing Store, Comm. ACM 4,435-436 (Oct. 1961). 9. SAMELSON, K. and BAUER,F. L., Sequential Formula Translation, Comm. ACM 3 76-83 (Feb. 1960). 10. DIJKSTRA,E. W., Recursive Programming. Num. Mat/z. 2, 312-318 (1960).

Design of the Gier Algol Compiler

85

11. INGERMAN, P. Z., Thunks. Comm. ACM 4,55-58 (Jan. 1961). 12. GRAU, A. A., The Structure of an ALGOL Translator, Oak Ridge National Laboratory report ORNL-3054, (1961). 13. ARDEN, B. W., Graham, R. M., On GAT and the Construction of Translators. Comm.ACM 2, no. 7, 24-26 (1959). 14. HAMBLIN,C. L., Translation to and from Polish notation, ComputerJ. 5, 210-213 (1962). 15. KANTOROVICII, L. V., On a Mathematical Symbolism Convenient for Performing Machine Calculations, Dokl. AN USSR 113, no. 4, 738- 741 (1957).