No description
  • Modula-2 47.1%
  • Macaulay2 42%
  • DTrace 6.5%
  • Shell 4%
  • AMPL 0.3%
  • Other 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Eric Streit dd0d67ee33 README: SYSTEM coroutines and the showcase
Record that the C18 already emits CX 0 2 / TRA for SYSTEM.NEWPROCESS /
TRANSFER, that mcode implements both (ProcessDescriptor, per-coroutine
spill), that mp/COR runs and CORT aborts, and that the Interpreter
showcase prints the same output under mcc and m3pl.
2026-10-19 12:33:09 +02:00
gm2-port FileSystem: process-unique TMP.<pid>.* scratch files 2026-10-19 09:42:09 +02:00
selfhost triage-medos: --all over the whole tree; stage by MODULE name 2026-10-19 10:16:59 +02:00
src MCOperations/MCOperation: fold REAL constant expressions 2026-10-17 13:11:47 +02:00
.gitignore Track the mcc binary 2026-10-17 13:03:34 +02:00
LICENSE gm2-port: fix Pass2 Error 71 on FROM imports 2026-10-12 08:23:18 +02:00
License-Agreement gm2-port: fix Pass2 Error 71 on FROM imports 2026-10-12 08:23:18 +02:00
README.md README: SYSTEM coroutines and the showcase 2026-10-19 12:33:09 +02:00

Historic ETH Zürich Modula-2 Multipass Compiler for the Lilith architecture

The ETH Zürich Modula-2 Multipass Compiler for the Lilith architecture was developed by a team consisting of Leo Geissmann, Christian Jacobi, Urs Ammann, Svend Erik Knudsen, and others under the direction of Niklaus Wirth at the

Institut für Informatik ETH-Zürich CH-8092 Zürich

These are, however, not the original sources as shipped by ETH Zürich (unfortunately, no machine-readable copy of them survived at our site) but those we adapted to our needs such that we could run them on our Lilith emulator on a Perkin-Elmer 3220 machine running UNIX Edition VII.

Please note that this software is not free (free as freedom) in a sense comparable to the GNU General Public License. See the file License-Agreement how the original software was licensed to us, and see LICENSE for some further notes and explanations.

You'll find the sources in the src subdirectory along with the binaries C18.Base, C18.Init, C18.Lister, C18.Pass1, C18.Pass2, C18.Pass3, C18.Pass4, and C18.Symfile. These binaries were generated by exactly these sources in combination with mcl, the M-Code linker (to be found in a separate package) and they can be run on our ancient Lilith emulator (to be found in another package), i.e. this compiler is delivered in a steady-state.

The script mc allows to compile Modula-2 sources using this compiler, running on the Lilith emulator, into M-Code which can likewise be linked using mcl and run on the Lilith emulator.

See https://github.com/afborchert/lilith for more infos.

Andreas F. Borchert

gm2 port (gm2-port/, GNU Modula-2, PIM dialect)

./build.sh in gm2-port/ builds ./mcc (multi-phase link via a module list — direct linking fails with duplicate main). mcc compiles one or more files per invocation (.d definitions first for their .sy files, then .m2); scratch files are process-unique (TMP.<pid>.*), so several mcc runs may share a directory. It writes .o (M-code) + .r (debugger info).

SYSTEM coroutines (NEWPROCESS/TRANSFER) are emitted as CX 0 2 and TRA <mask>; mcode implements both (MEDOS-2 ProcessDescriptor in the workspace, context switch with the expression stack spilled per coroutine), so Interpreter/tests/mp/COR.m2 runs and CORT (transfer to a nil coroutine) aborts. The Interpreter/examples/showcase program exercises them together with the libraries and prints the same output when compiled by this compiler or by m3pl.

Batch mode: mcc T.m2 SC.m2 compiles the file(s) directly (no piping); several files run in one invocation, resetting the per-unit compiler state, and the objects are byte-identical to one-file-per-invocation compiles. --version/--help print the banner/usage. Compile errors print *** error <n> at line <l> pos <p> in addition to the driver's ---- error. Its .o files run under ../../Interpreter/src/mcode (see that repo's BUILD.md §4 and TUTORIAL-LANGUAGE.md §10–12); the interpreter synthesizes HostIO / Terminal / Math / Files / Time / Storage (traps) for .o programs, with multipass definitions in ../../Interpreter/mplib/.

