The linker32 is written for 32 bit platforms. The linker performs various tasks like:
When creating the target image or calculating a crc the endianess of the target platform has to be taken into account.
As a first step the linker must be initialized with the method init(). What happens is:
Each class has a constant block. Certain interfaces need a reduced constant block and arrays just need a type descriptor, see Type Descriptor. The constant block holds information about the class, which is constant:
The const pool contains values of type float and double which are not placed directly into the code. After all elements are added the constBlockSize can be determined and the indices (
Item.index) of all elements will be fixed. It's important to note that all elements, which are addresses (
ConstantBlockItem, …), simply contain a reference to the according item. Only later, after address fixing, can the real addresses be inserted!
When the system ist starting up, the Runtime System reads the start addresses of the constant block for all classes from the System Table (see below). With the predefined offset cblkClinitAddrOffset the address of the class constructor can be fetched in order to load a class. With another offset the heap manager can read the global references of this class. This information is used by the garbage collection. codeBase holds the start address of code of a class whereas codeSize is the size of the code. Methods, which are defined in the configuration as system methods and which are assigned a fixed offset are excluded from codeSize (Exception Handlers).
In the compiler the constant block is modeled as linked list of
ch.ntb.inf.deep.linker.ConstBlkEntry. Depending on the type of entry one of the following classes is used.
AddressEntry: For entries representing an address.
ConstantEntry: For entries representing a constant (StdConstant).
FixedValueEntry: For entries representing a fixed value (e.g. a size).
InterfaceEntry: For entries representing an interface entry (id and offset).
StringEntry: For entries representing a constant string (StringLiteral).
OffsetEntry: For entries representing an offset to the table with all instance references.
The structure and purpose of the Type Descriptor is described separately.
The string pool holds the constant strings of a class. They are stored as follows (also see Strings:
[ tag ] [ stringClassAddress ] [ Object ] [ nofChars ] [ chars ] [ ⋮ ]
The characters of the string are stored in 2-Byte-Unicode.
The constant pool holds the constants of a class. Currently we only store there floating point numbers (float and double). Integer numbers are packed into the instructions directly (even long). Floating point numbers are stored in IEEE 754 format, the high word of a 64 bit double is at the lower address (bid endian).
[40490FDB] 3.1415927 (float) [401921FB] 6.283185307179586 (double) [54442D18]
Before the memory map can be fixed, a couple of sizes and offsets must be calculated for each class:
void calculateCodeSizeAndOffsets(Class): This methods determines the size of the machine code of the whole class and sets the field offset of each method of this class. offset is the byte offset of the machine code of a method in the memory relative to the start address of the code of this class. If a method offset is given by the configuration (e.g. exception handlers), then this method is not included in the calculation of the code size. The offset given by the configuration is not relative to the base of a class but to the base of the appropriate segment.
void calculateGlobalConstantTableSize(): This method calculates the size of the global constant table.
The linker assembles a system table for the whole system. This table must be loaded to a prefixed address in the target system and holds information for the Runtime System. Various settings must be handled:
In the compiler the system table is modeled as list. as elements it uses the same blocks as for the constant block. Additionally
SysTabEntry is used to reference the constant block of a class.
The structure of the system table is:
First, the class constructor of the class kernel must be found. For this, the class name of the kernel is fetched from the configuration and the method <clinit> is searched. Then, the table can be built. The offset to the addresses with the constant blocks is placed at the beginning. Heap and stack information is added. For each class the address of the constant block must be entered. The table ends with the value 0.
The references to the constant blocks are assembled as follows. First come the classes with class constructors. These classes are already sorted in correct order as described in Initialization. Classes without initialization follow afterwards. Arrays and interfaces have no constant block and are not listed. The exception to this rule are interfaces with class constructor, see Interfaces.
In this step the code is placed in the memory, as well as the class variables and the constants. First, each class is assigned a memory segment for the code (
Class.codeSegment), class variables (
Class.varSegment) and constants (
Class.constSegment). Which segments to use can be read from the configuration. The linker traverses the possible segments and choses a suitable one. While doing this, the used space of each segment is stored (
Segment.usedSize) and the appropriate offsets are set (
Class.constOffset). Arrays and interfaces must be handled as well. Further, the size of the segment for the system table has to be set and also the size of the segment for the global constant table.
Now, the base address of each used segment can be determined.
IMPORTANT System methods with offsets given by the configuration must be placed in their appropriate segments. This leads to holes. Such holes could be eliminated by a more efficient allocation, which is currently not done.
First, the addresses of static fields are fixed. For this the linker traverses the list
Class.classFields and sets
Item.address. This address has only to be set for non-constant fields. Next, the addresses of methods are set. Here again, methods which are placed at absolute position by the configuration must be handled separately. Third, addresses of the constants in the constant pool and strings in the string pool can be fixed. To conclude, the address of the class itself (
Class.address) will be set to the field size in the type descriptor.
After calculating absolute addresses the constant block has to be refreshed.
void updateConstantBlock(Class) fixes the base address of the code (codeBase). After this the base address of the class variables (
varBase) and their size (
varSize) is written. As a last step the size of an object of this class (field size) in the type descriptor is written.
This table holds constants which do not belong to a specific class. To give an example: Certain constants are needed to convert an int to a float.
Finally, the target image is put together. For each method of each class and for each constant block (one for each class, array or interface) a target segment is created and inserted into a list.