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
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).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)..../gcc/x86_64-pc-linux-gnu/16.0.1/m2/m2iso/; FIO at m2/m2pim/.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 <Prog>.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
./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)
step1 … step11
(+ step2.1, step10.1 fixes) all point at their commits.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).:: VM accepts THE-SPEC–conformant images only.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.AND in a program printed a misleading “is not a boolean expression”
diagnostic pointing at a VAR declaration.Pop(); (ignored return value) is a compile error.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..mod goes to the link step; libraries are .o files."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.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.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.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.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.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).d3856a1,
tag step11) — DEFINITION/IMPLEMENTATION/IMPORT across files
into one .MC4 (157/157 tests, Showcase4 -> 225).mkdtest.mod -> example.MC4, 24 opcode-checks pass under ./mcint.Interpreter.mod
(ReadLine), mc64-spec.md §9.3 updated, mkread.mod -> readtest.MC4 proves
line reads + line-mark consumption under piped input.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..MCD binaries can run on mcint
(widening MCode operands 2→8 bytes and descriptor fields); currently unimplemented.0x08 output with
cursor positioning, more .mod demos (blocks/frames/0x40-calls), cross-check vs the
Turbo-reloaded reference interpreter.