# Modula-2 VM/Interpreter — session summary Goal: reproduce, in GNU Modula-2 (`gm2 -fiso`) running on real hardware, the classic Turbo Modula-2 Z80 interpreter as a **64-bit MC64** machine, driven by `mc64-spec.md`. Constraints honored so far: no analysis/disassembly of `.MCD` binaries — work from `.mod`/`.def` sources and the spec; module library compiled separately with `-c`. Companion effort: `m2c/` — a single-pass Modula-2 compiler (Coco/R `M2c.atg` + hand-written `SymTab`, `MGen`) targeting MC64 `.MC4` images runnable under `mcint`. Currently at **step 11** (separate compilation units), 157/157 tests green (see `summary_step1.md` … `summary_step11.md`). ## Project layout - `M2compiler/mc64/` — all VM code: `Memory, Local, Stack, Console, FileIO, Global, Instruction, Extended, Interpreter, Loader2` (.def + .mod); programs `MC64.mod` (REPL harness), `mkdemo.mod` (`boot.MC4`), `mcint.mod` (`mcint file.MC4` launcher), `mkdtest.mod` (`example.MC4` generator); built: `mkdemo`, `mc64`, `mcint`, `mkdtest`, `boot.MC4`, `example.MC4`. - `M2compiler/m2c/` — the compiler: `M2c.atg` (grammar + attributes), `compiler.frm` (**true source** of generated `M2c.mod`: driver loop + `Msg` table via `-->Errors`), `SymTab` (checking), `MGen` (MC64 emitter), `M2cS`/`M2cP` (CR-generated scanner/parser — never edit), `M2c` (binary), `run_tests.sh`, `tests/` (`.mod` + `.def` + `.LST`), per-test `.MC4` in `m2c/` root, `Showcase*.mod` tours. - `M2compiler/mc64-spec.md` — normative spec (§5.2 frames, §7.1 descriptor layout, §8.3 checksum rule, §9.3 SYSTEM ABI, §10 0x12/0x40 sub-dispatches, §11 opcode table, §12 switch/strings, §16 GNU-M2 ISO implementation notes). - `M2compiler/m-code-summary-64.md`, `m-code-summary.md` — opcode summaries. - `M2compiler/summary_stepN.md` — per-compiler-step log (commit + tag per step). - `M2compiler/Resources/Turbo-reloaded/Reversing-Turbo-Modula2-main/MCode_specification/` — reference Turbo M2 sources (used only as Modula-2 sources, never binary analysis). - gm2 ISO libs at `.../gcc/x86_64-pc-linux-gnu/16.0.1/m2/m2iso/`; `FIO` at `m2/m2pim/`. ## Build recipes Compiler (`m2c/`, via `./build.sh`, needs `CR` at `../../CocoGm2/CR`): regenerates `M2cS`/`M2cP`/`M2c.mod` from `M2c.atg` (+ `Msg` from `M2c.err` via `compiler.frm`), recompiles, relinks `./M2c`. NEVER edit `M2c.mod` (directly: it is overwritten), `M2cS.*`, `M2cP.*` — edit `M2c.atg`, `compiler.frm`, `SymTab.*`, `MGen.*` instead. Single-file: `./M2c tests/foo.mod` (writes `tests/foo.LST`, `FOO.MC4`). Multi-file session (step 11): `./M2c lib.def impl.mod prog.mod` (definitions, then implementations, then exactly one program; one `.MC4`, one `.LST` per input, fail-fast). Test: `bash run_tests.sh` (needs `../mc64/mcint`). Libraries are pre-compiled and linked as `.o` (only the program `.mod` is passed to the link step — multiple `.mod` files would collide on `main`): ``` for m in Memory Local Global Stack Instruction Extended Interpreter Loader2 Console FileIO; do gm2 -fiso -c $m.mod; done gm2 -fiso -o mkdemo mkdemo.mod FileIO.o Console.o gm2 -fiso -o mc64 MC64.mod Stack.o Memory.o Console.o ... # library set only gm2 -fiso -o mcint mcint.mod Loader2.o Interpreter.o Extended.o Instruction.o \ Stack.o Global.o Local.o Memory.o Console.o FileIO.o gm2 -fiso -o mkdtest mkdtest.mod FileIO.o Console.o ``` ## Verification status (all green) - `./mkdemo` -> `boot.MC4` ; `./mc64` -> `Hello MC64!` + `[vm end]` (0) ; `./mcint boot.MC4` -> `Hello MC64!` (0) ; `./mcint` -> usage (0) ; `./mcint nope.MC4` -> Fatal (1). - `m2c`: 157/157 (`bash run_tests.sh`): steps 1–10 incl. step-10.1 init/proc-slot fix (regression `b_mix` -> 110, Showcase3 -> 168); step 11 adds 7 multi-file run tests (`d_basic` 25, `d_from` 12, `d_multi` 82, `d_init` 510, `d_func` 55, `d_type` 1122, `d_case` 25) + 6 single-error rejections (201/231/200). Tags `step1` … `step11` (+ `step2.1`, `step10.1` fixes) all point at their commits. - Showcases (hand-computed tours): 157, 83, 168, 225 — all print their value under `mcint`. - `./mkdtest` -> `example.MC4` (1350 B, MC64 format) ; `./mcint example.MC4` — 24/24 opcode-checks pass (exit 0): loop g:=2g+1 x10 = 2047; add/sub/mul/div/mod; bit_and/or/xor & power2; extended 0x40: drop, uc_add, uc_mul, uc_div, uc_mod, long_negate, build_field_mask; load_imm_word; real_add 3.5+2.25 -> 5 (real_to_long); dup/swap; copy_block -> "abc"; global/local_dw load/store; load_global_dw; forward jp_fwd; jpfalse_back loops; proc_call into decimal `print_num` (enter/fct_leave, load_param, reserve, udiv/umod, store_indexed_byte). - `./mkread` -> `readtest.MC4` (453 B) ; `./mcint readtest.MC4` — SYSTEM service 2 reads stdin lines: `printf 'first line\nsecond line\n' | ./mcint readtest.MC4` echoes `you said: first line` then `again: second line` (0). Edge cases verified: EOF without trailing newline still echoes; empty line -> count 0; immediate EOF -> 0; two consecutive reads prove the line mark is consumed (Skip) so the next read starts fresh. - `./mcint boot.MC4` -> `Hello MC64!` (0) ; `./mcint example.MC4` — 24/24 (0). - Public-dollar checks: `::` VM accepts THE-SPEC–conformant images only. ## Key technical facts learned - `AND`/`OR` are BOOLEAN-only under `-fiso`; integer bitwise ops run through `BitOp(a,b,mode)` (64 iterations of DIV/MOD) in `Interpreter.mod`. Masks use `MOD`/`DIV` (e.g. `opc MOD 16`, `(fl DIV 4) MOD 2`), never integer `AND`. - A stray integer `AND` in a program printed a misleading “is not a boolean expression” diagnostic pointing at a VAR declaration. - Standalone `Pop();` (ignored return value) is a compile error. - Variant records need BOOLEAN tags (`CASE : BOOLEAN OF | TRUE: | FALSE:`); `FROM m IMPORT x AS y` aliases not supported; set constructors inline can ICE gm2 (assign to a VAR first); `HALT` works. - `argv` works via `TextIO.ReadString(ArgChan(), s)` after `NextArg` (one arg per call, program name excluded; a trailing empty argument means “no argument”). - `FIO` (m2pim) links fine under `-fiso`: `OpenToRead`, `ReadNBytes(f,n,ADR(buf))`, `IsNoError`, `Close`. `Loader2.mod` reads images with it. - Linking: only the program `.mod` goes to the link step; libraries are `.o` files. - On-disk image: file bytes 0..63 header, magic `"MC64"`; image starts at file 64; descriptor image offsets: DescDeps=0, DescLink=256, DescName=264, DescLoadAddr=280, DescChecksum=288 (u32), DescFlags=292, DescVarCount=293, DescDepCount=294, DescPad=295, DescProcs=296, DescVarSizes=304. - Checksum = 32-bit sum of file bytes 64..(size-1) excluding file 352..355 (the checksum field itself). - `LoadImage(VAR data; off; dst; n)` gained an offset (`off=64`) since images start at file offset 64; 32-bit fields read with `ReadLong`, not 8-byte `ReadWord`/`ReadSlot`. - Loader: image copied to `ArenaBase`=1048576; `dataWin = desc+imgLen` (8-aligned); `GP = dataWin`; `procAddr(k) = procsAddr + k*8 + cell[k]` (cell relative to its slot; negative when the target precedes the table); TOINIT = `flags` bit 2 ⇒ `SetIP(ProcedureAddress(0,0)); Run()`; `Run` returns on opcode 0x50. - Stack machine stack grows downward; `Push` = `SetSP(SP-8)` then write slot; params land at callee `FP[3..7]` (param 1 = the value on top at the call). - `store_indexed_byte` (0x1D) pops `(value, index, ptr)` — push `(addr, 0, val)`; `copy_block` (0x30) pops `(size, src, dst)` — push `dst, src, size`; `build_field_mask` pops high/low — push low first, then high. - Proc table must not overlap code (put it after the code). - SYSTEM service 2 (read line) implemented via ISO `StdChans.StdInChan` + `IOChan.Look/Skip` char loop (avoids `TextRead`'s "current line" blocking); stores chars up to (not incl.) line mark or EOF, NUL-terminates, pushes byte count. Documented in spec §9.3; readtest.MC4 exercises it. - m2c gotchas: `M2c.mod`/`M2cS`/`M2cP` are REGENERATED by CR every build — edit `M2c.atg`, `compiler.frm`, `SymTab`, `MGen` only. Coco/R numbers tokens by first occurrence: new keywords go in end-of-file productions and `M2c.mod`'s `Msg` re-mirrors `M2c.err` automatically (verify names after grammar edits). gm2 `HALT` aborts (SIGABRT) — use loop `EXIT` for clean failures. Proc/init table slots share one pool (`SymTab.AllocInitNum`; step-10.1 fixed init stealing a later proc's slot). Never compare against a negative literal inside an `&` chain in `.atg` actions (gm2 16.0.1 ICE in generated `M2cP.mod`; nest IFs). ## Current task state - m2c step 11 — separate compilation units: **DONE** (`d3856a1`, tag `step11`) — `DEFINITION`/`IMPLEMENTATION`/`IMPORT` across files into one `.MC4` (157/157 tests, Showcase4 -> 225). - Writing an **MC4 example file** to exercise the interpreter: **DONE** — `mkdtest.mod` -> `example.MC4`, 24 opcode-checks pass under `./mcint`. - **stdin support for SYSTEM service 2**: **DONE** — implemented in `Interpreter.mod` (`ReadLine`), `mc64-spec.md` §9.3 updated, `mkread.mod` -> `readtest.MC4` proves line reads + line-mark consumption under piped input. ## Next steps / open ideas - m2c hardening (deferred): `M.x` as `VAR` actual (still 233), `WITH`-field/`CHAR` tails as `VAR` actuals, open-to-open, multi-`.MC4` images with `depCount` loading, circular imports, opaque types, `LONGINT`/`LONGREAL` quads, procedure types, `cones`. - 16→64 translation pass so classic 16-bit Z80 `.MCD` binaries can run on `mcint` (widening MCode operands 2→8 bytes and descriptor fields); currently unimplemented. - Hardware traps 0x45 (button/joystick/audio — currently no-ops), `0x08` output with cursor positioning, more `.mod` demos (blocks/frames/0x40-calls), cross-check vs the Turbo-reloaded reference interpreter.