Modula-2 for VAX OpenVMS 7.3

Modula-2 for VAX OpenVMS 7.3

My VAXstations now have Modula-2: the Hamburg VAX-11 Modula-2 compiler, as a kit for OpenVMS VAX 7.3, which compiles itself. It is Niklaus Wirth’s language in the implementation of the DBPL group at the University of Hamburg (after the ETH Zurich compiler for the PDP-11): a native compiler that is written in Modula-2, produces ordinary VAX object files for the linker, and comes with foreign definition modules for the VMS system services, RMS, the run-time library and SMG. The kit is a BACKUP saveset with a DCL installer and a DCL uninstaller; it needs nothing but the DEC tools every VMS has (LINK and BACKUP on the target; MACRO and LIBRARIAN only to rebuild it).

Claude Code assisted, which is to say, did much of the hard work.

Downloads, with SHA-256 sums in SHA256SUMS.TXT:

Credits

The Hamburg compiler’s files carry the authors’ names, J. Koch, M. Mall, P. Putfarken, H. Eckhardt, for the group of J. W. Schmidt, and the grant that paid for it (DFG, Schm 450/2-1). The project’s own page once said stable systems for VAX/VMS were available on request, and bitsavers.org and the DECUS VAX SIG library have carried the files for decades.

Everything that is mine (the installer, the uninstaller, the setup procedure, the examples, the build procedures) is marked as mine; the one change to a Hamburg file is a diff of the MODULA command definition, in the source kit.

Which Modula-2

Modula-2 on the VAX is a story of several compilers, and most of them were products. What I could actually get, and run on VMS, was one:

  • The Hamburg compiler, version 3.1 on a 1983 tape image at bitsavers (the tape has the sources and the object code), and version 3.2 in the DECUS VAX SIG library, a 1989 VMSINSTAL kit holding the same system as released in 1985-1988. The compiler’s banner reads VAX-11 Modula-2 V3.2-25.
  • DEC’s own Western Research Lab Modula-2 was announced for VMS “soon” in 1990; what exists is for Ultrix and BSD. ModulaWare’s MVR was a product. MOCKA is for Linux. Modula-3 is another language.

This kit is the V3.2 distribution. The V3.1 tape is there as a baseline: I read it on Linux and on the simulator, and its compiler runs too.

What you get

$ @SYS$COMMON:[SYSLIB.MODULA2]MODULA2_SETUP
Modula-2 (Hamburg VAX-11 Modula-2 V3.2-25) is ready: MODULA, PRETTY, EBNF
$ MODULA HELLO
$ LINK HELLO
$ RUN HELLO
Hello from Modula-2 on OpenVMS VAX
  • MODULA is a DCL verb (from MODULA.CLD) with /LIST, /LOG, /CROSS_REFERENCE, /DEBUG, /MACHINE_CODE, /NOCHECK, /QUERY. A definition module compiles to a .SYM file (the compiler reads these for every import), an implementation or main module to a .OBJ.
  • The run-time library MODULA.OLB is found through LNK$LIBRARY, so a plain LINK works. InOut, Terminal, FileSystem, Storage, Conversions, MathLib0..3, DateTime, Measures, RMSFiles, and Wirth’s ProcessScheduler.
  • Foreign definition modules (%FOREIGN DEFINITION MODULE) describe system services and run-time library routines, with parameter modes %IMMED, %REF and %STDESCR. The kit has VMS (system services), RMS, the ...Definitions constant sets, ScreenManagementProcedures (SMG), StringHandlingProcedures, CommonInputOutputProcedures and more. Writing your own takes ten lines (below).
  • PRETTY is a Modula-2 source pretty printer and EBNF a grammar checker, both written in Modula-2, as is the compiler.
  • Reserved words and standard identifiers are upper case, identifiers are case sensitive, and the first nine letters of a module name are its file name.

It compiles itself

The compiler’s sources are in the distribution. Compiling all of them, with the compiler that is shipped (as object code), takes 22 seconds on the simulator; the result is a compiler that I can use to compile all of them again:

StageMODULA.EXEdiffers from the previous stage
The shipped object code, relinked180,736 bytes–
1: compiled by the shipped compiler180,224 bytesmany blocks (older compiler versions made the shipped objects)
2: compiled by stage 1180,224 bytes8 bytes, in the image header (link time)
3: compiled by stage 2180,224 bytes6 bytes, in the image header