Origin note: src/ holds the adapted originals; gm2-port/ holds the Linux port (see gm2-port/PORT-SUMMARY.md for the porting changes).

Self-hosting status

All of it is gated by one command: selfhost/bootstrap.sh (or make bootstrap from ../../Interpreter/) builds the three binaries and runs every check — single-pass fixpoint 6/6, make test, make test-multi, the phase-3 corpus, phase 5, the MEDOS triage and phase 4 — exiting 0 only if all pass (10 ok, 0 failed). SKIP_BUILD=1 reuses existing binaries.

The multipass chain rebuilds itself — exactly. selfhost/build-phase4.sh compiles every src/ module with the self-hosted C18 (the phase-2 staging, driven by MCPublic.o under mcode): 34/34 modules compile, and the self-hosted objects are byte-identical to the host mcc's (0 bytes differ). The one residue was never emitted code: MCP4Global's loadAddress := procnumber must run after Pass2 (the original runs each pass via Program.Call with shared globals); the single-process driver now redoes it at the start of RunPass4.

mcc (the host binary) compiles its own gm2-port/ sources: selfhost/build-phase5.sh reports 37/37 core modules, 0 failures (only FileSystem/HostAddr/Program/Terminal stay out, by design), so the port's sources are in mcc's own dialect. The single-pass sibling is also exact: test-fixpoint.sh is now 6/6 — MAX(REAL) is the exact single max and PutLit masks to the 16-bit target width (see ../../Interpreter/Open-Work.md § 1–2).

selfhost/build-phase1.sh is the beachhead: it compiles the dependency leaves of src/ with host mcc (renaming files to module names — mcc names outputs after the file — shortening MCOperations/WriteStrings to the 11-char .sy lookup limit, and normalizing Terminal.d to trap order) and loads every .o under mcode, plus Conversions/NewStreams functional checks (all green). selfhost/build-phase2.sh goes further: every src/ module compiles (33 .sy + 33 .o), every library .o loads, and a capacity report confirms all fit the interpreter (largest: MCPass3, 7288 code bytes / 473 relocs — well under 16KB / 1024). Loads fall in three expected classes: clean inits (end.), MCPublic-driver inits (status 1, the Program.Call stub has no C18 chain), and file-dependent inits (clean NewStreams abort — bare loads provide no pass files). The port's own-source dialect work is done (18 Oct): the last CARDINAL64 in the core was moved into HostAddr (and MCP3IO/MCP4Global routed through it), the 32-bit REAL code was rewritten with REAL arithmetic, VAL(<basic type>, x) conversions were replaced by the casts mcc accepts, nested-module imports were normalised, and the remaining .defs got explicit EXPORT clauses. build-phase5.sh reports 37/37 core modules, 0 failures; see ../../Interpreter/Open-Work.md § 1 for the full list.

Two host-width codegen fixes found by phase 2 (both verified by failing-then-passing builds, both locked by regression tests):

  • Set-constant bit order is Lilith order, unconditionally (MCPass2/MCPass3 SetConstructor, MCOperations folded IN): the host-TSIZE branch emitted PDP-11 inverse order on 64-bit hosts ({wr} → 4096). Locked by SE in make test-multi.
  • Named string constants crashed pass 3 (AllocString fault): pass 2 never stored svalue, so pass 3 read stale scanner val (e.g. 3, 22H) as a pointer. MCPass2 ConstFactor now captures MCP2IO.GetSVal() (new accessor) into cval.svalue. Locked by SC in both suites.
  • BY -1 loops failed pass 4 (spurious error 202): step := val relied on 16-bit wraparound to recover negativity. Pass4 sign-extends the 16-bit BY word. (Found in MCP4CodeSys, FOR ... BY -1 twice.)

Phase 2a verdict: the FileSystem footprint (measured, not guessed)

