deep

a Cross Development Platform for Java

User Tools

Site Tools


dev:crosscompiler:backend_arm:code_generator

Code Generator for ARM

All results of all SSA instructions have an assigned register. Now, each SSA instruction can be translated into one or a sequence of machine instructions. In order to do this, we must define the stackframe, which is used when calling a method.

Stackframe

We use a stack pointer (R14) but no frame pointer.

Stack frame for method calls

Explanation: LR is saved onto the stack together with the necessary nonvolatile GPRs. Even if a method is a leaf method LR must be saved onto the stack because LR serves as a scratch register within a method. Considering the GPR's and EXTRs, all nonvolatile register, which are used within this method, must be saved on the stack. Nonvolatiles in EXTR registers occupy either 8 bytes (EXTRD) or 4 bytes (EXTRS).
Important: volatile EXTR's must be saved as well if US.ENABLE_FLOATS() is called in this method (see Exceptions).

The field local variables is only used if the number of registers does not suffice and locals must be assigned a slot on the stack. Variables of type long or double occupy two slots.
When calling interface methods some temporary space on the stack might be necessary (see below).
The field parameter serves to hold parameters for method calls, which can not be placed directly into registers. The size of the field parameters is determined by considering all the calls to other methods within this method and taking the maximum size of their parameters. parameters must be at the top of the stack! This garantees that in all called methods these parameters can be accessed with the same offset. Each floating point parameter on the stack occupies 8 bytes (EXTRS or EXTRD).
The stack pointer always points to the top of the actual frame. At the top the stack pointer of the caller has to be stored. The back chain pointer is used for the debugger, for exceptions and for the garbage collection.

Exception Stackframe

In case of an exception all volatile GPRs together with LR must be saved. Further, the nonvolatile GPRs which are used in this method must be saved as well.
EXTR's need no saving, as they are not allowed to be used in exceptions. If an exception method calls a method where EXTR are used (e.g. in an interrupt handler or in a decrementer subclass) you have to use US.ENABLE_FLOATS() to store the them.

Stack frame for exception method

For efficiency, the exception stack frame does not contain space for locals. Hence, you are not allowed to use so many locals that this becomes necessary. This will be checked for when compiling exception methods. Storage for interface methods as well as the area for parameter passing are not necessary as well.

Optimization: The ARM architecture has lots of EXTR's. One could use half of them in normal methods and the other half in exception methods. For such a case all normal methods which could be called from within exception methods must be translated a second time with the second set. The compiler would have to find out how to handle each method.

Method Call

Parameter Passing

All parameters must be copied in the appropriate registers, see Register Allocation. During this it might be necessary that two or more registers must be swapped in a cycle. For this purpose two arrays destGPR and destFPR are determined. They show which source register goes into which destination register, if the register holds a parameter.

Return Value

As shown in Register Allocation, return values must be passed in R0 (together with R1 for longs) or D0 / S0. For longs we must consider the case that R0 and R1 must be swapped in order to contain the correct return value.

Fixup

While generating code certain addresses are not known.

  • Addresses of class methods (same class or different)
  • Addresses of class variables
  • Addresses of constants (floats, strings, type descriptors)

Such addresses must be loaded with the aid of an auxiliary registers. After linking these addresses are known and must be corrected in the code. For this purpose a table called fixups is maintained. It contains all references to the objects, which were created by the class file reader and whose final addresses must be inserted into the code. In order to know at what position in the machine code array a certain correction has to be made, the next position is stored in the instruction movw as an immediate operand beginning with the last position (lastFixup).

Accessing the Hardware

Java does not allow direct access and manipulation of absolute memory locations. Nevertheless this is essential for embedded programming. We therefore include this possibility by using a special class ch.ntb.inf.deep.unsafe.arm.US.java (US stand for unsafe). Wenn methods of this class are used the code generator has to insert machine code directly. The register allocator does not have to allocate registers for this instructions. US.java therefore serves as simple stubs.

Low Level Classes

There is a set of methods contained in the class LL.java. These methods handle functions which should cause no call to a method but which could be implemented directly with one or several machine instructions depending on the target architecture. Here again, LL.java contains simple stubs.

Compiler Specific Subroutines

Subroutines are methods for which there is no Java code (and hence no Bytecode or SSA) but only machine code. This is useful for the delegation of interface methods (see Interfaces) or for exception handling.
Such methods are listed in Method.compSpecSubroutines and the code generator issues code (if subroutines are used). These subroutines must be linked as well. Currently, there are two types of compiler specific methods

  • Delegation of interface methods: address of this method inserted into the type descriptor by the linker
  • Exception handling: call to this methods inserted by the code generator

Practical Implementation of Searching the Correct Interface Method

imDelegIiMm needs 3 auxiliary registers. At compile time we cannot reserve such auxiliary registers as these registers must always be the same. Therefore, we use a special chunk of memory on the stack. This storage is only necessary and allocated for methods invoking interface methods. At the start of the delegate method three volatile registers which might contain parameters are saved onto the stack and reloaded at the end of the delegate method.

dev/crosscompiler/backend_arm/code_generator.txt · Last modified: 2019/11/17 19:54 by ursgraf