Stages 1, 2 and 3 are the same program. Every stage is 0 failed compilations of 137. The object and symbol files of two stages differ in a few records each (0 to 15 for an object, 1 to 4 for a symbol file: they carry compilation dates), but the linked images are identical but for the header, so none of those records is code. The kit holds stage 3.

The kit

  • 267 files, 4,855 blocks (512 bytes blocks) installed in SYS$COMMON:[SYSLIB.MODULA2]: the compiler, the library, the 87 symbol files, the 87 definition modules and 59 sources of the system, the user guide and four other documents, the examples. It is installed from a 2.9 MB BACKUP saveset in 8 seconds on the simulator and 30 on the VAXstation 4000/96.
  • Nothing outside that directory changes: no startup file, no SYSUAF, no DCL table, no system logical name, and not STARLET (the 1989 VMSINSTAL kit inserts the library into STARLET; this one has its own MODULA.OLB). Every user runs MODULA2_SETUP.COM; it defines MOD$SYSTEM, the MODULA command and the first free LNK$LIBRARY.
  • The installer writes a manifest with the size and revision date of every file, runs a smoke test in a subprocess (compile, link and run a program), and refuses to install over an existing tree without REPLACE. The uninstaller deletes only files the manifest lists and that are still unchanged (a file you edited, and anything you added, stays), then empty directories, then the manifest. A lifecycle test does all of that: install, refuse, check, change and add files, uninstall (kept, exit status 4), REPLACE stops, clean, uninstall, reinstall, REPLACE: 20 checks, all pass.

Installing

You need OpenVMS VAX 7.3, SYSTEM (or SYSPRV), and about 12,500 free blocks on the system disk while installing. No compiler.

  1. Put the saveset and the two .COM files into one directory on the VAX. Transfer modes matter: the .BCK in binary, the .COM files in ASCII.

  2. A binary transfer loses the saveset’s record attributes; the installer puts them back itself:

     $ SET FILE/ATTRIBUTE=(RFM:FIX,LRL:32256,MRS:32256,RAT:NONE) -
           MODULA2_VAX_20261011.BCK
    
  3. As SYSTEM:

     $ @MODULA2_INSTALL
    
  4. Each user, at the prompt or in LOGIN.COM:

     $ @SYS$COMMON:[SYSLIB.MODULA2]MODULA2_SETUP
    

@MODULA2_UNINSTALL CHECK says what an uninstall would remove, and @MODULA2_UNINSTALL does it.

Programs

All of these are in the examples archive, each with a DCL test; 8 of 8 pass as an ordinary user, in 3 seconds on the simulator (13 on the VAXstation 4000/96), including 8 compilations.

A foreign definition module of your own. VMSINFO asks VMS who and where it is. The library’s VMS module declares the process name of $GETJPI as a string descriptor, but “the calling process” means passing nothing, so VMSINFO declares what it needs:

%FOREIGN DEFINITION MODULE VmsX;
FROM SYSTEM IMPORT WORD, ADDRESS;
EXPORT QUALIFIED SYS$GETJPIW, SYS$GETSYIW;

PROCEDURE SYS$GETJPIW(  %IMMED efn   : WORD;
                    VAR         pidadr: WORD;
                        %IMMED  prcnam: ADDRESS;
                        %IMMED  itmlst: ADDRESS;
                        %IMMED  iosb  : ADDRESS;
                        %IMMED  astadr: ADDRESS;
                        %IMMED  astprm: WORD): CARDINAL;

The item list is a RECORD of length-and-code, buffer, return length; the program fills six of them (JPI$_USERNAME, JPI$_PID, JPI$_IMAGNAME, JPI$_CPUTIM, …) and calls the service. Its test compares the output with DCL’s own F$GETJPI and F$GETSYI.

RMS. PHONEBK is a telephone book in an indexed RMS file with a key on the name (an XABKEY), through the library’s record-level interface: create or open, keyed Get, Put, Update, Delete, a sequential list in key order, and “first key greater or equal” for prefix search. The file is Indexed, Prolog: 3 and the second run finds what the first left.

