ModulaTor logo, 7.8KB

The ModulaTor

Oberon-2 and Modula-2 Technical Publication

Ubaye's First Independent Modula-2 & Oberon-2 Journal! Nr. 75, Nov-1998

MaX V5

64 bit Modula-2 for OpenVMS Alpha -- Implementation Notes

© (1998-1999) by Günter Dotzel, ModulaWare

3rd edition, 16-Feb-1999

MaX V4, ModulaWare's 32 bit Modula-2 compiler for OpenVMS Alpha uses a pointer size of 32 bit, but already allows 64 bit whole number arithmetic. OpenVMS Alpha V7.0 or later supports the full 64 bit address space for dynamic data, i.e.: heap. To exploit the 64 bit address space, MaX V5 introduces

The goal of the 64 bit address extension of OpenVMS Modula-2 compiler is that portable programs that compile with MaX V4 should be fully upward compatible with MaX V5. This article describes the language and compiler extensions to achieve this goal.

The compilation command qualifier /pointersize=x was added, where x, specifying the number of bits, is either 32 or 64.

Independent from the /pointersize value, MaX V5 exports the following types from module SYSTEM


 Type            with storage size/bytes

[UN]SIGNED_32 4 [UN]SIGNED_64 8 PROC_32 4 PROC_64 8 ADDRESS_32 4 ADDRESS_64 8 PROCESS_32 4 PROCESS_64 8
Note, the types [UN]SIGNED_32 and [UN]SIGNED_64 from SYSTEM were already available in MaX V4.

Independent from /pointersize, the pervasive type SIGNED_32 is declared as a subrange of SIGNED_64, with the lower- and upper-bounds specified by their respective minimal and maximal values. The same applies to UNSIGNED_32, which is a subrange of UNSIGNED_64,


Whole number MIN/MAX values:

MIN(SIGNED_32) = -2147483648 MAX(SIGNED_32) = 2147483647 MIN(SIGNED_64) = -9223372036854775808 MAX(SIGNED_64) = 9223372036854775807 MIN(UNSIGNED_32) = 0 MAX(UNSIGNED_32) = 4294967295 MIN(UNSIGNED_64) = 0 MAX(UNSIGNED_64) = 18446744073709551615

Arithmetic with 32 bit and 64 bit whole number values is allowed with any /pointersize in declaration sections and statement part of procedures and module body. Whole number literals and constant expressions are always evaluated in 64 bit mode and they are compatible with [UN]SIGNED_32, if the constant value is in the types range.


Whole number range                     | Expression compatible with

[MIN(SIGNED_64) ..MIN(SIGNED_32)-1] | SIGNED_64 [MIN(SIGNED_32) ..-1] | SIGNED_32, SIGNED_64 [0 ..MAX(SIGNED_32)] | [UN]SIGNED_32, [UN]SIGNED_64 [MAX(SIGNED_32)+1 ..MAX(UNSIGNED_32)] | UNSIGNED_32, [UN]SIGNED_64 [MAX(UNSIGNED_32)+1..MAX(SIGNED_64)] | [UN]SIGNED_64 [MAX(SIGNED_32)+1 ..MAX(UNSIGNED_64)] | UNSIGNED_64
When the base-type, also called host-type, in a subrange type declaration is not explicitely specified, its size matches the /pointersize.

Examples of subrange declarations:


    Subrange declaration                  Base-type, when compiled with
                                         /pointersize=32 | /pointersize=64

TYPE s1 = [0..255]; | UNSIGNED_32 UNSIGNED_64 s2 = [-65536..65535]; | SIGNED_32 SIGNED_64 s3 = [-2147483648..2147483647]; | SIGNED_32 SIGNED_64 s4 = [-2147483648..2147483648]; | not allowed SIGNED_64 s5 = SIGNED_64 | [-2147483648..2147483648]; | SIGNED_64 SIGNED_64 s6 = SIGNED_32 | [-2147483648..2147483648]; | not allowed not allowed

The standard Modula-2 expression- and assignment-compatibility rules for subranges and their base-types apply to [UN]SIGNED_32.

Pervasive identifiers

The types of the following pervasive identifiers depend on the /pointersize:


/pointersize=32:

INTEGER = SIGNED_32 CARDINAL = UNSIGNED_32 PROC = PROC_32 SYSTEM.ADDRESS = ADDRESS_32 SYSTEM.PROCESS = PROCESS_32
The size of pointer-, hidden-, and procedure-types, as well as constant procedures is 4 bytes.

/pointersize=64:

