No description
  • Modula-2 70%
  • Macaulay2 11.8%
  • Shell 7.4%
  • DTrace 5.7%
  • AMPL 4.8%
  • Other 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Eric Streit 6e8045cc87 doc: running the MEDOS-2 display on a ReGIS terminal (analysis)
Records what was verified and what work it implies:

- xterm 411 in /usr/local/bin has ReGIS, but graphics decoding is off by
  default (decGraphicsID = 0); `-xrm 'XTerm*decGraphicsID: 340'` plus
  `regisScreenSize: 768x592` makes the Lilith geometry fit (screenshot
  included).
- The MEDOS display is a 768x592 1-bit framebuffer with hardware REPL
  (343B), BBLT (344B), DCH (345B) and GET/PUT (240B/241B); mcode aborts
  on the first three, so the (compilable) display stack cannot run.  A
  host port of DisplayDriver alone is insufficient: Screen and
  ScreenDriver2 issue the opcodes directly.
- Plan P1..P5: emulate the device in mcode + PPM dumps (headless tests),
  a ReGIS presenter (dirty rects, run rasterisation), the text shortcut
  (DisplayDriver.Write/TerminalBase), a vector fast path, then input and
  windows.  Limits: no bitblt/bitmap upload in ReGIS, no font download,
  terminal-speed animation.
- Recommended first step: P1.

Uses ../VT100/Regis.def for the presenter primitives.
2026-10-19 13:06:12 +02:00
examples docs: the single-pass coroutines round is released, not a branch 2026-10-19 12:43:38 +02:00
lib git update 2026-10-16 18:04:17 +02:00
mplib examples/showcase: the same program through m3pl and mcc 2026-10-19 12:09:06 +02:00
src mcode + tests: single-pass SYSTEM coroutines 2026-10-19 11:52:26 +02:00
tests mcode + tests: single-pass SYSTEM coroutines 2026-10-19 11:52:26 +02:00
.gitignore Track the mcode binary 2026-10-17 13:19:08 +02:00
BUILD.md docs: the single-pass coroutines round is released, not a branch 2026-10-19 12:43:38 +02:00
git.txt git update 2026-10-16 18:04:17 +02:00
install.sh install.sh + medos driver 2026-10-18 15:28:20 +02:00
Makefile examples/showcase: the same program through m3pl and mcc 2026-10-19 12:09:06 +02:00
Mcode-Phase3v2-Summary.md MEDOS-2: fix self-compile crash -- module-selector 0 collision 2026-10-16 21:18:00 +02:00
Mcode-Phase3v4-Summary.md mcode: move the mbase/truncate invariant into MTrunc (file-layer audit) 2026-10-17 11:29:08 +02:00
medos install.sh + medos driver 2026-10-18 15:28:20 +02:00
MEDOS-Display-ReGIS-xterm.png doc: running the MEDOS-2 display on a ReGIS terminal (analysis) 2026-10-19 13:06:12 +02:00
MEDOS-Display-ReGIS.md doc: running the MEDOS-2 display on a ReGIS terminal (analysis) 2026-10-19 13:06:12 +02:00
MEDOS2-SelfHosting-Summary.md docs: decode reads both object layouts 2026-10-17 13:28:59 +02:00
Open-Work.md docs: the single-pass coroutines round is released, not a branch 2026-10-19 12:43:38 +02:00
Phase3-Overlay-Summary.md MEDOS-2: fix self-compile crash -- module-selector 0 collision 2026-10-16 21:18:00 +02:00
README.md docs: the single-pass coroutines round is released, not a branch 2026-10-19 12:43:38 +02:00
REAL-Support-Summary.md REAL support & toolchain: summary & release note 2026-10-17 15:14:26 +02:00
ROADMAP.md docs: the single-pass coroutines round is released, not a branch 2026-10-19 12:43:38 +02:00
Self-Hosting-Artifacts.md Self-Hosting-Artifacts: single-pass fixpoint is 6/6; multipass mcc self-compile done 2026-10-18 11:19:42 +02:00
SelfHost-Diagnostic-and-Next-Steps.md mcode: move the mbase/truncate invariant into MTrunc (file-layer audit) 2026-10-17 11:29:08 +02:00
Toolchain-Summary.md mcode: SYSTEM coroutines (NEWPROCESS / TRANSFER) 2026-10-19 10:59:40 +02:00
TUTORIAL-LANGUAGE.md docs: the single-pass coroutines round is released, not a branch 2026-10-19 12:43:38 +02:00
TUTORIAL.md docs: the single-pass coroutines round is released, not a branch 2026-10-19 12:43:38 +02:00

