run_com_tests.sh 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #!/bin/bash
  2. # Build and run the .COM linker harness (tests/ComTest.mod), then verify every
  3. # .COM it produced with an INDEPENDENT checker.
  4. #
  5. # The independent pass matters: ComTest computes the expectations from the same
  6. # Compiler state it is testing, so a bug in the compiler would be invisible to
  7. # it. The Python pass re-derives what the file must contain - the runtime's
  8. # first bytes, a zero gap, a size that covers the data area - from the
  9. # constants only, and cross-checks.
  10. set -u
  11. D=/home/eric/Projets/Projets-Modula2/MyWork/TP3-comp/shell
  12. GM2=/home/eric/bin/Modula2/Gm2/bin/gm2
  13. cd "$D" || exit 9
  14. FLAGS="-fiso"
  15. echo "== support modules =="
  16. [ -f Posix.o ] || cc -c Posix.c || exit 1
  17. for m in TextBuf Compiler Runtime Linker; do
  18. $GM2 $FLAGS -c $m.mod >/tmp/cm_c_$m 2>&1 \
  19. || { echo "COMPILE_FAIL $m"; grep -m5 "error:" /tmp/cm_c_$m; exit 1; }
  20. done
  21. $GM2 $FLAGS -c tests/ComTest.mod >/tmp/cm_c_ComTest 2>&1 \
  22. || { echo "COMPILE_FAIL ComTest"; grep -m5 "error:" /tmp/cm_c_ComTest; exit 1; }
  23. rm -f tests/ct.lst comtest
  24. $GM2 $FLAGS -fgen-module-list=tests/ct.lst -o /dev/null \
  25. tests/ComTest.mod TextBuf.o Posix.o Compiler.o Runtime.o Linker.o \
  26. >/tmp/cm_p1 2>&1
  27. p1=$?
  28. $GM2 $FLAGS -fuse-list=tests/ct.lst -o comtest \
  29. tests/ComTest.mod TextBuf.o Posix.o Compiler.o Runtime.o Linker.o \
  30. >/tmp/cm_p2 2>&1
  31. p2=$?
  32. if [ $p2 -ne 0 ]; then
  33. echo "LINK_FAIL p1_rc=$p1 p2_rc=$p2"
  34. grep -E "error:|undefined" /tmp/cm_p2 | head -10
  35. exit 1
  36. fi
  37. echo "comtest built (p1_rc=$p1, phase 1 rc=1 is the expected rollup)"
  38. # .COM files are written beside the harness, so run it in a scratch dir
  39. OUT=$(mktemp -d) || exit 9
  40. trap 'rm -rf "$OUT"' EXIT
  41. cd "$OUT" || exit 9
  42. ls "$D"/tests/fixtures/*.pas | "$D"/comtest > "$OUT/raw.txt" 2>&1
  43. sed 's/^.*fixtures\///' "$OUT/raw.txt"
  44. echo "----------------------------------------------------------------"
  45. nok=$(grep -c " OK com=" "$OUT/raw.txt")
  46. nerr=$(grep -c " ERROR " "$OUT/raw.txt")
  47. nbad=$(grep -cE "WRITE_COM_FAILED|CANNOT" "$OUT/raw.txt")
  48. echo "linked: $nok .COM files, $nerr fixtures rejected at compile time, $nbad harness failures"
  49. if [ "$nbad" -ne 0 ]; then
  50. echo "RESULT: FAIL (harness could not link every compiling fixture)"
  51. exit 1
  52. fi
  53. # ---- independent verification of the bytes on disk ------------------------
  54. python3 - "$OUT" <<'PYEOF'
  55. import sys, os, re, glob
  56. out = sys.argv[1]
  57. # Compiler layout constants, restated here on purpose: the checker must not
  58. # ask the code under test what the answer is.
  59. #
  60. # Re-baselined 385 -> 391 with the runtime bug fixes (see the re-baseline
  61. # rationale in tests/runtime.golden and the ModRM table in Runtime.mod). The
  62. # size is what a change to the runtime shows up in first; if this constant
  63. # ever needs changing again, find out why the runtime moved rather than just
  64. # editing the number, and check tests/check_runtime.py for the reason.
  65. RTSZ = 391 # Runtime.RT_Size()
  66. DATAB = RTSZ + 0x1000 # Compiler: data base = rtSz + 1000H
  67. # initmem's prologue, which is the runtime's only reader of the program
  68. # header. The displacements +4 and +6 below are the whole point of this
  69. # constant: the header word checks further down read hdrDS at +4 and hdrHeap
  70. # at +6, and initmem has to read the SAME two words or it clears the wrong
  71. # range. It used to read +8 (hdrMax, which the compiler patches to 0), so it
  72. # zeroed nothing at all, and nothing here noticed -- the emitted loop was
  73. # perfectly well formed, it just never ran. Asserting the bytes and the
  74. # header offsets together is what closes that gap.
  75. HEAD = '8B F0 8B 54 04 8B 4C 06' # 11 bytes of initmem, see below
  76. HDR_DS_WORD = 4 # header word holding the data base
  77. HDR_HEAP_WORD = 6 # header word holding the data end
  78. # Byte 4 of HEAD is the displacement of initmem's MOV DX,[SI+?], and byte 7
  79. # the displacement of its MOV CX,[SI+?]. The checks below read the header
  80. # words at HDR_DS_WORD and HDR_HEAP_WORD, so tying those two displacements to
  81. # the same two constants is what makes the runtime and the compiler agree by
  82. # construction rather than by coincidence.
  83. assert [int(HEAD.split()[4], 16), int(HEAD.split()[7], 16)] == \
  84. [HDR_DS_WORD, HDR_HEAP_WORD], \
  85. 'initmem no longer reads the two header words this checker verifies'
  86. raw = open(os.path.join(out, 'raw.txt')).read()
  87. rows = re.findall(r'(\S+\.pas)\s+OK\s+com=(\d+)\s+image=(\d+)\s+data=(\d+)\s+nonzeroInGap=(\d+)', raw)
  88. if not rows:
  89. print('RESULT: FAIL (no linked fixtures found in output)')
  90. sys.exit(1)
  91. bad = 0
  92. for name, com, image, data, nzg in rows:
  93. com, image, data, nzg = int(com), int(image), int(data), int(nzg)
  94. # ComTest writes the .COM by BASENAME beside itself (it cannot graft a
  95. # directory onto a source path), so the checker must look for the bare
  96. # name, not the full source path the fixture was read from.
  97. path = os.path.join(out, os.path.basename(name)[:-4] + '.COM')
  98. errs = []
  99. if not os.path.exists(path):
  100. errs.append('no .COM file')
  101. d = b''
  102. else:
  103. d = open(path, 'rb').read()
  104. if d[:len(HEAD.split())].hex(' ').upper() != HEAD:
  105. errs.append('runtime not at offset 0 (first bytes %s, want %s)'
  106. % (d[:len(HEAD.split())].hex(' ').upper(), HEAD))
  107. if len(d) != com:
  108. errs.append('file is %d bytes, harness said %d' % (len(d), com))
  109. if DATAB + data != len(d):
  110. errs.append('size %d != dataBase+data %d' % (len(d), DATAB + data))
  111. # the gap between the image and the data area must be entirely zero
  112. gap = d[image:DATAB]
  113. if any(gap):
  114. errs.append('%d non-zero bytes in the code/data gap' % sum(1 for b in gap if b))
  115. if nzg != 0:
  116. errs.append('harness itself reported %d non-zero gap bytes' % nzg)
  117. # the program must start exactly where the runtime ends
  118. if image < RTSZ:
  119. errs.append('image %d shorter than the runtime %d' % (image, RTSZ))
  120. # the program header sits at offset rtSz, and its words must describe the
  121. # image that is actually in the file
  122. if len(d) > RTSZ + 8:
  123. hdr = d[RTSZ:RTSZ+16]
  124. flag = int.from_bytes(hdr[0:2], 'little')
  125. cs = int.from_bytes(hdr[2:4], 'little')
  126. ds = int.from_bytes(hdr[HDR_DS_WORD:HDR_DS_WORD+2], 'little')
  127. heap = int.from_bytes(hdr[HDR_HEAP_WORD:HDR_HEAP_WORD+2], 'little')
  128. if flag != 1:
  129. errs.append('hdrFlag=%d' % flag)
  130. # hdrCS is pc, the end of the code, and the harness's `image` is
  131. # rtSz + code ALREADY - so the two are the same number. Adding
  132. # RTSZ here counted the runtime twice.
  133. if cs != image:
  134. errs.append('hdrCS=%d, want %d (end of image)' % (cs, image))
  135. if ds != DATAB:
  136. errs.append('hdrDS=%d, want %d' % (ds, DATAB))
  137. if heap != DATAB + data:
  138. errs.append('hdrHeap=%d, want %d' % (heap, DATAB + data))
  139. # initmem must read hdrDS and hdrHeap, not some other pair of header
  140. # words. (It read +8, hdrMax, which the compiler patches to 0, so
  141. # the range it cleared was empty and every global kept whatever the
  142. # loader left in it.)
  143. if [d[4], d[7]] != [HDR_DS_WORD, HDR_HEAP_WORD]:
  144. errs.append('initmem reads header words +%d/+%d, but the data '
  145. 'base and data end are at +%d/+%d'
  146. % (d[4], d[7], HDR_DS_WORD, HDR_HEAP_WORD))
  147. if errs:
  148. bad += 1
  149. print(' %-24s FAIL %s' % (os.path.basename(name)[:-4], '; '.join(errs)))
  150. else:
  151. print(' %-24s PASS %d bytes' % (os.path.basename(name)[:-4], com))
  152. print('----------------------------------------------------------------')
  153. print('independent .COM check: %d checked, %d failed' % (len(rows), bad))
  154. sys.exit(1 if bad else 0)
  155. PYEOF
  156. rc=$?
  157. [ "$rc" -eq 0 ] || exit 1
  158. echo "RESULT: ALL PASS"