Ulm's Modula-2 System

Historic Material of our Modula-2 System


Because of multiple requests I make that software available that was used to bootstrap to a Modula-2 compiler for the Lilith architecture running on a Lilith emulator on a Perkin-Elmer 3220 machine running UNIX Edition 7. All this happened in summer 1983.

I haven't changed any of the sources and you will even see the original timestamps of the files as I archived them at the end of this project. This means that the emulator and its associated tools can no longer be compiled with recent C compilers and even if you manage to get rid of all the K&R style within the C sources, it won't work because of huge portability problems. These utilities were just developed to serve in the development of a Modula-2 compiler for the Perkin-Elmer 3220 architecture and were no longer needed once this goal was achieved.

Following compressed tar archives are available for download:

Please note that the Lilith emulator, the M-Code decoder, and the M-Code linker are available under the terms of the GPL. The Multipass Modula-2 compiler for the Lilith architecture, however, was developed by a team under the direction of Niklaus Wirth at the Institut für Informatik at ETH Zürich. We received this software under a license agreement which allows us to redistribute it. You have to consent to this licence before you download it.

The package historic-lilith-binaries.tar.gz provides the binaries lilith, mcd, and mcl that were compiled from the sources of the first three packages in 1984. Unfortunately they cannot be reproduced using the simulation system (see below) as the C compiler shipped with the provided instance of UNIX Edition 7 has bugs we hadn't on our system running at that time.

If you intend to play with these tools, you will need to setup an environment that is close enough to our system we used at that time. I recommend to use the most recent simulator of the Computer History Simulation Project along with a copy of Unix Edition 7 for the Interdata architecture which is available under a license provided by Caldera Corporation. The Interdata is a predecessor of the Perkin-Elmer architecture we had. This is no problem for the Lilith emulator and the compiler running on it but for our Modula-2 compiler for that architecture which took advantage of instructions the Interdata didn't have.