Lilith M-code interpreter (GNU Modula-2)

Runs M-code objects under Linux: .OBJ from the single-pass compiler port (../SinglePassCompiler/gm2-port, program m3pl) and .o from the multipass compiler port (../lilith-multipass-modula2-compiler-master/gm2-port, program mcc) — no Lilith hardware, no Medos disk image, no linker needed.

New here? Start with TUTORIAL-LANGUAGE.md — it walks through building both tools, Hello world, HostIO output, multi-module builds, tracing, and the test suite. For install-from-scratch instructions for all three binaries (m3pl, mcc, mcode), see BUILD.md. For the whole chain end to end — both gm2 ports and both self-hosted compilers — see TUTORIAL.md. Prioritised follow-up work is in ROADMAP.md.

Build and test

make            # builds src/mcode (gm2 -fpim, no extensions needed)
make test       # compiles the tests with m3pl, runs them, diffs output
make test-multi # compiles tests/mp/*.m2 with mcc, runs the .o files
make bootstrap  # builds all three tools and runs every self-hosting gate
make showcase   # the same program through both compilers, outputs diffed
./install.sh    # builds all three tools, installs them + `medos` (~/.local/bin)

make test needs the sibling SinglePassCompiler checkout in place (paths: ../src/mcode, ../../SinglePassCompiler/gm2-port/m3pl).

make showcase compiles examples/showcase/ with both compilers and checks that the two runs are byte-identical (examples/showcase/README.md lists everything it exercises).

./medos is a small dispatcher for the whole chain, usable installed or straight from this checkout: medos m3pl HELLO, medos mcc T.m2 SC.m2, medos run DK.HELLO.OBJ, medos decode …, medos version.

Use

$ ./src/mcode
Lilith M-code interpreter (gm2 port)
OBJ> DK.T1.OBJ
14
...
end.
OBJ> <ESC>

OBJ> prompt, ESC/empty line exits (piped input works). Filename suffixes: /T traces each opcode (PC in octal + mnemonic + eval depth/top), /D dumps main-module globals at end. --version/--help print the banner/usage and exit.

Machine model

16-bit words, 64K-word data memory, per-module byte-coded code, 4-word frames (static, dynamic, retPC, pad; locals at L+4), 16-deep eval stack. Capacity per module: 16KB code, 128 procedures, 1024 relocations, 31 imports (64 modules total) — sized to cover what the compilers emit (CodeLength 12000, RelTabLength 1000, ProcTabLength/StackDepth). Module data word 0 holds the runtime module index. Imports load recursively with name+key checks; each module body runs once as its own init guard (LGA/TS) requires. Normal end (RTN to 0) exits 0; anything else (TRAP, checks, bounds) prints a message and aborts nonzero.

Opcode semantics follow lilith-emulator-c/l_exec.c (1983, after Wirth ETH Report 40), cross-checked against M3GL.mod code generation and the decode.mod mnemonic table (copied for /T).

Container format (OUR OBJs only!)

m3pl's gm2 port writes W fields as 4-byte little-endian CARDINALs and characters as single bytes (original Lilith files use 2-byte words and are NOT accepted). Layout per module: magic 200B,1,4, header 201B,14 + 16-byte name + 3-word key + datasize/codesize, optional imports 202B (7 words/entry), data 204B blocks (words, then at most one raw-byte string block), entries 203B, code 203B (bytes), relocation 205B (byte offsets into the image, patched to runtime module numbers at load like mcl does).

Multipass .o files (imports included)

