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.
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).
instructionPointer — current bytecode addresssp — 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 baseold frame pointer / outer frame pointer — link for display/static chain| 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.
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.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).| 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.
+8000H offset for unsigned comparison; tables can double as call-tables (Push(returnAddr)).AND THEN / OR ELSE via 0DEH/0DFH with jump offsets.reserve/reserve_string allocate locals/strings on it.TRANSFER), ASM, IOTRANSFER, exception raise (0): these raise catchable exceptions instead.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.