Here is a step-by-step instruction how this can be set up:

  1. Download the newest release of the simulator (at the time of this writing this is simhv38-0.zip), compile, and install it. We need the id32 binary that simulates an Interdata 8/32.

  2. Download iu7swre.zip and unpack it. (The license by Caldera is to be found in the file AncientUnix.pdf). The associated documentation is to be found here.

  3. The most efficient method to transfer files to this system is by preparing a tar file that is made available as a disk. Under the UNIX system, we do not mount this disk but let tar unpack this archive from the raw device. This, however, works only reliably if we restrict all file and directory names to a maximal length of 14 characters as longer names weren't supported by UNIX Edition 7. Go to the directory where you have unpacked all the software packages downloaded from above and rename the top-level directories:

    clonard$ mv historic-lilith-emulator lilith
    clonard$ mv historic-mcode-decoder mcd
    clonard$ mv historic-mcode-linker mcl
    clonard$ mv historic-multipass-modula2-compiler-lilith mll
    clonard$ mv historic-lilith-binaries bin
    clonard$ tar cf modula.tar lilith mcd mcl mll bin
    

  4. Prepare a file named id32.ini with following contents:

    set ttp ena
    set pas dev=12
    att -e dp0 iu7_dp0.dsk
    att -e dp1 iu7_dp1.dsk
    att -e dp2 modula.tar
    set console brk=3
    boot dp0
    

    (The set console command configures the console to send a BREAK whenever CTRL-c is typed in.)

  5. Start the simulator of the Interdata 8/32:

    clonard$ id32
    
    Interdata 32b simulator V3.8-0
    
    Boot
    : dsk(1,0)unix
    Memory = 248.0 K
    # ^D
    Restricted rights: Use, duplication, or disclosure is subject
    to restrictions stated in your contracts with Western Electric
    Company, Inc. and the University of Wollongong.
    Fri Jan  2 18:12:58 EST 1970
    
    login: root
    Password: root
    # 
    

    (Please note that ^D represents CTRL-d and that the root password is not echoed.)

  6. In the next step we unpack the prepared tar archive into the /tmp directory. This is the only location where sufficient space is left. The modula.tar archive is available through /dev/dr2 (block device) and /dev/rdr2 (raw device) which need to be created first:

    # cd /dev
    # /etc/mknod dr2 b 0 4
    # /etc/mknod rdr2 c 2 4
    # cd /tmp
    # tar xf /dev/rdr2
    Tar: blocksize = 20
    tar: lilith/ - cannot create
    tar: lilith/src/ - cannot create
    tar: mcd/ - cannot create
    tar: mcd/src/ - cannot create
    tar: mcl/ - cannot create
    tar: mcl/src/ - cannot create
    tar: mll/ - cannot create
    tar: mll/src/ - cannot create
    tar: bin/ - cannot create
    tar: bin/bin/ - cannot create
    #
    
    (The error messages of tar can be ignored.)

  7. Now we can add /tmp/bin/bin to our path and attempt to compile a first "hello world" program:

    # PATH=$PATH:/tmp/bin/bin
    # export PATH
    # mkdir /tmp/hello
    # cd /tmp/hello
    # ed HelloWorld.m2
    ?HelloWorld.m2
    a
    MODULE HelloWorld;
    
       FROM Terminal IMPORT WriteString, WriteLn;
    
    BEGIN
       WriteString("Hello, world!"); WriteLn;
    END HelloWorld.
    .
    w
    128
    q
    # for x in /tmp/mll/src/C18.*
    > do ln $x `basename $x`
    > done
    # ln /tmp/mll/src/mc mc
    # ln /tmp/mll/src/SYM .
    # mc HelloWorld.m2
     source file> HelloWorld.m2
    p1
     Terminal: Terminal.sy
    p2
    p3
    p4
    end compilation
    # 
    

  8. If the compilation was successful (as shown above), we got an object file named HelloWorld.o which by itself alone is not yet executable on the Lilith emulator. We can, however, decode it if we like to:

    # mcd HelloWorld.o
    decode of `HelloWorld.o' :
    
    codekey    = 3
    module name:  HelloWorld
    datasize   = 12
    key          0 0 0
    import  Terminal
     is #   1
            HelloWorld
     is #   2
    
    data, relative to G
        1:      0B
        2:      0B
    
    procedure  #   0 at   2 bytes relative to F
    data, relative to G
        3:  44145B
        4:  66154B
        5:  67454B
        6:  20167B
        7:  67562B
       10:  66144B
       11:  20400B
    
    code at F +   1 words
        1     2   353       ENTR      0 
        2     4    25       LGA       1 
        3     6   224       TS    
        3     7    32       JPFC  [  2]   ->    12
        4    11   354       RTN   
        5    12    25       LGA       3 
        6    14   122       SGW2  
        6    15   355       CX      1   0 
       10    20   102       LGW2  
       10    21    14       LI12  
       11    22   355       CX      1   6 
       12    25   355       CX      1   5 
       14    30   354       RTN   
       14    31   336       NOP   
    fixups at    26    23    16 
    
    #
    

    The module body begins with a test whether this module was already initialized by loading the address of the first global variable (LGA 1) and performing a test-and-set operation on it (TS). If this flag was false, we jump to position 12, continuing the body, otherwise we return (RTN) immediately. Next follows the storage of a pointer to the hello world string in the global variable 2 (LGA 3 and SGW2). Next follows the invocation of the module body of Terminal (CX 1 0, i.e. call external module 1, procedure 0). Then we are ready to invoke WriteString and WriteLn. An open array parameter requires two values to be passed, the address of the array (LGW2 = load global word 2) and its length (LI12 = load immediate constant 12). Finally, we return. The NOP instruction is used to fill up to a word boundary of 16 bits. Some of the instructions require one byte only (like RTN), others are longer (CX, for example, requires three bytes).

  9. In the next step we can attempt to create a binary which is actually executable on the Lilith emulator. For this we will need the Terminal.o object:

    # ar x /tmp/mll/src/OBJECTS Terminal.o
    # mcl -o HelloWorld HelloWorld.o Terminal.o
    # lilith HelloWorld
    Hello, world!
    # 
    

    You might wonder why no other library modules were needed or why the source for Terminal.m2 is missing. It may be interesting to decode Terminal.o:

    # mcd Terminal.o
    decode of `Terminal.o' :
    
    codekey    = 3
    module name:  Terminal
    datasize   = 3
    key          123373 745 152654
    import  Terminal
     is #   1
    
    data, relative to G
        1:      0B
        2:      0B
    
    procedure  #   1 at  16 bytes relative to F
    code at F +   7 words
        7    16   246       SVC      74 
       10    20   354       RTN   
       10    21   336       NOP   
    
    procedure  #   2 at  22 bytes relative to F
    code at F +  11 words
       11    22   246       SVC      75 
       12    24   354       RTN   
       12    25   336       NOP   
    
    procedure  #   3 at  26 bytes relative to F
    code at F +  13 words
       13    26   246       SVC      76 
       14    30   354       RTN   
       14    31   336       NOP   
    
    procedure  #   4 at  32 bytes relative to F
    code at F +  15 words
       15    32   246       SVC      77 
       16    34   354       RTN   
       16    35   336       NOP   
    
    procedure  #   5 at  36 bytes relative to F
    code at F +  17 words
       17    36   246       SVC     100 
       20    40   354       RTN   
       20    41   336       NOP   
    
    procedure  #   6 at  42 bytes relative to F
    code at F +  21 words
       21    42   246       SVC     101 
       22    44   354       RTN   
       22    45   336       NOP   
    
    procedure  #   0 at  46 bytes relative to F
    code at F +  23 words
       23    46   353       ENTR      0 
       24    50    25       LGA       1 
       25    52   224       TS    
       25    53    32       JPFC  [  2]   ->    56
       26    55   354       RTN   
       27    56    25       LGA       3 
       30    60   122       SGW2  
       30    61   354       RTN   
    
    #
    

    As you can see, most of these procedures are implemented with the help of a SVC (super visor call) instruction which doesn't belong to the original instruction set of the Lilith architecture. I added this instruction as a hook that allowed me to implement these procedures within the Lilith emulator. You'll find the implementations of this within the l_svc.c source file of the Lilith emulator (in the /tmp/lilith/src subdirectory. SVC 101, for example, is implemented here (note that 101 is octal which is 65 in decimal notation):

            case 65 :       /* PROCEDURE WriteString(s: ARRAY OF CHAR); */
                    len = pop() + 1;
                    index = pop();
                    ptr = (char *) &stack[index];
    #ifdef TRACE
                    trace("WriteString(\"");
    #endif TRACE
                    while ( len && *ptr ) {
    #ifdef TRACE
                            trace("%c",*ptr);
    #endif TRACE
                            putchar(*ptr++);
                            --len;
                            }
    #ifdef TRACE
                    trace("\")\n");
    #endif TRACE
                    break;
    

    These objects were not derived from the original sources but from stripped-down variants where the procedure bodies were replaced by CODE-constructs, a language-extension which was supported by the Modula-2 compiler for the Lilith. This allowed the inclusion of native machine code. These stripped-down sources existed on the PDP-11/40 only and were not transferred with the other files.

    In general, the surviving library modules are quite minimalistic in their selection, even InOut is missing as I considered strictly those sources only which were required for bootstrapping.

  10. If you want to save the current state of this simulated UNIX system for further experiments, you will need a clean shutdown procedure. Commands like shutdown or halt didn't exist at that time. Hence we have only sync. Type it twice and let some seconds pass. Type CTRL-e afterwards to return to the prompt of the simulator:

    # sync
    # sync
    # ^E
    Simulation stopped, PC: 00D20 (EPSR R1,R0)
    sim> exit
    Goodbye
    clonard$
    

    Next time, you just need to boot the system again, following the instructions above, and you will find everything back again including the installed directories below /tmp.


Andreas F. Borchert, 18 December 2008