Every qualified FileSystem.X use in src/:

module procedures used record fields touched
NewStreams SetOpen, SetPos, Close, SetWrite, Doio, SetRead, GetPos ela×18, flags×20, elodd×9, topa/ina/inodd, eof, res
FileLookup Lookup, Close (+Response) res
FilePool none (type File + Storage only) —
MCPublic Lookup, Rename, Close (+File, Response) res
MCInit Create, Close (+Response) res

Union: {Lookup, Create, Rename, Close, SetRead, SetWrite, SetOpen, SetPos, GetPos, Doio} — and all Doio/mode/position traffic flows through NewStreams.m2 alone (176 lines, the narrow waist), which also owns the buffer arithmetic (ela/topa/ina pointers, elodd byte-half packing, bytemode flag) in plain M-code.

Decision: implement a Doio trap plus the mode/position/open traps against the verbatim src/FileSystem.d record (field-for-field offsets the trap relies on), rather than re-porting NewStreams. Reasons: byte-exactness for free (.o CODETEXT bytes are packed hi-first by MCP4CodeSys.PutCodeText into whole words, and text streams pack hi-first as well — NewStreams writes the first byte high with elodd starting FALSE, matching Medos LXB semantics; any re-port must preserve both, while the trap approach runs the original buffering untouched), and a single narrow implementation (~one trap + record reader) instead of re-validating every stream client. Exact Doio/seek semantics come from FileSystem.MOD in ETHZ_Medos-master/medos_txt/. Two load-time consequences, both handled: the multipass FileSystem needs its own synth (same module name, fuller shape — sessions are format-pure: .OBJ programs keep the 10-proc synth, .o programs get the full-record one), and Terminal.d copies stay normalized to trap order (WriteString before WriteLn; src/ has them swapped). Still open for 2b: SetModify/Length/Reset/Again are declared but unused — map them to abort stubs; confirm GetPos/SetPos units stay self-consistent within a toolchain run (positions are never shared across formats).

Phase 3 triage: real MEDOS-2 modules (2026-10-16)

First five ETHZ_Medos-master/medos_txt/ modules compile with host mcc and load/run under mcode, all unchanged except mechanical renames (11-char .sy limit): Conversions (functional check ConvertCardinal(421,5) → 3232525049, ConvertHex(255,4) → 48487070), MCMnemonics, MCSymFileDefs (as MCSymDefs — it is the compiler's own mcsymf, C19 vs C18 version comment only), UpdateStringIO (as UpdStringIO; functional round-trip → 656667), WriteStrings (as WriteString; WriteLn emits the Medos 36C EOL byte, kept verbatim — the compiler tree's copy uses 12C instead). Conversions.MOD/.DEF are byte-identical to the compiler tree's (except nothing); the Medos tree generally holds newer versions of shared modules. Next candidates: CardinalIO (Terminal only), FilePool/InOut (FileSystem+Storage, both synthesized), Memory. Avoid first: the 4 corrupt String/Printer entries (disk dumps, not source) and the 52 CODE modules.

Later status (19 Oct). triage-medos.sh now covers the library and system/kernel layers with no compiler change: 37/37 definitions and 33/33 implementations, adding Program (execution + heap), System, FilePool, FileLookup, FileNames, Options, MathLib0, RealInOut, OutWindow, WindowHandler, MCLookup, the Linker (file link.MOD) and CompFile. The two fixes were in the closure parser: it now reads the old-style IMPORT a, b, c; list (MEDOS uses it — WindowHandler imports ScreenDriver2 that way) and traverses a target's own .DEF as well as its .MOD.

triage-medos.sh --all targets the whole tree: 119/120 reachable definitions and 144/178 implementations compile. Staging keys on the module name, not the file name (link.MOD = MODULE Linker, MIA.TextIO.DEF = MODULE TextIO), which is what made the MIA.*/MD*/ ED.* family resolve. The remaining 32 implementation failures all cascade from one malformed source (CursorStuff.DEF has a stray *) at line 25); MCP2Main/MCP3Main target an older MCP2IO/MCP3IO interface. No port bug surfaced.