The multipass compiler (../lilith-multipass-modula2-compiler-master/gm2-port, program mcc) writes one .o per module: flat big-endian 16-bit words as { type, size, payload } frames — 0200B VERSION (must be 3), 0201B MODULE (16 name bytes hi-first + 3 key words + datasize), 0202B IMPORT (11 words/entry ordered by modnum, self at its own position), 0204B DATATEXT (word offset + data), 0203B CODETEXT (fs=2: procnum → byte offset; longer: body chunk at word offset, bytes hi-first), 0205B FIXUP (byte offsets patched at load by the shared PatchRelocs). The opcode set is the same M-code, so the existing dispatch runs it unchanged; data[1] is the init flag, data[2] the string pointer. At the OBJ> prompt, names ending in .o take this loader (imports resolve as <name>.o / DK.<name>.o); everything else takes the single-pass loader. .r files are debugger info, never loaded. The loader also synthesizes the HostIO/Terminal/Math/Files trap modules for .o programs, so multipass code can do I/O, math and files: compile the mplib/*.d definitions with mcc once per work directory for their .sy files. make test-multi compiles tests/mp/ (.d first for the .sy, then .m2) with mcc and diffs the runs: IO (all four synth modules), MS (Strings), MI/MR/MT (InOut/Random/Time) and the Stats data-file analyzer.

HostIO / Terminal / Math / Files — running programs with output

Medos FileSystem doesn't exist here. Instead the loader synthesizes four modules (no .OBJ needed, trusted without key check) — definitions live in lib/; compile them with m3pl once per work directory for type-checking:

DEFINITION MODULE HostIO;
PROCEDURE WriteCard(c: CARDINAL);
PROCEDURE WriteInt(i: INTEGER);
PROCEDURE WriteLn;
END HostIO.

Compile it with m3pl for type-checking test programs; bodies are 3-byte LIB n / TRAP stubs (traps 20/21/22) handled by the interpreter with resume. Terminal (Medos interface v4, lib/DK.Terminal.MOD) works the same way via traps 30..35 (Read, BusyRead, ReadAgain, Write, WriteString, WriteLn). Math (lib/DK.Math.MOD) provides Sqrt, Exp, Ln, Sin, Cos, Tan, ArcTan, Entier, Log10, Power via traps 40..49 (host libm; domain errors abort). Files (lib/DK.Files.MOD) offers sequential host text files via traps 50..60 (OpenRead/OpenWrite/ OpenAppend → slot handle or INVALID, Close, Eof, ReadChar, WriteChar, WriteString, WriteLn, ReadLine, Remove). TRAP n otherwise aborts (TRAP 10, i.e. HALT, too). Note the dialect facts this exposed: TRUNC returns CARDINAL (so TRUNC of a negative correctly traps via the compiler's CHKS), and unary minus / operators on Double constants are rejected at compile time (original GenNeg, error 112) — write them with variables.

Real format decision: IEEE-754 single

M2Emul's le_mcode.c scales floats ×4/×0.25 claiming non-IEEE Lilith format; lilith-emulator-c/l_exec.c (contemporary, 1983, used in real bootstrap work) uses plain C floats, and Wirth's code generator emits FLOAT/TRUNC/arithmetic with no scaling anywhere. This port implements IEEE single on both sides (compiler M3GL.PutReal, interpreter F32ToR/RToF32; runtime arithmetic stays gm2 double — last-ulp differences only). If real-Lilith evidence ever lands for bias-129, flip exactly these two spots (×4 in, ×0.25 out).

Porting traps found (useful for the multipass interpreter)

Writing the dispatch in Modula-2 after C references kept biting:

  1. PC += next()-1 re-reads PC after the call in C; Modula-2 PC := PC + Cb() - 1 uses the stale value. Sequence every PC-relative branch explicitly (t := Cb(); PC := PC + t - 1).
  2. C switch inside else if (IR < 0240) silently skips 0220B–0237B (no matching case, chain done); the same tier split in ELSIF needs merged CASE labels instead.
  3. gm2 silently ignores unmatched CASE (observed!) — add explicit Die labels for gaps (0214B/0215B); never rely on the trap.
  4. FOR i := 0 TO n-1 with CARDINAL n = 0 underflows to 4 billion — use WHILE for possibly-empty ranges (reloc patching died on this).
  5. Mixed-sign expressions (CARDINAL vs negative literals, INTEGER loop counters with DEC) — convert explicitly (VAL, guarded IFs); the e: CARDINAL typo alone broke every float op.
  6. Uninitialized module-record fields (nrel, synth, keys) — the language doesn't zero them; init on both load paths.

Tests (tests/ sources, lib/ libraries, examples/ demos, all run by run.sh)

test covers expected
HELLO load/init/exit, globals dump end.
T1 precedence, wrap, FOR, signed, WHILE, IF 14 0 55 -7 0 1
BAR+FOO imports, cross-module calls, module init 9 16
PV+PVM procedure variables: assign, call, pass as param, reassign (same-module + imported) 107 105 16 27 8 8
MSK opcode 326B via CODE procedure: mask {0..n-1} 0 1 15 32767
MODT MOD/DIV by powers of two (mask codegen; 577 MOD 256 was 512) 65 255 2 345 15
T3 reals, sets, recursion, nested procs, LONGINT, CASE 3 2 1 0 720 42 1 20
T4 long arithmetic, TRUNC division 1 1 1 25 0
TNEG negative test: TRUNC(-2.5) must abort via CHKS abort
MNEG negative test: Sqrt(-1.0) must abort via math domain abort
FBAD negative test: Close(7) must abort via bad file handle abort
CORN NEWPROCESS builds a context but runs nothing until a TRANSFER 111 222
COR SYSTEM coroutines: NEWPROCESS + TRANSFER back and forth 1 2 3 4 5 6
CORT negative test (multi only): TRANSFER to a coroutine never created must abort abort
TCHK negative test (multi only): negative INTEGER→CARDINAL must abort via CHKS abort
TNEG negative test (multi only): WriteInt(TRUNC(-2.5)) must abort via CHKS abort
SA Storage synth module (alloc order + no-op deallocate) 1 1
SC named string constant declare + use (assign, read back) 104 105 33
SE sets, multi only (constructors bit order, IN T/F, =/<>, +/-) 11 14 1 0 0 1
TS Strings lib + Terminal output (length/compare/copy/concat/numtostr/pos/sub/append/case/trim) 5 0 -1 1 5 hello!! 12345 -678 0 2 65535 ell hello!! MIXED mixed pad|
TM Math lib (sqrt/exp/ln/sin/cos/tan/arctan/entier/pow/log10, scaled) 141 271 230 479 877 546 7853 2 -2 1024 27 3 25 3010 1
TF Files lib (write/read round-trip, missing file, EOF, append, remove) hello abc Z 1 0 65535 1 one two 1 0
Echo Terminal stdin→stdout, ends at host EOF echoes input
Caesar Terminal+Strings Caesar+3 over one line caesar+3, one line: Khoor, Zruog! len=13
Tour all five libs over one line (upper/len/pos/stats/file round-trip) up=HELLO, WORLD! len=13 pos-o=4 root100=360 log1000=3113 pow=1024 file:… removed=1
TI InOut reads (card/int/bad input + Done) 42 1 -7 0
TR Random with fixed seed 22 87 0
TT Time monotonicity 1 1
TRI RealIO fixed output + parse (Done) 3.14 1 -2.5 100 0.000 0
HFS Heap/Clock/FileSystem synth modules (alloc order, clock ranges, word round-trip via SetOpen, delete/missing) 1 1 1 1 1 0 12 20741 0 65535 0 1

lib/ holds the shared libraries (see lib/README.md): HostIO, Terminal, Math, Files, Time (all synthesized at load, compile the definitions only for checking) plus Heap, Clock, FileSystem — the three synthesized host modules needed to run a self-compiled m3pl (see ../SinglePassCompiler/selfhost/) — plus the pure-M-code Strings library (definition Strings + implementation StringsImp). examples/ holds the Echo / Caesar demos and the two-dialect showcase/ tour (see examples/README.md and examples/showcase/README.md). run.sh copies tests/ + lib/ + examples/ DK.*.MOD into a temp dir, so keep reusable modules in lib/ and demo programs in examples/; showcase/ is run separately by make showcase.

Roadmap

  • Multipass .o loader: imports + FIXUP linking done (make test-multi). Procedure variables work in both worlds (PV test each side: the single-pass loader patches LIW descriptors like LEA/CX module bytes; indirect calls run through the existing CF dispatch). SYSTEM coroutines are implemented on the multipass side: NEWPROCESS (CX 0 2) builds the MEDOS-2 ProcessDescriptor in the workspace and TRANSFER (TRA) saves the running context and switches (COR test: two routines interleave 1 2 3 4 5 6; TRANSFER to a nil coroutine aborts — CORT). The single-pass compiler's SYSTEM provides them too: standard procedures 21/22 emit CLX 0 2 / TRA (PROCESS is a SYSTEM type; coroutine handles are word-sized), with the DK.COR test in tests/ and the two-dialect examples/showcase (identical output from m3pl and mcc, coroutines and all the libraries).
  • Self-hosting loop closed: the original compiler sources compile under m3pl itself (../SinglePassCompiler/selfhost/build-self.sh, all six DK.M*.OBJ), run under mcode via synthesized Heap/Clock/ FileSystem (traps 70–81, HFS test), and compile test programs to byte-identical OBJs modulo timestamp keys. test-self.sh replays the loop as a regression test (8 positive + 2 negative diffs), and test-fixpoint.sh now reports 6/6 — every object, including the gen 1→2 step, is bit-identical modulo keys. The two one-time host differences (the LIW-vs-LIN encoding from 32- vs 16-bit CARDINAL width, and the MAX(REAL) low byte from double- vs single-path parsing) are fixed in the port itself.
  • The multipass twin is closed the same way: ../lilith-multipass-modula2-compiler-master/selfhost/build-phase4.sh rebuilds every src/ module with the self-hosted C18 and gets byte-identical objects to the host mcc (0 deltas), and build-phase5.sh has mcc compile its own gm2-port sources (37/0).