DEFINITION MODULE Exec86 ; (* Exec86 -- an in-process 8086 interpreter. This is what the shell's `R` key runs: the compiled image is copied into the interpreter's own 64 KB and executed here, with no DOS, no loader and no emulator outside this program. TP3's `R` does the same thing - it runs the generated code in place in the same 64 KB - so this is also the shape the original had. The reason to want one at all is documented in SUMMARY.md: qemu-system-i386 cannot be an 8086 oracle, because its lowest CPU model is a 486, where the opcodes that were the `0F 8x' bug are ordinary instructions. An interpreter written against the 8086's own reference and then required to agree with qemu byte-for-byte on the same images is a second, independent execution oracle - and the one that can actually see an 8086-only fault. The interface is deliberately three calls: zero the machine, poke the image in, run it. Everything the guest can do outside its own code - the INT 21h services, where its input comes from, where its output goes - is inside, because those are part of "running it", not part of setting it up. The names carry an 86 suffix, which reads as redundant inside Exec86 and is not. ISO Modula-2 has no import renaming - gm2 -fiso rejects `FROM M IMPORT x AS y' outright - and an import cannot be scoped to a procedure either, so every module that imports Exec86 shares one flat namespace with everything else it imports. Shell needs TextBuf.Clear, Editor.Run, Exec86.Clear and Exec86.Run at the same time, and the first two were spoken for by modules that predate this one. This is the module that moved: Clear86, Poke86, Run86. NOTE, because Runtime.def records the same trap: this .def is HAND-MAINTAINED. gm2 resolves `FROM Exec86 IMPORT ...' against it and checks the implementation against it, but never rewrites it. An interface change means editing this file by hand in the same commit. Keep changes ADDITIVE where possible. *) PROCEDURE Clear86 ; (* Reset the machine to the state a DOS .COM starts in: all of memory zeroed, CS = DS = ES = SS = 0, IP = 0100H (where DOS puts a .COM), SP = 0FFFEH (the top of the segment, where DOS puts the stack), every general register zero, DF clear, every other flag clear. Zeroing the general registers is a CHOICE, not a fact about DOS: bootcom.s leaves them as the BIOS found them. All 31 fixtures are deterministic under qemu, so no guest of ours reads an undefined initial register - and this is the first place that would show up if one ever did. Memory below 0100H is left zero rather than holding bootcom's interrupt vector, because the interpreter services INT 21h itself and never looks at the vector. *) PROCEDURE Poke86 (addr, value : CARDINAL) ; (* Store one byte at flat address `addr' (value is taken modulo 256). Poking also records how far up memory has been written, and execution outside that region is a fault: a guest that jumps out of its own loaded image has gone somewhere it cannot come back from, and running on into whatever happens to be there is how a wild jump turns into a wild hang. *) PROCEDURE Run86 (VAR exitCode : CARDINAL; VAR steps : LONGCARD) : CARDINAL ; (* Execute until something stops it. Returns 0 the program terminated with INT 21h AH=4Ch, and exitCode is its AL 1 the interpreter faulted - a diagnostic is already on stderr 2 the step limit was reached; steps says how many `steps' is always the number of instructions actually executed. The step limit is a runaway guard, and it is a limit rather than a solution: a program that idles in a tight loop will reach it. It exists so that a broken image fails instead of never returning. *) END Exec86.