The ModulaTor logo 7KB

The ModulaTor

Oberon-2 and Modula-2 Technical Publication

The ModulaTor
Erlangen's First Independent Modula_2 Journal! Nr. 0, Jan-1994 
______________________________________________________________

 
Inside H2O 

How to combine Modula-2 and Oberon-2 programs under VAX/VMS 

by Guenter Dotzel, ModulaWare GmbH 

This is a collection of questions and answers to assist you in a mixed language 
project. At the end an outlook to the new AXP/OpenVMS version of Modula-2 
and Oberon-2 compiler is given. 

________________________________________________________________________ 

Q: What are the main advantages of Oberon-2 when compared to Modula-2? 

A: The real advantages of Oberon-2 are 

- simple but powerful object oriented language, 

- extensibility and 

- persistent objects facilities. 

________________________________________________________________________ 

Q: If I want to use Modula-2 code compiled with the MVR compiler, do I need to 
proceed as though I was calling those modules from language like C or 
PASCAL, or is there a simpler way? 

A: Generally, it is easily possible to import Modula-2 modules in Oberon-2 with 
H2O and vice versa. Only variable record formal parameters need special care. 

If you want to call M2 code from O2, then you should obey the following rules: 

- derive a dummy O2 module from your M2 def and mark every object with a "*", 
change to O2 syntax (array's index ranges and pervasive types); it is 
recommended to use module CTR for declaration of M2 types such as 
enumeration, subranges, CARDINAL and ADDRESS, as it is done in the O2 
declaration of the ISO M2 Std Lib modules (see module [h2okit.oli]CTR.DEF). 

- Attention when using Modula-2 compilers from other manufacturers: do not 
export variables; the offset for the first VAR is 12 bytes in MVR and H2O (it could 
be only 4 bytes in others). To find out about the data sections offset, declare a 
variable in a definition module, write an empty implementation module and the 
compile the implementation module with /machine_code/list to see how large 
the offset of the first variable on the $data$ section is. 

If the offset is not equal to 12, then use the following work around: 

Try to put a dummy variable of 8 bytes size in front of your first VAR declaration 
in your Modula-2 definition module. 

VAR a: LONGREAL; 

VAR ... your variable declarations start here ... 

Then recompile the M2 definition and implementation module. Note, you must 
not include the declaration of the dummy M2 variable in the O2 interface 
module. 

- When declaring variables in O2 interface modules they are allowed to be 
declared read-only (even though M2's vars can't be protected for write); it is 
completely handled by the O2 front end. The read-only mechanism is not 
completely safe in Oberon-2: you can write to O2's read-only exported variables 
by getting their address with SYSTEM.ADR and using a pointer to access them. 

- after your O2 dummy interface module is ready, compile the O2 module to get 
an O2 .syn file. 

- import the M2 module via the O2 dummy module, as you do with other O2 
modules, but don't forget to delete the O2 .obj (or compile with /noobject) and 
link the O2 module to your M2 .obj file. 

- When linking, you'll notice a linker warning/error because of module key 
mismatch (entity check). At this stage, you could ignore the message, since it is 
only a warning (even though the linker says "error"). The .exe will be generated 
if the olny error(s) are module key mismatches. 

To get the proper version key into your O2 symbol file, use the program 
[h2okit.oli]k2syn.exe (distributed in source code, see k2syn.mod), run it, enter 
the name of the .sym file (without extension) at the prompt and k2syn will modify 
the O2 key to the value of the M2 .sym file. To run k2syn, you'll need both .syn 
and .sym file with the same file name in your default directory. You need to do 
that only once for every .syn file (as long as the interface doesn't change). Use 
with care. It is always a good idea to use a command procedure for this 
purpose. You can install k2syn.exe as a foreign command: 

$ k2syn :== $$disk:[h2okit.oli]k2syn.exe 

$ k2syn file 

will lookup the files file.sym and file.syn and copy the module key from .sym to 
.syn. 

- if you use different module.procname separators ("." is default in both, MVR 
and H2O), use the H2O/nameseparator="_" compilation qualifier, for example to 
adjust for the module name space of the M2 compiler you are using. 

H2O Eval Kit/ISO M2 Std Lib restriction: The Eval Kit is limited in the respect, 
that you can't re-compile the ISO M2 Std Lib modules to get another module 
separator than the default "." ("_" with A2O on OpenVMS Alpha). The goal of the 
H2O evaluation kit is to evaluate H2O. This can be done with the pre-compiled 
ISO M2 Std Lib module set. 

You actually need to be able to compile our set of ISO M2 Std Lib modules to 
generate another module name separator and also to eventually apply the 
compilation qualifier /foreign; these modules are written in M2 and normally 
distributed in preparsed/encrypted source. With proper H2O you'll on request 
also get our M2 compiler MVR (restricted version which allows to compile 
encrypted M2 source modules only) to be able to apply any compilation switch 
(including the /foreign qualifier, see below). 

So as long as you can't re-compile the ISO M2 Std Lib modules (e.g. when you 
only have the H2O Eval Kit) and when you want to use a foreign M2 compiler 
which doesn't allow the "." ("_" on Alpha) module name separator, then you 
must import only the lib modules available with Modula-2 compiler, e.g. not the 
ISO Lib. (Note, that ISO M2 has a module called TextIO which may be quite 
different to yours.) 

Note, you don't need to use H2O's ISO lib modules nor any other lib module 
except for the H2O run-time system. H2ORTS will not raise any conflict as long 
as you don't use coroutines which by the way are supported by H2ORTS too via 
module Processes and another more low-level O2 foreign interface module 
calle NewKernel. NewKernel is written in Modula-2. See files 

newkernel.def Modula-2 definition 

newkernel.deo Oberon-2 interface module, 

newkernel.mod Modula-2 implementation 

on directory [h2okit.oli]. 

This is a simple example for a O2 interface to a Modula-2 implementation. 

By the way, ModulaWare also has a replacement module for MODRTS and 
H2ORTS for very fast coroutine switching without stack switching (50 
microseconds on a microVAXII without stack-overflow check), which is available 
on request as part of proper H2O kit. (On Alpha, the fast coroutine transfer is 
default and supports stack-overflow check.) 

________________________________________________________________________ 

Q: How to call O2 routines from M2? 

A: You have to write a dummy M2 definition module for the O2 implementation, 
just like calling M2 from O2. Type bound procedures of O2 can't be called by 
M2. Also record extensions (objects) are not available in M2. 

Read-only O2 variables are then read/write when accessed from M2. There is 
no way to overcome this problem. It can't be checked at run-time. 

________________________________________________________________________ 

Q: Is code generated when using /FOREIGN qualifier re-entrant? I mean, do 
you store R11 in some global variable or is it put on the stack? 

A: When using /foreign, R11 is saved/restored via RTS routines each time it is 
used, in both, M2 and O2 compiler (MVR and H2O). ModulaWare uses this 
technique since at least 1987. It is reentrant and it allows to write M2 and O2 
routines to be called by ASTs (which don't save/restore R11. R11 is used only to 
access the static link in nested procedures only). These INITR11, SAVER11 and 
RESTORER11 routines are part of module MODRTS_FOREIGN which is a 
replacement for MODRTS (also available as H2ORTS_FOREIGN which is not 
part of the H2O Eval Kit to not confuse the users). The R11 init/save/restore 
routines may even be replaced by your own routines if necessary. In fact they 
store R11 in a global variable in the RTS, but the compiler doesn't touch it 
directly. 

Note: R11 (and it's save/restore business) is no longer used since H2O/A2O 
v3.01 and MVR/MaX v4.14; instead the static link is stored in the stack-frame of 
nested procedures to access outer scope local procedure varibales. So the 
qualifier /foreign makes only sense with A2O/MaX in combination with 
stack-check (for coroutines). 

________________________________________________________________________ 

Q: What do I have to do, when I have already MVR and want to use the H2O 
compiler with the default MVR library mod$system:modula.olb? 

A: You'll have to extract the modules ctr, h2orts, modrts and objects_types from 
H2O's:modula.olb with the command 

$libr/extr=(ctr,h2orts,modrts,object_types) modula.olb 

and then explicity link your main program with 

$link /deb test,h2orts,modrts,object_types 

or, to shorten the link command, make your own h2orts.olb. Oberon-2 needs 
module MODRTS (which, when seen as migration from M2 to O2, is simply an 
upward compatible extension of Modula-2's MODRTS), because 

- mod$memalloc is located there (O2's NEW) 

- mod$storemodobjects is there (O2's run-time type information (name,size). 

Use $lib/list modula.olb/full/name to see what's in the module modrts. 

H2ORTS are the O2's run-time messages. CTR is simple, central type 
declaration module. Objects_types imports CTR as well as module Storage. 
Storage is called from module Objects_Types. It uses the "." module separator 
which doesn't matter, because Objects_Types is called from the MODRTS and 
not from Oberon-2 program directly. When your M2 compiler library doesn't 
have Storage, you need to extract that module from H2O's Modula.olb too. 

You can apply any qualifier when compiling encrypted modules such as 
Objects_Types. For example: 

$set default [h2okit.oli] 

$H2O Objects_Types.MOC/name_sep="_" 

(It's not magic, but ModulaWare has to hide some internal structures for 
run-time type information. If someone needs the source, she/he can get it, but I 
didn't want to make it public). 

To show you that there is no magic in H2O, here is the source code of 

1. modrts.mar (stripped for H2O written in Macro32) 

2. vir$.def (Oberon-2 foreign interface module) 

3. storage.mod (full Oberon-2 source, to replace [h2okit.oli]storage.mod, .obj) 

-------module modrts.mar without mod$newprocess and mod$transfer-----
 
        .TITLE  MODRTS RUNTIME SUPPORT FOR H2O (Oberon-2 on VAX/VMS)
;
;Copyright (1994) Guenter Dotzel, ModulAware

;
        .EXTERNAL       LIB$GET_VM
        .EXTERNAL       Objects_Types.StoreModObjects
;
        .PSECT MODULA2.$CODE$ ,PIC,REL,SHR,EXE,RD,NOWRT,LONG
 
;       [hG] 29.04.92
;
        ; .EXTERNAL       LIB$GET_VM
;
        .ALIGN  LONG,0
MOD$MEMALLOC::
        ; NEW(adr,size)
        PUSHR #^M<R2, R3, R4, R5>;        save registers
        PUSHL R1;                         size in bytes
        PUSHL R0;                         adr
        PUSHAL (SP);                      ref to adr (elemptr)
        PUSHAL B^8(SP);                   ref to size
        CALLS #2, G^LIB$GET_VM;           pops two longwords with param
 refs
        CMPL B^4(SP), #65535;
        BLEQU 1$
        MOVL (SP), R3
2$:     MOVC5 #0,(R3),#0,#65535,(R3);      clear more than 64K in a loop
        ACBL    #65535, #-65535,B^4(SP),2$;
        MOVC5 #0,(R3),#0,B^4(SP),(R3);
        BRB 3$
1$:     MOVC5 #0,@(SP),#0,B^4(SP),@(SP);  clear heap space
3$:     MOVL (SP)+,R0;                    get elemptr as funct result in R0
        ADDL #4,SP;                       clear-up stack
        POPR #^M<R2, R3, R4, R5>;         restore registers
        RSB
 
        .ALIGN  LONG,0
MOD$STOREMODOBJECTS::
        ;
        ; R0 = adr of start of object/typename area
        ; R1 = adr of module name string (CHR(0) terminated)
        ; definition module = objects_Types.mod;GD/13-Jan-1993
        PUSHL R0
        PUSHL R1
        PUSHAL 4(SP)
        PUSHAL 4(SP)
        CALLS #2,Objects_Types.StoreModObjects
        ADDL2 #8,SP
        RSB
;
;
; VECTOR PSECT TO ALLOW FOR REAL SHAREABLE IMAGES
        .PAGE
        .SUBTITLE VECTOR SECTION FOR RUNTIME SUPPORT
        .PSECT  MODULA2.$VECTOR$ ,PIC,REL,SHR,GBL,EXE,RD,NOWRT,LONG
;
;       TRANSFER VECTOR SECTION FOR RUNTIME SUPPORT
;
        .TRANSFER MOD$MEMALLOC
        JMP MOD$MEMALLOC
        .BLKB 2
;
        .TRANSFER MOD$STOREMODOBJECTS
        JMP MOD$STOREMODOBJECTS
        .BLKB 2
;
        .END
----------------end of stripped module MODRTS-----------------------
 
MODULE VIR$;
(* Oberon-2 (H2O) Run-Time Library Interface
 
   GD/23-Sep-1993
*)
 
IMPORT CTR, SYSTEM;
TYPE
  CARDINAL*=CTR.CARDINAL;
  ADDRESS*=CTR.ADDRESS;
  WORD*=SYSTEM.WORD;
 
PROCEDURE LIB$GET_VM*(
       numbyt: CARDINAL;
   VAR basadr: ADDRESS
   ): CARDINAL;
END LIB$GET_VM;
 
PROCEDURE LIB$FREE_VM*(
       numbyt: CARDINAL;
       basadr: ADDRESS
   ): CARDINAL;
END LIB$FREE_VM;
 
PROCEDURE LIB$STAT_VM*(
       code:  CARDINAL;
       value$I: ADDRESS (* %IMMED instead of VAR value: ADDRESS (GD 03-
Apr-1990) *)
   ): CARDINAL;
END LIB$STAT_VM;
 
PROCEDURE LIB$SHOW_VM*(
       code:   CARDINAL;
       action: ADDRESS;
       usrarg: WORD
   ): CARDINAL;
END LIB$SHOW_VM;
 
PROCEDURE LIB$GET_LUN*(
   VAR basadr: ADDRESS
   ): CARDINAL;
END LIB$GET_LUN;
PROCEDURE LIB$FREE_LUN*(
       basadr: ADDRESS
   ): CARDINAL;
END LIB$FREE_LUN;
 
PROCEDURE LIB$GET_EF*(
   VAR eventflag: CARDINAL (* corrected: GD/Jan-1990 *)
   ): CARDINAL;
END LIB$GET_EF;
 
PROCEDURE LIB$FREE_EF*(
       eventflag: CARDINAL
   ): CARDINAL;
END LIB$FREE_EF;
 
PROCEDURE LIB$RESERVE_EF*(
       eventflag: CARDINAL
   ): CARDINAL;
END LIB$RESERVE_EF;
 
END VIR$.
------------end vir$.def------------
 
MODULE Storage;
(* Oberon-2 module for H2O by GD/23-Sep-1993 *)
 
IMPORT CTR, SYSTEM, lib:=LIB$, vir:=VIR$;
 
TYPE
  ExceptionType*=CTR.ENUM8;
 
CONST NoException*=0; NotAnException*=1; NilDeallocation*=2;
    PointerToUnallocatedStorage*=3; WrongStorageToUnallocate*=4;
 
PROCEDURE ALLOCATE*(VAR v: CTR.ADDRESS; n: CTR.CARDINAL);
VAR Result: CTR.CARDINAL;
BEGIN
    Result := vir.LIB$GET_VM (n, v);
    IF ~ ODD(Result) THEN
      lib.LIB$SIGNAL(Result)
    END
END ALLOCATE;
 
PROCEDURE DEALLOCATE*(VAR v: CTR.ADDRESS; n: CTR.CARDINAL);
VAR Result: CTR.CARDINAL;
BEGIN
    Result := vir.LIB$FREE_VM (n, v);
    IF ~ ODD(Result) THEN
      lib.LIB$SIGNAL(Result)
    END;
    v := SYSTEM.VAL(CTR.ADDRESS,NIL);
END DEALLOCATE;
 
PROCEDURE ExceptionValue*(): ExceptionType;
(* not implemented: will result in "no proc return exception" *)
END ExceptionValue;
 
END Storage.
-----------------end module Storage.MOD----------------------------
 

Extract the files, replace the two references to "Objects_Types." in file 
modrts.mar by "Objects_Types_" and then execute the commands 

$MACRO modrts.mar 

$H2O vir$.def 

$H2O Storage.MOD/name_sep="_" ! only if you don't already have a module 
Storage 

and then link your application again. 

The next release of H2O will include these modules to get rid of module 
dependencies between M2 and O2 run-time system. 

________________________________________________________________________ 

Q: What about procedures with a formal parameter of record types? 

A: In the case of variable parameter (VarParRecord), the O2 compiler assumes 
that an object is to be substituted at a call. This means that information about 
the dynamic type of the object must be provided. So in addition to the record's 
address, the type descriptor is also passed to the procedure as a hidden 
[immediate] parameter. (In the case of value parameters this is not the case, 
because subsitution takes place like in assignment, i.e.: projection.) 

The easiest way to avoid the problem with VarParRecord is to avoid 
VarParRecord in definition modules. The ISO M2 Std Lib only has one module 
(SysClock) with a VarParRecord. 

If VarParRecord can't be avoided, then 

1. Make a foreign interface module and use param name suffix $S or $R or $N 
(see H2O User's Guide), or 

2. care about the argument count in the called routine if it is written in M2 (see 
H2O user's guide for an example how to do that: Apendix C, at the end). 

4. If the formal parameter type is a VarParRecord, e.g. an explicit string 
descriptor type 

TYPE RECORD len: CARDINAL; adr: SYSTEM.ADDRESS END; 

then use a VAR parameter of ARRAY OF CHAR; If you have a VAR parameter 
of ARRAY OF CHAR, then the parameters values are not examined by the 
compiler. Only the reference and the (static) size of the variable are passed to 
the called routine. Just as with your string desriptor. Otherwise the called routine 
couldn't fill up the string to the full size, which is perfectly legal for VAR 
parameters. In the case of value parameters of open char arrays, this might be 
different. 

If you pass an explicit string descriptor, the mechanism is always VAR (by 
reference) which you sure don't want in every case. 

If you now are confused, then I try to explain the VarParRecord problems again, 
using a different wording: 

In the following, we deal with the combination of M2 and O2 below only, 
because there is no problem with foreign (language) definitions where you can 
explicitly specify the passing mechanism (e.g. the $N parameter name suffix 
which stands for No-type-descriptor; see H2O User's Guide). 

If a procedure written in M2 has a value record parameter, then there is no 
problem since O2 does a projection of the record's value (like in an assignment). 

If a procedure written in M2 has VARiable record parameter, then there are 
several ways to deal with that problem: 

1. Clean and gain in saftey (do I need to explain why?): re-write this module M2 
in O2. That's easy in most cases even with larger modules. 

2. Hack (which perfectly works with our M2 compiler, but will also work with 
yours, since the parameter passing mechanisms are almost the same for any 
language processor under VAX/VMS): Cheat and define the procedure's record 
parameter as value instead of VAR in the dummy O2 interface module, e.g.: 

 
  DEFINITION MODULE x; (* M2 def *)
    TYPE R: RECORD ... END;
    PROCEDURE y (VAR z: R);
  END x.
 
  MODULE x; (* O2 def/imp-dummy to get a symbol file *)
    TYPE R*: RECORD ... END;
    PROCEDURE y* ( (**VAR**) z: R);
  END x.
 
  MODULE Import_x;
  IMPORT x;
  VAR r: x.R;
  BEGIN
    y(r);
  END Import_x.
 

This will work because parameter passing is by reference (by default) for all 
parameter data types, be they basic or structured type, doesn't matter whether 
VARiable or value paramter. The only difference between value and VAR 
parameter is that the called procedure is responsible for making a local copy of 
all value parameters before the procedure's body is executed. 

(This also works, when an extension of the records base-type is passed 
because M2 knows only the static type and would copy only the static size which 
is always at least the size of the static type.) 

If the parameter is VAR (reference), then it is possible to also modify the data of 
the caller. So your M2 procedure still deals with a VAR parameter, while the 
caller in O2 thinks it is value and therefore doesn't pass the additional type 
descriptor. The pushing of the record's reference is the same for both passing 
mechanisms. It is dangerous, because your O2 interface doesn't match exactly 
the M2 def, but after all, it is a dummy and the M2 def is still the master. A 
comment should be inserted as shown in O2's dummy interface as shown 
above in module x. 

3. Compile the M2 module with /omit_module_name qualifier which is possible 
with our M2 compiler. This forces the M2 compiler not to generate 
"modulename.procname" (or maybe "modulename_procname"), but only 
"procname" and hence the M2 compiler generated object looks like a foreign 
implementation. Then make a foreign interface module for O2. Then you can 
use the $N suffix as with other foreign interfaces for VarParRecord. 

4. If you can modify the M2 implementation module (if you have the source code 
and if there is only a low number of procedures using VarParRecords): Stay with 
the VarParRecord in your O2 interface for M2, but check the parameter count in 
the M2 procedure at run-time which allows you to determine whether O2 or M2 
called you. Then deal with the parameter directly via argument pointer and 
offset. ModulaWare did that for ISO M2's Std Lib module SysClock. This 
low-level but fully save solution works is shown in App. C of the H2O User's 
Guide. 

Note: With MaX Modula-2 for OpenVMS Alpha, there is the /typedescriptor 
compilation qualifier, which allows to ignore the Oberon-2 typedescriptor. 

________________________________________________________________________ 

Q: In his book Wirth mentions a tool that extracts the interface information from 
an Oberon-2 module. Do you have such a tool for the H2O compiler? 

A: In Wirth's Oberon System there is a so-called browser which reads the 
symbol file of a module. 

ModulaWare also has such a browser tool; it runs under the Alpha Oberon 
System (AOS). The AOS Browser can decode the non-object model 
(non-fine-grained symbol files) symbol files of the stand-alone compiler. 

________________________________________________________________________ 

Q: Do you have any plans for adding garbage collection (GC) to the VMS 
compiler? 

A: GC is only supported in AOS. 

With the stand-alone H2O/A2O compiler you have the possibility to explicitly 
dispose objects using Storage.DEALLOCATE. Module Objects_Types exports 
procedures to get the run-time type size in the case of dynamic objects. 

I know that you need garbage collection for implementing save, complex 
OO-based software, but for real-time and embedded systems, garbage 
collection is not sensible. This is why OO-extension of ISO M2 (which is under 
design by the committee) will most probably not have GC. 

________________________________________________________________________ 

Alpha AXP/OpenVMS Portation 

Q: At the end, I'd like to ask, what your plans are regarding the Alpha 
architecture? Will Oberon be ported to Alpha? 

A: Yes, both M2 and O2 are currently [at the time of writing] ported to 
AXP/OpenVMS.  [Ed. note: the port was completed in 1995.]

Q: Also, is there a UNIX based Oberon-2 compiler? 

A: Yes, OM2-XDS native code for PC(Inyel) under Linux and XDX ANSI CX 
Translator for Sun-Sparc and HP-PA/HP-UX. 

________________________________________________________________


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 Guenter Dotzel; he can be reached by tel/fax: [removed due to abuse] or by mailto:[email deleted due to spam]

Home Site_index Contact Legal Buy_products OpenVMS_compiler Alpha_Oberon_System DOS_compiler ModulaTor Bibliography Oberon[-2]_links Modula-2_links

Amazon.com [3KB] [Any browser]


Webdesign by www.otolo.com/webworx, 14-Nov-1998