Coroutines. SIEVE is the sieve of Eratosthenes as a pipeline: one coroutine generates 2, 3, 4, … and every prime found adds a filter coroutine. A coroutine is a PROCESS made with NEWPROCESS(procedure, workspace, size, p) and entered with TRANSFER(me, other); the 46 primes below 200 take 47 of them, each with 4 KB from ALLOCATE.

Four floating point formats. The VAX has F, D, G and H floating point. REAL is F; D_FLOATING, G_FLOATING and H_FLOATING come from SYSTEM, with all the operators and no implicit conversions. Summing the series for e in each:

e = sum of 1/n!, 40 terms
  F_floating    2.718282
  D_floating    2.718281828459045
  G_floating    2.71828182845905
  H_floating    2.71828182845904523536028747135266

(The test compares the leading digits with Python’s decimal.)

Separate compilation. CALC is a recursive descent calculator (^ * / %, variables A-Z) with its scanner Tokens in a definition and an implementation module: compile TOKENS.DEF, TOKENS.MOD, CALC.MOD, link CALC,TOKENS. Its expected output was computed by a separately written Python evaluator, as were the Life, sieve and phone book outputs.

Issues

  • /LIST is the compiler’s default in a batch job, and it crashes (%SYSTEM-F-INTDIV), also the shipped 1988 MODULA.EXE. The kit’s MODULA command does not default to it; use /NOLIST otherwise.
  • The library must be compiled without range checks. InOut.WriteText computes n-LEN(s)+1 in CARDINAL; WriteCard(c, 0) then raises %MODULA-F-SUBVALERR. The shipped objects were built that way; so are ours. Your own programs keep the checks.
  • Files made by DCL OPEN/WRITE are VFC with print carriage control and the compiler reads the two header bytes of each record as source. Edit with a real editor, or CONVERT them.
  • The bitsavers zip holds only the sources; the object code is on the tape image, which is why a “just compile it” path did not exist: the compiler is written in the language it compiles, so you need the object code to start.

What I did not check

  • Real hardware, mostly. The kit was built and tested on a simulated MicroVAX 3900 (Open SIMH). I then installed it on one real machine, my VAXstation 4000/96, system-wide, and RUN_TESTS passed 8 of 8 there as an ordinary user. I have not run it on my 4000/60 or any other VAX, at least not yet. Simulator times say nothing about a real VAX except ratios.
  • Programs larger than the examples (the compiler itself is 21,559 lines of Modula-2 in 27 modules and works, which is the best test I have).
  • Interactive use of SMG, DEBUG with Modula-2 symbols, /MACHINE_CODE and /CROSS_REFERENCE output (they run; I have not read them closely).
  • HELP MODULA: DCL’s HELP does not find the kit’s help library on 7.3; the kit defines M2HELP MODULA instead.

Rebuilding

REBUILD.md in the source kit has every command, in order, from the DECUS modula32.zip (hashes given) to a tested kit, with the expected result and the measured time of each step: reading the V3.2 saveset (278 files), relinking the compiler from the shipped objects (1.2 s), three self-hosting stages (95 s on the simulator), making the kit (6.5 s). The only host-side tools are three small Python 3 scripts.

How this was done

As with Emacs, Perl and Poplog: Claude sessions with written briefs, on a VAXstation and a simulator, with the rule that nothing counts until a test shows it, and the transcripts kept. Every number above comes from one of those transcripts.

References

  • N. Wirth, Programming in Modula-2, Springer, 1982.
  • J. Koch, M. Mall, P. Putfarken, H. Eckhardt, VAX-11 Modula-2 User’s Guide (DBPL Memo 041-83) and Installing the VAX-11 Modula-2 Compiler (Memo 021-83), Universitaet Hamburg, 1983: on the media.
  • bitsavers, bits/DEC/vax/vms/Hamburg_Modula2/ (tape image, log, zip).
  • DECUS VAX SIG library, modula32.zip (kit MODULA032, Frankfurt/Hamburg versions, 1989).

Credits: the DBPL research project of the University of Hamburg (J. W. Schmidt; J. Koch, M. Mall, P. Putfarken, H. Eckhardt) and Niklaus Wirth’s group at ETH Zurich, who made the language and its first compilers; the DECUS VAX SIG library and bitsavers, who kept the files for decades; and Claude Code (Anthropic’s Claude models) for much of the building, debugging, testing and writing.