|
|
@@ -47,6 +47,11 @@ FROM TextBuf IMPORT Length, CharAt ;
|
|
|
|
|
|
FROM SYSTEM IMPORT BYTE ;
|
|
|
|
|
|
+FROM Runtime IMPORT RT_Build, RT_Size, RT_Byte, RT_Entry ;
|
|
|
+(* The runtime is copied to the front of the code buffer and pc/dc start past
|
|
|
+ it, so every emitted address is image-absolute and no relocation pass is
|
|
|
+ needed. See Inittur. *)
|
|
|
+
|
|
|
(* ---------------------------------------------------------------- *)
|
|
|
(* constants *)
|
|
|
(* ---------------------------------------------------------------- *)
|
|
|
@@ -86,28 +91,6 @@ CONST
|
|
|
TkSet = 36 ; TkPacked = 37 ; TkForward = 38 ; TkExternal = 39 ;
|
|
|
TkAbsolute = 40 ; TkOverlay = 41 ; TkString = 42 ;
|
|
|
|
|
|
- (* Runtime entry offsets in the emitted image.
|
|
|
-
|
|
|
- Standard-procedure entries: TP3 does NOT pass a descriptor -
|
|
|
- TPSRC8 pwriteln/pwrloop inspects each argument's class in CL and emits
|
|
|
- a *different* call per type, so the type is fixed at compile time and
|
|
|
- the runtime needs only the value. Mirrored here.
|
|
|
-
|
|
|
- These offsets are still PLACEHOLDERS on a fixed ladder. The real
|
|
|
- offsets are known - Runtime.RT_Entry derives them from the assembled
|
|
|
- blob (wrtinl is really at 8EH) - but the compiler does not copy the
|
|
|
- runtime into its code buffer yet, so it cannot call it, and using the
|
|
|
- true offsets here would only look like it works. They all get replaced
|
|
|
- by RT_Entry in one go when the runtime is wired in. *)
|
|
|
- TU_InitMem = 8H ;
|
|
|
- TU_ProgEnd = 10H ;
|
|
|
- TU_StackChk = 18H ;
|
|
|
- TU_WrInt = 20H ; TU_WrChar = 28H ; TU_WrBool = 30H ;
|
|
|
- TU_WrReal = 38H ; TU_WrLn = 40H ;
|
|
|
- TU_RdInt = 48H ; TU_RdChar = 50H ; TU_RdBool = 58H ;
|
|
|
- TU_RdLn = 60H ; TU_Halt = 68H ;
|
|
|
- TU_WrInl = 70H ; (* inline string literal; takes NO stack argument *)
|
|
|
-
|
|
|
(* TP3 error numbers *)
|
|
|
ENoSemi = 1 ; EPointExp = 10 ; ESimpType = 30 ;
|
|
|
EUnknown = 41 ; EConstRange = 45 ; EMemOvf = 98 ;
|
|
|
@@ -214,6 +197,34 @@ VAR
|
|
|
|
|
|
codeSz, dataSz : CARDINAL ;
|
|
|
|
|
|
+ (* Image layout, all image-absolute. rtSz is where the runtime ends and
|
|
|
+ the program header begins; dataBase is where the data area begins
|
|
|
+ (rtSz + 1000H, a fixed 4 KiB above the code). codeSz and dataSz are
|
|
|
+ PROGRAM sizes - the runtime is excluded - so the numbers the fixture
|
|
|
+ table pins keep meaning what they meant before the runtime was
|
|
|
+ prepended. *)
|
|
|
+ rtSz, dataBase : CARDINAL ;
|
|
|
+
|
|
|
+ (* Runtime entry offsets inside the emitted image, i.e. offsets into the
|
|
|
+ runtime blob, which the linker places at offset 0. They were a
|
|
|
+ hand-written placeholder ladder until the runtime was wired in; they are
|
|
|
+ now taken from Runtime.RT_Entry, which derives them from where the code
|
|
|
+ actually lands in the assembled blob. Not a CONST block any more
|
|
|
+ because RT_Entry is a function.
|
|
|
+
|
|
|
+ Standard-procedure entries: TP3 does NOT pass a descriptor -
|
|
|
+ TPSRC8 pwriteln/pwrloop inspects each argument's class in CL and emits
|
|
|
+ a *different* call per type, so the type is fixed at compile time and
|
|
|
+ the runtime needs only the value. Mirrored here. *)
|
|
|
+ TU_InitMem : CARDINAL ;
|
|
|
+ TU_ProgEnd : CARDINAL ;
|
|
|
+ TU_StackChk : CARDINAL ;
|
|
|
+ TU_WrInt : CARDINAL ; TU_WrChar : CARDINAL ; TU_WrBool : CARDINAL ;
|
|
|
+ TU_WrReal : CARDINAL ; TU_WrLn : CARDINAL ;
|
|
|
+ TU_RdInt : CARDINAL ; TU_RdChar : CARDINAL ; TU_RdBool : CARDINAL ;
|
|
|
+ TU_RdLn : CARDINAL ; TU_Halt : CARDINAL ;
|
|
|
+ TU_WrInl : CARDINAL ; (* inline string literal; NO stack argument *)
|
|
|
+
|
|
|
abortFac : BOOLEAN ;
|
|
|
errNum : CARDINAL ; (* NOT "errNo": Compile's formal of that
|
|
|
name would shadow it, and the caller's
|
|
|
@@ -376,6 +387,19 @@ BEGIN
|
|
|
Ebyte (VAL (BYTE, (w DIV 100H) MOD 100H))
|
|
|
END Eword ;
|
|
|
|
|
|
+PROCEDURE PatchWord (at, w : CARDINAL) ;
|
|
|
+(* Store a 16-bit word into cbuf at an absolute offset.
|
|
|
+
|
|
|
+ A helper, because writing this inline got it wrong in all four header
|
|
|
+ words: the low byte was (w DIV 16) MOD 100H, which is a NIBBLE shift, not
|
|
|
+ the byte shift (w MOD 100H). So 1181h - the data base - was stored as
|
|
|
+ 0118h = 280. It was invisible for as long as nothing read those words,
|
|
|
+ which is exactly what "write it inline once and trust it" buys you. *)
|
|
|
+BEGIN
|
|
|
+ cbuf [at] := VAL (BYTE, w MOD 100H) ;
|
|
|
+ cbuf [at + 1] := VAL (BYTE, (w DIV 100H) MOD 100H)
|
|
|
+END PatchWord ;
|
|
|
+
|
|
|
PROCEDURE AddPatch (place, target : CARDINAL) ;
|
|
|
BEGIN
|
|
|
IF nPatch < MaxPatch THEN
|
|
|
@@ -2835,14 +2859,46 @@ END DefBuiltins ;
|
|
|
|
|
|
PROCEDURE Inittur () ;
|
|
|
(* reset compiler state and define the standard types *)
|
|
|
+VAR i, rt : CARDINAL ;
|
|
|
BEGIN
|
|
|
abortFac := FALSE ;
|
|
|
errNum := 0 ;
|
|
|
txerrPos := 0 ;
|
|
|
srcPos := 0 ;
|
|
|
srcLen := Length () ;
|
|
|
- pc := 0 ;
|
|
|
- dc := 100H ;
|
|
|
+ (* Copy the runtime to the front of the code buffer and start pc past it,
|
|
|
+ which is what the original does: TPSRC7 "copyrt" runs REPZ MOVSB with
|
|
|
+ SI=DI=0 and then "MOV pc,#$2D7C". The image is therefore
|
|
|
+
|
|
|
+ [runtime][program header][program code]
|
|
|
+
|
|
|
+ and because the runtime sits at offset 0, every address the compiler
|
|
|
+ emits is already image-absolute - the data symbols' offsets, the TU_*
|
|
|
+ call targets and the rel16 displacements all need no relocation pass.
|
|
|
+ (The base shift would in fact cancel in EmCall's arithmetic, since both
|
|
|
+ sides of a CALL move together; making the offsets absolute just means
|
|
|
+ the linker has nothing to do but copy bytes.)
|
|
|
+
|
|
|
+ dc is put a fixed 4 KiB above the end of the program so that data cannot
|
|
|
+ collide with code in a single 64 KiB .COM segment. LIMITATION: a
|
|
|
+ program whose code exceeds 4 KiB overruns its own data area. TP3 had
|
|
|
+ overlay segments for this; we do not, and the check belongs where the
|
|
|
+ limit is documented rather than as a silent truncation. *)
|
|
|
+ RT_Build () ;
|
|
|
+ rt := RT_Size () ;
|
|
|
+ IF rt >= MaxCode THEN
|
|
|
+ Err (EMemOvf) ; (* cannot happen: rt is 385 *)
|
|
|
+ RETURN
|
|
|
+ END ;
|
|
|
+ i := 0 ;
|
|
|
+ WHILE i < rt DO
|
|
|
+ cbuf [i] := RT_Byte (i) ;
|
|
|
+ INC (i)
|
|
|
+ END ;
|
|
|
+ pc := rt ;
|
|
|
+ rtSz := rt ;
|
|
|
+ dataBase := rt + 1000H ;
|
|
|
+ dc := dataBase ;
|
|
|
strTop := 0 ;
|
|
|
strCnt := 0 ;
|
|
|
rdStrX := 0 ;
|
|
|
@@ -2874,7 +2930,31 @@ BEGIN
|
|
|
tmpA := NewSym ("@@T1", KVar, TScalar, 2, 2, dc, 0, FALSE) ;
|
|
|
dc := dc + 2 ;
|
|
|
tmpB := NewSym ("@@T2", KVar, TScalar, 2, 2, dc, 0, FALSE) ;
|
|
|
- dc := dc + 2
|
|
|
+ dc := dc + 2 ;
|
|
|
+ (* Runtime entry offsets, derived from the blob rather than assumed. This
|
|
|
+ has to happen after RT_Build, since RT_Entry only knows where the code
|
|
|
+ landed once the blob is assembled. *)
|
|
|
+ TU_InitMem := RT_Entry (0) ;
|
|
|
+ TU_ProgEnd := RT_Entry (1) ;
|
|
|
+ TU_StackChk := RT_Entry (2) ;
|
|
|
+ TU_WrInt := RT_Entry (3) ;
|
|
|
+ TU_WrChar := RT_Entry (4) ;
|
|
|
+ TU_WrBool := RT_Entry (5) ;
|
|
|
+ TU_WrReal := RT_Entry (6) ;
|
|
|
+ TU_WrLn := RT_Entry (7) ;
|
|
|
+ TU_RdInt := RT_Entry (8) ;
|
|
|
+ TU_RdChar := RT_Entry (9) ;
|
|
|
+ TU_RdBool := RT_Entry (10) ;
|
|
|
+ TU_RdLn := RT_Entry (11) ;
|
|
|
+ TU_Halt := RT_Entry (12) ;
|
|
|
+ TU_WrInl := RT_Entry (13) ; (* inline string literal *)
|
|
|
+ IF (TU_WrInl = 0) OR (TU_RdLn = 0) OR (TU_WrInt = 0) THEN
|
|
|
+ (* RT_Entry returns 0 for an unknown selector. initmem sits at 0
|
|
|
+ legitimately, so it cannot appear in this test - but wrtinl, rdln
|
|
|
+ and wrint never can, so catching them is enough to catch a runtime
|
|
|
+ that failed to build or a selector that went stale. *)
|
|
|
+ Err (EMemOvf)
|
|
|
+ END
|
|
|
END Inittur ;
|
|
|
|
|
|
PROCEDURE HeadWord (VAR slot : CARDINAL) ;
|
|
|
@@ -2897,6 +2977,12 @@ BEGIN
|
|
|
Eword (16) ; (* max open files *)
|
|
|
Eword (0) ; (* input buffer word *)
|
|
|
Eword (0) ; (* output buffer word *)
|
|
|
+ (* TU_InitMem takes the header offset in AX, not on the stack, so the
|
|
|
+ AX load has to precede the call. Previously the prologue called
|
|
|
+ offset 8 - which in this layout is the hdrMax word - and that was
|
|
|
+ coherent only because the runtime was not there. Now it is the
|
|
|
+ real header. *)
|
|
|
+ EmMovAxi (rtSz) ;
|
|
|
DropC (EmCall (TU_InitMem)) ;
|
|
|
EmMovBpSp () ;
|
|
|
IF MatchKey (tok) AND (tok = TkProgram) THEN
|
|
|
@@ -2929,18 +3015,21 @@ BEGIN
|
|
|
EmXorAxAx () ;
|
|
|
DropC (EmCall (TU_ProgEnd)) ;
|
|
|
ResolvePatches () ;
|
|
|
- codeSz := pc ;
|
|
|
- dataSz := dc ;
|
|
|
- cbuf [hdrCS] := VAL (BYTE, (codeSz DIV 16) MOD 100H) ;
|
|
|
- cbuf [hdrCS + 1] := VAL (BYTE, ((codeSz DIV 16) DIV 100H) MOD 100H) ;
|
|
|
- cbuf [hdrDS] := VAL (BYTE, (dataSz DIV 16) MOD 100H) ;
|
|
|
- cbuf [hdrDS + 1] := VAL (BYTE, ((dataSz DIV 16) DIV 100H) MOD 100H) ;
|
|
|
- cbuf [hdrFlag] := 1 ;
|
|
|
- cbuf [hdrFlag + 1] := 0 ;
|
|
|
- cbuf [hdrHeap] := 0 ;
|
|
|
- cbuf [hdrHeap + 1] := 0 ;
|
|
|
- cbuf [hdrMax] := 0 ;
|
|
|
- cbuf [hdrMax + 1] := 0
|
|
|
+ (* Program-only sizes. The runtime is not part of the
|
|
|
+ program's code, and the fixture table has always meant
|
|
|
+ "the program's own code", so subtract it here rather
|
|
|
+ than making every expectation in expected.tsv wrong. *)
|
|
|
+ codeSz := pc - rtSz ;
|
|
|
+ dataSz := dc - dataBase ;
|
|
|
+ (* Header words. The layout is ours (the original's is
|
|
|
+ bigger and serves a real overlay loader), but
|
|
|
+ Runtime.EmitInitMem reads +4 and +8, so hdrDS and
|
|
|
+ hdrHeap must be the data base and the data end. *)
|
|
|
+ PatchWord (hdrFlag, 1) ;
|
|
|
+ PatchWord (hdrCS, pc) ;
|
|
|
+ PatchWord (hdrDS, dataBase) ;
|
|
|
+ PatchWord (hdrHeap, dc) ;
|
|
|
+ PatchWord (hdrMax, 0)
|
|
|
END
|
|
|
ELSE
|
|
|
Err (EUnknown)
|
|
|
@@ -2974,6 +3063,32 @@ BEGIN
|
|
|
RETURN dataSz
|
|
|
END DataBytes ;
|
|
|
|
|
|
+PROCEDURE ImageBytes () : CARDINAL ;
|
|
|
+(* Total linked image size: rtSz (the runtime) + CodeBytes (the program).
|
|
|
+ The program is NOT padded out to the data base here - the linker does
|
|
|
+ that, and only it knows the .COM's final size. *)
|
|
|
+BEGIN
|
|
|
+ RETURN rtSz + codeSz
|
|
|
+END ImageBytes ;
|
|
|
+
|
|
|
+PROCEDURE DataBase () : CARDINAL ;
|
|
|
+(* image-absolute offset at which the data area begins (rtSz + 1000H). The
|
|
|
+ linker must place the program's data here and zero-fill from the end of
|
|
|
+ the code up to it. *)
|
|
|
+BEGIN
|
|
|
+ RETURN dataBase
|
|
|
+END DataBase ;
|
|
|
+
|
|
|
+PROCEDURE ImageByteAt (i : CARDINAL) : BYTE ;
|
|
|
+(* i-th byte of the WHOLE image, runtime included, so a test can check the
|
|
|
+ real thing a .COM would contain. Returns 0 past the end. *)
|
|
|
+BEGIN
|
|
|
+ IF i >= rtSz + codeSz THEN
|
|
|
+ RETURN 0
|
|
|
+ END ;
|
|
|
+ RETURN cbuf [i]
|
|
|
+END ImageByteAt ;
|
|
|
+
|
|
|
PROCEDURE CodeByteAt (i : CARDINAL) : BYTE ;
|
|
|
(* i-th byte of the emitted image, for test harnesses that need to check
|
|
|
the generated 8086 code rather than just its size. Returns 0 past the
|