Last revised 20-Mar-1999
Copyright (1994-1998) by Günter Dotzel, Email: [email deleted due to spam]
This article describes the design goals, quality issues, implementation details,
development stages and bootstrap of a Modula-2 and Oberon-2 Compiler
written in Modula-2 for the Digital Alpha AXP RISC-processor running under
OpenVMS V6.1 or later. The compilers are called MaX and A2O respectively.
Compared to other RISC processors, the Alpha architecture [Site] is uncommon in that there are no symbolic references in the code section, no program status word, 48 different modes for each floating point instruction and no exact trap delivery. The data alignment issues and exception handling are different. The impact of this challenging architecture on the back-end design is demonstrated.
In order to support the five different floating point data types and 64 bit signed/unsigned arithmetic of the Alpha processor, the necessary language extensions in module SYSTEM are discussed for both, [ISO] Modula-2 [Wirt, IsoM2] and Oberon-2 [Moes].
Extensions of the ISO Modula-2 library are proposed for I/O-, number/string-conversions, trigonometric complex math modules.
The project also included the porting of all operating system specific low-level interface modules (including layered products such as XWindows, OSF/Motif, GKS, ...) as well as the full ISO Modula-2 standard library including processes and semaphores module, which can all also be used with Oberon-2.
The Modula-2 compiler was derived from ModulaWare's VAX/VMS Modula-2
multi-pass compiler (which itself is based on ETH-Zürich's Modula/R compiler
[Sohm]). The compiler has been maintained since 1987 and substantially
improved and extended. ModulaWare's implementation supports most of the
new ISO Modula-2 language features as well as the full ISO Modula-2
The [stand-alone] Oberon-2 compiler is based on ETH-Zürich's portable Oberon-2 compiler OP2 [Crel], itself based on the single-pass Oberon compiler [GutWir].
The self-funded project was undertaken at ModulaWare by three full time and one part time coworkers of ModulaWare from Jan-94 to Aug-94.
The following concentrates more or less on MaX, because this compiler was released before A2O. Actually, A2O is written in Modula-2 and many modules of the A2O back-end are identical with MaX's back-end. Also, MaX is used to bootstrap A2O on Alpha.
Unless otherwise noted below and in respect to standard types and data type sizes as well as the language accepted, MaX is fully upward compatible with its VAX/VMS counterpart, MVR V4. A2O is fully upward compatible with its VAX/VMS counterpart, H2O V1.30.
The goals of the Alpha compiler project for both, Modula-2 as well as Oberon-2 were
REAL=SYSTEM.S_FLOATING; LONGREAL=SYSTEM.T_FLOATING; COMPLEX=SYSTEM.S_COMPLEX; LONGCOMPLEX=SYSTEM.T_COMPLEX;
and with /NOIEEE, these are the VAX floating point types:
REAL=SYSTEM.F_FLOATING; LONGREAL=SYSTEM.D_FLOATING; COMPLEX=SYSTEM.F_COMPLEX; LONGCOMPLEX=SYSTEM.D_COMPLEX;
The same rule applies to ISO Modula-2's literals of RR-type: With /IEEE, the
real literal 1.23 is of generic IEEE floating point type (S | T)_FLOATING and
with /NOIEEE, it is of VAX-floating point type (F | D)_FLOATING.
The same rule applies to ISO Modula-2's literals of CC-type: With /IEEE, the complex number constructor CMPLX(1.23, 4.56) is of generic IEEE complex type SYSTEM.(S | T)_COMPLEX and with /NOIEEE, it is of VAX-complex type SYSTEM.(F | D)_COMPLEX.
The Alpha processor doesn't have VAX's D_FLOATING arithmetic, but supports D_FLOATING number conversion from and to G_FLOATING. In MaX, D_FLOATING arithmetic is simulated with G_FLOATING operations as recommended in [Site] with a loss of 3 significant binary digits (about one decimal digit):
The VAX processor doesn't have Alpha's (S | T)_FLOATING arithmetic. For symmetry/source code compatibility reasons, SYSTEM.(F | D | S | T | G)_COMPLEX are also introduced in MVR. For backward compatibility, the data types SYSTEM.(S | T)_FLOATING are synonym for SYSTEM.(F | D)_COMPLEX, but SYSTEM.(S | T)_COMPLEX constants in symbol files are not allowed in MVR.
PROCEDURE REGISTER(num: CARDINAL): SYSTEM.UNSIGNED_64 (* num must be a constant expression. 0<= num <= 31*)
The use of num=31 in REGISTER is special: it allows to detect whether the program is compiled for or running on Alpha or VAX: REGISTER(31) returns the value 0 in MaX and 1 in MVR; with num=31, it is a constant value and may be used in the declaration section.
CONST isAXP = SYSTEM.REGISTER(31) = 0; CONST eps = LFLOAT( ORD(isAXP)) * 2.E-15 + LFLOAT (ORD (~ isAXP)) * 2.E-16;
In MVR, the result type of the procedure REGISTER is CARDINAL.
PROCEDURE FREGISTER(num: CARDINAL): T_FLOATING (* num must be a constant expression. 0<= num <= 31 (F31 is 0). returns the value of the floating point register num. *)
In MVR, there is no such procedure. To get other floating point types than T_FLOATING, use SYS'TEM.CAST. For example, to treat floating point register F0 as S_FLOATING, use CAST(S_FLOATING, FREGISTER(0)).
In Modula-2 there is no LONG/SHORT function, no type hierarchy and no
implicit type conversion as in Oberon-2. Instead of introducing new conversion
functions such as SYS'TEM.FLOAT(S | T) or SYS'TEM.(SHORT | LONG) for
real and integer conversion in MaX, the semantics of the standard function
VAL is extended to allow conversion between reals/reals (including those
defined in SYSTEM), reals/scalars and scalars/scalars (scalars are chars,
bools, enums, subranges, ints, cards). The Alpha processor has conversion
instructions for IEEE and for VAX floating point types. For conversion between
the two floating point families, the compiler generates a call to the OpenVMS'
run-time-library routine called cvt$convert_floating.
Modula-2's TRUNC, INT and ABS are generic in the sense that they take an argument of any real type including the system floating point types.
In Oberon-2, the type hierarchy as defined in the language report is
SHORTINT <= INTEGER <= LONGINT <= REAL <= LONGREAL
With A2O, SHORT and LONG can be used for conversion between SYSTEM.SIGNED_64 and LONGINT. The type hierarchy in VAX/VMS Oberon-2 is (the module qualifier SYSTEM for *_FLOATING is omitted):
SHORTINT <= INTEGER <= LONGINT <= REAL <= LONGREAL <= H_FLOATING
Oberon-2's ENTIER is of type LONGINT; arguments of type [LONG]REAL and
additionally (S | T | G | H)_FLOATING are allowed;
The type hierarchy in A2O is
SHORTINT <= INTEGER <= LONGINT <= SIGNED_64 <= REAL <= LONGREAL
The actual type of REAL and LONGREAL depends on the /[NO]IEEE
compilation qualifier. Also G_FLOATING doesn't fit into Oberon-2's type
hierarchy (LONG, SHORT aren't applicable), real literals being either of
(F|D)_FLOATING or (S|T)_FLOATING are assignment compatible with
G_FLOATING, if they are in the range MIN(G_FLOATING) ..
The question is: Where is (S|T)_FLOATING in the type hierarchy?
In figure 1 the following internal type hierarchy for implicit type conversion is assumed:
SHORTINT <= INTEGER <= LONGINT <= F <= D <= S <= G <= T <= H_FLOATING VAR si: SHORTINT; i: INTEGER; li: LONGINT; ll: SYSTEM.SIGNED_64; (* Alpha only *) f: SYSTEM.F_FLOATING; d: SYSTEM.D_FLOATING; s: SYSTEM.S_FLOATING; (* Alpha only *) g: SYSTEM.G_FLOATING; t: SYSTEM.T_FLOATING; (* Alpha only *) h: SYSTEM.H_FLOATING; (* VAX only *) \ \ source target\ si i li ll f d s* g t* h x = not allowed _____________________________________ si := x x x x x x x x x <space> = allowed i := x x x x x x x x * = only available on Alpha li := x x x x x x x # = only available on VAX ll*:= x x x x x x % = only allowed on Alpha _ _ _ _ _ _ _ _ _ because of VAX restriction f := | x x x x x - = not available d := | x % x x s* := | x x x g := | % x x t* := | x h# := | - - _____________________________________
Figure 1: Basic type hierarchy in Alpha Oberon-2
Conversion from SIGNED_64 to any real type is done implicitly in
assignments within Oberon-2's type hierarchy.
Note, there is no standard function in Oberon-2 to convert from any integer type to a real type, e.g.: with the declaration above, A2O allows to assign either variable si, i, li, ll, f, s, d, or g to the variable t.
The result type of Oberon-2's standard function ENTIER is LONGINT. On Alpha, it would be better to have the result type SIGNED_64. For compatibility reasons A2O stays with the LONGINT result type, but allows as argument (S | T | G)_FLOATING in addition to [LONG]REAL.
PROCEDURE ENTIER (r: T): LONGINT; T is SYSTEM.(F | D | G | H | S | T)_FLOATING
With ENTIER being of LONGINT type, it is not possible to avoid the introduction of a new conversion function such as SYSTEM.LENTIER to convert from real type to SIGNED_64. This is also due to the lack of a flexible conversion function such as Modula-2's VAL in Oberon-2.
PROCEDURE SYSTEM.LENTIER (r: T): SIGNED_64; T is SYSTEM.(F | D | G | H | S | T)_FLOATING
VAL((S | T)_FLOATING, expr) converts expr of any real or scalar type to the real type specified in the first parameter.
CMPLX(VAL((S | T)_FLOATING, exprRe), VAL((S | T)_FLOATING, exprIm)) constructs a (S | T)_COMPLEX constant with real part exprRe and imaginary part exprIm, each of any real or scalar type.
VAL (INTEGER, expr) and VAL (SYSTEM.SIGNED_64, expr) converts any real or scalar type to the type specified as first parameter.
Since Alpha/OpenVMS is a 32 bit operating system on a 64 bit processor, the
compilers are 32 bit compilers with provisions to also deal with 8 byte
quantities such as pointers, procedure values, and quadwords. Some
structures such as procedure descriptors, procedure entry addresses and
procedure linkage pairs already support 64 bit addressing. Currently all
pointer values and addresses evaluated at run-time have the upper 4 bytes
set to 0. The process' virtual address space of OpenVMS is in the range
0..2^31. As soon as DEC extends the OpenVMS' data structures to 64 bit
addresses (DEC's schedule is for end-of-1995), MaX could be bootstrapped
with the type size of CARDINAL, INTEGER, ADDRESS and pointers set to 8
byte. To illustrate one of the OpenVMS 32 bit limits (Alpha and VAX), take for
example the routine LIB$GET_VM called from within Storage.ALLOCATE; it
returns only a 32 bit address. In order to support 64 bit virtual addressing,
there must be replacement routines for all system calls that currently have 32
bit address parameters.
MaX also supports %FOREIGN definition modules as with MVR and the goal is that all MVR foreign interface modules (source and symbol file) are compatible with MaX. MaX allows AST routines to be written in M2 as with MVR. The rules are the same as with MVR (see compilation qualifier /foreign_code).
The (Modula|Oberon)-2 symbol files are compatible under Alpha/OpenVMS and VAX/VMS with one restriction: SYSTEM.(S | T)_FLOATING literals in symbol files are currently not supported with MVR|H2O. Variables of type (S | T)_(FLOATING |COMPLEX) are not supported on the VAX. MVR|H2O implement these types as synonym to (F | T)_(FLOATING | COMPLEX). Trying to load binary data of (S | T)_FLOATING type generated on the Alpha on VAX will result in normalizing the floating point values to (F | D)_FLOATING which results in incorrect values.
Some careful considerations and precautions are necessary to allow MaX to
process MVR's symbol files without getting problems with the new data types
SYSTEM.(S | T)_FLOATING and SYSTEM.[UN]SIGNED_64 and also to avoid
unaligned program section offsets for variable and constant data structures.
Proper data alignment is one of the most crucial preconditions in order to take full advantage of Alpha Alpha's high-performance RISC architecture. In symbol files, the alignment problem arises with variables, record types and also with value constructors declared in definition modules. In the program modules, alignment is critical for fast access of variables in the global data section (RD = R14), string section (RS = R13), stack (SP = R30), procedure frame (FP = R29) and heap.
Data on the stack, global data in a data-section, strings, literals and constant record- and array-constructors in the string-section and all data in the new linkage-section (procedure descriptors and other internal and external references) are always quadword aligned. (The only restriction is with variable declarations in definition modules whose symbol file was generated with a pre V4 version of MVR).
Variables of real type are always assumed to be naturally aligned (even if the compiler knows they are badly aligned, e.g. when their byte offset in a packed record is not a multiple of 4 or 8) since there is no unaligned access instruction for floating point load/store. In the case that real variables are not naturally aligned, unalignment traps are handled by the OpenVMS operating system. Pointers to base types whose size is a multiple of a longword or quadword are assumed to be naturally aligned. Pointer access to variables of other type sizes are assumed to be unaligned and the compiler generates code for [badly] unaligned access.
Packed records in MaX and MVR: There is a new compilation qualifier /[no]packed_records which allows to enable and disable alignment of record fields. In the case of record fields which are not naturally aligned, the compiler generates code for unaligned access. With unpacked records, the minimum for natural alignment is 4 bytes, even if the type size is smaller; if the record field type is larger than 8 bytes, the alignment is 8 bytes.
MaX generates an unaligned access when it knows it is unaligned or when it
knows that it could be. MaX generates run time checks in the case it doesn't
know whether it is unaligned or not (unless the compilation option
/optimize=(NoAlignCheck) to suppress them is present).
Values of the /optimize qualifier:
/Optimize = (schedule, alignCheck, rotatePreserved, rotateScratch, stackCheck, eliminateUnreachableCode)
There is a subtle difference between knowing that something could be
unaligned and not knowing anything. It is exemplified by the difference
between (say) a VAR parameter and an array access to a field of a record
which is not a whole number of quadwords: The VAR parameter might be
unaligned but we do not know anything more --- this generates a runtime
check. For the array access, which is unaligned for some values of the index
and aligned for others, the situation is slightly different --- in this case we know
that in some cases it will be unaligned and so generate unaligned access code
whether or not the switch to disable runtime checks is present. Array element
access for base type size of 1 byte is always treated as unaligned.
A very efficient block-copy routine in the Modula-2 run-time system called mod$rtscopy serves to copy from and to unaligned source and destination addresses. This routine is used where the size of the data to be copied is not known at compile time, i.e.: in the entry code of procedures with value parameters of open array type. The maximal size copied is 2^31-1 bytes. There are no 16 bit restrictions. The copy routine is a masterpiece in software engineering. The best possible speed is achieved by asymmetric load and store operations. If the data source or destination is unaligned, the main copy loop uses unaligned loads but stores are always quadword aligned. Efficient in-line code is generated for the pervasive procedure LENGTH. Again, there is no 16 bit restriction on the size of the strings.
Because the Alpha processor was designed as a multiple issue machine, i.e. multiple instructions can execute at the same time, it does not have a program status word (PSW). Having such a central resource would limit the scaleablity of the Alpha architecture. Also, arithmetic operations don't set any conditions. Instead there are compare instructions which compare two operands and deliver a value of 0 or 1 in a third register. Because the integer and floating point units are fully separated so that they can operate independently in parallel, floating point compares deliver a result of 0 or 0.5 (or 0 and 2.0 depending on the floating data type interpretation) in a floating point register. MaX uses a dedicated register called PDR (which is also used procedure descriptor pointer in the prologue of a procedure) as replacement for the PSW; it contains either 0 or 1. In combined integer and floating relational expressions, it is necessary to move floating point compare result into PDR. Thus a redesign of the handling of conditional branches was necessary. The fix-up of conditional branch targets needed to be changed. A new technique was used to invert the branch condition in the fix-up link. The re-implementation of conditional expressions, to produce reasonably efficient code on Alpha with as few branches as practicable, turned out to be one of the most crucial (ie. time consuming) tasks.
There are a bewildering number of options for floating point arithmetic
The instruction mode options defined at the moment are as follows
/InstructionMode = ( FptTrapB, ChoppedDECRounding, SoftwareDECTrap, UnderflowDECTrap, OverflowDECTrap, ChoppedIEEERounding, DynamicRounding, PlusInftyRounding, MinusInftyRounding, InexactIEEETrap, SoftwareIEEETrap, UnderflowIEEETrap, OverflowIEEETrap, IntTrapB, IntOverflow)
There are two integer arithmetic options: The IntOverflow option controls
whether the /V qualifier (overflow trap enable) is used and IntTrapB controls
whether a trap barrier (TrapB) instruction is inserted for precise detection of
which instruction failed. The rest of this set of options permits the generation
of each of the 48 different floating point add instructions (!).
According to Appendix B IEEE Floating Point Conformance of [Site], we only need to insert TRAPB instructions in the case that the '/S' (Software trap handler) Alpha instruction qualifier is selected.
If it turns out that we need to supply the user trap handler(s), then we propose not to implement the /S format instructions at all. With other words, if the user specifies (eg.) '/InstructionMode=(inexactIEEEresult)' then, if we attempt to generate an ADDT/I instruction, which is illegal, we emit an error message along the lines of
"IEEE Inexact result exception handling requires the software trap switch".
According to [Site], the combination of the floating point modes are:
If you want '/I' for IEEE floating point arithmetic instructions (ADD, SUB etc.), you have to have '/S', and if you have '/S' you must have '/U'. For the convert instruction, if you have the '/S' switch, you must have the '/V' switch and, as for the arithmetic instructions, '/I' requires '/S'.
For DEC floating point there are no restrictions; all combinations of '/S', '/U', '/V' and '/C' options (not instructions!) are possible.
So we give the following new error messages, emitted by the back end if it encounters an instruction which requires an illegal combination of options
MaX strictly follows the procedure calling conventions as specified for
OpenVMS [Opcc]. Since even non exported procedures at global level might
be called from foreign language by exporting them via procedure variables or
by using SYSTEM.ADR, it is necessary to setup the base pointer registers for
global data- and string section as well as the static link pointer (see below) if
they are used in the procedure body. Fortunately, in order to load these base
pointer registers, only a single indirect access via the procedure descriptor
pointer register PDR is required. Unfortunately this means that it is not
possible to generate so-called light-weight or even null-frame procedures.
Oberon-2 programs can call Modula-2 procedures and vice versa. This is possible because MaX|A2O use
Modula-2 allows access of local variables in nested procedures and nested
modules within procedures. As with MVR, an access to outer-scope local
variables is accomplished via a static-link chain, where nested procedures
store the pointer to the directly surrounding procedure in the procedure's
frame. Although this technique does not limit the number of nesting levels, the
implementation allows up-to 15 nested procedures. If a nested procedure is to
be called which accesses local variables, the local base pointer passed in R1.
To access outer-scope local variables, a procedure loads the proper base
pointer by walking the static link chain and then uses the compile time fixed
variable offsets in the same way it accesses local variables of its own stack
Foreign language interactions and AST compatibility: In contrast to the In contrast to the Dijkstra display space (DDS), which was used in an earlier implementation, the static link chain avoids all problems which could arise with foreign languages and/or OpenVMS ASTs when calling Modula-2 procedures which have nested procedures.
MaX implements coroutines with the data type SYSTEM.PROCESS which is a
pointer to the coroutine's stack. SYSTEM.NEWPROCESS sets up the initial
stack frame of a coroutine and is completely in-line code. The workspace for a
coroutine in Modula-2 is 2KB (requirement of the Alpha processor architecture)
plus a minimum of 2KB for the coroutine's stack. The main coroutine has no
The first activation of a coroutine via SYSTEM.TRANSFER is a procedure call via a wrapper procedure in the run-time system modula2$run- timesystem.mod$newprocess. Each subsequent TRANSFER is only a load of RV and a stack swap; so TRANSFER is completely inline code. RV is the current process pointer and points either to the main process or to the work-space of a coroutine. The value for RV is stored in a global variable in the Modula-2 run-time system. All processor registers used by a procedure that calls TRANSFER are saved at the entry code and restored at the exit code of the procedure.
Normally a process (coroutine) is cyclic, i.e. a procedure is declared to be a process by calling NEWPROCESS. The procedure contains a (mostly endless) loop where it calls TRANSFER. If needed, additional registers are saved before the TRANSFER and restored after the transfer. But since MaX uses only a simple register allocation mechanism, this is only required if the procedure uses locked registers (i.e. the first WITH statement usually locks one preserved register). The time needed for the coroutine context switch is independent of the coroutine's stack-size.
If the transfer destination is not the main coroutine, the stack limit is checked
at each coroutine transfer. The compilation option /optimize=(stackcheck)
controls whether additional stack-overflow checking is done at each
procedure entry. In the case that a procedure has open array value
parameters, an additional stack-check is performed before copying in each
open array to the dynamically enlarged stack of procedure. Note, that RV is
needed to do the stack check. When a foreign routine calls a Modula-2
routine, RV can not be relied on, because the static link pointer RV is not
set-up (foreign languages know nothing about Modula-2's RV). In the case
that RV is used in a procedure, it is loaded via the procedure descriptor at the
procedure entry, if the /foreigncode compilation qualifier is used. In order not
to slow down applications which are written completely in Modula-2 or where
Modula-2 calls foreign procedures but not vice versa, the compilation qualifier
/ForeignCode controls whether RV gets loaded if it is used.
Coroutines in Oberon-2: A2O performs the stack-check in the procedure entry code exactly as MaX. This allows to Oberon-2 to use Modula-2's coroutines for stand-alone programs and to call Oberon-2 routines from Modula-2.
The Alpha Oberon System (AOS) [DotGoe] has a module Coroutines which is completely written in Oberon-2: Coroutines.NEWPROCESS sets-up the coroutine's stack-limit and the so-called Djikstra display space used to access outer-scope local variables. Coroutines also supports exception handling and garbage collection:
DEFINITION coroutines; TYPE PROC = PROCEDURE; PROCESS = POINTER TO WorkspaceDesc; PROCEDURE Count (): SYSTEM.SIGNED_64; PROCEDURE Current (): PROCESS; PROCEDURE DeInst; PROCEDURE NEWPROCESS (p: PROC; stacklen: SYSTEM.SIGNED_64; VAR new: PROCESS); PROCEDURE Reset; PROCEDURE TRANSFER (to: PROCESS); END coroutines.
We've found that, in the case of coroutine transfer Reiser's law is not valid.
Reiser says (in one of the Oberon books) that software is getting faster slower
than hardware is getting faster. Now that we've done coroutines for the
Alpha/OpenVMS Modula-2 compiler, we measured the speed of a full coroutine
context switch: A coroutine transfer takes only one microsecond on
Detailed timings: Transfer including safe stack-overflow check, stack-swap and provisions for foreign language or AST calling: Alpha/125 MHz: 2.5 us, Alpha/200 MHz 1.6 us.
Without stack-overflow check: Alpha/125 MHz: 1.8 us, Alpha/200 MHz 1.1 us.
Compared to a VAXstation 3100/30, a transfer on Alpha/200MHz is 80 times faster than the fastest possible transfer method (i.e. stack-swap *without* stack-overflow check on transfer).
Here are some recommendations on how to select compilation options. All options are selectable on a per compilation unit basis.
The qualifier /Foreigncode is needed only if Modula-2 procedures are called by foreign procedures.
Using the qualifier /NoForeign still allows foreign procedures to be called. VMS system calls, run-time library and foreign language routines, which do not themselves call Modula-2, need no special treatment.
The qualifier value /optimize=(stackcheck) tells the compiler to emit code to check for stack overflow at the procedure entry code. If the main coroutine calls procedures with a stack check, the check is bypassed because there is no stack limit. This option is generally only needed if coroutines are used.
The proposed default for general use library modules where one does not know whether the application has coroutines and/or foreign code is to set the most pessimistic options and thus have stack-check and foreign code compatibility enabled. The MaX distribution kit uses these pessimistic options at installation time, but the user-default is optimistic.
Recommended combination of compilation options:
1 = /optimize=(stackcheck)/foreigncode 2 = /optimize=() /foreigncode 3 = /optimize=(stackcheck)/noforeigncode 4 = /optimize=() /noforeigncode | coroutines | no coroutines ------------------------------------------ foreigncode| 1 | 2 ------------------------------------------ noforeign | 3 | 4
Figure 2: Recommended combination of compilation options
The way exception handling (LIB$ESTABLISH and LIB$REVERT) work on Alpha/OpenVMS changed radically when compared to VAX/OpenVMS, in that these routines are no longer available in the run-time system and in fact they can't be implemented as routines. DEC's Alpha-compilers now treat these functions as intrinsic and emit inline code directly. With MaX, these two functions must now be imported from SYSTEM and they produce in-line code. The exception model chosen is dynamic. The declaration of the SYSTEM functions LIB$ESTABLISH and LIB$REVERT is
PROCEDURE SYSTEM.LIB$ESTABLISH ( %IMMED handle: ConditionHandler ): PROC; PROCEDURE SYSTEM.LIB$REVERT(): PROC;
The result type of both functions is actually a function type, but since there is
no standard function type in Modula-2, MaX uses PROC for that purpose. The
use of LIB$ESTABLISH is system dependent anyway, so it is not a drawback
to require the import of CAST where the proper function type is needed.
The argument type ConditionHandler must be structurally compatible with the procedure type
TYPE ConditionHandler = PROCEDURE (VAR %REF a,b: ARRAY OF LOC);
which, because of the procedure calling conventions (OpenVMS knows nothing about the special case of Modula-2's open array parameter passing conventions), usually requires a procedure declaration such as
TYPE aType = ARRAY [0..10] OF CARDINAL; PROCEDURE CondHand (VAR a,b: aType);
Listing 1 illustrates the use of exception handling.
%FOREIGN DEFINITION MODULE VMS_Exceptions; (* hG/22/Aug-1995 corrected for Alpha *) IMPORT SYSTEM; TYPE ADDRESS = INTEGER; ADDRESS64= SYSTEM.SIGNED_64; SigArgs = ARRAY [1..256] OF INTEGER; CONST (* NB. In Modula-2, indices are the same as for the SIGARGS() macro *) sigArgs = 1; (* n = Additional Longwords *) sigName = 2; (*sigPC = n *) (*sigPSL= n+1*) TYPE MechArgs = POINTER TO RECORD args : INTEGER; flags : BITSET; frame : ADDRESS64; depth : INTEGER; resvdf1: INTEGER; dAddr : ADDRESS64; esfAddr: ADDRESS64; sigAddr: ADDRESS64; savR : ARRAY [0..14] OF SYSTEM.QUADWORD; (* use indices below for access *) savF : ARRAY [0..22] OF SYSTEM.QUADWORD; (* use indices below for access *) END; CONST savR0 =0; savR1 =1; savR16=2; savR17=3; savR18=4; savR19=5; savR20=6; savR21=7; savR22=8; savR23=9; savR24=10; savR25=11; savR26=12; savR27=13; savR28=14; savF0 =0; savF1 =1; savF10=2; savF11=3; savF12=4; savF13=5; savF14=6; savF15=7; savF16=8; savF17=9; savF18=10; savF19=11; savF20=12; savF21=13; savF22=14; savF23=15; savF24=16; savF25=17; savF26=18; savF27=19; savF28=20; savF29=21; savF30=22; TYPE HANDLER = PROCEDURE(VAR SigArgs, MechArgs): INTEGER; PROCEDURE LIB$SIG_TO_RET(VAR sigArgs: SigArgs; mechArgs: MechArgs): I NTEGER; PROCEDURE LIB$SIGNAL(%IMMED signal: CARDINAL); PROCEDURE SYS$PUTMSG(%IMMED msgvec: ADDRESS; %IMMED actrtn: ADDRESS; %STDESCR facnam: ARRAY OF CHAR; %IMMED actprm: INTEGER): INTEGER; END VMS_Exceptions.
MODULE Test_Exceptions; (* DW/20-Jul-1994, ModulaWare Exception handling example for MaX Alpha/OpenVMS Modula-2 *) FROM SYSTEM IMPORT LIB$ESTABLISH, LIB$REVERT; FROM VMS_Exceptions IMPORT LIB$SIG_TO_RET, LIB$SIGNAL, SigArgs, MechArgs; IMPORT STextIO; VAR old: PROC; i: INTEGER; PROCEDURE MyHandler (VAR x: SigArgs; y: MechArgs): INTEGER; BEGIN (* Convert exception to return condition value *) RETURN LIB$SIG_TO_RET(x,y); END MyHandler; PROCEDURE DivByZeroTry1():INTEGER; VAR x,y: REAL; BEGIN old := LIB$ESTABLISH(LIB$SIG_TO_RET); y := 0.0; x := 5.0 / y; RETURN TRUNC(x); END DivByZeroTry1; PROCEDURE DivByZeroTry2():INTEGER; VAR x,y: REAL; BEGIN old := LIB$ESTABLISH(MyHandler); y := 0.0; x := 5.0 / y; RETURN TRUNC(x); END DivByZeroTry2; BEGIN old := LIB$REVERT(); (* A NoOp - because LIB$ESTABLISH *) i := 0; (* has not been called *) i := DivByZeroTry2(); IF ~ ODD(i) THEN STextIO.WriteString("Arithmetic error in DivByZeroTry2"); STextIO.WriteLn; END; i := 0; i := DivByZeroTry1(); IF ~ ODD(i) THEN STextIO.WriteString("Arithmetic error in DivByZeroTry1"); STextIO.WriteLn; LIB$SIGNAL(i); (* Re-raise exception *) END; END Test_Exceptions.
Listing 1: Example for Exception Handling
ModulaWare uses the ISO M2 Std Lib for both, Modula-2 and Oberon-2
compilers on VAX and Alpha.
The VAX/VMS implementation modules use certain run-time library routines (OTS$ and FOR$) for number to string-conversion. These routines are also available on Alpha/OpenVMS but they are translated-VAX (TV) routines. To be able to link such routines to an application program, the linker qualifier /NoNativeOnly has to be used. Additionally, to call such a routine at run-time, a so called jacket supplied by the compiler is required. The fact that those routines are available as TV only shows that they are to be retired and should no longer be used. We looked for replacement procedures in the OpenVMS run-time library and found the possible candidates such as the cvtas$-routines, but we couldn't use them because they are undocumented. So we replaced the implementation of the following modules which now perform the output conversion directly in Modula-2 without calling any OpenVMS routine: Conversions (conversio.mod), long_str.mod, real_str.mod, longg_str.mod
In addition to the ISO Modula-2 library modules WholeIO, SWholeIO, WholeStr, WholeConv [and auxiliary module Whole_Str], there are equivalent new modules for the new SYSTEM.[UN]SIGNED_64 data types:
WholeLConv, [and auxiliary module
(In a previous compiler release some WholeL-implementation modules imported the auxiliary module WholeLDiv which provides 64 bit whole number division, modulus and remainder operations. WholeLDiv is now obsolete, because MaX|A2O now have these operations built-in.)
In addition to the ISO Modula-2 library modules RealIO, SRealIO, RealStr, RealConv [and Real_Str], there are equivalent new modules for the new SYSTEM.S_FLOATING data type:
- RealSIO, SRealSIO, RealSStr, RealSConv [and RealS_Str].
In addition to the ISO Modula-2 library modules LongIO, SLongIO, LongStr, LongConv [and Long_Str], there are equivalent new modules for the new SYSTEM.T_FLOATING as well as the VAX SYSTEM.G_FLOATING data types:
- LongTIO, SLongTIO, LongTStr, LongTConv [and LongT_Str],
- LongGIO, SLongGIO, LongGStr, LongGConv [and LongG_Str].
In addition to RealMath and LongMath, there are equivalent new modules for the new data types SYSTEM.(S | T | G)_FLOATING:
- RealSMath, LongTMath, LongGMath (only the latter shown below).
DEFINITION MODULE LongGMath; (* ISO Modula-2 Standard Library extension, (Alpha | VAX) / OpenVMS implementation Based on LongMath (C) 1994 by ModulaWare GmbH, CS/09-Mar-1994 *) IMPORT SYSTEM; TYPE LONGREAL = SYSTEM.G_FLOATING; CONST pi = VAL(LONGREAL, 3.1415926535897932384626433832795028841972); exp1 = VAL(LONGREAL, 2.7182818284590452353602874713526624977572); PROCEDURE sqrt (x: LONGREAL): LONGREAL; (* Returns the positive square root of x *) PROCEDURE exp (x: LONGREAL): LONGREAL; (* Returns the exponential of x *) PROCEDURE ln (x: LONGREAL): LONGREAL; (* Returns the natural logarithm of x *) (* The angle in all trigonometric functions is measured in radians *) PROCEDURE sin (x: LONGREAL): LONGREAL; (* Returns the sine of x *) PROCEDURE cos (x: LONGREAL): LONGREAL; (* Returns the cosine of x *) PROCEDURE tan (x: LONGREAL): LONGREAL; (* Returns the tangent of x *) PROCEDURE arcsin (x: LONGREAL): LONGREAL; (* Returns the arcsine of x *) PROCEDURE arccos (x: LONGREAL): LONGREAL; (* Returns the arccosine of x *) PROCEDURE arctan (x: LONGREAL): LONGREAL; (* Returns the arctangent of x *) PROCEDURE power (base, exponent: LONGREAL): LONGREAL; (* Returns the value of the number base raised to the power exponent *) PROCEDURE round (x: LONGREAL): INTEGER; (* Returns the value of x rounded to the nearest integer *) PROCEDURE IsRMathException (): BOOLEAN; (* not yet impl. on MVR | MAX *) (* Returns TRUE if the current coroutine is in the exceptional execution state because of the raising of an exception in a routine from this module; otherwise returns FALSE. *) END LongGMath.
Listing 3: Module LongGMath
In addition to [Long]ComplexMath, there are equivalent new modules for the new data types (S | T | G)_COMPLEX):
- ComplexSMath, LongComplexTMath, LongComplexGMath.
SYSTEM.G_COMPLEX was introduced because OpenVMS run-time library functions of complex type return complex values in F0 and F1. Hence the compiler must know this type, since it is not possible to fake the declaration of a foreign procedure such as
PROCEDURE conjg (x: G_COMPLEX): G_COMPLEX;
by declaring TYPE G_COMPLEX = RECORD re, im: G_FLOATING END; In this case, the function result would be expected to be returned as a first hidden variable parameter on the stack. The type SYSTEM.G_COMPLEX is needed to tell the compiler, that this is not a structured function and that the function returns the result in floating point registers.
DEFINITION MODULE LongComplexGMath; (* ISO Modula-2 Standard Library extension, (Alpha | VAX) / OpenVMS implementation Based on LongComplexMath (C) 1994 by ModulaWare GmbH, CS/30-May-1994 *) IMPORT SYSTEM; TYPE LONGREAL = SYSTEM.G_FLOATING; LONGCOMPLEX = SYSTEM.G_COMPLEX; VAR i, one, zero: LONGCOMPLEX; (* read only *) PROCEDURE abs (z: LONGCOMPLEX): LONGREAL; (* Returns the length of z *) PROCEDURE arg (z: LONGCOMPLEX): LONGREAL; (* Returns the angle that z subtends to the positive real axis *) PROCEDURE conj (z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the complex conjugate of z *) PROCEDURE power (base: LONGCOMPLEX; exponent: LONGREAL): LONGCOMPLEX; (* Returns the value of the number base raised to the power exponent *) PROCEDURE sqrt (z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the principal square root of z *) PROCEDURE exp (z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the complex exponential of z *) PROCEDURE ln (z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the principal value of the natural logarithm of z *) PROCEDURE sin (z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the sine of z *) PROCEDURE cos (z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the cosine of z *) PROCEDURE tan (z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the tangent of z *) PROCEDURE arcsin (z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the arcsine of z *) PROCEDURE arccos (z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the arccosine of z *) PROCEDURE arctan (z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the arctangent of z *) PROCEDURE polarToComplex (abs, arg: LONGREAL): LONGCOMPLEX; (* Returns the complex number with the specified polar coordinates *) PROCEDURE scalarMult (scalar: LONGREAL; z: LONGCOMPLEX): LONGCOMPLEX; (* Returns the scalar product of scalar with z *) PROCEDURE IsCMathException (): BOOLEAN; (* Returns TRUE if the current coroutine is in the exceptional execution state because of the raising of an exception in a routine from this module; otherwise returns FALSE. *) (* not yet impl. on MVR | MAX *) END LongComplexGMath.
Listing 4: Module LongComplexGMath
In addition to LowReal and LowLong, there are equivalent new modules for the new data types SYSTEM.(S | T | G)_FLOATING:
- LowRealS, LowLongT, LowLongG (only the latter is shown here).
DEFINITION MODULE LowLongG; (* ISO Modula-2 Standard Library extension, (Alpha | VAX) / OpenVMS implementation Based on LowLong (C) 1994 by ModulaWare GmbH, CS/16-Mar-1994 *) IMPORT SYSTEM; TYPE LONGREAL = SYSTEM.G_FLOATING; CONST radix = 2; places = 20 + 32; expoMin = -1023; expoMax = 1023; large = MAX (LONGREAL); (*small = VAL(LONGREAL,5.562684646268003E-309); (*= CAST(LONGREAL,0000000000000010H)*) *) (*temporary adjusted for MVR scanner to produce exactly the above pattern: *) n = VAL(LONGREAL,1.0E-30); (* n is for internal use only *) small = VAL(LONGREAL,5.562684646267999)* n*n*n*n*n*n*n*n*n*n *VAL(LONGREAL,1.0E-9); IEEE = FALSE; ISO = FALSE; rounds = FALSE; gUnderflow = FALSE; exception = FALSE; extend = FALSE; nModes = 1; TYPE Modes = SET OF [0 .. nModes-1]; PROCEDURE exponent (x: LONGREAL): INTEGER; (* Returns the exponent value of x *) PROCEDURE fraction (x: LONGREAL): LONGREAL; (* Returns the significand (or significant part) of x *) PROCEDURE sign (x: LONGREAL): LONGREAL; (* Returns the signum of x *) PROCEDURE succ (x: LONGREAL): LONGREAL; (* Returns the next value of the type LONGREAL greater than x *) PROCEDURE ulp (x: LONGREAL): LONGREAL; (* Returns the value of a unit in the last place of x *) PROCEDURE pred (x: LONGREAL): LONGREAL; (* Returns the previous value of the type LONGREAL less than x *) PROCEDURE intpart (x: LONGREAL): LONGREAL; (* Returns the integer part of x *) PROCEDURE fractpart (x: LONGREAL): LONGREAL; (* Returns the fractional part of x *) PROCEDURE scale (x: LONGREAL; n: INTEGER): LONGREAL; (* Returns the value of x * radix ** n *) PROCEDURE trunc (x: LONGREAL; n: INTEGER): LONGREAL; (* Returns the value of the first n places of x *) PROCEDURE round (x: LONGREAL; n: INTEGER): LONGREAL; (* Returns the value of x rounded to the first n places *) PROCEDURE synthesize (expart: INTEGER; frapart: LONGREAL): LONGREAL; (* Returns a value of the type LONGREAL constructed from the given expart and frapart *) PROCEDURE setMode (m: Modes); (* not yet impl. on MVR | MAX *) (* Sets status flags appropriate to the underlying implementation of the type LONGREAL *) PROCEDURE currentMode (): Modes; (* not yet impl. on MVR | MAX *) (* Returns the current status flags in the form set by setMode *) PROCEDURE IsLowException (): BOOLEAN; (* not yet impl. on MVR | MAX *) (* Returns TRUE if the current coroutine is in the exceptional execution state because of the raising of an exception in a routine from this module; otherwise returns FALSE. *) END LowLongG.
Listing 5: Module LowLongG
Equivalent Oberon-2 interface modules to the Modula-2 implementation are available. Since there are no complex type constant literals in Oberon-2, the auxiliary module LongComplexG exports the constant values defined in [Long]ComplexGMath as (read-only) variables.
Since at the time of writing, no other Alpha compiler implementor has published details about how they support Alpha's
Our initial development platform was a Alpha 3000-300LX workstation running
OpenVMS V1.5H1 (later upgraded to 6.2) with a DECNET connection to a
VAXstation 3100/30. The bootstrap process was relatively easy because the
VAX and the Alpha have identical file systems. Once the Alpha Modula-2 cross
compiler version was running on VAX (by taking care that the compiler itself
does not do constant expression evaluation for (S | T)_FLOATING types), we
did a self compilation and linked the native Alpha Modula-2 compiler on Alpha.
Alpha/OpenVMS worked reliably.
The only drawback we've found during the development process was that the Alpha OpenVMS debugger cannot handle the way we do the CASE-statement and TRANSFER: When single stepping by line, the debugger crashes when trying to do a line step at "CASE expression OF". A workaround is to use the debugger mode "dbg> set step/instr/into", do some single instructions steps and then use "dbg> set step/line", to set source line step mode. We found out that the OpenVMS debugger can't handle a "step" for Alpha instructions of the form BR Rx, 0 with x # 31. A "step/into" at this instruction position would do, but "dbg> set step/line/into" is not enough. Since we don't assume that users would accept that work-around, we generate slightly different (less efficient) code for CASE and TRANSFER if the /DEBUG compilation qualifier is set.
The requirement that even CAST and VAL should have the same semantics as on the VAX by simulating 32 bit arithmetic on a 64 bit register with a fat-sign bit (even in the case of an arithmetic integer/cardinal overflow with overflow check is disabled or with unaligned record fields) was a lot of work. But this compatibility saved a lot of maintenance work and allowed us to compile and run virtually any existing VAX/VMS Modula-2 program without any modifications to the source code.
The only current implementation restriction of MaX is that variables declared in foreign definition modules can not be accessed. The back-end displays an error message in this case. A work around for this situation, using a foreign procedure to manipulate such variables is easy.
It was fun to see that H2O, which is itself written in Modula-2, can be compiled with MaX. H2O worked in the first place and now we are able to compile Oberon-2 programs on the Alpha and produce VAX/VMS object code.
Since 1997, MaX|A2O is used under OpenVMS Alpha V7.1 without problems.
In Jun-1994, MaX V4 passed the final test stage and was able to generate Alpha/OpenVMS code for Modula-2 programs and the compiler is able to compile itself and runs native on Alpha. The first version of MaX V4.05, which contained the full ISO Modula-2 Standard Library plus OpenVMS extended module set, was delivered to the customers in Jul-94. Minor errors were detected and corrected since then.
Later MaX V5 got 64 bit pointers [Dotz] to support the very large memory (VLM64) feature of OpenVMS V7.
A2O V1 was completed in Dec-1994. It runs native on Alpha and is able to compile Oberon-2 programs, generates directly Alpha native code in OpenVMS object file format with debugger information and features the same instruction scheduler and disassembler as MaX.
A2O V1 and MaX V4 are 32 bit compiler, although both also support
64 bit whole-number data types and associated operations.
Certain memory hungry applications require 64 bit addressing, because
the 32 bit address space became a restricting factor.
To satisfy the demand to support 64 bit addressing,
both, A2O and MaX got 64 bit pointers and
thus became true 64 bit compiler:
A2O V3.0, which supports 64 bit pointers was released in Dec-1997.
MaX V5.0, which supports 64 bit pointers was released in Jan-1999.
The 64 bit process private virtual address space can now be exploited under Compaq's (formerly Digital) OpenVMS Alpha V7.0 and later.
At ModulaWare, software developed in Modula-2 includes MaX itself, A2O, and the ISO Modula-2 library.
Software developed in Oberon-2 includes
In 1998, AOS was the first and only 64 bit Oberon System implementation and AOS the first only system which integrated both, Ref and the Object Model.
The size of AlphaOberon's binary distribution (program code files of the AOS and tools, symbol files, compiler, font files, and on-line documentation) has grown to about 20 MB.
I'd like to thank
[Crel] Régis Crelier: OP2 - A Portable Oberon Compiler,
ETH-Zürich Report No. 125, 1990.
[Cre1] Régis Crelier: Separate Compilation and Module Extension, Diss. ETH-Zürich No. 10650, 1994.
[Dotz] Günter Dotzel: 64 bit extension for Modula-2 for OpenVMS Alpha -- Implementation Notes See http://www.modulaware.com/mdlt75.htm
[DotGoe] Günter Dotzel, Hartmut Goebel: Porting the Oberon System to AlphaAXP, ModulaWare, Nov-1995. See http://www.modulaware.com/mdlt66.htm
[DotGo1] Günter Dotzel, Hartmut Goebel: 64 Bit Address Extension of the Alpha Oberon-2 Compiler, ModulaWare, Aug-1997. See http://www.modulaware.com/mdlt68.htm
[GutWir] Jürg Gutknecht, Niklaus Wirth: Project Oberon, Addison Wesley, 1992.
[IsoM2] International Standardisation Organisation: DIS ISO/IEC 10154 Modula-2, June 1994.
[Moes] Hanspeter Mössenböck: Object Oriented Programming in Oberon-2, Springer Verlag, 1993.
[Opcc] OpenVMS Alpha Documentation: Procedure Calling Conventions, Digital Equipment Corporation, 1992.
[Site] Richard L. Sites: Alpha Architecture Reference Manual, Digital Press, 1992.
[Sohm] Alfred Sohm: The Modula/R Compiler for VAX-11. LIDAS Memo 033-86, ETH-Zürich, Institut für Informatik, 1986.
[Wirt] Niklaus Wirth: Programming in Modula-2, 3rd ed., Springer Verlag, 1985.
This article is available in html-format at http://www.modulaware.com/max_sum.htm.