The MPW linker is capable of combining code fragments from every MPW compiler (e.g. FORTRAN, Pascal, Modula-2, C, C++, and many more) into Macintosh code segments [Apple1]. Furthermore it generates the above mentioned jump tables, and adds resources into the executable (applications, system extension, drivers, desk accessories, etc.).
The linker makes a particularly important Oberon feature hard to implement however, the dynamic type checks [Wirth3]. Tags identify the type of a dynamic variable and communicate their structure to the garbage collector. Similar to user types, they are arranged in a tree like fashion, where each type tag holds references to predecessors. These references are generated symbolically by the compiler and transformed into addresses when the program is loaded.
Macintosh code is organized in segments which are loaded automatically when they are needed. A program is able to purge segments when it decides that the code in a particular segment is no longer needed [Apple3]. An MPW compiler doesn't make the final decision about segmentation. Segments are ultimately built by the linker. Therefore the compiler cannot guarantee that every reference in a defining module is available when a segment is loaded. In the case of MPW Oberon, the effect is that the tag addresses cannot be entered into every reference during program initialization. Some segments with tag references might be loaded at a later time. The linker doesn't include information on type and position of references into the application either.
MacOberon chose to adapt the type tag hierarchy when it loads a module with the special loader application [Franz]. A type tag is referenced as a global variable and the respective address is entered at all pertinent locations. The loader has to fix every reference within type tags and every access operation in the defining module. Furthermore it substitutes addresses in importing modules as if they were global variables.
MPW Oberon has to rely on the standard Macintosh segment loader, i.e. it needs a static (compile time) solution for the problem. MPW Oberon uses a trick in order to enable the linker to make the necessary connections: it treats each type tag as a procedure, turning references to tags into procedure calls. Type tags are kept in a special code segment together with their initialization procedure. This tag segment is loaded as soon as the main initialization calls the tag initialization procedure. The latter transforms the jump-instructions together with the relative addresses in the tag-segment into absolute addresses which remain constant during the execution of the program (figure 4). Access to type tags from true code uses the segment table, which is adjusted properly by the segment loader.
Figure 4: Segment table entry
The PO compiler was extended accordingly to fill its output into object file records. MPW Oberon creates an object file for every Oberon module with at least one content record. There is only one content record for each procedure, limiting the size of a procedure to 64 KByte. The linker resolves references based on names. For every reference to global variables or calls to other procedures, there will be reference records with the name of the referenced object and the position of the reference (see figure 5). Reference records actually contain unique numbers instead of strings. These numbers are associated with the module names (strings) by means of a dictionary record. MPW Oberon creates this record right after the first record.
Figure 5: Source code and object file
TYPE ScalarType = INTEGER; Example = RECORD a : ScalarType; b : REAL; c : ARRAY 50 OF CHAR; d : ARRAY 10 OF INTEGER END;Names of datatypes and recordfields are stored in dictionary records similar to procedures and globals. ScalarType doesn't require special information, it can be reduced to the simple type 'signed word'. Two LocalType records are necessary for the proper representation of 'Example', component d needs its own LocalType. Component c can be mapped on a C-string, Oberon uses the string format as defined in C.
LocalType
Type = 8005
physicalSize = 20
Type data:
TTE(8005)[20] = vectorOf(scalarOf(Long,a0), SignedWord)
LocalType
Type = "Example"
physicalSize = 80
Type data:
Example[80] = recordOF(4,
0, namedTypeOf('a', SignedWord),
2, namedTypeOf('b', Double),
10, namedTypeOf('c', CString),
60, namedTypeOf('d', TTE(8005))
)
MPW Oberon generates this information with a recursive walk through the symbol
table. Expressing the coding in the required binary format is however tedious in
Oberon as it requires bit manipulations.