| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- #!/bin/bash
- # Build and run the .COM linker harness (tests/ComTest.mod), then verify every
- # .COM it produced with an INDEPENDENT checker.
- #
- # The independent pass matters: ComTest computes the expectations from the same
- # Compiler state it is testing, so a bug in the compiler would be invisible to
- # it. The Python pass re-derives what the file must contain - the runtime's
- # first bytes, a zero gap, a size that covers the data area - from the
- # constants only, and cross-checks.
- set -u
- D=/home/eric/Projets/Projets-Modula2/MyWork/TP3-comp/shell
- GM2=/home/eric/bin/Modula2/Gm2/bin/gm2
- cd "$D" || exit 9
- FLAGS="-fiso"
- echo "== support modules =="
- [ -f Posix.o ] || cc -c Posix.c || exit 1
- for m in TextBuf Compiler Runtime Linker; do
- $GM2 $FLAGS -c $m.mod >/tmp/cm_c_$m 2>&1 \
- || { echo "COMPILE_FAIL $m"; grep -m5 "error:" /tmp/cm_c_$m; exit 1; }
- done
- $GM2 $FLAGS -c tests/ComTest.mod >/tmp/cm_c_ComTest 2>&1 \
- || { echo "COMPILE_FAIL ComTest"; grep -m5 "error:" /tmp/cm_c_ComTest; exit 1; }
- rm -f tests/ct.lst comtest
- $GM2 $FLAGS -fgen-module-list=tests/ct.lst -o /dev/null \
- tests/ComTest.mod TextBuf.o Posix.o Compiler.o Runtime.o Linker.o \
- >/tmp/cm_p1 2>&1
- p1=$?
- $GM2 $FLAGS -fuse-list=tests/ct.lst -o comtest \
- tests/ComTest.mod TextBuf.o Posix.o Compiler.o Runtime.o Linker.o \
- >/tmp/cm_p2 2>&1
- p2=$?
- if [ $p2 -ne 0 ]; then
- echo "LINK_FAIL p1_rc=$p1 p2_rc=$p2"
- grep -E "error:|undefined" /tmp/cm_p2 | head -10
- exit 1
- fi
- echo "comtest built (p1_rc=$p1, phase 1 rc=1 is the expected rollup)"
- # .COM files are written beside the harness, so run it in a scratch dir
- OUT=$(mktemp -d) || exit 9
- trap 'rm -rf "$OUT"' EXIT
- cd "$OUT" || exit 9
- ls "$D"/tests/fixtures/*.pas | "$D"/comtest > "$OUT/raw.txt" 2>&1
- sed 's/^.*fixtures\///' "$OUT/raw.txt"
- echo "----------------------------------------------------------------"
- nok=$(grep -c " OK com=" "$OUT/raw.txt")
- nerr=$(grep -c " ERROR " "$OUT/raw.txt")
- nbad=$(grep -cE "WRITE_COM_FAILED|CANNOT" "$OUT/raw.txt")
- echo "linked: $nok .COM files, $nerr fixtures rejected at compile time, $nbad harness failures"
- if [ "$nbad" -ne 0 ]; then
- echo "RESULT: FAIL (harness could not link every compiling fixture)"
- exit 1
- fi
- # ---- independent verification of the bytes on disk ------------------------
- python3 - "$OUT" <<'PYEOF'
- import sys, os, re, glob
- out = sys.argv[1]
- # Compiler layout constants, restated here on purpose: the checker must not
- # ask the code under test what the answer is.
- #
- # Re-baselined 385 -> 391 with the runtime bug fixes (see the re-baseline
- # rationale in tests/runtime.golden and the ModRM table in Runtime.mod). The
- # size is what a change to the runtime shows up in first; if this constant
- # ever needs changing again, find out why the runtime moved rather than just
- # editing the number, and check tests/check_runtime.py for the reason.
- RTSZ = 391 # Runtime.RT_Size()
- DATAB = RTSZ + 0x1000 # Compiler: data base = rtSz + 1000H
- # initmem's prologue, which is the runtime's only reader of the program
- # header. The displacements +4 and +6 below are the whole point of this
- # constant: the header word checks further down read hdrDS at +4 and hdrHeap
- # at +6, and initmem has to read the SAME two words or it clears the wrong
- # range. It used to read +8 (hdrMax, which the compiler patches to 0), so it
- # zeroed nothing at all, and nothing here noticed -- the emitted loop was
- # perfectly well formed, it just never ran. Asserting the bytes and the
- # header offsets together is what closes that gap.
- HEAD = '8B F0 8B 54 04 8B 4C 06' # 11 bytes of initmem, see below
- HDR_DS_WORD = 4 # header word holding the data base
- HDR_HEAP_WORD = 6 # header word holding the data end
- # Byte 4 of HEAD is the displacement of initmem's MOV DX,[SI+?], and byte 7
- # the displacement of its MOV CX,[SI+?]. The checks below read the header
- # words at HDR_DS_WORD and HDR_HEAP_WORD, so tying those two displacements to
- # the same two constants is what makes the runtime and the compiler agree by
- # construction rather than by coincidence.
- assert [int(HEAD.split()[4], 16), int(HEAD.split()[7], 16)] == \
- [HDR_DS_WORD, HDR_HEAP_WORD], \
- 'initmem no longer reads the two header words this checker verifies'
- raw = open(os.path.join(out, 'raw.txt')).read()
- rows = re.findall(r'(\S+\.pas)\s+OK\s+com=(\d+)\s+image=(\d+)\s+data=(\d+)\s+nonzeroInGap=(\d+)', raw)
- if not rows:
- print('RESULT: FAIL (no linked fixtures found in output)')
- sys.exit(1)
- bad = 0
- for name, com, image, data, nzg in rows:
- com, image, data, nzg = int(com), int(image), int(data), int(nzg)
- # ComTest writes the .COM by BASENAME beside itself (it cannot graft a
- # directory onto a source path), so the checker must look for the bare
- # name, not the full source path the fixture was read from.
- path = os.path.join(out, os.path.basename(name)[:-4] + '.COM')
- errs = []
- if not os.path.exists(path):
- errs.append('no .COM file')
- d = b''
- else:
- d = open(path, 'rb').read()
- if d[:len(HEAD.split())].hex(' ').upper() != HEAD:
- errs.append('runtime not at offset 0 (first bytes %s, want %s)'
- % (d[:len(HEAD.split())].hex(' ').upper(), HEAD))
- if len(d) != com:
- errs.append('file is %d bytes, harness said %d' % (len(d), com))
- if DATAB + data != len(d):
- errs.append('size %d != dataBase+data %d' % (len(d), DATAB + data))
- # the gap between the image and the data area must be entirely zero
- gap = d[image:DATAB]
- if any(gap):
- errs.append('%d non-zero bytes in the code/data gap' % sum(1 for b in gap if b))
- if nzg != 0:
- errs.append('harness itself reported %d non-zero gap bytes' % nzg)
- # the program must start exactly where the runtime ends
- if image < RTSZ:
- errs.append('image %d shorter than the runtime %d' % (image, RTSZ))
- # the program header sits at offset rtSz, and its words must describe the
- # image that is actually in the file
- if len(d) > RTSZ + 8:
- hdr = d[RTSZ:RTSZ+16]
- flag = int.from_bytes(hdr[0:2], 'little')
- cs = int.from_bytes(hdr[2:4], 'little')
- ds = int.from_bytes(hdr[HDR_DS_WORD:HDR_DS_WORD+2], 'little')
- heap = int.from_bytes(hdr[HDR_HEAP_WORD:HDR_HEAP_WORD+2], 'little')
- if flag != 1:
- errs.append('hdrFlag=%d' % flag)
- # hdrCS is pc, the end of the code, and the harness's `image` is
- # rtSz + code ALREADY - so the two are the same number. Adding
- # RTSZ here counted the runtime twice.
- if cs != image:
- errs.append('hdrCS=%d, want %d (end of image)' % (cs, image))
- if ds != DATAB:
- errs.append('hdrDS=%d, want %d' % (ds, DATAB))
- if heap != DATAB + data:
- errs.append('hdrHeap=%d, want %d' % (heap, DATAB + data))
- # initmem must read hdrDS and hdrHeap, not some other pair of header
- # words. (It read +8, hdrMax, which the compiler patches to 0, so
- # the range it cleared was empty and every global kept whatever the
- # loader left in it.)
- if [d[4], d[7]] != [HDR_DS_WORD, HDR_HEAP_WORD]:
- errs.append('initmem reads header words +%d/+%d, but the data '
- 'base and data end are at +%d/+%d'
- % (d[4], d[7], HDR_DS_WORD, HDR_HEAP_WORD))
- if errs:
- bad += 1
- print(' %-24s FAIL %s' % (os.path.basename(name)[:-4], '; '.join(errs)))
- else:
- print(' %-24s PASS %d bytes' % (os.path.basename(name)[:-4], com))
- print('----------------------------------------------------------------')
- print('independent .COM check: %d checked, %d failed' % (len(rows), bad))
- sys.exit(1 if bad else 0)
- PYEOF
- rc=$?
- [ "$rc" -eq 0 ] || exit 1
- echo "RESULT: ALL PASS"
|