INTEGER = SIGNED_64 CARDINAL = UNSIGNED_64 PROC = PROC_64 SYSTEM.ADDRESS = ADDRESS_64 SYSTEM.PROCESS = PROCESS_64
The size of pointer-, hidden-, and procedure-types, as well as constant procedures is 8 bytes.

Subrange checks

With /pointersize=32, no checks are made at run-time for [UN]SIGNED_32, otherwise many existing programs, designed for 32 bit INTEGER/CARDINALs would fail at run-time. e.g.: SYSTEM.CAST(T, expr) just changes the type if the size of T is equal to the size of expr.
On a 64 bit processor, signed 32 bit whole number values are always represented in their canonical form, i.e. the sign bit is replicated into the upper 32 bits. A statement sequence of


  int32 := -1; (* int32 has the value=0FFFFFFFFFFFFFFFFH, when loaded into a 64 register *)
  card32 := CAST(card32, int32);
would raise an overflow exception in the second assignment, because the value 0FFFFFFFFFFFFFFFFH, being equal to MAX(UNSIGNED_64), is outside the range of UNSIGNED_32. On a 32 bit machine, using 32 bit registers, such subrange check could not be performed.

Size of pointer-, hidden, and procedure-variables

If a module which contains the declaration of a pointer-, hidden-, or procedure-type T is compiled with /pointersize=x, the size of T is x DIV 8.
Independent from /pointersize, in a module which contains the declaration of a pointer-, hidden-, or procedure-variable V:T, the size of V is SIZE(T).

A procedure variable on OpenVMS Alpha is a pointer to a procedure linkage pair. A procedure linkage pair consists of two entries, a pointer to the procedure descriptor and an entry address, which are both 64 bit addresses, even with /pointersize=32.

Pervasive procedures and functions

Since the pervasive types depend on /pointersize, this also affects the type of the formal parameters in the following pervasive procedures and functions, and the result types of the latter:

MaX V5 allows 32/64 bit mixed pointer applications, through import of 32 bit symbol-files in modules compiled with /pointersize=64.

Assignment compatibility of pointer types

32 bit pointers may be assigned to 64 bit pointer variables, but not vice-versa.

Variables of type ADDRESS_32 may be assigned to variables of type ADDRESS_64, but not vice-versa.

The type safety of the Modula-2 language pays-off here already at compile time, in order to avoid errors resulting from address truncation and writing to not-allocated memory which can cause data corruption.

According to the standard OpenVMS calling conventions, all parameters are passed by reference, even if the formal parameter is a value parameter. In the case of a value parameter, the callee is responsible to make a copy on the stack. So in either case, be it a value or variable parameter, if the parameter pointer sizes match, only the parameters address is passed.

This approach allows to have a single module library implementation, where the caller may be in either mode (32 or 64 bit) for most modules, where the module interface does not contain any VARiable formal parameter of pointer type.

If the formal parameter is a 64 bit pointer value parameter and the actual parameter is 32 bit pointer, the pointer value is pushed on the stack as a 64 bit address in canonical form and the stack address is passed as reference. But it is not possible to substitute a 32 bit pointer to a formal variable parameter of 64 bit pointer type, because the called procedure can not know the caller's pointer type size.

Since all address calculations are performed with 64 bit arithmetics, even when compiled with /pointersize=32, the only problem is the pervasive function procedure SYSTEM.ADR, which requires that the argument's address MBSE (must be sign-extended, i.e.: bit 31 must be replicated from bit 32 to bit 63) in 32 bit mode, because the function's result type is ADDRESS_32. This check takes two Alpha instructions. In a module compiled with /pointersize=32, where the substituted actual parameter of the pervasive function SYSTEM.ADR is a formal VAR procedure parameter, the compiler can not check at compile-time, whether the address fits into 32 bits. In this case, a run-time check for MBSE is generated.

Example:



DEFINITION MODULE Bit32; PROCEDURE P(VAR x: CHAR): INTEGER; END Bit32.
IMPLEMENTATION MODULE Bit32; IMPORT SYSTEM; PROCEDURE P(VAR x: CHAR): INTEGER; BEGIN RETURN SYSTEM.ADR(x); END P; END Bit32.
MODULE UseBit32; (* to be compiled with /pointersize=64 *) FROM Storage IMPORT ALLOCATE, DEALLOCATE; IMPORT Bit32; VAR p: POINTER TO RECORD x: CHAR END; a: INTEGER; BEGIN NEW(p); a := Bit32.P(p^.x); DISPOSE(p); END UseBit32.
If the variable p in module UseBit32 is pointing to an address outside in the 64 bit address space, this address can't be represented in a 32 bit integer variable. In this case (and under the presumption that the implementation module Bit32 was compiled with MaX V5), an exception with the code SS$_ARG_GTR_32_BITS (SYSTEM-F-ARG_GTR_32_BITS, argument greater 32 bit) is signaled, which means that the parameter address is not a 32-bit sign-extended value. This status code was introcduced in OpenVMS 7.0.

