|
|
@@ -0,0 +1,809 @@
|
|
|
+# MC64 — A 64-Bit Stack Machine
|
|
|
+
|
|
|
+**Instruction-Set Specification, derived from the Turbo Modula-2 MCode bytecode**
|
|
|
+
|
|
|
+MC64 is a 64-bit generalization of the MCode virtual machine that Borland Turbo Modula-2
|
|
|
+for CP/M executed on Z80. Where MCode/16 is a 16-bit *word*-addressed stack machine with
|
|
|
+all scalars 16 bits wide, MC64 keeps the stack slot (word) at 64 bits but preserves
|
|
|
+Modula-2's type ladder: CARDINAL and INTEGER are **32 bits**, SET is **64 bits**,
|
|
|
+LONGINT and LONGCARD are **64 bits**, REAL is a **64-bit float** and LONGREAL is a
|
|
|
+**128-bit quad**.
|
|
|
+
|
|
|
+The 256-entry opcode map is preserved 1:1. The scalar word-op families (0A0H–0BFH and
|
|
|
+the checked forms) now operate on the 32-bit types; the "long"/"dword" families
|
|
|
+(0C5H–0CCH) operate on the 64-bit LONGINT type. Unsigned 64-bit (LONGCARD) arithmetic
|
|
|
+is provided by the extended sub-dispatch (§10.2).
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 1. Naming and document purpose
|
|
|
+
|
|
|
+- **word (slot)** = 8 bytes = 64 bits — the unit of the stack, frame cells and global cells.
|
|
|
+- **scalar** = a typed value held in one slot: 32-bit types (CARDINAL, INTEGER, and
|
|
|
+ 8-bit BYTE/CHAR/BOOLEAN) or 64-bit types (LONGINT, LONGCARD, SET, REAL, pointer).
|
|
|
+- **quad** = two slots = 16 bytes = 128 bits (the LONGREAL type).
|
|
|
+- Operands that in MCode/16 were *16-bit words* are here *64-bit words*; *byte* operands
|
|
|
+ stay bytes unless stated otherwise.
|
|
|
+
|
|
|
+This document is the normative reference for a VM implementation (e.g. as a C port)
|
|
|
+and for a compiler code generator that emits MC64.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. Memory model
|
|
|
+
|
|
|
+- Flat, byte-addressable address space; all addresses are 64-bit little-endian.
|
|
|
+- Basic storage unit is the **byte**; the machine word is 8 bytes; words are naturally
|
|
|
+ aligned (address % 8 == 0) unless stated otherwise.
|
|
|
+- Little-endian everywhere, including stack entries, code operands and multibyte values.
|
|
|
+- Memory layout convention (shared by the loader, not enforced by the ISA):
|
|
|
+
|
|
|
+```
|
|
|
+ high address
|
|
|
+ +-----------------------------+ ^
|
|
|
+ | stack region | | grows DOWN toward globals/heap
|
|
|
+ | (SP moves toward lower adr)|
|
|
|
+ +-----------------------------+
|
|
|
+ | heap region | |
|
|
|
+ +-----------------------------+ | grows UP (ALLOCATE/MARK/RELEASE)
|
|
|
+ | module images + globals|
|
|
|
+ +-----------------------------+ v
|
|
|
+ | system tables |
|
|
|
+ low address
|
|
|
+```
|
|
|
+
|
|
|
+The evaluation stack and the heap share the middle; the loader places module images at
|
|
|
+low addresses and lets the stack come down from the top.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. Registers / machine state
|
|
|
+
|
|
|
+| Register | Meaning |
|
|
|
+|--------------|----------------------------------------------------------------|
|
|
|
+| `IP` | instruction pointer — byte address of next opcode |
|
|
|
+| `SP` | stack pointer — byte address of the *top* word (lowest used) |
|
|
|
+| `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` |
|
|
|
+
|
|
|
+The stack **grows down**: `Push(w)` = `SP -= 8; *(u64*)SP = w`.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. Data types
|
|
|
+
|
|
|
+| Modula-2 type | Width | Stack slots | Notes |
|
|
|
+|---------------|---------|-------------|------------------------------------|
|
|
|
+| BYTE, CHAR, BOOLEAN | 8 bits | 1 slot | held in the low bits of a slot |
|
|
|
+| CARDINAL | 32 bits | 1 slot | unsigned arithmetic, zero-extended |
|
|
|
+| INTEGER | 32 bits | 1 slot | two's complement, sign-extended |
|
|
|
+| SET | 64 bits | 1 slot | bit set, elements 0..63 |
|
|
|
+| LONGINT | 64 bits | 1 slot | two's complement signed arithmetic |
|
|
|
+| LONGCARD | 64 bits | 1 slot | unsigned 64-bit arithmetic (new) |
|
|
|
+| REAL | 64 bits | 1 slot | IEEE 754 binary64 (double) |
|
|
|
+| LONGREAL | 128 bits| 2 slots| IEEE 754 binary128 (quad) |
|
|
|
+| pointer/address | 64 bits | 1 slot | byte address |
|
|
|
+
|
|
|
+Storage convention: stack slots are always 64 bits. 32-bit values live in the *low* 32
|
|
|
+bits of their slot — CARDINAL is kept zero-extended, INTEGER is kept sign-extended —
|
|
|
+while SET and the 64-bit types occupy the full slot. Conversion between a 32-bit type
|
|
|
+and a 64-bit type is a value no-op. ALL loads and stores move a full slot; the *width*
|
|
|
+is a semantic property of the arithmetic and comparison opcodes, not of memory access.
|
|
|
+
|
|
|
+Stack representation of a quad (little-endian):
|
|
|
+
|
|
|
+```
|
|
|
+ memory: [ low word ... high word ] low word on top (at SP)
|
|
|
+```
|
|
|
+
|
|
|
+Push order for a quad constant or a quad load: high word first, low word last, so the
|
|
|
+low word ends up on top of the stack.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. Stack and frames
|
|
|
+
|
|
|
+One stack holds evaluation data and activation frames.
|
|
|
+
|
|
|
+### 5.1 Frame layout
|
|
|
+
|
|
|
+```
|
|
|
+ byte offset from FP stack slot
|
|
|
+ FP - N*8 ........ first local word FP[-N]
|
|
|
+ FP - 8 ........ last (shallowest) local FP[-1]
|
|
|
+ FP + 0 ........ OPF (outer frame pointer) FP[0]
|
|
|
+ FP + 8 ........ OLD FP (dynamic link) FP[1]
|
|
|
+ FP + 16 ........ return IP FP[2]
|
|
|
+ FP + 24 ........ parameter 1 FP[3]
|
|
|
+ FP + 32 ........ parameter 2 FP[4]
|
|
|
+ ...
|
|
|
+```
|
|
|
+
|
|
|
+- **Locals** use negative word offsets: `FP[-1]`, `FP[-2]`, … The last reserved word
|
|
|
+ (adjacent to the frame link) is offset `-1`.
|
|
|
+- **Parameters** use positive word offsets: parameter *k* is at `FP[k+2]`.
|
|
|
+- The caller pushes arguments in **reverse declaration order** (last parameter pushed
|
|
|
+ first), so parameter 1 is the deepest argument and lands at `FP[3]`.
|
|
|
+
|
|
|
+### 5.2 Call sequence
|
|
|
+
|
|
|
+```
|
|
|
+ ; evaluate actuals, push them (right to left)
|
|
|
+ PROCCALL n ; push return IP, jump to proc n (OFP set as needed)
|
|
|
+ ENTER k ; callee prologue (below)
|
|
|
+ ...
|
|
|
+ LEAVE0 / PROC_LEAVE n ; callee epilogue (below)
|
|
|
+```
|
|
|
+
|
|
|
+`ENTER k`:
|
|
|
+```
|
|
|
+ push OLD FP; push OFP ; FP := SP ; NewFrame
|
|
|
+ push IP ; procedure start address
|
|
|
+ Reserve( (255-k) * 8 ) ; locals, in bytes (k=0FFH => none)
|
|
|
+```
|
|
|
+
|
|
|
+`PROC_LEAVE n` (and the leave family):
|
|
|
+```
|
|
|
+ SP := FP ; discard frame + locals
|
|
|
+ OFP := pop ; outer frame pointer
|
|
|
+ FP := pop ; OLD FP (restore caller FP)
|
|
|
+ IP := pop ; return address
|
|
|
+ drop n words ; discard parameter area (n is 7-bit; see below)
|
|
|
+ if (bit 7 of n) and (OFP ≠ NIL): GP := OFP ; re-enter outer module
|
|
|
+```
|
|
|
+
|
|
|
+Function returns:
|
|
|
+- `FCT_LEAVE n`: `res := pop; PROC_LEAVE n; push res`
|
|
|
+- `LONGREAL_FCT_LEAVE n`: same with a quad (two words) preserved across the leave.
|
|
|
+
|
|
|
+The low 7 bits of `n` in leave opcodes count **words** to drop from the caller's
|
|
|
+parameter area. Bit 7 is a flag: when set the procedure returns *into its outer
|
|
|
+(statically enclosing) module*, so `GP` is reloaded from `OFP`.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. Addressing families
|
|
|
+
|
|
|
+| Family | Base | Word offset n from |
|
|
|
+|-----------|-------------------------------|-----------------------------------|
|
|
|
+| Local | `FP` | immediate (s8) or tiny code |
|
|
|
+| Global | `GP` (current module window) | immediate (u8) — `GP[n]` |
|
|
|
+| Extern | `MTBL[moduleNumber]` | `mod` and `var` operands |
|
|
|
+| Indirect | a pointer popped from the stack | immediate (u8) — `*(ptr + n)` |
|
|
|
+| Indexed | a pointer popped from stack + index popped from stack | `*(ptr + index)` |
|
|
|
+
|
|
|
+Word offset *n* means byte offset `n*8`. Exceptions: the indexed *byte* ops (0DH/1DH)
|
|
|
+address single bytes (CHAR/BYTE arrays, strings), and the indexed *quad* ops (0FH/1FH)
|
|
|
+address two-slot LONGREAL elements.
|
|
|
+
|
|
|
+The `GP` window is an array of at least 65536 words; modules loaded so far live in it.
|
|
|
+`MTBL` is a system-wide array of 256 64-bit module data bases. `EXTERN mod,var`
|
|
|
+operations read `MTBL[mod][var]`.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 7. Modules, procedures and linkage
|
|
|
+
|
|
|
+### 7.1 Module descriptor (in-memory)
|
|
|
+
|
|
|
+```
|
|
|
+ModuleDesc (offsets are image-relative; u64 fields little-endian):
|
|
|
+ 0 dependencies : ARRAY[0..31] OF ADDRESS ; 256 bytes (module base pointers)
|
|
|
+ 256 link : ADDRESS ; next module in chain
|
|
|
+ 264 name : ARRAY[0..15] OF CHAR ; 16-char, NUL-padded
|
|
|
+ 280 loadAddr : ADDRESS ; base of module data image
|
|
|
+ 288 checksum : u32 (32-bit; see §8.3 for the coverage rule)
|
|
|
+ 292 flags : u8 (bit0 OVERLAY, bit1 Z80/NATIVE, bit2 TOINIT, bit3 RECURSE)
|
|
|
+ 293 varCount : u8 (number of global variable entries)
|
|
|
+ 294 depCount : u8 (number of dependencies)
|
|
|
+ 295 pad : u8 (reserved, zero)
|
|
|
+ 296 procsAddr : ADDRESS ; location of the proc table
|
|
|
+ 304 varSizes : varCount × ADDRESS (u64 global variable sizes in bytes)
|
|
|
+```
|
|
|
+
|
|
|
+The descriptor lives at **image offset 0** (i.e. immediately after the 64-byte file
|
|
|
+header). In a single-module file the whole image is a bare descriptor + code + data, so
|
|
|
+`moduleStart` (file header, §8.1) is taken as 0.
|
|
|
+
|
|
|
+### 7.2 Procedure address resolution
|
|
|
+
|
|
|
+Each module has a **procedure table** at `procsAddr`: `N` contiguous 64-bit **relative
|
|
|
+byte offsets**, one per procedure.
|
|
|
+
|
|
|
+```
|
|
|
+procedureAddress(k) = procsAddr + k*8 + (i64) table[k]
|
|
|
+```
|
|
|
+
|
|
|
+The offset is relative to the *end* of the entry cell, so entry `0` can point directly
|
|
|
+after its own cell. The table consecutive cells live just below (or beside) the module
|
|
|
+data; `procsAddr` is fixed up by the loader.
|
|
|
+
|
|
|
+### 7.3 External calls
|
|
|
+
|
|
|
+`EXTERN_CALL mod,proc`:
|
|
|
+```
|
|
|
+ OFP := GP ; remember current module
|
|
|
+ GP := MTBL[mod] ; switch to callee module
|
|
|
+ push IP
|
|
|
+ IP := procedureAddress(mod, proc)
|
|
|
+```
|
|
|
+
|
|
|
+`EXTERN_CALL1` (address+proc number on the stack) does the same with
|
|
|
+`procsNum = pop; modBase = pop; GP = modBase`.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 8. Loader and on-disk format
|
|
|
+
|
|
|
+A module file ("\*.MC4") holds one or several linked modules plus their dependencies.
|
|
|
+
|
|
|
+### 8.1 File header (64 bytes)
|
|
|
+
|
|
|
+| Offset | Size | Field | Meaning |
|
|
|
+|--------|------|------------------|--------------------------------------|
|
|
|
+| 0 | 8 | magic | `"MC64\0\0\0\0"` (constant `0x_4D_43_36_34`) |
|
|
|
+| 8 | 8 | fileSize | bytes of the image block (not header)|
|
|
|
+| 16 | 8 | moduleStart | offset of the module descriptor within the image |
|
|
|
+| 24 | 8 | depsOffset | offset of the dependency table within the image |
|
|
|
+| 32 | 8 | nbDependencies | number of dependencies |
|
|
|
+| 40 | 24 | reserved | zero |
|
|
|
+
|
|
|
+### 8.2 Dependency record (32 bytes each)
|
|
|
+
|
|
|
+| Size | Field | Meaning |
|
|
|
+|------|---------------|-------------------------------|
|
|
|
+| 16 | name | module name, NUL-padded |
|
|
|
+| 8 | version | import version (0 = any) |
|
|
|
+| 8 | location | offset of the fixup that must point at the module data base after load |
|
|
|
+
|
|
|
+### 8.3 Load sequence
|
|
|
+
|
|
|
+```
|
|
|
+LoadWithDependencies(modName, referencer, version):
|
|
|
+ open file; read header
|
|
|
+ origin := allocAddr; load image blob at origin
|
|
|
+ read dependency records (depsOffset + i*32)
|
|
|
+ walk the module chain starting at origin + moduleStart:
|
|
|
+ add origin to loadAddr, procsAddr, each non-NIL dependency pointer
|
|
|
+ set RECURSEFLAG
|
|
|
+ verify checksum of the requested (last) module
|
|
|
+ if not already loaded:
|
|
|
+ relocation pass: for each fixup pair (location,count) in the descriptor,
|
|
|
+ add origin to the target pointed by the (relocated) location
|
|
|
+ translate dependency fixups: moduleBase := loadModule(dep); *(location+origin) := moduleBase
|
|
|
+ init module globals (varCount × sizes), zero-filled
|
|
|
+```
|
|
|
+
|
|
|
+**Checksum rule (as implemented):** the module checksum is the plain 32-bit sum of the
|
|
|
+image bytes (file offsets `HeaderSize..fileSize-1`) **excluding** the four checksum
|
|
|
+bytes themselves (file `352..355` = image `288..291`). For the reference loader the
|
|
|
+image is placed at `ArenaBase` (1 MiB) with the descriptor at image offset 0, and the
|
|
|
+module's data window (`dataWin`) is allocated right after the image, 8-byte aligned, and
|
|
|
+zero-filled; `dependencies`, `link` and multi-module relocation are currently
|
|
|
+unsupported (`depCount > 0` is rejected).
|
|
|
+
|
|
|
+### 8.4 Global variable allocation
|
|
|
+
|
|
|
+After loading, the loader allocates `varSize` bytes for each entry, fills them with
|
|
|
+zeros and records the allocated base in the module descriptor's var-size table slot.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 9. Execution
|
|
|
+
|
|
|
+### 9.1 Run loop
|
|
|
+
|
|
|
+```
|
|
|
+Run:
|
|
|
+ loop
|
|
|
+ opcode := NextByte()
|
|
|
+ dispatch opcode
|
|
|
+ loop forever
|
|
|
+```
|
|
|
+
|
|
|
+`NextByte` reads one byte and advances `IP`. `NextWord` reads a 64-bit little-endian
|
|
|
+word (8 bytes). `NextSigned` reads a signed 8-bit value.
|
|
|
+
|
|
|
+### 9.2 Exceptions
|
|
|
+
|
|
|
+| Exception | Raised by |
|
|
|
+|-------------------------|--------------------------------------------------|
|
|
|
+| IllegalInstruction | opcode 00 |
|
|
|
+| Unimplemented | opcodes 01, 87, native-stub services |
|
|
|
+| Overflow | checked arithmetic (0C0H–0C2H, 0D0H–0D1H) |
|
|
|
+| RangeError | 0DAH, 0DBH, 0DCH, 0DDH |
|
|
|
+| DivideByZero | DIV/MOD with zero divisor |
|
|
|
+| StackOverflow | reserve crossing the stack/heap limit |
|
|
|
+| OutOfMemory | ALLOCATE breathing the stack |
|
|
|
+| StringTooLong | copy_string overflow |
|
|
|
+| LoadError | loader (module not found / version conflict) |
|
|
|
+
|
|
|
+A raising opcode aborts the current `Run` and unwinds to the nearest handler
|
|
|
+(register via `RAISE`/handler services). If no handler exists, the VM terminates the
|
|
|
+current module and reports the exception record (name, kind, `IP`), then returns to the
|
|
|
+caller of `Call`. Concurrency/process support (`TRANSFER`, `NEWPROCESS`) is out of
|
|
|
+scope for the baseline machine and raises `Unimplemented`.
|
|
|
+
|
|
|
+### 9.3 Host interface (0C3H `SYSTEM`)
|
|
|
+
|
|
|
+`SYSTEM` is the escape hatch to the host environment (successor to the CP/M BDOS):
|
|
|
+
|
|
|
+```
|
|
|
+ id := pop ; service number
|
|
|
+ param := pop ; address (or scalar)
|
|
|
+```
|
|
|
+
|
|
|
+Baseline services (implementation provided by the VM):
|
|
|
+
|
|
|
+| id | Service |
|
|
|
+|----|--------------------|
|
|
|
+| 0 | EXIT (param = status ignored) |
|
|
|
+| 1 | write NUL-terminated string at param |
|
|
|
+| 2 | read line into buffer at param |
|
|
|
+| 3+ | host-defined extensions |
|
|
|
+
|
|
|
+Service 2 (`read line`) pulls characters from the host standard input up to
|
|
|
+(but not including) the line mark, or until end of input. The bytes are stored at
|
|
|
+`param`, a NUL terminator is appended, and the byte count read (excluding the
|
|
|
+terminator) is pushed onto the operand stack. An empty line or immediate end of
|
|
|
+input pushes 0. A guest therefore reads a line with:
|
|
|
+`load_imm_word buf; load_imm_byte 2; system;` and may echo it straight back with
|
|
|
+`service 1` because the buffer is NUL-terminated.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 10. Sub-opcode dispatches
|
|
|
+
|
|
|
+Two opcodes delegate to a secondary byte:
|
|
|
+
|
|
|
+### 10.1 Sub-opcode `0x12` — LONGREAL (quad) operations
|
|
|
+
|
|
|
+| sub | mnemonic | operands | effect |
|
|
|
+|-----|-----------------|----------|-------------------------------------------------|
|
|
|
+| 00 | load_local_q | s8 n | push quad `FP[n]`..`FP[n+1]` (local) |
|
|
|
+| 01 | load_global_q | u8 n | push quad `GP[n]`..`GP[n+1]` (global) |
|
|
|
+| 02 | load_i_q | u8 n | pop p; push quad `p[n]`..`p[n+1]` (indirect) |
|
|
|
+| 03 | load_extern_q | mod, var | push quad `MTBL[mod][var..var+1]` |
|
|
|
+| 04 | store_local_q | s8 n | store quad of stack into local |
|
|
|
+| 05 | store_global_q | u8 n | store quad into global |
|
|
|
+| 06 | store_i_q | u8 n | pop p; store quad into `p[n..]` |
|
|
|
+| 07 | store_extern_q | mod, var | store quad into external module |
|
|
|
+| 08 | load_indexed_q | — | pop i (index), pop p; push quad `p[i]` |
|
|
|
+| 09 | store_indexed_q | — | pop q (quad); pop i; pop p; store quad `p[i]` |
|
|
|
+| 0A | quad_fct_leave | u8 n | q:=pop; proc_leave n; push q (quad fct leave) |
|
|
|
+| others | illegal | | |
|
|
|
+
|
|
|
+(`0x40 0x12` is `reserve_string`, see extended table.)
|
|
|
+
|
|
|
+### 10.2 `0x40` — extended operations
|
|
|
+
|
|
|
+| sub | mnemonic | effect |
|
|
|
+|-----|-------------------|-----------------------------------------------------|
|
|
|
+| 00 | drop | pop 1 word |
|
|
|
+| 01 | enter_monitor | no-op (host hook for preemption) |
|
|
|
+| 02 | leave_monitor | no-op |
|
|
|
+| 03 | long_negate | pop LONGINT; push -LONGINT (64-bit signed negate) |
|
|
|
+| 04 | build_field_mask | pop hi; pop lo; push (1<<hi) - (1<<lo) |
|
|
|
+| 05 | ALLOCATE | pop size; pop p; alloc; *p := block |
|
|
|
+| 06 | DEALLOCATE | pop size; pop p; free *p; *p := NIL |
|
|
|
+| 07 | MARK | pop p; *p := heapMark |
|
|
|
+| 08 | RELEASE | pop p (mark addr); release; *p := NIL |
|
|
|
+| 09 | FREEMEM | push bytes free |
|
|
|
+| 0A | TRANSFER | unimplemented (processes) |
|
|
|
+| 0B | IOTRANSFER | unimplemented |
|
|
|
+| 0C | NEWPROCESS | unimplemented |
|
|
|
+| 0D | BIOS | host call: fct:=pop; param:=pop; BIOS(fct,param) |
|
|
|
+| 0E | MOVE | pop size; pop dst; pop src; memcpy(dst,src,size) |
|
|
|
+| 0F | FILL | pop value; pop size; pop addr; memset(addr,value,size) |
|
|
|
+| 10 | INP | host I/O read port |
|
|
|
+| 11 | OUT | host I/O write port |
|
|
|
+| 12 | reserve_string | see §13 |
|
|
|
+| 13 | assert | assertion: pop 0 → raise RangeError |
|
|
|
+| 14 | uc_less | pop b; pop a; push (a < b) (LONGCARD) |
|
|
|
+| 15 | uc_less_eq | pop b; pop a; push (a <= b) (LONGCARD) |
|
|
|
+| 16 | uc_greater | pop b; pop a; push (a > b) (LONGCARD) |
|
|
|
+| 17 | uc_greater_eq | pop b; pop a; push (a >= b) (LONGCARD) |
|
|
|
+| 18 | uc_add | pop b; pop a; push (a + b) mod 2^64 (LONGCARD) |
|
|
|
+| 19 | uc_sub | pop b; pop a; push (a - b) mod 2^64 (LONGCARD) |
|
|
|
+| 1A | uc_mul | pop b; pop a; push (a * b) mod 2^64 (LONGCARD) |
|
|
|
+| 1B | uc_div | pop b; pop a; push (a DIV b) (raise DivideByZero)|
|
|
|
+| 1C | uc_mod | pop b; pop a; push (a MOD b) (raise DivideByZero)|
|
|
|
+| 1D | uc_to_real | pop LONGCARD; push REAL (rounded; optional) |
|
|
|
+| 1E | real_to_uc | pop REAL; push LONGCARD (truncated; optional) |
|
|
|
+| others | | illegal |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 11. Instruction set reference
|
|
|
+
|
|
|
+Notation: stack effects are written bottom…top → result. `pop` reads the top word.
|
|
|
+`u8/i8/u64/i64` are operand encodings. Multi-word quads are noted as *q*.
|
|
|
+
|
|
|
+### 11.1 0x00–0x1F — loads, stores, params, indexed
|
|
|
+
|
|
|
+| Hex | Mnemonic | Operand | Effect |
|
|
|
+|-----|-----------------|---------|-----------------------------------------------|
|
|
|
+| 00 | reserved | — | raise IllegalInstruction |
|
|
|
+| 01 | RAISE | — | unimplemented (see §9.2) |
|
|
|
+| 02 | load_proc_addr | u8 n | push procedureAddress(current_mod, n) |
|
|
|
+| 03–07 | load_param (1–5) | — | push `FP[3..7]` (param 1..5) |
|
|
|
+| 08 | load_local_dw | i8 n | push slot `FP[n]` (legacy 64-bit slot / LONGINT)|
|
|
|
+| 09 | load_global_dw | u8 n | push slot `GP[n]` (LONGINT/LONGCARD) |
|
|
|
+| 0A | load_stack_dw | u8 n | pop p; push slot `p[n]` (64-bit) |
|
|
|
+| 0B | load_extern_dw | mod,var | push slot `MTBL[mod][var]` (64-bit) |
|
|
|
+| 0C | load_extern_w | nibble | push slot `MTBL[m][n]` (nibble m,n) |
|
|
|
+| 0D | load_indexed_byte | — | pop i; pop p; push (u8)`p[i]` |
|
|
|
+| 0E | load_indexed_w | — | pop i; pop p; push `p[i*8]` (one slot) |
|
|
|
+| 0F | load_indexed_q | — | pop i; pop p; push quad `p[i*8]`,`p[i*8+1]` (LONGREAL) |
|
|
|
+| 10 | load_outer | — | push `FP` of enclosing frame (display +1) |
|
|
|
+| 11 | load_outer_n | u8 n | push frame pointer n display-steps up |
|
|
|
+| 12 | LONGREAL op | sub | secondary dispatch (§10.1) |
|
|
|
+| 13–17 | store_param (1–5) | — | pop; `FP[3..7]` := value |
|
|
|
+| 18 | store_local_dw | i8 n | pop; store slot into `FP[n]` |
|
|
|
+| 19 | store_global_dw | u8 n | pop; store slot into `GP[n]` |
|
|
|
+| 1A | store_stack_dw | u8 n | pop; pop p; store slot into `p[n]` |
|
|
|
+| 1B | store_extern_dw | mod,var | pop; store slot into `MTBL[mod][var]` |
|
|
|
+| 1C | store_extern_w | nibble | pop; store slot into `MTBL[m][n]` |
|
|
|
+| 1D | store_indexed_byte | — | pop v; pop i; pop p; `p[i] := v (byte)` |
|
|
|
+| 1E | store_indexed_w | — | pop v; pop i; pop p; `p[i*8] := v` |
|
|
|
+| 1F | store_indexed_q | — | pop q; pop i; pop p; store quad at `p[i*8]` |
|
|
|
+
|
|
|
+### 11.2 0x20–0x3F — stack ops, local/global loads & stores, copies
|
|
|
+
|
|
|
+| Hex | Mnemonic | Operand | Effect |
|
|
|
+|-----|-----------------|---------|-----------------------------------------------|
|
|
|
+| 20 | dup | — | push top |
|
|
|
+| 21 | swap | — | exchange top two words |
|
|
|
+| 22–2B | load_local_n | — | push `FP[-(op & 0x0F)]` (offsets +2..−11) |
|
|
|
+| 2C | load_local | i8 n | push `FP[n]` (n<0 local, n>0 param) |
|
|
|
+| 2D | load_global | u8 n | push `GP[n]` |
|
|
|
+| 2E | load_stack | u8 n | pop p; push `p[n]` |
|
|
|
+| 2F | load_extern | mod,var | push `MTBL[mod][var]` |
|
|
|
+| 30 | copy_block | — | pop size; pop src; pop dst; memcpy |
|
|
|
+| 31 | copy_string | — | pop srcSize; pop dstSize; pop src; pop dst; copy NUL-terminated |
|
|
|
+| 32–3B | store_local_n | — | pop; `FP[-(op & 0x0F)] := value` (s2..−11) |
|
|
|
+| 3C | store_local | i8 n | pop; `FP[n] := value` |
|
|
|
+| 3D | store_global | u8 n | pop; `GP[n] := value` |
|
|
|
+| 3E | store_stack | u8 n | pop; pop p; `p[n] := value` |
|
|
|
+| 3F | store_extern | mod,var | pop; `MTBL[mod][var] := value` |
|
|
|
+
|
|
|
+### 11.3 0x40–0x5F — extended dispatch, global 2–15
|
|
|
+
|
|
|
+| Hex | Mnemonic | Effect |
|
|
|
+|-----|-----------------|-----------------------------------------------|
|
|
|
+| 40 | extended | secondary dispatch (§10.2) |
|
|
|
+| 41 | load_stack_d0 | pop p; push slot `p[0]` (alias of 60H, retained) |
|
|
|
+| 42–4F | load_global_n | push `GP[op & 0x0F]` (offsets 2–15) |
|
|
|
+| 50 | end_program | return control to the loader/kernel |
|
|
|
+| 51 | store_stack_d0 | pop v; pop p; store slot `p[0]` (alias of 70H)|
|
|
|
+| 52–5F | store_global_n | pop; `GP[op & 0x0F] := value` (2–15) |
|
|
|
+
|
|
|
+### 11.4 0x60–0x7F — indirect loads/stores (offsets 0–15)
|
|
|
+
|
|
|
+| Hex | Mnemonic | Effect |
|
|
|
+|-----|--------------|----------------------------------------------------|
|
|
|
+| 60–6F | load_i_n | pop p; push `p[op & 0x0F]` (one slot) |
|
|
|
+| 70–7F | store_i_n | pop v; pop p; `p[op & 0x0F] := v` (one slot) |
|
|
|
+
|
|
|
+### 11.5 0x80–0x9F — addresses, leaves, strings, immediates
|
|
|
+
|
|
|
+| Hex | Mnemonic | Operand | Effect |
|
|
|
+|-----|-----------------|---------|-----------------------------------------------|
|
|
|
+| 80 | load_local_addr | i8 n | push byte addr `FP + n*8` |
|
|
|
+| 81 | load_global_addr| u8 n | push byte addr `GP + n*8` |
|
|
|
+| 82 | load_stack_addr | u8 n | pop p; push `p + n*8` |
|
|
|
+| 83 | load_extern_addr| mod,var | push byte addr `MTBL[mod] + var*8` |
|
|
|
+| 84 | proc_leave | u8 n | leave: drop `n & 0x7F` words; re-enter if bit7 |
|
|
|
+| 85 | fct_leave | u8 n | res:=pop; proc_leave n; push res |
|
|
|
+| 86 | longfct_leave | u8 n | q:=pop; proc_leave n; push q (quad) |
|
|
|
+| 87 | asmcode | u8 n | native code block; unimplemented in baseline |
|
|
|
+| 88–8B | leave (0..3) | — | leave, drop 0..3 words, bit7 set (outer return)|
|
|
|
+| 8C | call_rel | u8 n | push `IP`; `IP += n` (pc-relative string ptr) |
|
|
|
+| 8D | load_imm_byte | u8 | push zero-extended u8 |
|
|
|
+| 8E | load_imm_word | u64 | push 64-bit immediate (8 bytes) |
|
|
|
+| 8F | load_imm_quad | 16 bytes| push quad constant (low word on top) |
|
|
|
+| 90–9F | load_imm 0–15 | — | push small constant `op & 0x0F` |
|
|
|
+
|
|
|
+### 11.6 0xA0–0xBF — comparisons, integer arithmetic, conversions
|
|
|
+
|
|
|
+| Hex | Mnemonic | Effect |
|
|
|
+|-----|-----------------|-----------------------------------------------|
|
|
|
+| A0 | equal | pop b; push (pop = b) (full slot, any width)|
|
|
|
+| A1 | not_equal | pop b; push (pop # b) |
|
|
|
+| A2 | uless | pop b; push (pop < b) (CARDINAL, 32-bit) |
|
|
|
+| A3 | ugreater | pop b; push (pop > b) (CARDINAL) |
|
|
|
+| A4 | uless_eq | pop b; push (pop <= b) (CARDINAL) |
|
|
|
+| A5 | ugreater_eq | pop b; push (pop >= b) (CARDINAL) |
|
|
|
+| A6 | add | pop b; push (pop + b) (CARDINAL, mod 2^32) |
|
|
|
+| A7 | sub | pop b; push (pop - b) (CARDINAL, mod 2^32) |
|
|
|
+| A8 | umul | pop b; push (pop * b) (CARDINAL) |
|
|
|
+| A9 | udiv | pop b; push (pop DIV b) (raise DivideByZero) |
|
|
|
+| AA | umod | pop b; push (pop MOD b) (raise DivideByZero) |
|
|
|
+| AB | eq0 | push (pop = 0) |
|
|
|
+| AC | inc | push (pop + 1) (32-bit) |
|
|
|
+| AD | dec | push (pop - 1) (32-bit) |
|
|
|
+| AE | add_imm | u8 n; push (pop + n) (CARDINAL) |
|
|
|
+| AF | sub_imm | u8 n; push (pop - n) (CARDINAL) |
|
|
|
+| B0 | shl_imm | u8 n; push (pop << n) (CARDINAL, logical)|
|
|
|
+| B1 | shr_imm | u8 n; push (pop >> n) (logical) |
|
|
|
+| B2 | iless | pop b i32; push (pop < b) (INTEGER) |
|
|
|
+| B3 | igreater | pop b i32; push (pop > b) (INTEGER) |
|
|
|
+| B4 | iless_eq | pop b i32; push (pop <= b) (INTEGER) |
|
|
|
+| B5 | igreater_eq | pop b i32; push (pop >= b) (INTEGER) |
|
|
|
+| B6 | not | push (¬ bool(pop)) |
|
|
|
+| B7 | complement | push (0xFFFFFFFF − pop) (32-bit bitwise NOT) |
|
|
|
+| B8 | imul | pop b i32; push (pop * b) (INTEGER) |
|
|
|
+| B9 | idiv | pop b i32; push (pop DIV b) (raise DivideByZero) |
|
|
|
+| BA | long_to_card | pop LONGINT; push CARDINAL (low 32 bits, zero-extended) |
|
|
|
+| BB | long_to_int | pop LONGINT; push INTEGER (low 32 bits, sign-extended) |
|
|
|
+| BC | abs | pop i32; push |v| |
|
|
|
+| BD | int_to_long | pop INTEGER; push LONGINT (sign-extended) |
|
|
|
+| BE | long_to_real | pop LONGINT; push REAL (rounded) |
|
|
|
+| BF | real_to_long | pop REAL; push LONGINT (truncated) |
|
|
|
+
|
|
|
+Notes: `CARDINAL`-`LONGCARD` and `LONGCARD`-`LONGINT` widenings are value no-ops (all are
|
|
|
+one slot; see §4). Because 32-bit errors would be silent, CARDINAL/INTEGER arithmetic
|
|
|
+uses the *checked* forms of §11.7 when `CHECK`-range diagnostics are enabled.
|
|
|
+
|
|
|
+### 11.7 0xC0–0xDF — checked/long/real arithmetic, switch, short-circuit
|
|
|
+
|
|
|
+| Hex | Mnemonic | Effect |
|
|
|
+|-----|-----------------|-----------------------------------------------|
|
|
|
+| C0 | uadd_checked | pop b; push (pop + b) (CARDINAL; raise Overflow on wrap) |
|
|
|
+| C1 | usub_checked | pop b; push (pop - b) (CARDINAL; raise on borrow) |
|
|
|
+| C2 | umul_checked | pop b; push (pop * b) (CARDINAL; raise on overflow) |
|
|
|
+| C3 | system | host call (§9.3) |
|
|
|
+| C4 | string_comp | see §12.1 |
|
|
|
+| C5 | long_compare | pop b; pop a; push (a>b), push (a<b) (LONGINT, signed 64) |
|
|
|
+| C6 | long_add | pop b; pop a; push a + b (LONGINT) |
|
|
|
+| C7 | long_sub | pop b; pop a; push a − b |
|
|
|
+| C8 | long_mul | pop b; pop a; push a × b |
|
|
|
+| C9 | long_div | pop b; pop a; push a DIV b (signed; raise on 0)|
|
|
|
+| CA | long_mod | pop b; pop a; push a MOD b (signed; raise on 0)|
|
|
|
+| CB | not_zero | push (pop # 0) |
|
|
|
+| CC | long_abs | pop a; push |a| (LONGINT) |
|
|
|
+| CD | switch | §12.2 (case tables) |
|
|
|
+| CE | jump_stack | pop; IP := value (computed jump/return) |
|
|
|
+| CF | push_code_addr | u64; push (IP - 1 + off) (pc-relative) |
|
|
|
+| D0 | iadd_checked | pop b i32; push (pop + b); raise Overflow (INTEGER) |
|
|
|
+| D1 | isub_checked | pop b i32; push (pop - b); raise Overflow (INTEGER) |
|
|
|
+| D2 | reserve | pop size; check limit; SP -= size; push new SP|
|
|
|
+| D3 | reserve_string | see §13 |
|
|
|
+| D4 | enter | u8 k — prologue, reserve (255−k)*8 bytes |
|
|
|
+| D5 | real_compare | pop r2; pop r1; push r1>r2, push r1<r2 (REAL) |
|
|
|
+| D6 | real_add | pop r2; pop r1; push r1+r2 (REAL) |
|
|
|
+| D7 | real_sub | pop r2; pop r1; push r1−r2 |
|
|
|
+| D8 | real_mul | pop r2; pop r1; push r1×r2 |
|
|
|
+| D9 | real_div | pop r2; pop r1; push r1/r2 |
|
|
|
+| DA | urange_check | pop low; pop size; Top in [low, low+size]? (CARDINAL) |
|
|
|
+| DB | irange_check | pop low i32; pop size; Top in [low, low+size]? (INTEGER) |
|
|
|
+| DC | limit_check | u8 n; if Top() > n raise RangeError (CARDINAL) |
|
|
|
+| DD | check_positive | if (i32)Top() < 0 raise RangeError (INTEGER) |
|
|
|
+| DE | and_jp | u8 n; if not pop then push false; IP += n |
|
|
|
+| DF | or_jp | u8 n; if pop then push true; IP += n |
|
|
|
+
|
|
|
+### 11.8 0xE0–0xFF — jumps, bit sets, calls
|
|
|
+
|
|
|
+| Hex | Mnemonic | Operand | Effect |
|
|
|
+|-----|-----------------|---------|-----------------------------------------------|
|
|
|
+| E0 | jp | i64 | IP += rel |
|
|
|
+| E1 | jpfalse | i64 | if not pop then IP += rel |
|
|
|
+| E2 | jp_fwd | i8 | IP += rel |
|
|
|
+| E3 | jpfalse_fwd | i8 | if not pop then IP += rel |
|
|
|
+| E4 | jp_back | u8 | IP -= rel |
|
|
|
+| E5 | jpfalse_back | u8 | if not pop then IP -= rel |
|
|
|
+| E6 | bit_or | — | pop b; push bitset(pop) ∪ bitset(b) |
|
|
|
+| E7 | bit_in | — | pop b; push (pop ∈ bitset(b)) |
|
|
|
+| E8 | bit_and | — | pop b; push bitset(pop) ∩ bitset(b) |
|
|
|
+| E9 | bit_xor | — | pop b; push bitset(pop) △ bitset(b) |
|
|
|
+| EA | power2 | — | push (1 << pop) |
|
|
|
+| EB | extern_proc_call| — | n:=pop; base:=pop; call via MTBL (base=addr) |
|
|
|
+| EC | nested_call | u8 n | call proc n with OFP = FP (static nesting) |
|
|
|
+| ED | proc_call | u8 n | call proc n, OFP = NIL |
|
|
|
+| EE | call_with_frame | u8 n | pop f; call proc n with OFP = f |
|
|
|
+| EF | extern_call | mod,proc| call proc in module `mod` (2 operands) |
|
|
|
+| F0 | extern_call_nib | nibble | call proc `n` in module `m` (high/low nibbles) |
|
|
|
+| F1–FF | call 1..15 | — | call proc `op & 0x0F`, OFP = NIL |
|
|
|
+
|
|
|
+Jump offsets are relative to the address **after** the operand(s). All jumps except
|
|
|
+`E0/E1` are byte offsets; `E0/E1` carry a full 64-bit signed offset.
|
|
|
+
|
|
|
+SET operations (0E6H–0EAH) operate on the 64-bit `SET` type; element indices are
|
|
|
+0..63 and `power2` computes `1 << pop` (mod 2^64).
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 12. Complex instructions
|
|
|
+
|
|
|
+### 12.1 String compare (0C4H)
|
|
|
+
|
|
|
+```
|
|
|
+pop size2; pop size1 ; buffer lengths (bytes)
|
|
|
+pop str2; pop str1
|
|
|
+compare char-by-char up to a NUL terminator or exhausted size
|
|
|
+push (str1 > str2) ; boolean
|
|
|
+push (str1 < str2) ; boolean
|
|
|
+```
|
|
|
+
|
|
|
+The caller consumes the two booleans (typically `or_jp` / `and_jp` to build lexicographic
|
|
|
+ordering tests).
|
|
|
+
|
|
|
+### 12.2 Switch / case statement (0CDH)
|
|
|
+
|
|
|
+Encoding after the opcode:
|
|
|
+
|
|
|
+```
|
|
|
+lowBound u64
|
|
|
+highBound u64
|
|
|
+retOffset i64 ; reserved; must be 0
|
|
|
+jumpTable (highBound − lowBound + 1) × i64
|
|
|
+```
|
|
|
+
|
|
|
+Dispatch:
|
|
|
+
|
|
|
+```
|
|
|
+N := high − low + 1
|
|
|
+first := address of the first table cell
|
|
|
+endOfTable := first + N*8
|
|
|
+if value < lowBound or value > highBound:
|
|
|
+ IP := endOfTable ; default code follows the table
|
|
|
+else:
|
|
|
+ cell := first + (value − lowBound)*8
|
|
|
+ off := (i64)*cell
|
|
|
+ if off < 0: ; call-switch form
|
|
|
+ push endOfTable ; return address after the table
|
|
|
+ IP := cell + 8 + off
|
|
|
+```
|
|
|
+
|
|
|
+Entry offsets are relative to the *end* of their own cell; a negative offset denotes a
|
|
|
+call-switch (each case is a procedure, returning to the code after the table).
|
|
|
+`retOffset` is retained for encoding compatibility with MCode/16 and must be 0.
|
|
|
+
|
|
|
+### 12.3 `reserve_string` (0D3H and `0x40 0x12`)
|
|
|
+
|
|
|
+```
|
|
|
+src := pop ; byte address of the string data
|
|
|
+nBytes := pop
|
|
|
+nWords := (nBytes + 7) DIV 8
|
|
|
+copy nWords words from src onto the stack (stack limit checked)
|
|
|
+push the address of the copy ; top of the copied region
|
|
|
+```
|
|
|
+
|
|
|
+Used by the compiler to materialize a local owned copy of a string constant (the
|
|
|
+constant itself lives in the code area at a pc-relative address).
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 13. Initialization sequence
|
|
|
+
|
|
|
+```
|
|
|
+Call(modName):
|
|
|
+ save current module chain and caller frame
|
|
|
+ MARK(heap)
|
|
|
+ LoadWithDependencies(modName)
|
|
|
+ for each newly loaded module:
|
|
|
+ allocate + zero global variables
|
|
|
+ for each module flagged TOINIT, in load order:
|
|
|
+ GP := module base
|
|
|
+ FP := fresh frame ; reference frame
|
|
|
+ IP := procedureAddress(module, 0) ; its initializer
|
|
|
+ Run()
|
|
|
+ RELEASE(heap mark)
|
|
|
+ restore caller module chain
|
|
|
+```
|
|
|
+
|
|
|
+The *last* module initialized is the requested one; this is how the system boots the
|
|
|
+target module and returns to the shell when it ``end_program``s.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 14. Differences from MCode/16 (migration notes)
|
|
|
+
|
|
|
+| MCode/16 | MC64 |
|
|
|
+|---------------------------------------|---------------------------------------|
|
|
|
+| word = 16 bits, address space 64 KB | word = 64 bits, address space 2^64 |
|
|
|
+| scalar types 16 bits; LONGINT/REAL 32 bits (2 words); LONGREAL 64 (4 words) | CARDINAL/INTEGER 32 bits; SET/LONGINT/LONGCARD 64 bits; REAL 64-bit float; LONGREAL 128 bits (2 slots) |
|
|
|
+| byte/nibble operands (bytes, # args, depths) | byte operands unchanged; all *word* operand fields widened 2→8 bytes |
|
|
|
+| local addressing in bytes (F+2n) | local addressing in words (FP ± n*8) |
|
|
|
+| global window 64 K words, module table in top entries | window of ≥64 K words + dedicated 256-slot module table `MTBL` |
|
|
|
+| module baseline tables relative to base−2/−18/−14 | explicit `procsAddr`, `loadAddr`, `dependencies[]` fields (module descriptor) |
|
|
|
+| `Enter` reserves 255−n **bytes** | `Enter` reserves (255−n)×**8** bytes |
|
|
|
+| immediates: byte / 2-byte word / 4-byte dword | byte / 8-byte word / 16-byte quad |
|
|
|
+| switch tables: 16-bit offsets, +8000H unsigned trick | 64-bit signed offsets, plain unsigned compare, `retOffset` reserved |
|
|
|
+| proc table: `base-2` cell, +1+offset | `procsAddr` + k*8 + offset |
|
|
|
+| BDOS 0C3H | `SYSTEM` host-call ABI (§9.3) |
|
|
|
+| `.MCD` (no magic, CP/M 8-char names) | `.MC4` (magic header, 16-char names, 64-bit fields) |
|
|
|
+
|
|
|
+All 256 opcodes keep their MCode/16 *position* in the map; only operand widths and the
|
|
|
+scalar→(32/64/128-bit) mapping differ. The scalar word-op families (0A0H–0BFH) are
|
|
|
+32-bit; the "long" families (0C5H–0CCH) are 64-bit; LONGCARD arithmetic is provided by
|
|
|
+the extended sub-dispatch (§10.2).
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 15. Minimal encoding example
|
|
|
+
|
|
|
+A procedure `P(x: CARDINAL): CARDINAL` that computes `2*x + 1` (32-bit CARDINAL ops):
|
|
|
+
|
|
|
+```
|
|
|
+ d4 fe enter -2 ; prologue: reserve (255-0xFE)*8 = 8 bytes (1 slot)
|
|
|
+ 8d 02 load_imm_byte 2
|
|
|
+ 3c fe store_local -2 ; tmp := 2
|
|
|
+ 03 load_param1 ; x (FP[3])
|
|
|
+ 2c fe load_local -2 ; tmp
|
|
|
+ a8 umul ; CARDINAL multiply (32-bit)
|
|
|
+ 8d 01 load_imm_byte 1
|
|
|
+ a6 add ; CARDINAL add (32-bit)
|
|
|
+ 85 01 fct_leave 1 ; return; drop 1 word (the parameter)
|
|
|
+```
|
|
|
+
|
|
|
+Provenance: transcribed from the reverse-engineering of Borland Turbo Modula-2 (CP/M
|
|
|
+Z80), the `MCode_specification` bytecode interpreter in `Reversing-Turbo-Modula2`.
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 16. GNU Modula-2 (ISO) implementation notes
|
|
|
+
|
|
|
+The reference interpreter in `mc64/` is written in **pure ISO Modula-2** and compiled
|
|
|
+exclusively with `gm2 -fiso`. These notes record the GNU M2 idioms and constraints the
|
|
|
+source relies on.
|
|
|
+
|
|
|
+### 16.1 Build rules
|
|
|
+
|
|
|
+- Every module is compiled with `gm2 -fiso -c <module>.mod`.
|
|
|
+- Only a **program module** is passed to the link step; library modules are given as
|
|
|
+ pre-compiled `.o`. Passing several `.mod` files at once makes gm2 emit one `main` per
|
|
|
+ module and the link fails on duplicate `main`.
|
|
|
+- Deterministic build: `Memory Local Global Stack Instruction Extended Interpreter
|
|
|
+ Loader2 Console FileIO` (each `.mod`), then `MC64.mod` and `mkdemo.mod`, then
|
|
|
+ `gm2 -fiso -o mc64 MC64.mod <12 .o>` and `gm2 -fiso -o mkdemo mkdemo.mod <12 .o>`.
|
|
|
+
|
|
|
+### 16.2 Language constraints observed
|
|
|
+
|
|
|
+- **`AND`/`OR` are Boolean-only**: gm2's ISO front end has no integer bitwise `AND`/`OR`.
|
|
|
+ Integer bitwise ops are done with a `BitOp(a, b, mode)` loop (64 iterations of
|
|
|
+ `DIV 2^k`/`MOD 2`); bit *masking* uses `MOD`/`DIV` arithmetic (`opc MOD 16`,
|
|
|
+ `(fl DIV 4) MOD 2` for the TOINIT bit). A stray integer `AND` typically surfaces as a
|
|
|
+ misleading *"is not a boolean expression"* diagnostic pointed at a `VAR` declaration
|
|
|
+ several lines earlier.
|
|
|
+- **Ignored function results are errors**: a bare `Pop () ;` (return value discarded)
|
|
|
+ does not compile; always capture into a variable.
|
|
|
+- **Variant records**: the tag must be an exhaustive choice; `CASE : BOOLEAN OF |
|
|
|
+ TRUE: ... | FALSE: ...` works, `CASE : CARDINAL OF` is rejected ("not all variant
|
|
|
+ record alternatives"). This is the mechanism used to reinterpret a 64-bit slot as
|
|
|
+ `REAL`/`LONGCARD` and a 128-bit `LONGREAL` as two slots.
|
|
|
+- **No import alias**: `FROM m IMPORT x AS y` is a parse error. Name clashes (e.g.
|
|
|
+ `IOChan.WriteLn` vs a local `WriteLn`) are avoided by not importing the colliding name
|
|
|
+ (the module writes `LF` itself).
|
|
|
+- **Set constructors**: an inline `FlagSet{...}` argument can crash gm2
|
|
|
+ ("internal compiler error: expecting ConstVar symbol"); assign the constructor to a
|
|
|
+ local `VAR` and pass the variable instead.
|
|
|
+- **Definition modules**: an `IMPORT` must precede `EXPORT QUALIFIED`.
|
|
|
+- **`HALT(n)`** works under `-fiso` and is used for fatal host errors (`THROW` aborts
|
|
|
+ with `SIGABRT`).
|
|
|
+
|
|
|
+### 16.3 Host services
|
|
|
+
|
|
|
+- No libc: no `DEFINITION MODULE FOR "C"`, no M2PIM legacy modules (`Args`,
|
|
|
+ `UnixArgs`, …). Vendor `m2pim` modules fail to link against `-fiso` objects.
|
|
|
+- `System.ProgramArgs.ArgChan` is **not usable** in this gm2 build: `TextRead` on it
|
|
|
+ blocks forever and `Look` returns a `'.'` filler instead of `endOfInput`. The VM
|
|
|
+ therefore boots a fixed `boot.mc4` (generated by `mkdemo`) instead of parsing argv.
|
|
|
+- File I/O uses `StreamFile.Open/Close` + `IOChan.RawRead/RawWrite` (`ChanConsts`
|
|
|
+ `FlagSet{readFlag, oldFlag, rawFlag}` / `{writeFlag, rawFlag}`).
|
|
|
+- Screens output writes go to the ISO standard output channel
|
|
|
+ (`StdChans.StdOutChan`) with `IOChan.TextWrite`; LC real values go out via
|
|
|
+ `RealStr.RealToStr`.
|
|
|
+
|
|
|
+### 16.4 Machine representation
|
|
|
+
|
|
|
+- Slots are 64-bit Little-Endian, addressed through a 16 MiB static `CHAR` arena
|
|
|
+ (`Memory.ms`, `MaxMem = 16777216`) with byte-assembling readers/writers
|
|
|
+ (`ReadSlot/WriteSlot`, `ReadLong/WriteLong`, `ReadWord/WriteWord`).
|
|
|
+- The image is loaded at `ArenaBase = 1048576` via `LoadImage(data, off, dst, n)`
|
|
|
+ (images start at file offset 64, hence the source offset parameter). 32-bit descriptor
|
|
|
+ fields (e.g. `checksum`) must be read with `ReadLong`, never with the 8-byte reader.
|
|
|
+- `LONGCARD` is used for addresses, stack slots and 64-bit arithmetic; untyped literals,
|
|
|
+ `H`-suffixed hex constants and `VAL` conversions all compile under `-fiso`.
|