Runtime.mod 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. IMPLEMENTATION MODULE Runtime ;
  2. (* 8086 runtime library, assembled byte by byte. Each little emitter below is
  3. one 8086 instruction; the ModR/M byte is spelled out in the comment so the
  4. encoding can be checked by hand against an 8086 table. This is the same
  5. approach the compiler itself takes in Compiler.mod (Ebyte/Eword/EmCall),
  6. so the library needs no external assembler and no binary artifact in the
  7. tree - RT_Build assembles it every run, which is why the entry offsets are
  8. known exactly and are not guesses.
  9. Memory model: a .COM image, so CS = DS = ES = SS = 0 and the whole thing
  10. lives in one 64K segment. The runtime occupies offsets 0..RT_Size-1, the
  11. generated program follows, and the program's globals follow at
  12. RT_Size + 1000H. Runtime data therefore sits at fixed low offsets.
  13. Calling conventions, matching Compiler.IoCall:
  14. WrInt/WrChar/WrBool/WrReal one 16-bit value on the stack (caller pops)
  15. RdInt/RdChar/RdBool one address on the stack (caller pops)
  16. WrLn/RdLn/StackChk nothing
  17. InitMem AX = offset of the program header word block
  18. ProgEnd/Halt nothing; exits with code 0
  19. All entries preserve BP and SP, and every register except the documented
  20. result, so they can be called from the middle of an expression. *)
  21. FROM SYSTEM IMPORT BYTE ;
  22. CONST
  23. MaxRt = 4096 ;
  24. MaxLbl = 64 ;
  25. MaxFix = 400 ;
  26. MaxNm = 15 ;
  27. (* offsets inside the runtime's own data block *)
  28. D_NUM = 0 ; (* 8 bytes, decimal conversion scratch *)
  29. D_TRUE = 8 ; (* "TRUE$" *)
  30. D_FALSE = 14 ; (* "FALSE$" *)
  31. D_CRLF = 21 ; (* CR LF '$' *)
  32. D_REAL = 24 ; (* "?REAL?" - reals are not formatted yet *)
  33. D_END = 31 ;
  34. TYPE
  35. LblRec = RECORD
  36. nm : ARRAY [0..MaxNm] OF CHAR ;
  37. off : CARDINAL ;
  38. END ;
  39. FixRec = RECORD
  40. kind : CARDINAL ; (* 0 = rel8, 1 = rel16, 2 = data address *)
  41. place : CARDINAL ; (* offset of the displacement/address field *)
  42. nm : ARRAY [0..MaxNm] OF CHAR ;
  43. val : CARDINAL ;
  44. END ;
  45. VAR
  46. rt : ARRAY [0..MaxRt - 1] OF BYTE ;
  47. rpos : CARDINAL ;
  48. lbl : ARRAY [0..MaxLbl - 1] OF LblRec ;
  49. ltop : CARDINAL ;
  50. fix : ARRAY [0..MaxFix - 1] OF FixRec ;
  51. nfix : CARDINAL ;
  52. dataAt : CARDINAL ;
  53. built : BOOLEAN ;
  54. entNm : ARRAY [0..13] OF ARRAY [0..MaxNm] OF CHAR ;
  55. (* ---------------------------------------------------------------- *)
  56. (* name helpers *)
  57. (* ---------------------------------------------------------------- *)
  58. PROCEDURE StrEq (a, b : ARRAY OF CHAR ) : BOOLEAN ;
  59. VAR i : CARDINAL ;
  60. BEGIN
  61. i := 0 ;
  62. WHILE (i <= HIGH (a)) AND (i <= HIGH (b)) DO
  63. IF a [i] # b [i] THEN
  64. RETURN FALSE
  65. END ;
  66. INC (i)
  67. END ;
  68. RETURN TRUE
  69. END StrEq ;
  70. PROCEDURE SetStr (VAR dst : ARRAY OF CHAR ; src : ARRAY OF CHAR ) ;
  71. VAR i : CARDINAL ;
  72. BEGIN
  73. i := 0 ;
  74. WHILE (i <= HIGH (dst)) AND (i <= HIGH (src)) DO
  75. dst [i] := src [i] ;
  76. INC (i)
  77. END ;
  78. IF i <= HIGH (dst) THEN
  79. dst [i] := 0C
  80. END
  81. END SetStr ;
  82. (* ---------------------------------------------------------------- *)
  83. (* primitive emitters *)
  84. (* ---------------------------------------------------------------- *)
  85. PROCEDURE B (b : CARDINAL ) ;
  86. (* emit exactly ONE byte. A two-byte opcode must be written as two B calls -
  87. B masks to 100H, so B (8BE4H) would silently emit just E4. *)
  88. BEGIN
  89. IF rpos >= MaxRt THEN
  90. RETURN (* blob is oversized: drop the byte *)
  91. END ;
  92. rt [rpos] := VAL (BYTE, b MOD 100H) ;
  93. INC (rpos)
  94. END B ;
  95. PROCEDURE W (w : CARDINAL ) ;
  96. BEGIN
  97. B (w MOD 100H) ;
  98. B ((w DIV 100H) MOD 100H) (* little endian *)
  99. END W ;
  100. PROCEDURE M (nm : ARRAY OF CHAR ) ;
  101. (* mark: nm is the current offset *)
  102. BEGIN
  103. IF ltop < MaxLbl THEN
  104. SetStr (lbl [ltop].nm, nm) ;
  105. lbl [ltop].off := rpos ;
  106. INC (ltop)
  107. END
  108. END M ;
  109. PROCEDURE AddFix (kind, place : CARDINAL ; nm : ARRAY OF CHAR ; val : CARDINAL ) ;
  110. BEGIN
  111. IF nfix < MaxFix THEN
  112. fix [nfix].kind := kind ;
  113. fix [nfix].place := place ;
  114. fix [nfix].val := val ;
  115. SetStr (fix [nfix].nm, nm) ;
  116. INC (nfix)
  117. END
  118. END AddFix ;
  119. PROCEDURE LblOff (nm : ARRAY OF CHAR ) : CARDINAL ;
  120. VAR i : CARDINAL ;
  121. BEGIN
  122. i := 0 ;
  123. WHILE i < ltop DO
  124. IF StrEq (lbl [i].nm, nm) THEN
  125. RETURN lbl [i].off
  126. END ;
  127. INC (i)
  128. END ;
  129. RETURN 0
  130. END LblOff ;
  131. PROCEDURE J8 (nm : ARRAY OF CHAR ) ;
  132. BEGIN
  133. B (0EBH) ; (* JMP rel8 *)
  134. AddFix (0, rpos, nm, 0) ;
  135. B (0)
  136. END J8 ;
  137. PROCEDURE C8 (nm : ARRAY OF CHAR ) ;
  138. BEGIN
  139. B (0E8H) ; (* CALL rel16 *)
  140. AddFix (1, rpos, nm, 0) ;
  141. W (0)
  142. END C8 ;
  143. PROCEDURE Jcc (code : CARDINAL ; nm : ARRAY OF CHAR ) ;
  144. BEGIN
  145. B (code) ; (* Jcc rel8 *)
  146. AddFix (0, rpos, nm, 0) ;
  147. B (0)
  148. END Jcc ;
  149. PROCEDURE Dd (delta : CARDINAL ) ;
  150. (* emit a 16-bit address into the runtime's data block; the value is only
  151. known once the data block has been placed, so it is a fixup *)
  152. BEGIN
  153. AddFix (2, rpos, "", delta) ;
  154. W (0)
  155. END Dd ;
  156. (* --- 8-bit / 16-bit register and memory forms, one instruction each --- *)
  157. (* THE 16-BIT ModR/M EFFECTIVE-ADDRESS TABLE, MEASURED NOT REMEMBERED.
  158. Every address form below was confirmed by EXECUTING it on a real 8086
  159. (qemu-system-i386) with a probe that stores a marker through the candidate
  160. encoding and then reports which physical address received it; see
  161. tests/probe/modrm19.s. The mod=11 column is not an address at all -- it
  162. names a register -- so it is confirmed instead by asking GNU as to
  163. ENCODE the eight register moves and checking FCML's decode of the result
  164. (tests/probe/modrm11.py, which also carries the hand-checking anchors).
  165. Do not "fix" any of these from memory. The version that comes to mind is
  166. wrong in exactly the cells called out below, and every one of those
  167. mistakes shipped as code that decoded cleanly:
  168. r/m mod=00 mod=01 mod=10 mod=11
  169. 000 [BX+SI] [BX+SI]+disp [BX+SI]+disp AX
  170. 001 [BX+DI] [BX+DI]+disp [BX+DI]+disp CX
  171. 010 [BP+SI] [BP+SI]+disp [BP+SI]+disp DX
  172. 011 [BP+DI] [BP+DI]+disp [BP+DI]+disp BX
  173. 100 [SI] [SI]+disp [SI]+disp SP
  174. 101 [DI] [DI]+disp [DI]+disp BP
  175. 110 disp16 [BP]+disp [BP]+disp SI
  176. 111 [BX] [BX]+disp [BX]+disp DI
  177. "disp" is disp8 for mod=01 and disp16 for mod=10.
  178. In mod=11 BOTH fields name a register and BOTH use the same list,
  179. AX CX DX BX SP BP SI DI -- the reg field and the r/m field do not
  180. differ, and there is no second BX at code 7. The memorable version
  181. drops AX off the front and invents a duplicate BX at the end, which
  182. shifts every code down by one; that is precisely how MovSiBx and
  183. CmpSiBx came to be written "89 DC" and "39 DC", which are MOV SP,BX
  184. and CMP SP,BX. Note what the shifted table gets right by luck: SP is
  185. at 100 either way, so the mistake is invisible until you check a
  186. register below it. rm=100 is SP, and SI is rm=110, not rm=100.
  187. And reg/rm swap direction with the opcode, which is the other trap:
  188. 88 /r MOV r/m8,r8 89 /r MOV r/m16,r16 reg is the SOURCE
  189. 8A /r MOV r8,r/m8 8B /r MOV r16,r/m16 reg is the TARGET
  190. So ModRM C6 names "DH and AL" either way, but 88 C6 is DH:=AL while
  191. 8A C6 is AL:=DH. The 8-bit register list is also its own, and it is not
  192. the word list:
  193. 000 001 010 011 100 101 110 111 = AL CL DL BL AH CH DH BH
  194. The two lists agree at every code except 100, where the byte form is AH
  195. and the word form is SP. Reading the same ModRM byte as the other size
  196. silently swaps AH for SP, and 88 C2 is DL:=AL while 88 17 is [BX]<-DL.
  197. The word-form list, and the four anchors nobody writes by hand:
  198. 83 C4 08 ADD SP, 8 rm=100 -> SP
  199. 83 C6 02 ADD SI, 2 rm=110 -> SI
  200. 8B EC MOV BP, SP reg=101 rm=100
  201. 8B E5 MOV SP, BP reg=100 rm=101
  202. Asking "which register is rm=100?" and answering SI is the single most
  203. common error in this file's history.
  204. Concrete recipes for opcode 8r / 9r (r/m = rm, 16-bit form):
  205. mod=00 rm=110 -> 06 <disp16> the only direct form
  206. mod=00 rm=111 -> 07 = [BX] 2 bytes
  207. mod=00 rm=101 -> 05 = [DI] 2 bytes
  208. mod=01 rm=110 -> 46 disp8 = [BP]+disp8
  209. mod=01 rm=111 -> 47 disp8 = [BX]+disp8
  210. mod=01 rm=101 -> 45 disp8 = [DI]+disp8
  211. There is no [SP] form in 16-bit mode: SIB bytes are 386-only. Anything
  212. wanting the top of the stack has to go through BP. *)
  213. PROCEDURE PushBp ; BEGIN B (55H) END PushBp ;
  214. PROCEDURE PopBp ; BEGIN B (5DH) END PopBp ;
  215. PROCEDURE MovBpSp ; BEGIN B (8BH) ; B (0ECH) END MovBpSp ; (* 8B EC: MOV BP,SP *)
  216. PROCEDURE MovSpBp ; BEGIN B (89H) ; B (0ECH) END MovSpBp ; (* 89 EC: MOV SP,BP *)
  217. PROCEDURE LeaveR ; BEGIN B (0C9H) END LeaveR ;
  218. PROCEDURE RetR ; BEGIN B (0C3H) END RetR ;
  219. PROCEDURE Int21 ; BEGIN B (0CDH) ; B (21H) END Int21 ;
  220. PROCEDURE PushDs ; BEGIN B (1EH) END PushDs ;
  221. PROCEDURE PopEs ; BEGIN B (7H) END PopEs ;
  222. PROCEDURE PushAx ; BEGIN B (50H) END PushAx ;
  223. PROCEDURE PopAx ; BEGIN B (58H) END PopAx ;
  224. PROCEDURE PushBx ; BEGIN B (53H) END PushBx ;
  225. PROCEDURE PopBx ; BEGIN B (5BH) END PopBx ;
  226. PROCEDURE PushCx ; BEGIN B (51H) END PushCx ;
  227. PROCEDURE PopCx ; BEGIN B (59H) END PopCx ;
  228. PROCEDURE PushDx ; BEGIN B (52H) END PushDx ;
  229. PROCEDURE PopDx ; BEGIN B (5AH) END PopDx ;
  230. PROCEDURE PushDi ; BEGIN B (57H) END PushDi ;
  231. PROCEDURE PopDi ; BEGIN B (5FH) END PopDi ;
  232. PROCEDURE XorAxAx ; BEGIN B (31H) ; B (0C0H) END XorAxAx ; (* 11 000 000 *)
  233. PROCEDURE XorCxCx ; BEGIN B (31H) ; B (0C9H) END XorCxCx ; (* 11 001 001 *)
  234. PROCEDURE XorDxDx ; BEGIN B (31H) ; B (0D2H) END XorDxDx ; (* 11 010 010 *)
  235. PROCEDURE XorDiDi ; BEGIN B (31H) ; B (0FFH) END XorDiDi ; (* 11 111 111 *)
  236. PROCEDURE IncCx ; BEGIN B (41H) END IncCx ;
  237. PROCEDURE IncSi ; BEGIN B (46H) END IncSi ;
  238. PROCEDURE DecSi ; BEGIN B (4EH) END DecSi ;
  239. PROCEDURE AddDi2 ; BEGIN B (83H) ; B (0C7H) ; B (2) END AddDi2 ; (* 11 000 111 *)
  240. PROCEDURE CmpAl (v : CARDINAL ) ; BEGIN B (3CH) ; B (v) END CmpAl ;
  241. PROCEDURE CmpAx0 ; BEGIN B (83H) ; B (0F8H) ; B (0) END CmpAx0 ;
  242. PROCEDURE CmpCx0 ; BEGIN B (83H) ; B (0F9H) ; B (0) END CmpCx0 ;
  243. PROCEDURE CmpArgW0 ;
  244. (* CMP WORD PTR [BP+2],0 - the one 16-bit argument of a BOOLEAN entry.
  245. [SP] cannot be encoded in 16-bit mode, so borrow BP for the three
  246. instructions and hand SP back untouched before the caller pops the
  247. argument. (Was 83 7C 24 00 00, which decodes as CMP WORD [SI+24h],0.)
  248. The imm8 of the 83 form is sign-extended to 16 bits, so 0 really is a
  249. 16-bit zero. *)
  250. BEGIN
  251. B (8BH) ; B (0ECH) ; (* MOV BP,SP *)
  252. B (83H) ; B (7EH) ; B (2) ; B (0) ; (* CMP WORD [BP+2],0 *)
  253. B (89H) ; B (0ECH) (* MOV SP,BP *)
  254. END CmpArgW0 ;
  255. PROCEDURE CmpSiBx ; BEGIN B (39H) ; B (0DEH) END CmpSiBx ; (* 39 DE: CMP SI,BX *)
  256. PROCEDURE CmpCxDx ; BEGIN B (39H) ; B (0D1H) END CmpCxDx ; (* 11 010 001 *)
  257. PROCEDURE CmpDiCx ; BEGIN B (39H) ; B (0CFH) END CmpDiCx ; (* 11 001 111 *)
  258. PROCEDURE AddDl (v : CARDINAL ) ; BEGIN B (80H) ; B (0C2H) ; B (v) END AddDl ;
  259. PROCEDURE SubAl (v : CARDINAL ) ; BEGIN B (2CH) ; B (v) END SubAl ;
  260. PROCEDURE NegAx ; BEGIN B (0F7H) ; B (0D8H) END NegAx ;
  261. PROCEDURE NegDi ; BEGIN B (0F7H) ; B (0DFH) END NegDi ;
  262. PROCEDURE DivCx ; BEGIN B (0F7H) ; B (0F1H) END DivCx ; (* 11 110 001 *)
  263. PROCEDURE MulBx ; BEGIN B (0F7H) ; B (0E3H) END MulBx ; (* 11 100 011 *)
  264. PROCEDURE MovAh (v : CARDINAL ) ; BEGIN B (0B4H) ; B (v) END MovAh ;
  265. PROCEDURE MovDl (v : CARDINAL ) ; BEGIN B (0B2H) ; B (v) END MovDl ;
  266. PROCEDURE MovBxV (v : CARDINAL ) ; BEGIN B (0BBH) ; W (v) END MovBxV ;
  267. PROCEDURE MovCxV (v : CARDINAL ) ; BEGIN B (0B9H) ; W (v) END MovCxV ;
  268. PROCEDURE MovDxV (v : CARDINAL ) ; BEGIN B (0BAH) ; W (v) END MovDxV ;
  269. PROCEDURE MovBxD (delta : CARDINAL ) ; BEGIN B (0BBH) ; Dd (delta) END MovBxD ;
  270. PROCEDURE MovDxD (delta : CARDINAL ) ; BEGIN B (0BAH) ; Dd (delta) END MovDxD ;
  271. PROCEDURE MovSiAx ; BEGIN B (8BH) ; B (0F0H) END MovSiAx ; (* 8B F0: MOV SI,AX *)
  272. PROCEDURE MovAxDi ; BEGIN B (8BH) ; B (0C7H) END MovAxDi ; (* 11 000 111 *)
  273. PROCEDURE MovCxSi6 ; BEGIN B (8BH) ; B (4CH) ; B (6) END MovCxSi6 ;
  274. PROCEDURE MovDxSi2 ; BEGIN B (8BH) ; B (54H) ; B (2) END MovDxSi2 ;
  275. (* The program header is a block of words laid out by the compiler:
  276. +0 hdrFlag 1 = image is valid
  277. +2 hdrCS end of the generated code, in bytes
  278. +4 hdrDS base of the data area <- initmem wants these two
  279. +6 hdrHeap end of the data area <-
  280. +8 hdrMax max open files
  281. initmem must read the words the COMPILER WRITES, and those are +4 (hdrDS,
  282. the data base) and +6 (hdrHeap, the data end). It used to read +8, which
  283. is hdrMax -- and the compiler patches that to 0 -- so CX came out as 0,
  284. "cmp cx,dx / jbe im_done" fired immediately, and initmem silently zeroed
  285. nothing at all. A 3-byte instruction, so no entry offset moved either
  286. way; only the header offset in the byte changed. Nothing caught it
  287. because the loop was well formed - it just did nothing. The cross-check
  288. that would have: tests/run_com_tests.sh now asserts the runtime's SI-relative
  289. reads against the same header offsets it verifies the words at. *)
  290. PROCEDURE MovDxSi4 ; BEGIN B (8BH) ; B (54H) ; B (4) END MovDxSi4 ;
  291. PROCEDURE MovAxBp4 ; BEGIN B (8BH) ; B (46H) ; B (4) END MovAxBp4 ; (* AX:=[BP+4] *)
  292. PROCEDURE MovBxBp4 ; BEGIN B (8BH) ; B (5EH) ; B (4) END MovBxBp4 ; (* BX:=[BP+4] *)
  293. PROCEDURE MovAlDh ; BEGIN B (8AH) ; B (0C6H) END MovAlDh ; (* 8A C6: AL:=DH *)
  294. PROCEDURE MovDlSi ; BEGIN B (8AH) ; B (14H) END MovDlSi ;
  295. PROCEDURE MovDlArg ; BEGIN B (8AH) ; B (56H) ; B (2) END MovDlArg ; (* DL:=[BP+2] *)
  296. PROCEDURE StDiAx ; BEGIN B (89H) ; B (5H) END StDiAx ; (* 89 05: [DI]:=AX *)
  297. PROCEDURE StDiBx ; BEGIN B (89H) ; B (1DH) END StDiBx ; (* 89 1D: [DI]:=BX *)
  298. PROCEDURE StBxCx ; BEGIN B (89H) ; B (0FH) END StBxCx ; (* 89 0F: [BX]:=CX *)
  299. PROCEDURE StSiDl ; BEGIN B (88H) ; B (14H) END StSiDl ; (* 88 14: [SI]:=DL *)
  300. PROCEDURE StBxDl ; BEGIN B (88H) ; B (17H) END StBxDl ; (* 88 17: [BX]:=DL *)
  301. PROCEDURE MovDhAl ; BEGIN B (88H) ; B (0C6H) END MovDhAl ;
  302. PROCEDURE MovDlAl ; BEGIN B (88H) ; B (0C2H) END MovDlAl ;
  303. PROCEDURE MovSiBx ; BEGIN B (89H) ; B (0DEH) END MovSiBx ; (* 89 DE: MOV SI,BX *)
  304. PROCEDURE MovDiDx ; BEGIN B (89H) ; B (0D7H) END MovDiDx ;
  305. PROCEDURE MovDiAx ; BEGIN B (89H) ; B (0C7H) END MovDiAx ;
  306. PROCEDURE AddDiAx ; BEGIN B (1H) ; B (0C7H) END AddDiAx ;
  307. PROCEDURE IncBx ; BEGIN B (43H) END IncBx ;
  308. PROCEDURE MovAlBx ; BEGIN B (8AH) ; B (07H) END MovAlBx ; (* 8A 07: AL:=[BX] *)
  309. PROCEDURE MovClBx ; BEGIN B (8AH) ; B (0FH) END MovClBx ; (* 8A 0F: CL:=[BX] *)
  310. PROCEDURE JmpBx ; BEGIN B (0FFH) ; B (0E3H) END JmpBx ; (* FF E3: JMP BX *)
  311. (* JE 74 JNE 75 JB 72 JBE 76 JGE 7D *)
  312. PROCEDURE Je8 (nm : ARRAY OF CHAR ) ; BEGIN Jcc (74H, nm) END Je8 ;
  313. PROCEDURE Jne8 (nm : ARRAY OF CHAR ) ; BEGIN Jcc (75H, nm) END Jne8 ;
  314. PROCEDURE Jb8 (nm : ARRAY OF CHAR ) ; BEGIN Jcc (72H, nm) END Jb8 ;
  315. PROCEDURE Jbe8 (nm : ARRAY OF CHAR ) ; BEGIN Jcc (76H, nm) END Jbe8 ;
  316. PROCEDURE Jge8 (nm : ARRAY OF CHAR ) ; BEGIN Jcc (7DH, nm) END Jge8 ;
  317. PROCEDURE Ja8 (nm : ARRAY OF CHAR ) ; BEGIN Jcc (77H, nm) END Ja8 ;
  318. PROCEDURE Jcxz8 (nm : ARRAY OF CHAR ) ; BEGIN Jcc (0E3H, nm) END Jcxz8 ;
  319. PROCEDURE Loop8 (nm : ARRAY OF CHAR ) ; BEGIN Jcc (0E2H, nm) END Loop8 ;
  320. (* ---------------------------------------------------------------- *)
  321. (* the entries *)
  322. (* ---------------------------------------------------------------- *)
  323. PROCEDURE EmitInitMem ;
  324. (* AX = offset of the program header. The header holds, at +4 the base of
  325. the program's data area and at +8 its end, so the globals can be zeroed -
  326. Pascal leaves them undefined, TP3's runtime clears them. Also makes
  327. ES = DS so that any string instruction in the library would work. *)
  328. BEGIN
  329. M ("initmem") ;
  330. MovSiAx ; (* SI = AX = the header offset the caller passed *)
  331. MovDxSi4 ; (* DX = [SI+4] = hdrDS = data base *)
  332. MovCxSi6 ; (* CX = [SI+6] = hdrHeap = data end *)
  333. CmpCxDx ;
  334. Jbe8 ("im_done") ;
  335. MovDiDx ; (* DI = data base *)
  336. M ("im_zero") ;
  337. XorAxAx ; (* AX = 0: the value written into every global.
  338. The caller passes the header offset in AX, so
  339. without this the "zeroing" loop would write the
  340. header offset into all of them. *)
  341. StDiAx ;
  342. AddDi2 ;
  343. CmpDiCx ;
  344. Jb8 ("im_zero") ;
  345. M ("im_done") ;
  346. PushDs ; PopEs ;
  347. RetR
  348. END EmitInitMem ;
  349. PROCEDURE EmitEnd ;
  350. (* progend and halt are the same code: the compiler already zeroes AX for
  351. progend, and a HALT argument is discarded at compile time, so both leave
  352. with exit code 0. *)
  353. BEGIN
  354. M ("progend") ;
  355. M ("halt") ;
  356. XorAxAx ;
  357. MovAh (4CH) ;
  358. Int21 ;
  359. RetR
  360. END EmitEnd ;
  361. PROCEDURE EmitStackChk ;
  362. (* called from every procedure prologue. Range and stack checking are not
  363. compiled in yet, so this must do nothing at all - in particular it must
  364. not touch a register, because the call site is in the middle of a
  365. partially evaluated expression. *)
  366. BEGIN
  367. M ("stackchk") ;
  368. RetR
  369. END EmitStackChk ;
  370. PROCEDURE EmitGetCh ;
  371. (* AL = next character, 1Ah at end of input. INT 21h AH=08h reads without
  372. echoing, so a redirected stdin behaves the same as a keyboard. *)
  373. BEGIN
  374. M ("getch") ;
  375. MovAh (8) ;
  376. Int21 ;
  377. RetR
  378. END EmitGetCh ;
  379. PROCEDURE EmitWrInt ;
  380. (* one signed 16-bit value on the stack. Div CX gives the remainder in DX,
  381. which is turned into a digit and stored backwards from the end of the
  382. scratch area, then printed forwards. *)
  383. BEGIN
  384. M ("wrint") ;
  385. PushBp ; MovBpSp ;
  386. MovAxBp4 ;
  387. CmpAx0 ;
  388. Jge8 ("wi_pos") ;
  389. PushAx ;
  390. MovDl (ORD ("-")) ; MovAh (2) ; Int21 ;
  391. PopAx ;
  392. NegAx ;
  393. M ("wi_pos") ;
  394. MovCxV (10) ;
  395. MovBxD (D_NUM + 8) ; (* BX = one past the last digit *)
  396. MovSiBx ;
  397. M ("wi_dig") ;
  398. XorDxDx ;
  399. DivCx ;
  400. AddDl (ORD ("0")) ;
  401. DecSi ;
  402. StSiDl ;
  403. CmpAx0 ;
  404. Jne8 ("wi_dig") ;
  405. M ("wi_out") ;
  406. CmpSiBx ;
  407. Je8 ("wi_done") ;
  408. MovDlSi ;
  409. MovAh (2) ; Int21 ;
  410. IncSi ;
  411. J8 ("wi_out") ;
  412. M ("wi_done") ;
  413. MovSpBp ; PopBp ; RetR
  414. END EmitWrInt ;
  415. PROCEDURE EmitWrChar ;
  416. (* the low byte of the one 16-bit argument, which sits above the return
  417. address. There is no [SP] addressing in 16-bit mode, so BP stands in for
  418. the stack pointer and is handed straight back before the RET. *)
  419. BEGIN
  420. M ("wrchar") ;
  421. MovBpSp ;
  422. MovDlArg ;
  423. MovSpBp ;
  424. MovAh (2) ;
  425. Int21 ;
  426. RetR
  427. END EmitWrChar ;
  428. PROCEDURE EmitWrInl ;
  429. (* Write an inline string literal - TP3 TPSRC4 "xwrtinl".
  430. The compiler emits
  431. CALL wrtinl <length byte> <character>...
  432. so the return address on the stack points at the length byte that follows
  433. the call. POP BX takes that address, CX picks up the length, and the
  434. routine finishes with JMP BX - returning to just past the last character.
  435. That is the whole trick: the literal is self-delimiting, so it needs no
  436. terminator, no length table and no space in the data segment, and it costs
  437. the code stream only the characters themselves (TPSRC10 "estring" emits
  438. exactly <length byte><chars> for the same reason).
  439. Consequently this entry has NO stack argument, unlike WrInt/WrChar: the
  440. return address has already been consumed by the POP. *)
  441. BEGIN
  442. M ("wrtinl") ;
  443. PopBx ; (* BX := address of the length byte *)
  444. XorCxCx ;
  445. MovClBx ; (* CX := length *)
  446. IncBx ; (* BX -> first character *)
  447. MovAh (2) ; (* INT 21h/02h: put character, AL *)
  448. Jcxz8 ("wn_end") ; (* empty string -> nothing to do *)
  449. M ("wn_loop") ;
  450. MovAlBx ; (* AL := next character *)
  451. Int21 ; (* (preserves every register but AL) *)
  452. IncBx ;
  453. Loop8 ("wn_loop") ;
  454. M ("wn_end") ;
  455. JmpBx (* resume past the string; no RET here,
  456. the return address is already gone *)
  457. END EmitWrInl ;
  458. PROCEDURE EmitWrBool ;
  459. BEGIN
  460. M ("wrbool") ;
  461. CmpArgW0 ;
  462. Jne8 ("wb_t") ;
  463. MovDxD (D_FALSE) ;
  464. J8 ("wb_o") ;
  465. M ("wb_t") ;
  466. MovDxD (D_TRUE) ;
  467. M ("wb_o") ;
  468. MovAh (9) ;
  469. Int21 ;
  470. RetR
  471. END EmitWrBool ;
  472. PROCEDURE EmitWrLn ;
  473. BEGIN
  474. M ("wrln") ;
  475. MovDxD (D_CRLF) ;
  476. MovAh (9) ;
  477. Int21 ;
  478. RetR
  479. END EmitWrLn ;
  480. PROCEDURE EmitWrReal ;
  481. (* the 6-byte real is on the stack but is not formatted: the compiler does
  482. not yet load real operands into a form the runtime could read. A visible
  483. marker beats printing the mantissa as an integer. *)
  484. BEGIN
  485. M ("wrreal") ;
  486. MovDxD (D_REAL) ;
  487. MovAh (9) ;
  488. Int21 ;
  489. RetR
  490. END EmitWrReal ;
  491. PROCEDURE EmitRdInt ;
  492. (* address on the stack; skips leading blanks, takes an optional sign, then
  493. digits, stopping *before* the delimiter so the following TU_RdLn throws
  494. away the rest of the line. Sign in CX, value in DI. *)
  495. BEGIN
  496. M ("rdint") ;
  497. PushBp ; MovBpSp ;
  498. PushAx ; PushBx ; PushCx ; PushDx ; PushDi ;
  499. M ("ri_skip") ;
  500. C8 ("getch") ;
  501. CmpAl (ORD (" ")) ; Je8 ("ri_skip") ;
  502. CmpAl (9) ; Je8 ("ri_skip") ;
  503. CmpAl (13) ; Je8 ("ri_skip") ;
  504. CmpAl (10) ; Je8 ("ri_skip") ;
  505. XorCxCx ;
  506. CmpAl (ORD ("-")) ;
  507. Jne8 ("ri_nos") ;
  508. IncCx ;
  509. C8 ("getch") ;
  510. J8 ("ri_dig0") ;
  511. M ("ri_nos") ;
  512. CmpAl (ORD ("+")) ;
  513. Jne8 ("ri_dig0") ;
  514. C8 ("getch") ;
  515. M ("ri_dig0") ;
  516. XorDiDi ;
  517. M ("ri_dig") ;
  518. CmpAl (ORD ("0")) ;
  519. Jb8 ("ri_done") ;
  520. CmpAl (ORD ("9")) ;
  521. Ja8 ("ri_done") ;
  522. SubAl (ORD ("0")) ;
  523. MovDhAl ; (* keep the digit across the multiply *)
  524. MovAxDi ;
  525. MovBxV (10) ;
  526. MulBx ; (* DX:AX := DI * 10 *)
  527. MovDiAx ;
  528. MovAh (0) ;
  529. MovAlDh ;
  530. AddDiAx ;
  531. C8 ("getch") ;
  532. J8 ("ri_dig") ;
  533. M ("ri_done") ;
  534. CmpCx0 ;
  535. Je8 ("ri_st") ;
  536. NegDi ;
  537. M ("ri_st") ;
  538. MovBxBp4 ;
  539. StDiBx ;
  540. PopDi ; PopDx ; PopCx ; PopBx ; PopAx ;
  541. MovSpBp ; PopBp ; RetR
  542. END EmitRdInt ;
  543. PROCEDURE EmitRdChar ;
  544. BEGIN
  545. M ("rdchar") ;
  546. PushBp ; MovBpSp ;
  547. PushAx ; PushBx ;
  548. C8 ("getch") ;
  549. MovDlAl ;
  550. MovBxBp4 ;
  551. StBxDl ;
  552. PopBx ; PopAx ;
  553. MovSpBp ; PopBp ; RetR
  554. END EmitRdChar ;
  555. PROCEDURE EmitRdBool ;
  556. (* one character, classified the way TP3 does: T/t/Y/y/1 true, anything else
  557. false. *)
  558. BEGIN
  559. M ("rdbool") ;
  560. PushBp ; MovBpSp ;
  561. PushAx ; PushBx ; PushCx ;
  562. C8 ("getch") ;
  563. XorCxCx ;
  564. CmpAl (ORD ("T")) ; Je8 ("rb_t") ;
  565. CmpAl (ORD ("t")) ; Je8 ("rb_t") ;
  566. CmpAl (ORD ("Y")) ; Je8 ("rb_t") ;
  567. CmpAl (ORD ("y")) ; Je8 ("rb_t") ;
  568. CmpAl (ORD ("1")) ; Je8 ("rb_t") ;
  569. J8 ("rb_s") ;
  570. M ("rb_t") ;
  571. IncCx ;
  572. M ("rb_s") ;
  573. MovBxBp4 ;
  574. StBxCx ;
  575. PopCx ; PopBx ; PopAx ;
  576. MovSpBp ; PopBp ; RetR
  577. END EmitRdBool ;
  578. PROCEDURE EmitRdLn ;
  579. (* discard the rest of the line, including the terminator *)
  580. BEGIN
  581. M ("rdln") ;
  582. PushAx ;
  583. M ("rl_loop") ;
  584. C8 ("getch") ;
  585. CmpAl (13) ; Je8 ("rl_e") ;
  586. CmpAl (10) ; Je8 ("rl_e") ;
  587. CmpAl (26) ; Je8 ("rl_e") ; (* ^Z: end of input *)
  588. J8 ("rl_loop") ;
  589. M ("rl_e") ;
  590. PopAx ;
  591. RetR
  592. END EmitRdLn ;
  593. PROCEDURE EmitData ;
  594. BEGIN
  595. dataAt := rpos ;
  596. (* 8 bytes of scratch, never read before written *)
  597. B (0) ; B (0) ; B (0) ; B (0) ; B (0) ; B (0) ; B (0) ; B (0) ;
  598. B (ORD ("T")) ; B (ORD ("R")) ; B (ORD ("U")) ; B (ORD ("E")) ; B (ORD ("$")) ;
  599. B (ORD ("F")) ; B (ORD ("A")) ; B (ORD ("L")) ; B (ORD ("S")) ;
  600. B (ORD ("E")) ; B (ORD ("$")) ;
  601. B (13) ; B (10) ; B (ORD ("$")) ;
  602. B (ORD ("?")) ; B (ORD ("R")) ; B (ORD ("E")) ; B (ORD ("A")) ;
  603. B (ORD ("L")) ; B (ORD ("?")) ;
  604. WHILE rpos < dataAt + D_END DO
  605. B (0)
  606. END
  607. END EmitData ;
  608. PROCEDURE FixUp ;
  609. VAR i, t, rel : CARDINAL ;
  610. BEGIN
  611. i := 0 ;
  612. WHILE i < nfix DO
  613. IF fix [i].kind = 2 THEN
  614. t := (dataAt + fix [i].val) MOD 10000H ;
  615. rt [fix [i].place] := VAL (BYTE, t MOD 100H) ;
  616. rt [fix [i].place + 1] := VAL (BYTE, (t DIV 100H) MOD 100H)
  617. ELSE
  618. t := LblOff (fix [i].nm) ;
  619. IF fix [i].kind = 0 THEN
  620. (* rel8 is measured from the end of the instruction, i.e. one
  621. byte past the displacement field *)
  622. rel := (t + 100H - (fix [i].place + 1)) MOD 100H ;
  623. rt [fix [i].place] := VAL (BYTE, rel)
  624. ELSE
  625. rel := (t + 10000H - (fix [i].place + 2)) MOD 10000H ;
  626. rt [fix [i].place] := VAL (BYTE, rel MOD 100H) ;
  627. rt [fix [i].place + 1] := VAL (BYTE, (rel DIV 100H) MOD 100H)
  628. END
  629. END ;
  630. INC (i)
  631. END
  632. END FixUp ;
  633. (* ---------------------------------------------------------------- *)
  634. (* public interface *)
  635. (* ---------------------------------------------------------------- *)
  636. PROCEDURE RT_Build ;
  637. BEGIN
  638. IF built THEN
  639. RETURN
  640. END ;
  641. rpos := 0 ; ltop := 0 ; nfix := 0 ; dataAt := 0 ;
  642. EmitInitMem ;
  643. EmitEnd ;
  644. EmitStackChk ;
  645. EmitWrInt ; EmitWrChar ; EmitWrBool ; EmitWrReal ; EmitWrLn ;
  646. EmitWrInl ;
  647. EmitRdInt ; EmitRdChar ; EmitRdBool ; EmitRdLn ;
  648. EmitGetCh ;
  649. EmitData ;
  650. FixUp ;
  651. SetStr (entNm [0], "initmem") ;
  652. SetStr (entNm [1], "progend") ;
  653. SetStr (entNm [2], "stackchk") ;
  654. SetStr (entNm [3], "wrint") ;
  655. SetStr (entNm [4], "wrchar") ;
  656. SetStr (entNm [5], "wrbool") ;
  657. SetStr (entNm [6], "wrreal") ;
  658. SetStr (entNm [7], "wrln") ;
  659. SetStr (entNm [8], "rdint") ;
  660. SetStr (entNm [9], "rdchar") ;
  661. SetStr (entNm [10], "rdbool") ;
  662. SetStr (entNm [11], "rdln") ;
  663. SetStr (entNm [12], "halt") ;
  664. SetStr (entNm [13], "wrtinl") ;
  665. built := TRUE
  666. END RT_Build ;
  667. PROCEDURE RT_Size () : CARDINAL ;
  668. BEGIN
  669. IF NOT built THEN
  670. RT_Build ()
  671. END ;
  672. RETURN rpos
  673. END RT_Size ;
  674. PROCEDURE RT_Byte (i : CARDINAL ) : BYTE ;
  675. BEGIN
  676. IF NOT built THEN
  677. RT_Build ()
  678. END ;
  679. IF i >= rpos THEN
  680. RETURN 0
  681. END ;
  682. RETURN rt [i]
  683. END RT_Byte ;
  684. PROCEDURE RT_Entry (i : CARDINAL ) : CARDINAL ;
  685. BEGIN
  686. IF NOT built THEN
  687. RT_Build ()
  688. END ;
  689. IF i > 13 THEN
  690. RETURN 0
  691. END ;
  692. RETURN LblOff (entNm [i])
  693. END RT_Entry ;
  694. PROCEDURE RT_CodeEnd () : CARDINAL ;
  695. BEGIN
  696. IF NOT built THEN
  697. RT_Build ()
  698. END ;
  699. RETURN dataAt
  700. END RT_CodeEnd ;
  701. END Runtime.