[ModulaWare logo 9.5KB]
The ModulaTor logo 7KB

MaX | A2O

OpenVMS Alpha Modula-2 and Oberon-2 Compiler

Implementation Notes

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.

1. Introduction

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 Standard library.
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.

2. Design goals

The goals of the Alpha compiler project for both, Modula-2 as well as Oberon-2 were

3. Data types

MaX introduces

The VAX real types SYSTEM.(F | D | G | H)_FLOATING are still available, but real arithmetic and conversion with SYSTEM.H_FLOATING is no longer supported in MaX, because Alpha does not have a 128 bit real data type. (load/store operations, i.e. assignment and cast are still possible). For symmetry/source code compatibility reasons, SYSTEM.H_FLOATING is a synonym for SYSTEM.G_FLOATING in MaX, but SYSTEM.H_FLOATING constants in symbol files are not allowed in MaX.
With the compilation qualifier /IEEE, the default pervasive real types and additionally in case of Modula-2, the default complex-types are



and with /NOIEEE, these are the VAX floating point types:



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.

4. Alpha's VAX-D_FLOATING restriction

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):

Note, the following implications: if the VAX rounding mode is enabled (for default settings, see below), then with r:=MAX (SYSTEM.D_FLOATING), the assignment r := ABS(r), raises a floating overflow exception at run-time. This is because for any d_floating operation, a d_floating is first converted to a g_floating (which has a greater exponent range but less precision in the mantissa), then the operation is performed and converted back to d_floating in a store operation. Since the precision of the g_floating is lower that that of a d_floating, the value is rounded up. The new value is slightly larger than MAX (D_FLOATING) which then results in an overflow when converting it to d_floating. When compiling with chopped rounding mode, this problem doesn't occur.

5. VAX's (S | T)_FLOATING restriction

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.

6. Standard functions in module SYSTEM

  (* 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 eps = LFLOAT( ORD(isAXP)) * 2.E-15 
              + LFLOAT (ORD (~ isAXP)) * 2.E-16;

In MVR, the result type of the procedure REGISTER is CARDINAL.


  (* 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)).

7. Number Conversions

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


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):


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


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) .. MAX(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:


 ll: SYSTEM.SIGNED_64;  (* Alpha only *)
  s: SYSTEM.S_FLOATING; (* Alpha only *)
  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.


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.


8. Conversion examples with MaX

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.

9. Operating system interface

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).

10. Symbol files

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.

11. Data allocation/alignment

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.

12. Data access

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.

13. Conditonal expressions

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.

14. Floating point arithmetic exceptions

There are a bewildering number of options for floating point arithmetic instructions.
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

Only one IEEE rounding mode may be specified.
The alternative, would be to fix up the options as needed, possibly emitting a warning message that it had been done (e.g.: Only one IEEE rounding mode may be specified: default rounding used.)

15. Procedure calling conventions

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

The only difference in parameter passing are Oberon-2's objects, i.e.: for variable formal parameters of record type (VarParRecord). If the MaX compilation qualifier /RecordTypeDescriptor is present, procedures declared in (non-foreign) Modula-2 definition modules, which have a (VarParRecord), get an additional hidden parameter (immediate value) in addition to the record variable's address. It's value is always NIL when the procedure is called by non-Oberon-2 programs. The record type descriptor value is ignored in Modula-2. Foreign language interface: No such type descriptor is substituted in a call of a procedure declared in foreign definition modules. As a consequence, for safety reasons, if the qualifier is present, an implementation restriction applies to assignment of procedure variables in MaX: If the formal parameter list of a procedure variable has a VarParRecord, it is not allowed to assign a procedure which is declared in a foreign definition module to that variable.

16. Static link

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 frame.
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.

17. Coroutines

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 stack-limit.
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.

17.1. Coroutines stack-overflow check

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;
    PROCESS = POINTER TO WorkspaceDesc;

END coroutines.

17.2. Performance of coroutine transfer

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 Alpha/200MHz.
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

18. Exception handling

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



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

19. ISO Modula-2's Standard I/O-Library

19.1. Number/string conversions

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

19.2. Library extension

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:

- WholeLIO, SWholeLIO, WholeLStr, WholeLConv, [and auxiliary module WholeL_Str].
(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


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.

20. Portability and Compatiblity

Since at the time of writing, no other Alpha compiler implementor has published details about how they support Alpha's

ModulaWare chose the new module names carefully and recommends to other Alpha Modula-2 compiler implementors that they should follow the naming guidelines shown above.

21. Alpha/OpenVMS development platform

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.

22. Availability

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.

23. Software developed with MaX and A2O

At ModulaWare, software developed in Modula-2 includes MaX itself, A2O, and the ISO Modula-2 library.

Software developed in Oberon-2 includes

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.

24. Acknowledgements

I'd like to thank

Note: All data is subject to change without notice.

25. References

[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.

Home | Site_index | Legal | OpenVMS_compiler | Alpha_Oberon_System | ModulaTor | Bibliography | Oberon[-2]_links | Modula-2_links |

Books Music Video Enter keywords...

Amazon.com logo
Webdesign by www.otolo.com/webworx, 20-Mar-1999. © (1998-2002) Günter Dotzel, ModulaWare.com