DECLARATIONS
For each variable declaration within a function body, additional space will be reserved in the frame. Also, for each function declaration, a new "fragment" of Tree code will be kept for the function's body.
VARIABLE DEFINITION
The translation of a variable declaration should return an augmented type environment that is used in processing the remainder of the function body. However, the initialization of a variable translates into a Tree expression that must be put just before the body of the function. Therefore, the translator must return a Translate.Exp containing assignment expressions that accomplish these initializations.
If the translator is applied to function and type declarations, the result will be a "no-op" expression such as Ex(CONST(0)).
FUNCTION DEFINITION
A function is translated into a segment of assembly language with a prologue, a body, andan epilogue. The body of a function is an expression, and the body of the translation is simply the translation of that expression. The prologue, which precedes the body in the assembly-language version of the function, contains
- pseudo-instructions, as needed in the particular assembly language, to announce the beginning of a function;
- a label definition for the function name;
- an instruction to adjust the stack pointer (to allocate a new frame);
- instructions to save "escaping" arguments into the frame, and to move nonescaping arguments into fresh temporary registers;
- store instructions to save any callee-save registers - including the return address register - used within the function.
Then comes - the function body.
The epilogue comes after the body and contains - an instruction to move the return value (result of the function) to the register reserved for that purpose;
- load instructions to restore the callee-save registers;
- an instruction to reset the stack pointer (to deallocate the frame);
- a return instruction (JUMP to the return address);
- pseudo-instructions, as needed, to announce the end of a function.
Some of these items (1, 3, 9, and 11) depend on exact knowledge of the frame size, which will not be known until after the register allocator determines how many local variables need to be kept in the frame because they don't fit in registers. So these instructions should be generated very late, in a FRAME function called procEntryExit3 (see also page 252). Item 2 (and 10), nestled between 1 and 3 (and 9 and 11, respectively) are also handled at that time. To implement 7, the Translate phase should generate a move instruction
MOVE(RV, body)
that puts the result of evaluating the body in the return value (RV) location specified by the machine-specific frame structure:
package Frame; public abstract class Frame { ⋮ abstract public Temp RV(); }
Item 4 (moving incoming formal parameters), and 5 and 8 (the saving and restoring of callee-save registers), are part of the view shift described on page 128. They should be done by a function in the Frame module:
package Frame; public abstract class Frame { ⋮ abstract public Tree.Stm procEntryExit1(Tree.Stm body); }
The implementation of this function will be discussed on page 251. Translate should apply it to each procedure body (items 5-7) as it is translated.
FRAGMENTS
Given a function definition comprising an already-translated body expression, the Translate phase should produce a descriptor for the function containing this necessary information:
- frame: The frame descriptor containing machine-specific information about local variables and parameters;
- body: The result returned from procEntryExit1.
Call this pair a fragment to be translated to assembly language. It is the second kind of fragment we have seen; the other was the assembly-language pseudo-instruction sequence for a string literal. Thus, it is useful to define (in the Translate interface) a frag datatype:
package Translate; public abstract class Frag { public Frag next; } public ProcFrag(Tree.Stm body, Frame.Frame frame); public DataFrag(String data);
A MiniJava program.
class Vehicle { int position; int gas; int move (int x) { position = position + x; return position; } int fill (int y) { gas = gas + y; return gas; } }
public class Translate { ⋮ private Frag frags; // linked list of accumulated fragments public void procEntryExit(Exp body); public Frag getResult(); }
The semantic analysis phase calls upon new Translate.Level(…) in processing a function header. Later it calls other methods of Translate to translate the body of the function. Finally the semantic analyzer calls procEntryExit, which has the side effect of remembering a ProcFrag.
All the remembered fragments go into a private fragment list within Translate;then getResult can be used to extract the fragment list.
CLASSES AND OBJECTS
Image 7.5 shows a MiniJava class Vehicle with two instance variables position and gas, and two methods move and fill. We can create multiple Vehicle objects. Each Vehicle object will have its own position and gas variables. Two Vehicle objects can have different values in their variables, and in MiniJava, only the methods of an object can access the variables of that object. The translation of new Vehicle() is much like the translation of record creation and can be done in two steps:
- Generate code for allocating heap space for all the instance variables; in this case we need to allocate 8 bytes (2 integers, each of size, say, 4).
- Iterate through the memory locations for those variables and initialize them- in this case, they should both be initialized to 0.
Methods and the this pointer. Method calls in MiniJava are similar to function calls; but first, we must determine the class in which the method is declared and look up the method in that class. Second, we need to address the following question. Suppose we have multiple Vehicle objects and we want to call a method on one of them; how do we ensure that the implementation knows for which object we are calling the method? The solution is to pass that object as an extra argument to the method; that argument is the this pointer. For a method call
Vehicle v; ... v.move();
the Vehicle object in variable v will be the this pointer when calling the move method. The translation of method declarations is much like the translation of functions, but we need to avoid name clashes among methods with the same name that are declared in different classes. We can do that by choosing a naming scheme such that the name of the translated method is the concatenation of the class name and the method name. For example, the translation of move can be given the name Vehicle move. Accessing variables In MiniJava, variables can be accessed from methods in the same class. Variables are accessed via the this pointer; thus, the translation of a variable reference is like field selection for records. The position of the variable in the object can be looked up in the symbol table for the class.