String descriptors

String descriptors (%STDESCR) are used for formal string parameters when calling a foreign procedure.
Independent from /pointersize, when an actual parameter is substituted for a %STDESCR and whenever it is not known at compile time, whether both, the string's length and its address will fit into a 16/32 bit length/address string descriptor, the string's address is checked at run-time. If the address is sign-extended, a 16/32 bit length/address string descriptor is generated, otherwise a 64/64 bit length/address string descriptor is constructed.

16/32 bit length/address string descriptor has a size of 8 bytes:


    RECORD strLen:  SHORTWORD; (* 16 bit *)
           ident:   SHORTWORD; (* := 0; *)
           address: SIGNED_32
    END
The 64/64 bit length/address string descriptor is a self-identifying structure with a size of 24 bytes.

    RECORD selfId1: SHORTWORD; (* :=  1; must be one (MBO) *)
           ident:   SHORTWORD; (* :=  0; *)
           selfId2: SIGNED_32  (* := -1; must be minus one (MBMO) *)
           strLen:  SIGNED_64;
           address: SIGNED_64
    END
The advantage is that even code compiled with /pointersize=32 is able to handle 64 bit string descriptors.

When compiled with /pointersize=32, string descriptors and SYSTEM.ADR are the only places, where more code is generated under certain conditions with MaX V5 than with MaX V4.

Symbol-file lookup strategy

Using /pointersize=64 also changes the default symbol-file lookup strategy. If a module M is imported, first M.sym64 is looked-up and if this file is not found, M.sym is looked-up.

Import of 64 bit pointer-, procedure-, hidden-, and subrange-types or any variables of these types is not possible, because the compiler never imports .sym64 with /pointersize=32.

Storage allocation

The pervasive procedures NEW and DISPOSE are resolved using ALLOCATE and DEALLOCATE, whose declaration must match the procedure type


    PROCEDURE (VAR ADDRESS, CARDINAL)
independent from the /pointersize.

The 32 bit version of module Storage, which exports the following substitution procedures:


    PROCEDURE ALLOCATE(
       VAR p:    ADDRESS_32;
           size: UNSIGNED_32);
    
    PROCEDURE DEALLOCATE(
       VAR p:    ADDRESS_32;
           size: UNSIGNED_32);
For using mixed pointers, it is recommended to use explicit calls to the substitution procedures.

The following module Storage is proposed for 64 bit and 32/64 bit-mixed pointer applications:



DEFINITION MODULE Storage; (* 32/64 Bit Modula-2 Storage handler (heap management), to be compiled with /pointersize=64 *) FROM SYSTEM IMPORT ADDRESS, ADDRESS_32, ADDRESS_64, UNSIGNED_32, UNSIGNED_64; PROCEDURE ALLOCATE( VAR p: ADDRESS; size: CARDINAL); PROCEDURE DEALLOCATE( VAR p: ADDRESS; size: CARDINAL); PROCEDURE ALLOCATE_64( VAR p: ADDRESS_64; size: UNSIGNED_64); PROCEDURE DEALLOCATE_64( VAR p: ADDRESS_64; size: UNSIGNED_64); PROCEDURE ALLOCATE_32( VAR p: ADDRESS_32; size: UNSIGNED_32); PROCEDURE DEALLOCATE_32( VAR p: ADDRESS_32; size: UNSIGNED_32); END Storage.
When compiled with /pointersize=64, the symbol-file of module Storage is called Storage.sym64 by default.

The procedure pair ALLOCATE and ALLOCATE_64 have the same semantics and use the OpenVMS run-time library procedure


      PROCEDURE LIB$GET_VM_64* (
           numbyt: SYSTEM.UNSIGNED_64;
       VAR basadr: SYSTEM.ADDRESS_64): SYSTEM.SIGNED_32;
whereas the procedure pair DEALLOCATE and DEALLOCATE_64 use

      PROCEDURE LIB$FREE_VM_64* (
           numbyt: SYSTEM.UNSIGNED_64;
           basadr: SYSTEM.ADDRESS_64): SYSTEM.SIGNED_32;
When compiled with /pointersize=64, the object file of the implementation module Storage is called Storage.obj64 by default.

If the module TestNew, which imports module Storage is compiled with /pointersize=32, NEW and DISPOSE require the 32 bit version of the substitution procedures ALLOCATE and DEALLOCATE. These are contained in the symbol-file Storage.sym.

