Syntax10.Scn.FntGWParcElemsAlloc Syntax24m.Scn.FntSyntax14.Scn.Fnt GW Syntax14b.Scn.FntSyntax12b.Scn.FntGW Syntax12.Scn.FntGW GW GW  Syntax14i.Scn.FntC . 'rGW  GW  3  +I u3W3.@@XTableElemsAlloc#Syntax10.Scn.Fnt==/Table Perm Towers Queens Intmm Mm Puzzle Quick Bubble Tree FFT cc 262 341 194 498 476 1814 240 400 1232 540 cc -O4 105 180 82 242 180 483 93 127 958 314 SPARC-Oberon 121 201 97 284 192 1043 134 266 529 258 SPARC-Oberon/P 110 195 91 278 188 959 130 257 514 256 Ceres-Oberon 323 326 203 427 704 2593 223 316 307 1097  znN #Syntax10.Scn.Fntbb/Table Dhrystones/sec cc -O4: 19000 SPARC-Oberon: 16100 SPARC-Oberon/P: 16500 Ceres-Oberon: 6460  `.@#Syntax10.Scn.Fnt/Table sec Comment cc: 5.0 cc -O4: 1.4 SPARC-Oberon: 2.2 SPARC-Oberon/P: 2.0 SPARC-Oberon: 1.4 source code hand-optimized SPARC-Oberon/P: 1.3 source code hand optimized Ceres-Oberon: 3.6   M@*,#Syntax10.Scn.Fnt/Table Stanford Dhrystone Eratos cc 29.3 0.5 cc -O4 20.9 0.4 SPARC-Oberon 14.2 3.5 0.5 SPARC-Oberon/P 13.6 3.4 0.5 Ceres-Oberon 9.2 2.4 0.4  k@*,N #Syntax10.Scn.Fntqq/Table Stanford Dhrystone Eratos cc 5.7 2.7 cc -O4 29.3 4.3 SPARC-Oberon 1.1 0.3 0.1 Ceres-Oberon 3.0 1.7 0.5 {   6 < G"(F UGW ?GW ? |) 9"*E  SPARC-Oberon Implementation J. Templ Abstract  SPARC-Oberon is an implementation of Oberon for SPARC processors. It covers both, the Oberon programming language and the Oberon System running as a Unix process. This report describes how the SPARC-Oberon code generator works and gives an overview of the Oberon System implementation. It will be shown, that efficient code can be generated for SPARC without interprocedural register allocation or other expensive optimization techniques. Contents  1. Overview 2. Introduction to SPARC 3. Code Generation 4. Example 5. System Implementation 6. Measurements 7. Conclusions References Appendix A: Symbol Files Appendix B: Object Files Figures 1. Overview The implementation of SPARC-Oberon consists of two major parts. The first part is a compiler which produces SPARC code, the second part is the implementation of the Oberon System. Both parts are based on existing software running under the original Oberon environment on Ceres workstations. Hence, the SPARC-Oberon implementation could be done in relatively short time (7 month) as a cross development on Ceres for SPARC. The SPARC-Oberon compiler is based on the portable Oberon compiler [1], which is written in Oberon. The front end of this compiler remained unchanged, only the back end (=code generator) had to be rewritten to produce SPARC code [4, 5]. This means, the SPARC-Oberon compiler was developed as a cross compiler running on a host machine (Ceres) but producing code for a different target machine (SPARC). As a final step the cross compiler had to compile itself to produce the object files of the SPARC-Oberon compiler running on SPARC. Porting the Oberon System from Ceres to SPARC means, that one has to implement the inner core and the drivers [2, 3] for the Oberon System. This includes the modules Input, Display, Fonts, Files, Reals, Modules and Kernel. As the Oberon system relies on a runtime linker (module Modules), which is not available in current SunOS versions, it was necessary to use an object file format different to SunOS object files and to write a linking loader. The Oberon system also needs a garbage collector, which had to be implemented. The linking loader, the garbage collector and the main program to start an Oberon process are implemented in Sun Modula-2. Other modules (Input, Display, Files) could be implemented in Oberon using Oberon's low level facilities. These modules mainly provide interfaces to SunOS routines. However, it took a lot more time to figure out the right routines (e.g. 1 week for module Input) than to write the linking loader (2 days). 2. Introduction to SPARC SPARC (=Scalable Processor Architecture) is an architecture derived from Berkeley RISC I, RISC II and SOAR architectures. The term scalable referes to the fact, that SPARC is not a particular processor, but allows for a spectrum of possible price/performance implementations. The scalability lies in the number of integer registers, in the number of floating point units and in the low complexity, which allows an implementation in many different technologies. SPARC defines 3 main components: - an integer unit (iu) - a floating point unit (fpu) - a coprocessor interface (cp) All instructions are 4 bytes long and most of them execute in one cycle. Load and Store instructions are the only instructions that access memory. Addressing modes are register indirect (mem[reg + offset]) and register indexed (mem[reg1 + reg2]). Loads require an additional cycle if the loaded register is used in the subsequent instruction. Branches are executed delayed, that means, they take effect after the instruction following the branch is executed. The different execution units operate completely in parallel, but synchronization is done by the processor as if the instructions were executed sequentially. SPARC neither defines a memory management unit nor any kind of caching (both are implementation dependent). The integer unit (iu): supported data types for load and store instructions are: - signed and unsigned byte - signed and unsigned halfword (= 2 bytes) - word (= 4 bytes) - double word (= 8 bytes) The most significant byte is the byte with the smallest address (big endian). The sign-bit is the left-most bit, bit 0 is the right-most one. All data must be aligned to a multiple of the data's size. All integer operations are performed on 32-bit integer registers. Most instructions work on three registers, two for input and one for output. They also accept a 13-bit signed immediate operand instead of the second register. SPARC defines operations for shifts, addition, subtraction, conditional branches and conditional traps, some operations supporting procedure calls and a set of tagged operations, which are especially useful for languages like Lisp or Smalltalk. Multiplication and div/mod are not defined in the instruction set, there is only an instruction performing a one bit multiply step. Register windows: SPARC does not define a certain number of iu-registers, but instead states that every implementation must provide between 40 and 520 32-bit registers, which are arranged in so-called register windows. A register window is a set of 24 registers consisting of 8 input, 8 local and 8 output registers, where the 8 output registers of a window overlap with the 8 input registers of the next window. Each window corresponds to a procedure activation and the overlapping registers may be used for efficient parameter passing. The windows are organized as a cyclic queue in which traps are generated to handle over- or underflows. In addition to the windowed registers, SPARC defines 8 global registers, which are visible in every window. The number of windows lies between 2 and 32, where one window is reserved for efficient trap handling. See Fig. 1 for more details about register windows and procedure activation records. Special integer registers: zero register: Global register 0 holds the value zero. frame pointer, stack pointer: Per convention input register 6 always contains the address of the current activation record. Because of the overlap with output register 6 of the previous window, this register is at the same time the stack pointer of the calling procedure. SunOS expects this register to be aligned to 8 byte boundaries for efficient window trap handling. return address: Input register 7 always contains the return address of a procedure. Y-register: Used by the multiply step instruction to generate a 64-bit result. integer unit status register: 32-bit register containing (among other fields) a 4-bit integer unit condition code and a 5-bit current window pointer. window invalid mask: 32-bit register containing one bit for every window (used to generate window over- and underflow traps). trap base register: 32-bit register containing a 20-bit trap base address and an 8-bit trap type (used as an index into the trap table) The floating point unit (fpu): SPARC defines floating point operations according to the ANSI/IEEE 754-1985 standard. The floating point unit contains 32 global floating point registers, each 32 bit wide. Supported data types are: - single: 4 bytes - double: 8 bytes - extended: 16 bytes The floating point status register contains (among other fields) a 2-bit floating point condition code which is used by the floating point conditional branch instructions. SPARC defines a floating point queue with implementation dependent size (1 in the current implementation), which defines the number of floating point operations, that can operate in parallel. The coprocessor interface (cp): SPARC defines an interface for an arbitrary coprocessor (e.g. for graphics) and reserves 1024 instructions for it. The coprocessor contains 32 global 32-bit wide registers and supports the data types word and double word. A 2-bit coprocessor condition code is used by the coprocessor conditional branch instructions. The cp interface is much the same as the fpu interface, and in fact the fpu is an external coprocessor in current SPARC implementations. 3. Code Generation The SPARC-Oberon code generator was designed to be as efficient as possible and at the same time to produce fast and compact code. Optimizations independent from the target language are not the task of the back end and don't pay off for SPARC as much as for other processors, because most of the operands reside in registers without any optimizations. SPARC-Oberon uses one post-order tree traversal to translate the syntax-tree generated by the front-end into binary machine code. Register Usage The key to efficient code generation for SPARC lies in the usage of the register windows. Each window corresponds to a procedure activation and provides 6 windowed registers to be used as formal parameters and the remaining ones (8 local registers + 6 - number of parameters) may be used for local variables. Of course, it is only possible to allocate a variable in a register if its address is never used. This information is provided by the front end as a boolean flag for each variable and parameter. Variables and parameters which don't fit into the register window are allocated on the stack and are addressed relative to the frame resp. stack pointer, which are also windowed registers. Structured variables never reside in registers. Using this allocation scheme, most parameters and local variables are held in windowed registers without any optimizations. The next point to observe is that SPARC defines a three-address machine. This means, most operations work with three registers, two for source operands and one for the result. To generate good code it is necessary to evaluate expressions directly into the destination register, if the destination is a register. Otherwise evaluation may be done into an arbitrary register, that is stored into the result. This task is managed by evaluating the left hand side of an assignment first and then using the information about the designator as a hint for the evaluation of the right hand side. Code generation for an assignment z := x + y proceeds in the following steps:  1. evaluation of the designator z 2. evaluation of (x + y) using z as a hint 2.1 evaluation of x into any unused register (if not already in a register) 2.2 evaluation of y into any unused register (if not already in a register) 2.3 code generation for addition of x and y, if z is a register use z as destination, otherwise use any unused register 3. code generation for the assignment (no code if source and destination are the same register) In the common case, where all operands reside in registers, exactly one instruction "add x, y, z" will be produced, which needs only 4 bytes of code and one clock cycle. Register allocation is done easily by the compiler using two sets: One that contains all registers available for expression evaluation within a procedure (RegPool) and one that contains the registers used in a statement (RegSet). RegPool contains at least 13 registers (6 output and 7 global), because parameters and local variables are allocated in input and local registers only. RegSet is always empty at the beginning of a statement. Because of the three-address architecture of SPARC, the code generator procedures have (at least) three operands, two describing the source and one describing the target argument. e.g. PROCEDURE Add(VAR x, y, z: Item); (* generate code for z := x + y *) PROCEDURE PutF3(op: INTEGER; VAR x, y, z: Item); (* emit arithmetic instruction *) In passing we note that context-free code generation for two address architectures like NS32000 is not exactly the same as for three address architectures like SPARC and other RISC machines. For two address machines it means that code for an expression (x op y) is generated regardless of the expression's environment, for three address machines it means that code for a pattern (z := x op y) is generated regardless of the pattern's environment. Actually there is a similar generalization step between two address and stack architectures (zero address), where in the latter case context-free code generation means to generate code for an operator regardless of the operands. Procedure Calls Efficient procedure calls are very important in modern programming languages as they encourage the programmer to split up large procedures into managable pieces. On some processors, external and internal calls are treated differently for efficiency reasons. SPARC, on the other hand, does not provide hardware support for special external calls (via a fixup table) or for switching the module context. Instead of, it provides a pc-relative call instruction which covers the whole 32-bit address space. The context switch is not an issue, as there is no context, i.e. there are no special base registers bound to the current module. Call instructions are executed in one cycle, and there is no distinction between external and internal calls at runtime. Together with a one-cycle return instruction this accounts for a procedure call overhead of only two cycles in the best case. Although this procedure call protocol may be used to optimize leaf procedures (not done in SPARC-Oberon), it is not enough for general (possibly recursive) calls. In the general case, switching to a new register window when entering a procedure and restoring the previous window when leaving it, is necessary (Fig. 1). Register window switches execute in one cycle, if no window overflow or underflow occurs.  save sp, -96, sp switch to next window and reserve some local stack space ... procedure body jmpl i.7 + 8, g.0 return restore g.0, g.0, o.0 restore previous window (delay slot) The differences between internal and external calls are handled by the compiler and the loader. For internal calls, the pc-relative offset is known at compile time. Hence, the compiler can generate the appropriate instruction. For external calls the compiler provides some information (in the fixup table of the object file) for the loader, which calculates at load time the pc-relative offset and patches the call instructions. For each imported procedure there is an entry in the fixup table which points to the call instruction. Further calls to the same imported procedure are chained within the code. Delayed control transfer Delayed control transfer means, that a control transfer instruction takes effect after the instruction immediately following it (the delay slot) is executed. Filling the delay slot with a useful instruction instead of a nop saves 4 bytes of code and one cycle of time. Unconditional branches and untaken conditional branches may suppress the execution of the delay slot by the branch instruction's annul bit. Due to this feature, it is almost always possible to use the delay slot. However, the SPARC-Oberon code generator doesn't use it except for special code templates, because it is in contradiction with simple (context free) code generation and it is much easier to optimize this (and more) by a separate peephole optimizer. Global Variables When accessing global variables, the missing module context seems to be a drawback of SPARC. Using register indirect addressing (mem[high(adr)+low(adr)], global data accesses always need an additional instruction to load the high bits of the address into a register. As the absolute addresses are not known to the compiler, a mechanism similar to external procedure call fixups has to be applied to global variable accesses by the loader. The main difference lies in the fact, that two (or more) instructions have to be fixed instead of one. In the statement INC(a) for example, there is one instruction for loading high(adr of a) into a register and two instructions using low(adr of a) for loading and storing of a. To simplify these fixups, SPARC_Oberon forces the global data of a module to be aligned on 1024 byte boundaries. This makes it possible to load the start address of a module's global data (static base) with one sethi instruction. The offset used when accessing a global variable is the variable address relative to the global data start. This offset has not to be fixed, as it is known at compile time. The compiler generates only one entry in the fixup table (procedure number 255) for each module. This entry points to an instruction that loads the address of the global data. Other instructions using the same global data are chained within the code. Accesses to global data require two cycles. This is slower than accesses to local data (zero or one) and therefore encourages the programmer to use local instead of global data. This is in accordance with a good programming style and therefore should be considered as an advantage. The uniformity in accessing imported and non-imported global variables and, most important, the fast external procedure calls are other advantages of the empty module context. These points justify the SPARC design decision not to support any kind of module context. As an optimization (and violation of context-free code generation) a static base cache is maintained on a procedure level. The cache is filled at the first time a static base is loaded into a windowed register. To simplify cache maintainance, the cache state is frozen at the first occurrence of a forward jump. Run Time Support Some standard procedures and operators of the programming language Oberon can't be transformed directly into equivalent machine instructions by the compiler. These are the standard procedures NEW and SYSTEM.NEW and the operators *, DIV and MOD. They are compiled into calls of corresponding runtime routines using the same technique as for external procedure calls (module number is 255 in the fixup table). NEW, SYSTEM.NEW: Storage allocation is done using the first fit method with auxiliary free lists collecting small blocks of equal size. It should be noted that more advanced allocation strategies like the buddy system is not necessary as there are no explicit dispose operations in Oberon. Another advantage of this simple technique is that it is easy to guarantee heap consistency even in case of interrupt signals. All free list updates can be done by atomic store operations. Blocks allocated by NEW are initialized with zero for two reasons. First of all, all pointers must be set to NIL. This is a necessary precondition for automatic storage reclamation. Secondly, initializing integer fileds with value zero or strings with 0X is - although not defined in Oberon - assumed by some programmers. Storage allocation and garbage collection have been implemented using Oberon's low level facilities. *, DIV, MOD: Variable multiplications are compiled into calls to Sun's signed integer multiplication routine, because Oberon multiplication has the same semantics as C multiplication. Multiplications with constants are transformed into sequences of shifts, additions or subtractions. For DIV and MOD the compiler generates calls to runtime routines, which are based on unsigned DIV and MOD as used in C. PROCEDURE Div(x, y: INTEGER): INTEGER; BEGIN IF x >= 0 THEN RETURN x uDIV y ELSE RETURN -((-x-1+y) uDIV y) END END Div; PROCEDURE Mod(x, y: INTEGER): INTEGER; VAR m: INTEGER; BEGIN IF x >= 0 THEN RETURN x uMOD y ELSE m := -x uMOD y; IF m # 0 THEN RETURN y - m ELSE RETURN 0 END END END Mod; Peephole Optimizations A simple peephole optimizer for SPARC-Oberon has been implemented by M. Wunderli as a student project. The main task of this optimizer is to fill the delay slots of control transfer instructions. Another task is the rearrangement of load instructions to avoid wait cycles if the loaded register is used in the subsequent instruction. It turned out, that almost every delay slot may be filled with a useful instruction. However, the average speed improvement and code length reduction is only about five percent. 4. Example As an example for the code generated by SPARC-Oberon the Bresenham algorithm for drawing straight lines (in the first octant) is shown. This example has been taken, because it is one of the few cases, where assembly language must be used on most machines to get an efficient line drawing procedure. PROCEDURE Line*(x0, y0, x1, y1: INTEGER); VAR a, b, ba, h: INTEGER; BEGIN a := x1 - x0; b := y1 - y0; ba := b - a; h := b - a DIV 2; WHILE x0 < x1 DO Mark(x0, y0); IF h <= 0 THEN h := h + b ELSE h := h + ba; y0 := y0 + 1 END ; x0 := x0 + 1 END END Line; All parameters and local variables are allocated in windowed registers: variable: x0 y0 x1 y1 a b ba h register: i.0 i.1 i.2 i.3 i.4 i.5 l.7 l.6  save sp, -96, sp prologue sub i.2, i.0, i.4 a := x1 - x0 sub i.3, i.1, i.5 b := y1 - y0 sub i.5, i.4, l.7 ba := b - a sra i.4, 1, l.5 a DIV 2 sub i.5, l.5, l.6 h := b - a DIV 2 ba,a L1 anulled jump to while condition L2: add i.0, g.0, o.0 load param x0 add i.1, g.0, o.1 load param y0 call Mark Mark(x0, y0) nop subcc l.6, 0, g.0 bg L3 if h <= 0 then nop add l.6, i.5, l.6 h := h + b ba,a L4 L3: else add l.6, l.7, l.6 h := h + ba add i.1, 1, i.1 y0 := y0 + 1 L4: end add i.0, 1, i.0 x0 := x0 + 1 L1: subcc i.0, i.2, g.0 bl L2 while x0 < x1 DO nop jmpl i.7 + 8, g.0 return restore g.0, g.0, o.0 The code may be further improved by the peephole optimizer, which fills all delay slots with useful instructions. This saves 4 cycles in each iteration and produces optimal code in this particular example. save sp, -96, sp prologue sub i.2, i.0, i.4 a := x1 - x0 sub i.3, i.1, i.5 b := y1 - y0 sub i.5, i.4, l.7 ba := b - a sra i.4, 1, l.5 a DIV 2 ba L1 jump to while condition sub i.5, l.5, l.6 h := b - a DIV 2 delay slot L2: call Mark Mark(x0, y0) add i.1, g.0, o.1 load param y0 delay slot subcc l.6, 0, g.0 bg, a L3 if h <= 0 then add l.6, l.7, l.6 h := h + ba delay slot ba L4 add l.6, i.5, l.6 h := h + b delay slot L3: else add i.1, 1, i.1 y0 := y0 + 1 L4: end add i.0, 1, i.0 x0 := x0 + 1 L1: subcc i.0, i.2, g.0 bl, a L2 while x0 < x1 DO add i.0, g.0, o.0 load param x0 delay slot jmpl i.7 + 8, g.0 return restore g.0, g.0, o.0 5. System Implementation SPARC-Oberon is implemented as a single process running under SunOS, the Unix implementation for Sun computers. To boot Oberon, one needs a command that can be executed from a Unix shell. This command establishes the Oberon process and terminates only when Oberon is left (System.Quit). The boot-command oberon is written in Sun Modula-2. It implements the Oberon loader, and the runtime routines used for DIV and MOD. All other parts are programmed in Oberon. The loader also implements a generic interface (dlopen, dlsym) to SunOS shared object libraries which are used for the implementation of device driver modules like Input or Display. Those modules are mainly interfaces between Oberon and the appropriate SunOS libraries. Module Display implements Oberon raster operations on top of the SunOS Pixrect Library, which turned out to provide all of the raster operations necessary for Oberon. Except from some coordinate transformations, Display operations could be delegated directly to corresponding Pixrect operations. The Pixrect library is dynamically linked to the Oberon process. Therefore it is possible to exchange the Display subsystem completely and build it on top of another library such as X11. Module Fonts loads Oberon fonts by converting the character patterns into Pixrect objects. Module Input implements mouse and keyboard polling by means of select system calls with 10 msecs timeouts. Module Files maps Oberon files to Unix files. It maintains a table of open files and closes unused files automatically when the garbage collector is called. Because of a restriction in the Unix file system, it is not possible to open an arbitrary number of files (max. 64 on most machines). As Unix doesn't support anonymous files, new files always get a temporary working name (.tmp.pid.nr) that is renamed into the appropriate file name with Files.Register. Module System contains (among other things) the trap handler (in Unix called signal handler), that produces the trap viewer in case that the Oberon process receives a signal. Module Printer generates PostScript output and prints it via the Unix command lpr. A header file (named Oberon.Header.ps) is included to define the mapping from Oberon to PostScript fonts. All other modules (Oberon, Texts, Viewers, TextViewers, TextFrames ...) could be ported from Ceres-Oberon with almost no changes. 6. Measurements The following tables give some benchmark results for Ceres-Oberon, SPARC-Oberon and the Sun C compiler. cc means C-Compiler for Sparc without optimizations. cc -O4 means C-Compiler for Sparc with full optimizations. SPARC-Oberon/P means SPARC-Oberon with simple peephole optimizations. All timings were done without index-checks. Ceres2 runs an NS32532 with 25 MHz, Sparcstation1 runs with 20 MHz. Stanford Benchmarks: (msecs, less is better)  Dhrystone Benchmark: (V2.1, more is better)  Eratos: (less is better)  Object size: (kilobytes)  Compilation time: (secs)  When comparing object sizes and compilation times, one should note that C needs an additional linking step which does not exist in Oberon. Summary cc produces poor code. cc -O4 produces good code (up to 4 times faster than cc). SPARC-Oberon produces code which is not much slower than cc -O4. It is easy to tune most Oberon procedures by hand, so that they can hardly be optimized by a compiler (see Eratos). Sparcstation1 is between 2 and 3 times faster than Ceres2, however, deep recursions (as in Stanford Tree) are more expensive on SPARC than on NS32532 because of permanently generated window-underflow and -overflow traps. Floating point performance (measured in Stanford Mm and FFT) is better in SPARC-Oberon than in C, because C uses 64 bit reals internally. The simple peephole optimizer of SPARC-Oberon improves the code by about five percent. SPARC-Oberon statistics (including Oberon-2 extensions [6]): boot program: 563 lines of Modula-2 code code generator: 2721 lines of Oberon code (2896 for NS32000) compiler code size: 121 KB (64 KB for NS32000) system recompilation: 9 secs compiler recompilation: 9 secs 7. Conclusions There are many ways one can interpret the acronym RISC. The traditional interpretation is "reduced instruction set computer", another possibility is "reduced instruction set complexity" or "reduced instruction set cycles" and a third one is "regular instruction set computer". It turned out that SPARC is a RISC in all three interpretations. The instruction set is relatively small, most instructions execute in one cycle and the instruction set is fairly regular. The main irregularity is in the treatment of floating point operations with special floating point registers, special floating point condition codes and special conditional branches. This complicated the code generator slightly but, after all, it was not a real problem. SPARC, although developed independently, has many ideas in common with Oberon. Both were designed "as simple as possible", which distinguishes every good design. To provide enough flexibility for any kind of application, SPARC provides scalability on the hardware level which corresponds in some sense to Oberon's extensibility on the software level. SPARC also fits perfectly into the single process concept of Oberon, as context switches are relatively expensive due to the large register file. For heavy weight Unix processes this overhead is negligible, but for light weight processes (coroutines) this may be a bottleneck. The Oberon environment on Ceres has proved its effectiveness even in the task of cross developing programs for Unix machines. Considering the fact that, beside the runtime routines, mainly four modules (Input, Display, Fonts and Files) had to be implemented to "boot-strap" SPARC-Oberon, it has also proved its portability. However, program portability is not the only aspect that must be considered. The second aspect is portability of data files, which unfortunately has not been considered important by the authors of Oberon. Incompatible data files result for instance when using byte-block operations to read or write integer values because the external format depends on the byte ordering of the machine (little or big endian first). There are two ways to deal with incompatible data files. First, one can write a converter for each kind of document and second, one can adapt the programs. The first alternative is difficult to use in the case of extensible software systems because the converter has to be extensible too. Note, that a single unportable extension implies an unportable data file. The second alternative sacrifies program portability for data portability. During the course of this project, it became clear that data portability is particularily important for extensible software systems. Acknowledgements I wish to thank R. Crelier and M. Franz for their cooperation in this project. Robert Griesemer provided the terminal emulation software on Ceres. Karl Rege ported the Paint tool from Ceres to SPARC. For all the hints necessary to navigate through SunOS, I am grateful to C. Szyperski, U. Hlzle and many others. Finally thanks to U. Hiestand whose document editing system Leda was a pleasure to work with. References  1. R. Crelier. OP2: A Portable Oberon Compiler. Report 125, ETH Zurich, 1990. 2. J. Gutknecht. The Oberon Guide Report 138, ETH Zurich, 1990. 3. N. Wirth, J. Gutknecht. The Oberon System. Software - Practice and Experience 19, 9 (Sept. 1989) 4. Sun Microsystems. The SPARC Architecture Manual. Revision 50 of August 8, 1987. 5. Sun Microsystems. The Sparc Papers. SunTechnology, The Journal for Sun Users, Summer 1988 6. H. Moessenboeck The Programming Language Oberon-2 Report 160, ETH Zurich, 1991. Appendix A: Symbol Files N. Wirth / RC 20.2.90 / JT 15.8.91 (former symbol file format, should be updated) SymFile = SFtag ModAnchor {Element}. SFtag = 0FAX. ModAnchor = MOD key name. Element = ModAnchor | CON Constant | (TYPE | HDTYPE) Type | (VAR | RDVAR | FLD | RDFLD) Variable | (VALPAR | VARPAR) Parameter | PLIST {Element} (XPRO | IPRO | TPRO ref1 mno1 ) Procedure | PLIST {Element} TPRO TProcedure | PLIST {Element} CPRO CProcedure | PTR PointerType | PLIST {Element} PROC ProcType | ARR ArrayType | DARR DynArrType | FLIST {Element} REC RecordType | (HDPTR | HDPROC) HiddenFldOff | HDTPRO ref1 mthdno1 procno1 | FIX Fixup | NTPRO ref1 n1 | SYS Flag. Constant = (BYTE | CHAR | SINT) value1 name | BOOL (FALSE | TRUE) name | (INT | LINT | SET) value name | REAL value4 name | LREAL value8 name | STRING name name | NIL name. Type = ref1 modno1 name. Variable = ref1 offset name. Parameter = ref1 offset name. Procedure = ref1 procno1 name. CProcedure = ref1 len1 {code1} name. TProcedure = ref1 mthdno1 procno1 name. PointerType = baseRef1 modno1. ProcType = resultRef1 modno1. ArrayType = elemRef1 modno1 size boundAdr nofElem. DynArrType = elemRef1 modno1 size lenOff. RecordType = baseRef1 modno1 size descAdr. HiddenFldOff = offset. Fixup = ptrRef1 baseRef1. Flag = ref1 sysflag. CON = 1. FLIST = 16. predefined refs: TYPE = 2. FLD = 17. HDTYPE = 3. HDPTR = 18. UNDEF = 0. VAR = 4. HDPROC = 19. BYTE = 1. XPRO = 5. FIX = 20. BOOL = 2. IPRO = 6. SYS = 21. CHAR = 3. CPRO = 7. MOD = 22. SINT = 4. PTR = 8. RDVAR = 23. INT = 5. PROC = 9. RDFLD = 24. LINT = 6. ARR = 10. TPRO = 25. REAL = 7. DARR = 11. NTPRO = 26. LREAL = 8. REC = 12. HDTPRO = 27. SET = 9. PLIST = 13. STRING = 10. VALPAR = 14. FALSE = 0X. NIL = 11. VARPAR = 15. TRUE = 1X. NOTYP = 12. Names are sequences of characters (bytes) terminated by 0X. Lower case identifiers denote numbers, which are of variable or fixed length, depending on the presence of a digit appended to the identifier. In case of a fixed length number, this suffix is its length in bytes. Otherwise, the number is encoded in a machine independent format (least significant byte first, base 128, most significant bit as "stop bit", this bit cleared meaning stop). Sets are treated the same way as integer numbers. These numbers can be read and written using the following procedures:  PROCEDURE WriteNum(VAR R: Files.Rider; x: LONGINT); BEGIN WHILE (x < -64) OR (x > 63) DO Files.Write(R, CHR(x MOD 128 + 128)); x := x DIV 128 END; Files.Write(R, CHR(x MOD 128)) END WriteNum; PROCEDURE ReadNum(VAR R: Files.Rider; VAR x: LONGINT); VAR s: SHORTINT; ch: CHAR; n: LONGINT; BEGIN s := 0; n := 0; Files.Read(R, ch); WHILE ORD(ch) >= 128 DO INC(n, ASH(ORD(ch) - 128, s) ); INC(s, 7); Files.Read(R, ch) END; INC(n, ASH(ORD(ch) MOD 64 - ORD(ch) DIV 64 * 64, s) ); x := n END ReadNum; Real numbers are in the 32 bit IEEE format, longreal numbers in the 64 bit IEEE format, most significant byte first. Appendix B: SPARC-Oberon Object Files ObjFile = OFtag HeaderBlk EntryBlk CmdBlk PointerBlk ImportBlk FixupBlk ConstBlk CodeBlk TypeBlk RefBlk. OFtag = 0F8X 36X. HeaderBlk = objsize4 refsize4 nofentries2 nofcoms2 nofptrs2 nofrecs2 nofgmod2 nofixups2 datasize4 consize2 codesize2 key4 modname20. EntryBlk = 82X {pc2}. CmdBlk = 83X {name pc2}. PointerBlk = 84X {off4}. ImportBlk = 85X {key4 name}. FixupBlk = 86X {mno1 pno1 fixup2}. ConstBlk = 87X {con1}. CodeBlk = 88X {inst4}. TypeBlk = 89X {recsize4 tdadr2 recname basemod2 baseadr2 nptr2 ntpro2 {ptroffset4} {mno2 procno2} }. RefBlk = 8AX {0F8X procend name {Mode Form adr name}}. Mode = Var | VarPar. Form = Byte | Bool | Char | SInt | Int | LInt | Real | LReal | Set | Pointer | String. Var = 1. VarPar = 3. Byte = 1. Bool = 2. Char = 3. SInt = 4. Int = 5. LInt =6. Real = 7. LReal =8. Set = 9. Pointer = 13. String = 15. Names and integer values are encoded as described in Appendix A. Fixups are used for external procedure calls and for accessing global variables. File: SparcOberonImp.Text / JT 12-Feb-93