Under the 680x0 architecture floating-point operations are executed in a conceptually separate unit, the floating-point unit (FPU). It is either a separate chip, the MC 68881 or the MC 68882, or on the same chip (MC 68040). The FPUs have eight floating-point registers with 80 bits each, designated FPn. Conversion to and from the various supported number formats are performed automatically in the store and load instructions. Load and store instructions use the same addressing modes as the 680x0, the addresses are actually generated by the CPU.
MPW Oberon uses data registers only for expression-evaluation and not as variable storage. Registers D0 and FP0 serve to pass function results. It is the responsibility of the calling code to save registers before a procedure is called and to restore it afterwards. More register conventions will be discussed in chapter 3.2.
The only systematic problem we experienced was the register treatment in the PO compiler. It assumes that all registers are equal. We had to devote a substantial amount of effort to change the register allocation scheme of the PO compiler, so that the register semantics of the 68000 (see above) are not violated, i.e. addresses are kept in address registers and arithmetic data is loaded into data registers. Although the necessary information is available in the symbol table, the real work was to find all the instances where register selection takes place and to insert the symbol table based scheme.
MPW Oberon generates FPU instructions for the obvious speed reasons. That creates however a compatibility problem, especially since the new PowerPC Macintoshes emulate only the 680x0, not the FPU instructions. The Macintosh systems provides a generic floating-point package, SANE - the Standard Apple Numerics Environment. Although significantly slower, SANE is implemented on every Macintosh, with or without FPU and on the PPC platform for the PowerPC floating-point processor. Besides a significant speed penalty through the added indirection, every computation in SANE works memory-to-memory. Floating-point registers are not even conceptually present. Such a concept would allow the mapping of these virtual floating-point registers on processor registers where available.
For the compiler writer, the lack of FPU registers is not only a speed problem, it also complicates expression evaluation. When it generates code for SANE, MPW Oberon simulates the FPU registers with eight global variables. Together with a unified access scheme, we were able to keep the floating-point register scheme of the PO compiler almost unmodified.
As most other operating systems, the Macintosh creates an environment for applications before it hands control over to it. The most notable concept of a Macintosh application space is something affectionately called the 'A5 world' [Apple3] (see figure 3). Register A5 points to global variables and indirectly to 'invisible' globals, permanent data of the QuickDraw graphics package, the most prominent part of the Macintosh toolbox. The A5 world is divided in two parts, the lexical level 0 data below A5 and a code module jump table, the so-called segment table. As most other Macintosh compilers, MPW Oberon produces instructions with 16-bit offsets to access global data or jumps to other code segments. This imposes a 32 KByte size restriction on global data and the jump table.
Figure 3: Macintosh application environment
Macintosh code is supposed to be location independent and there is no relocating loader [Apple3]. MPW Oberon generates position independent, i.e. program counter relative code to jump within a segment and use the jump table to access other segments. The segment loader is responsible for adjusting this table when a segment has been loaded. As with global data, 16-bit offsets are used in order to reduce execution time and code size. The implication is another 32 KByte limit, this time for code segments. Advanced techniques such as the generation of jump islands are used by the linker to allow for larger code segments.
Another component of a Macintosh application space is the heap. The heap is controlled by another operating system component, the Memory Manager [Apple2]. In order to efficiently allocate and dispose memory areas, data is kept in relocatable blocks, the handles. A handle is basically a pointer to a pointer to the storage. The application keeps the handle and data access is performed through double dereferenceing. The repsective Pascal notation is simple:
TYPE Example = RECORD … END; ExPtr = ^Example; ExHandle = ^ExPtr;Thus the Memory Manager can move the storage in the heap and adjust the intermediate pointer accordingly. There is of course the possibility to lock handles and dereference it to improve access speed. Oberon does not allow such a construct. In order to use the Memory Manager from Oberon, the application programmer would have to declare pseudo-records to hide the intermediate pointer. For improved program readability MPW Oberon extends the Oberon syntax for type definitions: handles can be created with HANDLE TO.
TYPE Example = RECORD … END; ExHandle = HANDLE TO Example;The Macintosh operating system services are called according to Pascal conventions: parameters are passed on the stack, their sequence is from 'left to right', and structures bigger than four bytes are passed by reference. Furthermore record fields are aligned to 16 bits. MPW Oberon adheres to these conventions. Pascal interface files can be used without substantial modifications.
The OS implements calls to its routines as A-traps, unimplemented instructions of the 680x0 architecture. Parameters are loaded onto the stack in the case of toolbox routines (Quickdraw, Window Manager, Menu Manager, etc.) or into registers for file system or driver calls (OSIntf). In order to implement the toolbox and OS access interface modules for Oberon in Oberon, we had to extend the language with the keyword INLINE followed by the opcode for the appropriate A-trap. Several simple Macintosh system calls are not in the OS or the ROM, they are linked to the code. For the compiler they are external procedures, hence our language extension EXTERNAL.
PROCEDURE GetNewWindow*(windowID: INTEGER; wStorage: Types.Ptr; behind: WindowPtr): WindowPtr; INLINE PASCAL $A9BD; PROCEDURE AEObjectInit*(): Types.OSErr; EXTERNAL PASCAL;Not every external procedure has to follow the Oberon conventions however, Macintosh system calls use Pascal conventions. In order to stay flexible, we added the keyword PASCAL, which can follow INLINE or EXTERNAL to denote Pascal conventions. Future extensions might implement access according to C conventions with the keyword C, moving the burden of rearranging and converting parameters to the compiler.
PASCAL is also necessary to cope with completion routines. Many OS calls work in an asynchronous fashion: they return immediately and report the completion of a task later through a callback. In this case we have to instruct Oberon to use a different parameter arrangement for its own procedures.
As mentioned above, Macintosh OS access (filesystem, Memory Manager, etc.) works through the A-trap mechanism where the parameters are passed in the registers, especially A0, A1, and D0. Mixed parameter passing, part on the stack, others in registers, is not allowed. MacOberon uses SYSTEM.SETREG and SYSTEM.GETREG to access registers [Franz]. An example for a procedure expecting its parameters in registers is NewHandle. NewHandle is declared and used in MacOberon as follows:
PROCEDURE - NewHandle 0A1H, 22H; … SYSTEM.SETREG(0, 100); NewHandle; SYSTEM.GETREG(myHandle, 8);Evidently the declaration does not specify how many and what parameters NewHandle expects. The register assignment is not visible. Another approach would be the use of external glue routines to load parameters from the stack into registers. MPW Oberon uses compiler directives to map parameters onto registers. The same procedure NewHandle is declared in MPW Oberon as follows:
(*$Register NewHandle(D0): A0*) PROCEDURE NewHandle(size: LONGINT): Handle; INLINE $A122; … myHandle:=NewHandle(100);The register assignment is clearly visible and calling NewHandle is now simple. Thus we combine maximum program readability, i.e. no SYSTEM.XETREG, and maximum speed, i.e. no copy operations. It is also efficient in terms of debugging: all the call specific code (i.e. register assignments) are located in one spot.