IMPLEMENTATION MODULE Linker ; (* Linker and .COM writer for TP3-compiled programs. The compiler has already produced the whole image - Runtime is copied to offset 0 of the code buffer and the program follows it, so every address in the image is already absolute (see Compiler.Inittur). There is therefore no relocation to do: "linking" simply writes the code image as a DOS .COM. The globals are NOT in that file. They live at Compiler.DataBase = 8000h image (8100h after the load bias), and the runtime's initmem zeroes [hdrDS, hdrHeap) before the program runs. This is the flat-segment equivalent of the original's layout: TP3 puts the runtime and program in CS and the globals in a separate DS segment, so its .COM is code-only too. Keeping the data out of the file is what lets the code grow past 4 KiB without overlapping it, and it is why the file length is ImageBytes (). The original wrote its .COM through the overlay loader (TPSRC7 opendest) from a different buffer layout with a larger header. Our header is our own - see Runtime.EmitInitMem - but the file/data separation is the same. *) FROM SYSTEM IMPORT BYTE, ADR ; FROM Posix IMPORT open, write, close, unlink ; FROM Compiler IMPORT ImageBytes, ImageByteAt ; CONST O_WRONLY = 1 ; O_CREAT = 64 ; O_TRUNC = 512 ; PROCEDURE DropCh (v : CHAR) ; BEGIN END DropCh ; PROCEDURE DropB (v : BOOLEAN) ; BEGIN END DropB ; PROCEDURE DropC (v : CARDINAL) ; BEGIN END DropC ; PROCEDURE ZCopy (VAR dst : ARRAY OF CHAR ; src : ARRAY OF CHAR) ; (* Copy into a NUL-terminated buffer. The C bindings take a plain ADDRESS, so the string must be terminated in OUR buffer - passing the caller's unbounded array straight through would pass a pointer to whatever followed it. The VAR is load-bearing, and was found by running: without it the formal is a COPY, the writes land in the copy, and the caller writes to whatever garbage was in its buffer. The symptom was a .COM file named "END" - adjacent string data - created with the right size and the right contents in the wrong file. tests/OpenArrTest.mod measures the three variants. *) VAR i : CARDINAL ; BEGIN i := 0 ; WHILE (i <= HIGH (dst) - 1) AND (i <= HIGH (src)) AND (src [i] # 0C) DO dst [i] := src [i] ; INC (i) END ; dst [i] := 0C END ZCopy ; PROCEDURE LinkSize () : CARDINAL ; (* The .COM contains the runtime and the program's code ONLY. The globals live at Compiler.DataBase = 8000H in memory (8100H after the load bias), outside the file, and the runtime's initmem zeroes them at startup. This matches the original's separate DS segment: the file is code, the data area is zero-filled memory. *) BEGIN RETURN ImageBytes () END LinkSize ; PROCEDURE WriteCom (path : ARRAY OF CHAR) : BOOLEAN ; (* Write the linked image to `path` as a DOS .COM. Returns TRUE on success; on failure the file is removed, so a half-written .COM is never left behind to be mistaken for a good one. *) VAR fd : INTEGER ; n : LONGINT ; i, total, chunk : CARDINAL ; buf : ARRAY [0..255] OF BYTE ; z : ARRAY [0..511] OF CHAR ; BEGIN ZCopy (z, path) ; total := LinkSize () ; fd := open (ADR (z), O_WRONLY + O_CREAT + O_TRUNC, 420) ; IF fd < 0 THEN RETURN FALSE END ; i := 0 ; WHILE i < total DO chunk := 0 ; WHILE (chunk < 256) AND (i + chunk < total) DO buf [chunk] := ImageByteAt (i + chunk) ; INC (chunk) END ; n := write (fd, ADR (buf), chunk) ; IF n # VAL (LONGINT, chunk) THEN DropC (VAL (CARDINAL, close (fd))) ; DropB (unlink (ADR (z)) = 0) ; RETURN FALSE END ; i := i + chunk END ; IF close (fd) # 0 THEN DropB (unlink (ADR (z)) = 0) ; RETURN FALSE END ; RETURN TRUE END WriteCom ; END Linker.