| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126 |
- IMPLEMENTATION MODULE Exec86 ;
- (* Exec86 -- implementation. See Exec86.def for what this is for.
- WHAT IS IMPLEMENTED, and where the list comes from
- --------------------------------------------------
- Not "the 8086": a CPU has no meaning without the programs it must run, and
- this one has a corpus. The set below is the union of exactly two measured
- things:
- (a) every byte the runtime emits. Runtime.mod's code region is pure
- code with no inline data, so it can be swept instruction by
- instruction and the sweep must complete -- tests/check_8086.py does
- that, and it covers 219 instructions, 121 distinct forms. That
- sweep is the authority for the runtime.
- (b) every byte Compiler.mod's emitters can write. The generated code
- region CANNOT be swept the same way: inline string literals are
- emitted into it after the code, and a linear sweep desynchronises
- on them (t09_if dies at the bytes `FE E9 0D 00', which are ASCII
- text). So the authority there is the source -- the Em* procedures
- and their Ebyte constants -- not a disassembly.
- Everything outside that union faults rather than guessing. An
- interpreter that guesses at an unknown opcode does not fail; it produces
- an answer.
- FLAGS: CF, ZF, SF and OF are maintained, because every condition the
- corpus uses reads one of them: the compiler emits 4,5,C,D,E,F
- (JE JNE JL JGE JLE JG) and the runtime adds 2,6,7 (JB JBE JA).
- PF and AF are NOT maintained, and JP/JNP fault saying so rather than
- returning a value nobody computed. DF, IF and TF are not maintained;
- no string instruction is implemented, so nothing can read DF. Nothing in
- the corpus sets a flag that a later instruction in the corpus reads
- except through those four.
- SEGMENTS are checked every step. The machine is 64 KB flat, so a
- non-zero segment register would alias silently rather than fault; it
- faults instead.
- INT 21h PRESERVES THE FLAGS. That is not an approximation: a real INT
- pushes FLAGS, and IRET pops them back, so the handler's own CLC/STC are
- discarded before the caller can see them. bootcom.s does `clc' and
- `stc' in its handler and they change nothing outside it. This is the
- one place where matching the machine rather than the visible source is
- easy to get wrong, so it is written down. *)
- FROM Posix IMPORT read, write ;
- FROM SYSTEM IMPORT ADR ;
- CONST
- STDIN = 0 ;
- STDOUT = 1 ;
- STDERR = 2 ;
- LoadAt = 100H ; (* where DOS puts a .COM, and where bootcom
- jumps, so the entry point matches qemu *)
- MaxSteps = 2000000000 ; (* runaway guard; see Exec86.def *)
- VAR
- mem : ARRAY [0..65535] OF CARDINAL ; (* one CARDINAL per byte, 0..255 *)
- AX, BX, CX, DX, SI, DI, BP, SP, IP : CARDINAL ;
- CS, DS, ES, SS : CARDINAL ;
- CF, ZF, SF, OFl : BOOLEAN ;
- halted : BOOLEAN ;
- haltCode : CARDINAL ;
- isFault : BOOLEAN ;
- faultIP : CARDINAL ;
- loadHi : CARDINAL ; (* one past the highest address poked *)
- stepCnt : LONGCARD ;
- (* filled in by DoModRM *)
- eaAddr : CARDINAL ;
- eaReg : CARDINAL ;
- eaIsReg : BOOLEAN ;
- rmReg : CARDINAL ;
- (* ------------------------------------------------------------ diagnostics *)
- PROCEDURE WrErr1 (c : CHAR) ;
- VAR n : LONGINT ;
- BEGIN
- n := write (STDERR, ADR (c), 1)
- END WrErr1 ;
- PROCEDURE WrS (s : ARRAY OF CHAR) ;
- VAR i : CARDINAL ;
- c : CHAR ;
- BEGIN
- i := 0 ;
- WHILE (i <= HIGH (s)) AND (s [i] # 0C) DO
- c := s [i] ;
- WrErr1 (c) ;
- i := i + 1
- END
- END WrS ;
- PROCEDURE WrHex (v : CARDINAL) ;
- VAR k, d, p, n : CARDINAL ;
- c : CHAR ;
- BEGIN
- WrS ("0x") ;
- FOR k := 0 TO 3 DO
- p := 1 ;
- d := 3 - k ;
- WHILE d > 0 DO
- p := p * 16 ;
- d := d - 1
- END ;
- n := (v DIV p) MOD 16 ;
- IF n < 10 THEN
- c := CHR (ORD ('0') + n)
- ELSE
- c := CHR (ORD ('A') + n - 10)
- END ;
- WrErr1 (c)
- END
- END WrHex ;
- PROCEDURE WrDec (v : CARDINAL) ;
- VAR buf : ARRAY [0..10] OF CHAR ;
- k, j, x : CARDINAL ;
- c : CHAR ;
- BEGIN
- k := 10 ;
- buf [10] := 0C ;
- x := v ;
- REPEAT
- k := k - 1 ;
- buf [k] := CHR (ORD ('0') + x MOD 10) ;
- x := x DIV 10
- UNTIL x = 0 ;
- j := k ;
- WHILE j <= 9 DO
- c := buf [j] ;
- WrErr1 (c) ;
- j := j + 1
- END
- END WrDec ;
- PROCEDURE Fault (msg : ARRAY OF CHAR) ;
- BEGIN
- isFault := TRUE ;
- WrS ("exec86: fault at IP=") ;
- WrHex (faultIP) ;
- WrS (": ") ;
- WrS (msg) ;
- WrErr1 (CHR (10))
- END Fault ;
- (* --------------------------------------------------- widths and sign bits *)
- PROCEDURE Sz (w : CARDINAL) : CARDINAL ;
- BEGIN
- IF w = 1 THEN RETURN 65536 ELSE RETURN 256 END
- END Sz ;
- (* The value with only the sign bit set, for a byte (80H) or word (8000H). *)
- PROCEDURE Sg (w : CARDINAL) : CARDINAL ;
- BEGIN
- IF w = 1 THEN RETURN 8000H ELSE RETURN 80H END
- END Sg ;
- (* A signed byte widened to a 16-bit two's-complement CARDINAL, so that
- adding it does the right thing modulo 65536. 128 -> 65408 = -128. *)
- PROCEDURE SE8 (v : CARDINAL) : CARDINAL ;
- BEGIN
- IF v >= 80H THEN RETURN v + 65280 ELSE RETURN v END
- END SE8 ;
- (* Signed interpretations, for MUL/IMUL/DIV/IDIV. *)
- PROCEDURE S16 (v : CARDINAL) : LONGINT ;
- BEGIN
- IF v >= 8000H THEN
- RETURN VAL (LONGINT, v) - VAL (LONGINT, 65536)
- ELSE
- RETURN VAL (LONGINT, v)
- END
- END S16 ;
- PROCEDURE S8 (v : CARDINAL) : LONGINT ;
- BEGIN
- IF v >= 80H THEN
- RETURN VAL (LONGINT, v) - VAL (LONGINT, 256)
- ELSE
- RETURN VAL (LONGINT, v)
- END
- END S8 ;
- (* --------------------------------------------------------- memory and stack *)
- PROCEDURE MemWr (a, v, w : CARDINAL) ;
- BEGIN
- mem [a] := v MOD 256 ;
- IF w = 1 THEN
- mem [(a + 1) MOD 65536] := (v DIV 256) MOD 256
- END
- END MemWr ;
- PROCEDURE MemRd (a, w : CARDINAL) : CARDINAL ;
- BEGIN
- IF w = 1 THEN
- RETURN mem [a] + 256 * mem [(a + 1) MOD 65536]
- ELSE
- RETURN mem [a]
- END
- END MemRd ;
- PROCEDURE Push (v : CARDINAL) ;
- BEGIN
- SP := (SP + 65534) MOD 65536 ; (* SP - 2, wrapped *)
- MemWr (SP, v, 1)
- END Push ;
- PROCEDURE Pop () : CARDINAL ;
- VAR v : CARDINAL ;
- BEGIN
- v := MemRd (SP, 1) ;
- SP := (SP + 2) MOD 65536 ;
- RETURN v
- END Pop ;
- PROCEDURE Fetch8 () : CARDINAL ;
- VAR v : CARDINAL ;
- BEGIN
- v := mem [IP] ;
- IP := (IP + 1) MOD 65536 ;
- RETURN v
- END Fetch8 ;
- PROCEDURE Fetch16 () : CARDINAL ;
- VAR a, b : CARDINAL ;
- BEGIN
- a := Fetch8 () ;
- b := Fetch8 () ;
- RETURN a + 256 * b
- END Fetch16 ;
- (* --------------------------------------------------------------- registers *)
- PROCEDURE GetReg (r, w : CARDINAL) : CARDINAL ;
- BEGIN
- IF w = 1 THEN
- CASE r OF
- | 0 : RETURN AX
- | 1 : RETURN CX
- | 2 : RETURN DX
- | 3 : RETURN BX
- | 4 : RETURN SP
- | 5 : RETURN BP
- | 6 : RETURN SI
- ELSE RETURN DI
- END
- ELSE
- CASE r OF
- | 0 : RETURN AX MOD 256 (* AL *)
- | 1 : RETURN CX MOD 256 (* CL *)
- | 2 : RETURN DX MOD 256 (* DL *)
- | 3 : RETURN BX MOD 256 (* BL *)
- | 4 : RETURN AX DIV 256 (* AH *)
- | 5 : RETURN CX DIV 256 (* CH *)
- | 6 : RETURN DX DIV 256 (* DH *)
- ELSE RETURN BX DIV 256 (* BH *)
- END
- END
- END GetReg ;
- PROCEDURE PutReg (r, w, v : CARDINAL) ;
- VAR hi, lo : CARDINAL ;
- BEGIN
- IF w = 1 THEN
- CASE r OF
- | 0 : AX := v
- | 1 : CX := v
- | 2 : DX := v
- | 3 : BX := v
- | 4 : SP := v
- | 5 : BP := v
- | 6 : SI := v
- ELSE DI := v
- END
- ELSE
- lo := v MOD 256 ;
- CASE r OF
- | 0 : AX := (AX DIV 256) * 256 + lo (* AL *)
- | 1 : CX := (CX DIV 256) * 256 + lo (* CL *)
- | 2 : DX := (DX DIV 256) * 256 + lo (* DL *)
- | 3 : BX := (BX DIV 256) * 256 + lo (* BL *)
- | 4 : hi := lo * 256 ; AX := (AX MOD 256) + hi (* AH *)
- | 5 : hi := lo * 256 ; CX := (CX MOD 256) + hi (* CH *)
- | 6 : hi := lo * 256 ; DX := (DX MOD 256) + hi (* DH *)
- ELSE hi := lo * 256 ; BX := (BX MOD 256) + hi (* BH *)
- END
- END
- END PutReg ;
- (* ---------------------------------------------------------- addressing mode *)
- PROCEDURE DoModRM (w : CARDINAL) ;
- (* Fetch the ModR/M byte and, if the operand is in memory, its displacement.
- `w' is only needed so the caller can size the access afterwards; the
- address is the same either way.
- The 16-bit addressing modes, since this is the part where a wrong table
- still decodes cleanly and just reads the wrong variable:
- rm mod=00 mod=01/10
- 0 [BX+SI] [BX+SI+disp]
- 1 [BX+DI] [BX+DI+disp]
- 2 [BP+SI] [BP+SI+disp]
- 3 [BP+DI] [BP+DI+disp]
- 4 [SI] [SI+disp]
- 5 [DI] [DI+disp]
- 6 [disp16] [BP+disp] <- mod=00,rm=6 is the ONLY direct form
- 7 [BX] [BX+disp]
- Displacements are signed, and adding them modulo 65536 is exactly two's
- complement addition, so no branch is needed for a negative one. SE8
- handles the disp8 case by widening it first. *)
- VAR b, md, rg, rm, base, d : CARDINAL ;
- BEGIN
- b := Fetch8 () ;
- md := b DIV 64 ;
- rg := (b DIV 8) MOD 8 ;
- rm := b MOD 8 ;
- rmReg := rg ;
- IF md = 3 THEN
- eaIsReg := TRUE ;
- eaReg := rm ;
- eaAddr := 0
- ELSE
- eaIsReg := FALSE ;
- eaReg := 0 ;
- CASE rm OF
- | 0 : base := (BX + SI) MOD 65536
- | 1 : base := (BX + DI) MOD 65536
- | 2 : base := (BP + SI) MOD 65536
- | 3 : base := (BP + DI) MOD 65536
- | 4 : base := SI
- | 5 : base := DI
- | 6 : base := BP
- ELSE base := BX
- END ;
- IF (md = 0) AND (rm = 6) THEN
- eaAddr := Fetch16 ()
- ELSIF md = 0 THEN
- eaAddr := base
- ELSIF md = 1 THEN
- d := Fetch8 () ;
- eaAddr := (base + SE8 (d)) MOD 65536
- ELSE
- d := Fetch16 () ;
- eaAddr := (base + d) MOD 65536
- END
- END
- END DoModRM ;
- PROCEDURE RmRd (w : CARDINAL) : CARDINAL ;
- BEGIN
- IF eaIsReg THEN
- RETURN GetReg (eaReg, w)
- ELSE
- RETURN MemRd (eaAddr, w)
- END
- END RmRd ;
- PROCEDURE RmWr (v, w : CARDINAL) ;
- BEGIN
- IF eaIsReg THEN
- PutReg (eaReg, w, v)
- ELSE
- MemWr (eaAddr, v, w)
- END
- END RmWr ;
- (* ------------------------------------------------------------------- flags *)
- PROCEDURE SetZSF (r, w : CARDINAL) ;
- (* ZF and SF from the result. PF is deliberately absent: see the header. *)
- BEGIN
- ZF := r = 0 ;
- SF := r >= Sg (w)
- END SetZSF ;
- PROCEDURE DoAdd (a, b, cin, w : CARDINAL) : CARDINAL ;
- (* a + b + cin, setting the flags. raw is at most 65535+65535+1, so it
- never overflows the 32-bit CARDINAL and the carry can be read off it
- directly rather than inferred from a wrapped result. *)
- VAR raw, res, sz : CARDINAL ;
- ex : LONGINT ;
- BEGIN
- sz := Sz (w) ;
- raw := a + b + cin ;
- res := raw MOD sz ;
- CF := raw >= sz ;
- (* OF: the exact mathematical result left the signed range. Computing it
- exactly, rather than from the signs of the two operands, is what makes
- ADC work too: the carry can turn 127+0 into 128, and the two sign bits
- alone say nothing about that. S16/S8 are the signed readings of a
- value at this width, so the whole test is one addition and a range
- check. *)
- IF w = 1 THEN
- ex := S16 (a) + S16 (b) + VAL (LONGINT, cin) ;
- OFl := (ex < VAL (LONGINT, -32768)) OR (ex > VAL (LONGINT, 32767))
- ELSE
- ex := S8 (a) + S8 (b) + VAL (LONGINT, cin) ;
- OFl := (ex < VAL (LONGINT, -128)) OR (ex > VAL (LONGINT, 127))
- END ;
- SetZSF (res, w) ;
- RETURN res
- END DoAdd ;
- PROCEDURE DoSub (a, b, cin, w : CARDINAL) : CARDINAL ;
- (* Subtracts b and a borrow. Same reason for computing OF exactly: with
- cin = 1 and b = 127 the subtrahend is really 128, whose sign bit is not
- the sign bit of b, so the sign-bit rule gets SBB wrong. *)
- VAR sz, res, bb : CARDINAL ;
- ex : LONGINT ;
- BEGIN
- sz := Sz (w) ;
- bb := b + cin ;
- IF a >= bb THEN
- res := a - bb ; (* CARDINAL is 32-bit, so this cannot wrap *)
- CF := FALSE
- ELSE
- res := a + sz - bb ; (* a + sz >= bb always, so still non-negative *)
- CF := TRUE
- END ;
- res := res MOD sz ;
- IF w = 1 THEN
- ex := S16 (a) - S16 (b) - VAL (LONGINT, cin) ;
- OFl := (ex < VAL (LONGINT, -32768)) OR (ex > VAL (LONGINT, 32767))
- ELSE
- ex := S8 (a) - S8 (b) - VAL (LONGINT, cin) ;
- OFl := (ex < VAL (LONGINT, -128)) OR (ex > VAL (LONGINT, 127))
- END ;
- SetZSF (res, w) ;
- RETURN res
- END DoSub ;
- PROCEDURE SetLogic (r, w : CARDINAL) ;
- BEGIN
- CF := FALSE ;
- OFl := FALSE ;
- SetZSF (r, w)
- END SetLogic ;
- (* Bitwise operations. This dialect has NO bitwise operators at all - no
- BITAND, no BAND, no `&' - so sets stand in for them, which is the same
- trick Compiler.mod uses for its own constant folding (BitAnd/BitOr/BitNot
- there are three lines of this). `+' is union, `*' is intersection,
- `-` is difference, so XOR is union minus intersection. *)
- PROCEDURE BAnd (a, b, w : CARDINAL) : CARDINAL ;
- BEGIN
- IF w = 1 THEN
- RETURN CARDINAL (VAL (BITSET, a) * VAL (BITSET, b))
- ELSE
- RETURN CARDINAL (VAL (BITSET, a MOD 256) * VAL (BITSET, b MOD 256))
- END
- END BAnd ;
- PROCEDURE BOr (a, b, w : CARDINAL) : CARDINAL ;
- BEGIN
- IF w = 1 THEN
- RETURN CARDINAL (VAL (BITSET, a) + VAL (BITSET, b))
- ELSE
- RETURN CARDINAL (VAL (BITSET, a MOD 256) + VAL (BITSET, b MOD 256))
- END
- END BOr ;
- PROCEDURE BXor (a, b, w : CARDINAL) : CARDINAL ;
- VAR u, i : BITSET ;
- BEGIN
- IF w = 1 THEN
- u := VAL (BITSET, a) + VAL (BITSET, b) ;
- i := VAL (BITSET, a) * VAL (BITSET, b)
- ELSE
- u := VAL (BITSET, a MOD 256) + VAL (BITSET, b MOD 256) ;
- i := VAL (BITSET, a MOD 256) * VAL (BITSET, b MOD 256)
- END ;
- RETURN CARDINAL (u - i)
- END BXor ;
- PROCEDURE BNot (a, w : CARDINAL) : CARDINAL ;
- BEGIN
- IF w = 1 THEN
- RETURN CARDINAL (VAL (BITSET, 0FFFFH) - VAL (BITSET, a))
- ELSE
- RETURN CARDINAL (VAL (BITSET, 0FFH) - VAL (BITSET, a MOD 256))
- END
- END BNot ;
- PROCEDURE Alu (op, a, b, w : CARDINAL) : CARDINAL ;
- (* op is the group's reg field: 0 ADD 1 OR 2 ADC 3 SBB 4 AND 5 SUB 6 XOR
- 7 CMP. The result is returned for every op, including CMP; the caller
- decides whether to write it back, which is what the /r bit of the
- instruction already says. *)
- VAR cin, res : CARDINAL ;
- BEGIN
- IF (op = 2) OR (op = 3) THEN
- IF CF THEN cin := 1 ELSE cin := 0 END
- ELSE
- cin := 0
- END ;
- CASE op OF
- | 0 : res := DoAdd (a, b, cin, w)
- | 1 : res := BOr (a, b, w) ; SetLogic (res, w)
- | 2 : res := DoAdd (a, b, cin, w)
- | 3 : res := DoSub (a, b, cin, w)
- | 4 : res := BAnd (a, b, w) ; SetLogic (res, w)
- | 5 : res := DoSub (a, b, cin, w)
- | 6 : res := BXor (a, b, w) ; SetLogic (res, w)
- ELSE res := DoSub (a, b, cin, w)
- END ;
- RETURN res
- END Alu ;
- PROCEDURE Cond (n : CARDINAL) : BOOLEAN ;
- (* The 16 conditions. 0AH and 0BH need PF, which this machine does not
- maintain, so they fault instead of answering. *)
- VAR r : BOOLEAN ;
- BEGIN
- CASE n OF
- | 0H : r := OFl (* JO *)
- | 1H : r := NOT OFl (* JNO *)
- | 2H : r := CF (* JB *)
- | 3H : r := NOT CF (* JAE *)
- | 4H : r := ZF (* JE *)
- | 5H : r := NOT ZF (* JNE *)
- | 6H : r := CF OR ZF (* JBE *)
- | 7H : r := NOT (CF OR ZF) (* JA *)
- | 8H : r := SF (* JS *)
- | 9H : r := NOT SF (* JNS *)
- | 0AH : Fault ("parity flag is not maintained, so JP cannot be evaluated")
- ; r := FALSE
- | 0BH : Fault ("parity flag is not maintained, so JNP cannot be evaluated")
- ; r := FALSE
- | 0CH : r := SF # OFl (* JL *)
- | 0DH : r := SF = OFl (* JGE *)
- | 0EH : r := ZF OR (SF # OFl) (* JLE *)
- ELSE r := (NOT ZF) AND (SF = OFl) (* JG *)
- END ;
- RETURN r
- END Cond ;
- (* ----------------------------------------------------------------- INT 21h *)
- PROCEDURE DoInt21 ;
- (* The four services bootcom.s provides and the runtime calls, and nothing
- else. Anything else faults, because the alternative is inventing DOS.
- No flag is touched anywhere in here: IRET restores the caller's FLAGS, so
- neither this code nor bootcom's CLC/STC is visible to the guest. *)
- VAR ah, b, a, n, lim, stop : CARDINAL ;
- c : CHAR ;
- running : BOOLEAN ;
- BEGIN
- ah := AX DIV 256 ;
- CASE ah OF
- | 02H : (* display character in AL *)
- c := CHR (AX MOD 256) ;
- n := write (STDOUT, ADR (c), 1)
- | 09H : (* $-terminated string at DS:DX *)
- a := DX ;
- running := TRUE ;
- lim := 0 ;
- WHILE running DO
- b := mem [a] ;
- IF b = 24H THEN (* '$' *)
- running := FALSE
- ELSE
- c := CHR (b) ;
- n := write (STDOUT, ADR (c), 1) ;
- a := (a + 1) MOD 65536 ;
- lim := lim + 1 ;
- IF lim > 65536 THEN
- Fault ("INT 21h AH=09: walked the whole address space with no $") ;
- running := FALSE
- END
- END
- END
- | 08H : (* read a character, no echo *)
- n := read (STDIN, ADR (c), 1) ;
- IF n = 1 THEN
- b := ORD (c)
- ELSE
- b := 1AH (* end of input, as bootcom does *)
- END ;
- AX := (AX DIV 256) * 256 + b (* AL only: AH is the function *)
- | 04CH : (* terminate *)
- halted := TRUE ;
- haltCode := AX MOD 256
- ELSE
- stop := ah ;
- WrS ("exec86: INT 21h function ") ;
- WrHex (stop) ;
- WrS (" is not implemented") ;
- Fault ("unsupported INT 21h function")
- END
- END DoInt21 ;
- (* ------------------------------------------------------ F6/F7 unary group *)
- PROCEDURE DoUnaryGroup (w : CARDINAL) ;
- (* F6/F7, after DoModRM has run. reg selects:
- /0 /1 TEST /2 NOT /3 NEG /4 MUL /5 IMUL /6 DIV /7 IDIV *)
- VAR k, src, res, prod, q, remw : CARDINAL ;
- ma, mb, mq, mr, ldv, ldd, lq, lr : LONGINT ;
- neg : BOOLEAN ;
- BEGIN
- k := rmReg ;
- CASE k OF
- | 0, 1 : (* TEST r/m, imm *)
- IF w = 0 THEN src := Fetch8 () ELSE src := Fetch16 () END ;
- SetLogic (BAnd (RmRd (w), src, w), w)
- | 2 : (* NOT: no flags at all *)
- res := BNot (RmRd (w), w) ;
- RmWr (res, w)
- | 3 : (* NEG *)
- res := DoSub (0, RmRd (w), 0, w) ;
- RmWr (res, w)
- | 4 : (* MUL, unsigned *)
- src := RmRd (w) ;
- IF w = 1 THEN
- prod := AX * src ; (* max 65535^2 < 2^32 *)
- DX := prod DIV 65536 ;
- AX := prod MOD 65536 ;
- res := AX
- ELSE
- (* MUL r8 writes only AX: AL*src, high byte in AH, DX untouched.
- It is easy to write DX := 0 here out of a false tidiness. *)
- prod := (AX MOD 256) * src ;
- AX := prod MOD 65536 ;
- res := AX
- END ;
- (* CF and OF are the documented ones for MUL; SF and ZF are
- architecturally undefined and are set from the low result rather
- than left alone, so that they are at least deterministic. *)
- IF w = 1 THEN
- CF := DX # 0
- ELSE
- CF := prod >= 256
- END ;
- OFl := CF ;
- SetZSF (res, w)
- | 5 : (* IMUL, signed *)
- src := RmRd (w) ;
- IF w = 1 THEN
- ldd := S16 (AX) * S16 (src) ;
- lq := ldd
- ELSE
- ldd := S8 (AX MOD 256) * S8 (src) ;
- lq := ldd
- END ;
- (* The low word of a negative product is exactly lq MOD 65536, because
- ISO Modula-2's MOD always returns a non-negative remainder, which is
- the two's complement low word. The high word is lq DIV 65536 for
- the same reason: DIV floors, which is an arithmetic shift. *)
- AX := VAL (CARDINAL, lq MOD VAL (LONGINT, 65536)) ;
- IF w = 1 THEN
- DX := VAL (CARDINAL, (lq DIV VAL (LONGINT, 65536))
- MOD VAL (LONGINT, 65536)) ;
- CF := (lq < VAL (LONGINT, -32768))
- OR (lq > VAL (LONGINT, 32767)) ;
- res := AX
- ELSE
- CF := (lq < VAL (LONGINT, -128))
- OR (lq > VAL (LONGINT, 127)) ;
- res := AX
- END ;
- OFl := CF ;
- SetZSF (res, w)
- | 6 : (* DIV, unsigned *)
- src := RmRd (w) ;
- IF src = 0 THEN
- Fault ("divide by zero")
- ELSE
- IF w = 1 THEN
- prod := DX * 65536 + AX ;
- q := prod DIV src ;
- remw := prod MOD src ;
- IF q > 65535 THEN
- Fault ("DIV: quotient does not fit in AX")
- ELSE
- AX := q ;
- DX := remw ;
- res := AX
- END
- ELSE
- prod := AX MOD 65536 ;
- q := prod DIV src ;
- remw := prod MOD src ;
- IF q > 255 THEN
- Fault ("DIV: quotient does not fit in AL")
- ELSE
- AX := remw * 256 + q ;
- res := q
- END
- END ;
- IF NOT isFault THEN
- CF := FALSE ;
- OFl := FALSE ;
- SetZSF (res, w)
- END
- END
- ELSE (* /7 IDIV, signed *)
- src := RmRd (w) ;
- IF w = 1 THEN
- ldv := S16 (src) ;
- ldd := S16 (DX) * VAL (LONGINT, 65536) + VAL (LONGINT, AX)
- ELSE
- ldv := S8 (src) ;
- ldd := S8 (AX MOD 256)
- END ;
- IF ldv = VAL (LONGINT, 0) THEN
- Fault ("divide by zero")
- ELSE
- (* x86 IDIV truncates toward zero and gives the remainder the
- sign of the dividend. ISO Modula-2's DIV/MOD are Euclidean:
- the remainder is always non-negative (measured: (-7) MOD 2 = 1,
- 7 MOD (-2) = 1, (-7) DIV 2 = -4). So the division is redone on
- magnitudes, where floor and truncation coincide, and the signs
- are put back afterwards. *)
- IF ldd < VAL (LONGINT, 0) THEN ma := VAL (LONGINT, 0) - ldd
- ELSE ma := ldd END ;
- IF ldv < VAL (LONGINT, 0) THEN mb := VAL (LONGINT, 0) - ldv
- ELSE mb := ldv END ;
- mq := ma DIV mb ;
- mr := ma MOD mb ;
- neg := (ldd < VAL (LONGINT, 0)) # (ldv < VAL (LONGINT, 0)) ;
- IF neg THEN lq := VAL (LONGINT, 0) - mq ELSE lq := mq END ;
- IF ldd < VAL (LONGINT, 0) THEN lr := VAL (LONGINT, 0) - mr
- ELSE lr := mr END ;
- IF w = 1 THEN
- IF (lq < VAL (LONGINT, -32768)) OR (lq > VAL (LONGINT, 32767)) THEN
- Fault ("IDIV: quotient does not fit in AX")
- ELSE
- AX := VAL (CARDINAL, lq MOD VAL (LONGINT, 65536)) ;
- DX := VAL (CARDINAL, lr MOD VAL (LONGINT, 65536)) ;
- SetZSF (AX, 1)
- END
- ELSE
- IF (lq < VAL (LONGINT, -128)) OR (lq > VAL (LONGINT, 127)) THEN
- Fault ("IDIV: quotient does not fit in AL")
- ELSE
- (* IDIV r8 also overwrites both halves: AL := quotient,
- AH := remainder, so the old AH is gone. *)
- AX := VAL (CARDINAL, lr MOD VAL (LONGINT, 256)) * 256
- + VAL (CARDINAL, lq MOD VAL (LONGINT, 256)) ;
- SetZSF (VAL (CARDINAL, lq MOD VAL (LONGINT, 256)), 0)
- END
- END ;
- IF NOT isFault THEN
- CF := FALSE ;
- OFl := FALSE
- END
- END
- END
- END DoUnaryGroup ;
- (* --------------------------------------------------------- FF group (word) *)
- PROCEDURE DoFFGroup ;
- (* FF, after DoModRM has run with w = 1. *)
- VAR k, res : CARDINAL ;
- cfSave : BOOLEAN ;
- BEGIN
- k := rmReg ;
- CASE k OF
- | 0 : (* INC r/m: CF is preserved *)
- cfSave := CF ;
- res := DoAdd (RmRd (1), 1, 0, 1) ;
- CF := cfSave ;
- RmWr (res, 1)
- | 1 : (* DEC r/m: CF is preserved *)
- cfSave := CF ;
- res := DoSub (RmRd (1), 1, 0, 1) ;
- CF := cfSave ;
- RmWr (res, 1)
- | 2 : (* CALL near r/m *)
- Push (IP) ;
- IP := RmRd (1)
- | 4 : (* JMP near r/m *)
- IP := RmRd (1)
- | 6 : (* PUSH r/m *)
- Push (RmRd (1))
- ELSE
- Fault ("unsupported opcode in the FF group (far call/jump)")
- END
- END DoFFGroup ;
- (* ----------------------------------------------------------- one instruction *)
- PROCEDURE Step ;
- VAR op, w, f, alu, v, d, imm, res, n, k : CARDINAL ;
- cfSave : BOOLEAN ;
- BEGIN
- faultIP := IP ; (* what to report if we fault *)
- op := Fetch8 () ;
- IF (op = 06H) OR (op = 0EH) OR (op = 16H) OR (op = 1EH) THEN
- (* PUSH ES / CS / SS / DS. CS and SS are pushed by nothing in the
- corpus, but the four encodings are one instruction apart and
- leaving two of them out would be a gap nobody could explain. *)
- IF op = 06H THEN Push (ES)
- ELSIF op = 0EH THEN Push (CS)
- ELSIF op = 16H THEN Push (SS)
- ELSE Push (DS)
- END
- ELSIF (op = 07H) OR (op = 17H) OR (op = 1FH) THEN
- IF op = 07H THEN ES := Pop ()
- ELSIF op = 17H THEN SS := Pop ()
- ELSE DS := Pop ()
- END
- ELSIF (op = 26H) OR (op = 2EH) OR (op = 36H) OR (op = 3EH) THEN
- Fault ("segment override prefix: this interpreter is 64 KB flat")
- ELSIF op < 40H THEN
- (* The 00-3D family: eight ALU operations in six encodings each.
- `alu' is the group number, `f' the form.
- f = 0,1 op r/m, r f = 2,3 op r, r/m
- f = 4 op AL, imm8 f = 5 op AX, imm16
- f = 6,7 are the prefixes and DAA/DAS/AAA/AAS, all consumed above
- or unreachable, so reaching here is a real unknown. *)
- alu := (op DIV 8) MOD 8 ;
- f := op MOD 8 ;
- IF f <= 3 THEN
- w := f MOD 2 ;
- DoModRM (w) ;
- IF f <= 1 THEN
- res := Alu (alu, RmRd (w), GetReg (rmReg, w), w) ;
- IF alu # 7 THEN RmWr (res, w) END
- ELSE
- res := Alu (alu, GetReg (rmReg, w), RmRd (w), w) ;
- IF alu # 7 THEN PutReg (rmReg, w, res) END
- END
- ELSIF f = 4 THEN
- v := Fetch8 () ;
- res := Alu (alu, AX MOD 256, v, 0) ;
- IF alu # 7 THEN PutReg (0, 0, res) END
- ELSIF f = 5 THEN
- v := Fetch16 () ;
- res := Alu (alu, AX, v, 1) ;
- IF alu # 7 THEN PutReg (0, 1, res) END
- ELSE
- Fault ("unknown opcode in the ALU family")
- END
- ELSIF op < 50H THEN
- (* INC r16 / DEC r16. These do NOT affect CF, which is easy to lose:
- going through DoAdd sets it, so it is saved and restored. *)
- k := op - 40H ;
- cfSave := CF ;
- IF k < 8 THEN
- res := DoAdd (GetReg (k, 1), 1, 0, 1) ;
- PutReg (k, 1, res)
- ELSE
- res := DoSub (GetReg (k - 8, 1), 1, 0, 1) ;
- PutReg (k - 8, 1, res)
- END ;
- CF := cfSave
- ELSIF op < 60H THEN
- IF op < 58H THEN
- Push (GetReg (op - 50H, 1))
- ELSE
- PutReg (op - 58H, 1, Pop ())
- END
- ELSIF op < 70H THEN
- Fault ("opcode not implemented (60H-6FH is 80186 and later)")
- ELSIF op < 80H THEN
- d := Fetch8 () ;
- IF Cond (op - 70H) THEN
- IP := (IP + SE8 (d)) MOD 65536
- END
- ELSIF (op = 80H) OR (op = 81H) OR (op = 83H) THEN
- IF op = 80H THEN w := 0 ELSE w := 1 END ;
- DoModRM (w) ;
- k := rmReg ;
- IF op = 83H THEN
- imm := SE8 (Fetch8 ()) (* sign-extended to the full width *)
- ELSIF w = 0 THEN
- imm := Fetch8 ()
- ELSE
- imm := Fetch16 ()
- END ;
- res := Alu (k, RmRd (w), imm, w) ;
- IF k # 7 THEN RmWr (res, w) END
- ELSIF (op = 84H) OR (op = 85H) THEN
- (* TEST r/m, r. Same AND and same flag rule as F6/F7 /0; only the
- encoding differs, and leaving it out while having the other one
- would be a gap with no reason behind it. *)
- w := op - 84H ;
- DoModRM (w) ;
- SetLogic (BAnd (RmRd (w), GetReg (rmReg, w), w), w)
- ELSIF (op = 86H) OR (op = 87H) THEN
- w := op - 86H ;
- DoModRM (w) ;
- v := RmRd (w) ;
- RmWr (GetReg (rmReg, w), w) ;
- PutReg (rmReg, w, v)
- ELSIF (op >= 88H) AND (op <= 8BH) THEN
- w := op MOD 2 ;
- DoModRM (w) ;
- IF op >= 8AH THEN
- PutReg (rmReg, w, RmRd (w))
- ELSE
- RmWr (GetReg (rmReg, w), w)
- END
- ELSIF op = 8DH THEN
- DoModRM (1) ;
- IF eaIsReg THEN
- Fault ("LEA with a register operand is not an address")
- ELSE
- PutReg (rmReg, 1, eaAddr)
- END
- ELSIF (op >= 90H) AND (op <= 97H) THEN
- k := op - 90H ;
- IF k # 0 THEN (* 90 is NOP *)
- v := AX ;
- AX := GetReg (k, 1) ;
- PutReg (k, 1, v)
- END
- ELSIF op = 98H THEN (* CBW: sign-extend AL into AX *)
- IF (AX MOD 256) >= 80H THEN
- AX := 65280 + (AX MOD 256)
- ELSE
- AX := AX MOD 256
- END
- ELSIF op = 99H THEN (* CWD: sign-extend AX into DX *)
- IF AX >= 8000H THEN DX := 0FFFFH ELSE DX := 0 END
- ELSIF (op >= 0A0H) AND (op <= 0A3H) THEN
- d := Fetch16 () ; (* moffs: DS is 0, checked in Run86 *)
- CASE op OF
- | 0A0H : AX := (AX DIV 256) * 256 + mem [d]
- | 0A1H : AX := mem [d] + 256 * mem [(d + 1) MOD 65536]
- | 0A2H : mem [d] := AX MOD 256
- ELSE mem [d] := AX MOD 256 ;
- mem [(d + 1) MOD 65536] := (AX DIV 256) MOD 256
- END
- ELSIF (op >= 0B0H) AND (op <= 0BFH) THEN
- IF op < 0B8H THEN
- PutReg (op - 0B0H, 0, Fetch8 ())
- ELSE
- PutReg (op - 0B8H, 1, Fetch16 ())
- END
- ELSIF op = 0C3H THEN (* RET *)
- IP := Pop ()
- ELSIF op = 0C9H THEN (* LEAVE: SP := BP; BP := POP *)
- SP := BP ;
- BP := Pop ()
- ELSIF op = 0CDH THEN (* INT *)
- n := Fetch8 () ;
- IF n = 21H THEN
- DoInt21 ()
- ELSE
- Fault ("only INT 21h is provided; this is not a real-mode machine")
- END
- ELSIF op = 0E2H THEN (* LOOP: CX is not a flag *)
- d := Fetch8 () ;
- CX := (CX + 65535) MOD 65536 ;
- IF CX # 0 THEN
- IP := (IP + SE8 (d)) MOD 65536
- END
- ELSIF op = 0E3H THEN (* JCXZ *)
- d := Fetch8 () ;
- IF CX = 0 THEN
- IP := (IP + SE8 (d)) MOD 65536
- END
- ELSIF op = 0E8H THEN (* CALL rel16 *)
- d := Fetch16 () ;
- Push (IP) ;
- IP := (IP + d) MOD 65536
- ELSIF op = 0E9H THEN (* JMP rel16 *)
- d := Fetch16 () ;
- IP := (IP + d) MOD 65536
- ELSIF op = 0EBH THEN (* JMP rel8 *)
- d := Fetch8 () ;
- IP := (IP + SE8 (d)) MOD 65536
- ELSIF (op = 0F6H) OR (op = 0F7H) THEN
- DoModRM (op - 0F6H) ;
- DoUnaryGroup (op - 0F6H)
- ELSIF op = 0FFH THEN
- DoModRM (1) ;
- DoFFGroup
- ELSE
- WrS ("exec86: unknown opcode ") ;
- WrHex (op) ;
- WrS (" at ") ;
- WrHex (faultIP) ;
- WrErr1 (CHR (10)) ;
- Fault ("unknown opcode")
- END
- END Step ;
- (* ------------------------------------------------------------------ public *)
- PROCEDURE Clear86 ;
- VAR i : CARDINAL ;
- BEGIN
- FOR i := 0 TO 65535 DO
- mem [i] := 0
- END ;
- AX := 0 ; BX := 0 ; CX := 0 ; DX := 0 ;
- SI := 0 ; DI := 0 ; BP := 0 ;
- SP := 0FFFEH ; (* bootcom's SS:SP = 0000:FFFE *)
- CS := 0 ; DS := 0 ; ES := 0 ; SS := 0 ;
- IP := LoadAt ; (* bootcom's JMP 0000:0100 *)
- CF := FALSE ; ZF := FALSE ; SF := FALSE ; OFl := FALSE ;
- halted := FALSE ;
- haltCode := 0 ;
- isFault := FALSE ;
- faultIP := LoadAt ;
- loadHi := LoadAt ;
- stepCnt := 0 ;
- eaAddr := 0 ; eaReg := 0 ; eaIsReg := FALSE ; rmReg := 0
- END Clear86 ;
- PROCEDURE Poke86 (addr, value : CARDINAL) ;
- BEGIN
- IF addr > 65535 THEN
- faultIP := IP ;
- Fault ("Poke address is outside the 64 KB machine")
- ELSE
- mem [addr] := value MOD 256 ;
- IF addr >= loadHi THEN
- loadHi := addr + 1
- END
- END
- END Poke86 ;
- PROCEDURE Run86 (VAR exitCode : CARDINAL; VAR steps : LONGCARD) : CARDINAL ;
- VAR cap : LONGCARD ;
- BEGIN
- exitCode := 0 ;
- steps := 0 ;
- isFault := FALSE ;
- halted := FALSE ;
- cap := VAL (LONGCARD, MaxSteps) ;
- LOOP
- IF halted THEN
- exitCode := haltCode ;
- steps := stepCnt ;
- RETURN 0
- END ;
- IF isFault THEN
- steps := stepCnt ;
- RETURN 1
- END ;
- IF stepCnt >= cap THEN
- faultIP := IP ;
- WrS ("exec86: step limit ") ;
- WrDec (MaxSteps) ; (* runaway guard, Exec86.def *)
- WrS (" reached at IP=") ;
- WrHex (IP) ;
- WrErr1 (CHR (10)) ;
- steps := stepCnt ;
- RETURN 2
- END ;
- (* Checked before every step, not once at the start: the machine is
- 64 KB flat, so a non-zero segment would silently alias onto the
- same memory instead of faulting. *)
- IF (CS # 0) OR (DS # 0) OR (ES # 0) OR (SS # 0) THEN
- faultIP := IP ;
- Fault ("a segment register is not zero; this machine is 64 KB flat") ;
- steps := stepCnt ;
- RETURN 1
- END ;
- IF (IP < LoadAt) OR (IP >= loadHi) THEN
- faultIP := IP ;
- Fault ("execution left the loaded image") ;
- steps := stepCnt ;
- RETURN 1
- END ;
- Step () ;
- stepCnt := stepCnt + 1
- END
- END Run86 ;
- END Exec86.
|