# MC64 — a 64-bit Modula-2 virtual machine MC64 is a 64-bit generalization of the **MCode** virtual machine that Borland Turbo Modula-2 for CP/M executed on the Z80. The machine *word* — the unit of the stack, frame cells and global cells — is widened from 16 to **64 bits**, the address space becomes flat and byte-addressable, and the 256-entry opcode map is preserved 1:1: only operand widths and the scalar type mapping differ. The normative reference is [`docs/mc64-spec.md`](docs/mc64-spec.md), §14 of which contains the row-by-row MCode/16 → MC64 migration table. The machine loads **`.MC4` image files** (a 64-byte `"MC64"` header + a module descriptor + code + a procedure table) and boots them with the `mcint` launcher. It is the execution target of the experimental Modula-2 compilers in `MyWork/` — V1 (`M2c`), V2 (`M2comp`) and V3 — whose code generators emit `.MC4` images that this interpreter runs. As of this writing the loader is at the **dependency-chain milestone**: a guest module can name up to 32 dependencies and call across module boundaries through the 256-slot module table (`MTBL`). ## Quick start The repository ships prebuilt executables and ready-made sample images, so the first run takes one command: ```sh ./mkdemo # writes boot.MC4 in the current directory ./mcint boot.MC4 # prints "Hello MC64!" and exits 0 ``` ## The machine model A stack machine with a single stack shared by *values* and *activation frames*, growing **down** (`Push` = `SP -= 8`). | Register | Meaning | |---|---| | `IP` | instruction pointer — byte address of the next opcode | | `SP` | stack pointer — byte address of the *top* word | | `FP` | frame pointer — base of the current activation frame | | `GP` | global pointer — base of the current module's data window | | `OFP` | outer frame pointer — static chain / display link | | `MTBL` | module table — `MTBL[i]` = data base of loaded module `i` | ### Data types | Modula-2 type | Width | Stack slots | Notes | |---|---|---|---| | BYTE, CHAR, BOOLEAN | 8 bits | 1 | held in the low bits of a slot | | CARDINAL | 32 bits | 1 | unsigned, zero-extended | | INTEGER | 32 bits | 1 | two's complement, sign-extended | | SET | 64 bits | 1 | elements 0..63 | | LONGINT / LONGCARD | 64 bits | 1 | signed / unsigned 64-bit arithmetic | | REAL | 64 bits | 1 | IEEE 754 binary64 (double) | | LONGREAL | 128 bits | 2 | IEEE 754 binary128 (quad) | | pointer / address | 64 bits | 1 | byte address | Stack slots are always 64 bits; 32-bit values live in the *low* 32 bits (CARDINAL zero-extended, INTEGER sign-extended), so 32↔64-bit widening is a value no-op. Memory is little-endian everywhere. Locals live at `FP[-1]`, `FP[-2]`, …; parameter *k* at `FP[k+2]`; arguments are pushed in reverse declaration order. `ENTER k` reserves `(255−k)×8` bytes of locals (`k=0FFH` → none). `EXTERN_CALL mod,proc` switches `GP` to `MTBL[mod]` and jumps through the callee module's procedure table. ## Images (`.MC4`) A module file is a 64-byte header followed by the *image*: the module descriptor, its code, a procedure table and (optionally) a dependency-name table. - **File header** — magic `"MC64"` at offset 0; the descriptor itself sits at **image offset 0** (i.e. immediately after the 64-byte header) in the generator images. - **Descriptor** (image offsets, little-endian): | offset | field | |---|---| | 0 | `dependencies` — 256 bytes of module-base pointers | | 256 | `link` — next module in chain | | 264 | `name` — 16 chars, NUL-padded | | 280 | `loadAddr` | | 288 | `checksum` — u32 | | 292 | `flags` (bit 2 = TOINIT) | | 293 | `varCount` | | 294 | `depCount` | | 295 | `pad` | | 296 | `procsAddr` — location of the procedure table | | 304 | `varSizes` — `varCount` × u64 byte sizes | - **Procedure table** — `N` contiguous 64-bit relative byte offsets: `procedureAddress(k) = procsAddr + k*8 + table[k]`. - **Checksum** — plain 32-bit sum of the image bytes (file offsets 64 up to end of file), **excluding** the four checksum bytes themselves (file 352..355 = image 288..291). - **Dependencies** — `depCount` names appended at the image tail (8 NUL-padded bytes each); the loader loads dependency modules into low `MTBL` slots first (extern module 0 = the first import), then the main module. The loader places the image at `ArenaBase` (1 MiB) and allocates the module's globals (`varSizes` bytes per entry, zero-filled) in an 8-byte-aligned data window right after the image. Historically these files were called `.MCD`; the 64-bit format was renamed to `.MC4` per spec sections 8 and 14. ## The toolchain | Tool | Purpose | Sample | |---|---|---| | `mcint` | interpreter launcher — boots a single-module image or the main module of a dependency chain | `./mcint boot.MC4` | | `mc64` | demo wrapper — boots `boot.MC4` then prints `[vm end]` | `./mc64` | | `mkdemo` | writes `boot.MC4` ("Hello MC64!" via the SYSTEM write-string service) | `./mkdemo` | | `mkdtest` | writes `example.MC4`, a 24-instruction opcode/composite exercise | `./mkdtest && ./mcint example.MC4` | | `mkread` | writes `readtest.MC4`, which reads two stdin lines and echoes them back | `printf 'a\nb\n' \| ./mcint readtest.MC4` | | `mkdep` | writes `STAK.MC4` + `DEP.MC4`, a two-module cross-module dependency-call demo | `./mkdep && ./mcint DEP.MC4` | | `trans8to64` | translates a classic 16-bit Turbo Modula-2 `.MCD` binary into a 64-bit `.MC4` image | `./trans8to64 in.MCD out.MC4` | `mcint` takes the image path as its first program argument; exit status is 0 when the guest reaches `end_program` (`SYSTEM` service 0), 1 when the loader or a guest trap aborts. With no argument it prints its usage line. ## Building from source Everything is ISO Modula-2, compiled with GNU Modula-2: ```sh # in a build directory containing the sources (cp src/*.mod src/*.def .) for m in Memory Local Global Stack Instruction Extended Interpreter Loader2 \ Console FileIO; do gm2 -fiso -c "$m.mod" done 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 mc64 MC64.mod \ Stack.o Memory.o Console.o Global.o Loader2.o Interpreter.o Extended.o \ Instruction.o Local.o FileIO.o gm2 -fiso -o mkdemo mkdemo.mod FileIO.o Console.o gm2 -fiso -o mkdtest mkdtest.mod FileIO.o Console.o gm2 -fiso -o mkread mkread.mod FileIO.o Console.o gm2 -fiso -o mkdep mkdep.mod FileIO.o Console.o gm2 -fiso -o trans8to64 trans8to64.mod FileIO.o Console.o ``` One build rule matters (see spec §16.1): only the **program module** is passed to the link step; library modules are given as their precompiled `.o`. Passing several `.mod` files at once makes gm2 emit one `main` per module and the link fails with duplicate-`main` errors — and linking all `*.o` indiscriminately runs every module's init body at startup. ## Project layout ```text src/ VM library modules (Memory, Local, Global, Stack, Instruction, Extended, Interpreter, Loader2, Console, FileIO) and the seven tool programs (mcint, MC64, mkdemo, mkdtest, mkread, mkdep, trans8to64) — .mod/.def sources MC4/ generated sample images (boot.MC4, example.MC4, readtest.MC4, STAK.MC4, DEP.MC4) docs/ mc64-spec.md (normative spec), session-summary.md, SESSION.md, m-code-summary-64.md, m-code-summary.md, porting-plan-m2sp.md root built executables, *.o, reference files (skills.*, gnu-m2-grammar.txt, M2c-git instructions.txt, SESSION.MD5.trans8to64) ``` ## Documentation - **`docs/mc64-spec.md`** — the normative specification: memory model, registers, types, frames, addressing, module linkage, the on-disk format (§8), the host interface (§9.3), the extended and quad sub-dispatches (§10), the 256-opcode instruction-set reference (§11), the MCode/16 → MC64 migration table (§14) and the GNU Modula-2 build notes (§16). - **`docs/m-code-summary-64.md`** — condensed summary of the 64-bit machine. - **`docs/session-summary.md`** — day-by-day log of loader/interpreter milestones: build recipes, verified behaviors, hard-won tools-and-gotchas. - **`docs/porting-plan-m2sp.md`** — plan for porting Niklaus Wirth's M2SP single-pass compiler to ISO gm2 with an MC4 code generator. - **`docs/SESSION.md`** — working notes. ## Verification status All of the following are green with the freshly built binaries: - `./mkdemo` → `boot.MC4`; `./mcint boot.MC4` → `Hello MC64!` (0); `./mc64` → `Hello MC64!` + `[vm end]` (0); `./mcint` → usage (0); `./mcint nope.MC4` → loader fatal (1). - `./mkdtest` → `example.MC4`; `./mcint example.MC4` — 24/24 checks: `g after 10*2+1 = 2047`, add/sub/mul/div/mod, bit_and/or/xor, power2, the extended `0x40` sub-dispatch (uc_add/uc_mul/uc_div/uc_mod, long_negate, build_field_mask), `real_add 3.5+2.25 = 5`, dup/swap, global/local dword loads, `copy_block` → `abc` (0). - `./mkread` → `readtest.MC4`; `printf 'a\nb\n' | ./mcint readtest.MC4` echoes both lines through `SYSTEM` service 2 (0). - `./mkdep` → `STAK.MC4` + `DEP.MC4`; `./mcint DEP.MC4` executes an extern call into `STAK` through `MTBL[0]` and prints `cross-module ok` (0). ## Related projects Siblings in `MyWork/`: - **`m2compiler-V1`** — `M2c`, the single-pass compiler (Coco/R `M2c.atg` + `SymTab`/`MGen`), step 11, 158/158 tests; the original `.MC4` consumer. - **`m2compiler-V2`** — `M2comp`, the Blaise front/back (AST) compiler, step 7, 65/65 tests; its `run_tests.sh` defaults to `MCINT=../m-code-64/mcint`. - **`m2compiler-V3`** — the larger successor generation. - **`m-code`** — the original 16-bit Turbo Modula-2 reference material and MCode sources (used only as Modula-2 sources, never binary analysis). - **`CocoGm2`** — the GNU-Modula-2 port of Coco/R (`CR`) used to build the compiler front ends. ## Working conventions - The interpreter is written against `docs/mc64-spec.md` only — no disassembly or analysis of classic `.MCD` binaries. - Deterministic build order (§16.1), pure ISO dialect (`gm2 -fiso`). - Every instruction in the demos is hand-encoded: `mkd*.mod` generators are the known-good `.MC4` encoders used as ground truth for the compilers. - `SESSION.MD5.trans8to64` stores a baseline MD5 of `trans8to64.mod`: any step that rewrites that file without updating the lock note is a session bug.