# Turbo Modula-2 MCode Machine — Summary ## Overview MCode is the bytecode for Borland's Turbo Modula-2 (CP/M). It's a **16-bit word-addressed stack machine** with a single stack shared by evaluation values and call frames. The spec here is an interpreter written in Modula-2 (`Interpreter.mod`'s `Run` loop) that reads `.MCD` bytecode files, loads them, and executes them. In the original system, this dispatch loop was hand-written Z80 for speed. ## Timeline of a program ``` Call(modName) # Loader2.mod:313 LoadWithDependencies # load .MCD, read module desc, relocate addresses TranslateAddresses # fix up internal/external references allocate module globals for each TOINIT module: ProcCall(0,1); Interpreter.Run # run its init ``` A `.MCD` file: header (fileSize, moduleStart, codeSize, nbDependencies, reserved) + code blob + dependency records (8-char name, version, location). One file can bundle several interlinked modules (chain via `link`). ## Machine state - `instructionPointer` — current bytecode address - `sp` — word stack pointer (grows **down**; push = `DEC(sp,2)`) - `Local.framePointer` — current frame base (0-relative word indexing) - `Global.globalPointer` — current module's global data base - `old frame pointer` / `outer frame pointer` — link for display/static chain ## Data types on the stack | Size | Stack representation | |---|---| | 1 word | CARDINAL, INTEGER, BOOLEAN, CHAR, address | | 2 words | LONGINT (`DPush`), REAL (`FPush`) | | 4 words | LONGREAL (`QPush`) | Multi-word values are little-endian: high word pushed first. ## Calling convention - `Enter n` (0D4H): push new frame (old FP, outer FP), return-IP, reserve `255-n` bytes of locals (Instruction.mod:89). Stack order from low to high: **locals → return IP → outer FP → old FP → [caller data]**, so the frame pointer indexes params positively and locals negatively. - Calls: `ProcCall` pushes return address and jumps to the proc's code (resolved through a proc-address table stored just before the module base). - `ProcLeave n` (0E0x…): pops IP, FP, outer FP; discards `n` words; `n ≥ 80H` means the proc re-enters its outer module (sets `globalPointer`). ## Addressing families | Mnemonic | Meaning | Base | |---|---|---| | Local | FP-relative; params +n, locals −n | `Local.framePointer[n]` | | Global | current module's data | `globalPointer[n]` | | Extern | other module's data (mod#, var#), resolved via module table | `Global.Module(modNum)` | | Indirect (Stack) | load via address popped from stack | `mem[ptr+n]` | | Array | base address (stack) + index | `mem[base+idx]` | Full opcode map is the `CASE` in `Interpreter.mod:84`; `00H`–`FFH` covers: 0D0H-series real arith, 0C0H-series checked arith + long arith, 0E0H-series jumps/short-circuit (`AndThen`/`OrElse`), 0CDH switch tables, 0EBH-0FFH procedure calls, `40H` extended opcodes (ALLOCATE/MOVE/FILL/BIOS/Transfer…), and `12H` LONGREAL sub-dispatch. ## Highlights - **Switch tables** (0CDH): low/high bounds + jump table; uses a `+8000H` offset for unsigned comparison; tables can double as call-tables (`Push(returnAddr)`). - **Short-circuit evaluated** `AND THEN` / `OR ELSE` via `0DEH`/`0DFH` with jump offsets. - Stack doubles as the heap arena; `reserve`/`reserve_string` allocate locals/strings on it. - Not implemented in the spec interpreter: processes/coroutines (`TRANSFER`), `ASM`, `IOTRANSFER`, exception raise (0): these raise catchable exceptions instead. ## Tooling in the repo - `unassemble.c` — disassembles any `.MCD` (or the original `M2.COM`); prints the mnemonic names + a jump/switch-aware listing. - `MCode_disassembly/*.txt` — full disassembly of the original system (KERNEL, COMPILER, EDITOR, SHELL, system libs). This gives you a complete, executable description of Turbo Modula-2's bytecode — a clean reference if you want your compiler to emit (or a VM to run) it.