When the module TestNew is compiled with /pointersize=64, NEW and DISPOSE require the 64 bit version of the substitution procedures ALLOCATE and DEALLOCATE. These are contained in the symbol-file Storage.sym64. If the compiler does not find Storage.sym64, Storage.sym is looked-up, which results in the error message "unsatisfying parameters of substituted procedure".



MODULE TestNew; FROM Storage IMPORT ALLOCATE, DEALLOCATE; VAR p: POINTER TO RECORD x: CHAR END; BEGIN NEW(p); DISPOSE(p); END TestNew.
To link the 32 bit version of TestNew, use the link command

  $ Link TestNew
which takes Storage from the default object library Modula.OLB.

To link the 64 bit version of TestNew, use


  $ Link TestNew.obj64,Storage.obj64
which is the same as

  $ Link TestNew.obj64,Storage
because the file extension is carried over to the rest of the file-name list. It is also possible to use the 64 bit object library Modula.olb64

  $ Link TestNew.obj64,Modula.olb64/lib
or redefine the logical name LNK$Library to use Modula.olb64 by default

  $define LNK$Library Modula.olb64
and then link with the command

  $ Link TestNew.obj64
Or, if it is desired to work only with 64 bit pointers, it is also possible to overwrite the object file specification in the compilation command:

  $ MaX TestNew/pointersize=64/object=.obj
Or, to make /pointersize=64 and the object file extension /object=.obj default for V5MaX compilation command, change the following lines in the command language definition file v5max-user.cld:

      keyword 32  nonnegatable
      keyword 64  nonnegatable default
      QUALIFIER OBJECT,DEFAULT,VALUE(TYPE=$FILE,default=.obj)

The ModulaTor Forum

Recommended Reading

by Günter Dotzel

The Silicon Man by Charles Platt, Tafford Publishing, Houstan, Texas, USA, 1993.
Lew Platt, CEO of Hewlett-Packard, wrote this fascinating, futuristic fiction for "stubborn idealists in biology and computer science, searching even now for ways to free us from the injustice of mortality." This book is about hackers and the near future in cyberspace. A person finds himself inside of a computer being manipulated from the outside. Life can be eternal but death can be as instant as a power failure or system crash. This computerized reality, populated with "infomorphs", is described with unprecedented realism.

There are three mysterious science fiction books written by Robert A. Heinlein (1) The Puppet Master, about alien invaders, who did not get stopped by shooting fast, (2) Double Star, about the impersonation of a key statesman, who mysteriously disappeared, and (3) The Door into Summer. Dream your troubles away. Someone cares to invest your assets and you'd wake up rich in the future. Yet it wasn't money that motivated Dan Davis, an electronics wizard; it was the idea of coming back in 30 years, still young and strong, to confront his old and wrinkled ex-lover, who betrayed him with the partner he'd trusted. But the best-laid plans...

We by Yevgeny Zamyatin, 1924(!), first published in Russia only in 1988, translated by Clarencer Brown into English, Penguin book, 1993.
George Orwell acknowledged his debt to Zamyatin, giving him the inspiration for his famous story "1984". We describes life under the regimented totalitarian society of OneState, ruled over by the all-powerfull "Benefactor". We is the archetype of the modern dystopia or anti-Utopia: a great prose poem detailing the fate that might befall us all if we surrender our individual selves to some collective dream of technology and fail in the vigilance that is the price for freedom.

Complexity by M. Mitchell Waldrop, Touchstone Book, 1992.
In a world where nice guys often finish last, why do humans value trust and cooperation? The science of complexity studies how single elements, such as species, spontaneously organize into complicated structures like ecosystems and economies, almost as if these systems were obeying a hidden yearning for order. Mitchell Waldrop's groundbeaking, non-fiction bestseller takes the readers into the hearts and minds of the scientists working at a think tank called the Santa Fe Institute, to tell the story behind their revolutionary discoveries.


IMPRESSUM: The ModulaTor is an unrefereed journal. Technical papers are to be taken as working papers and personal rather than organizational statements. Items are printed at the discretion of the Editor based upon his judgement on the interest and relevancy to the readership. Letters, announcements, and other items of professional interest are selected on the same basis. Office of publication. The Editor of The ModulaTor is Günter Dotzel; he can be reached at


[ Home | Site_index | Legal | OpenVMS_compiler | Alpha_Oberon_System | ModulaTor | Bibliography | Oberon[-2]_links | Modula-2_links | General interesting book recommendations ]
Amazon.com [3KB] [Any browser]

Books Music Video Enter keywords...


Amazon.com logo

Webdesign by www.otolo.com/webworx, 16-Feb-1999