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