Files
tms-c-compiler/scripts/tests/m3/diff/check.sh
T

269 lines
9.7 KiB
Bash
Executable File

#!/bin/sh
# M3 differential TI-compiler control-flow/Xlib check.
#
# scripts/tests/m3/diff/check.sh
#
# The stored ti-branch.s/ti-xhello.s are one-time outputs of the 1987 TI
# compiler (GSP C Compiler 2.01) under DOSBox. CI never runs DOSBox: this
# script greps the stored golden files and compares their ABI shape with the
# assembly our compiler emits for the equivalent C.
#
# The oracle is the ABI shape, not instruction selection. Each requirement
# accepts the TI spellings and ours:
#
# sumto a conditional branch, an unconditional loop back-edge, and a last
# RETS of RETS 2
# max2 a conditional branch, and a last RETS of RETS 2
# xhello nine argument pushes before _XCreateSimpleWindow, and the ROM bit
# displacements 0x480 (Display.screens) / 0x40 (Screen.root)
#
# This check does not verify the result register, the argument order, or the
# "background first, display last" sequence: the MAME gate checks the returned
# values, and dis-roundtrip.sh checks the push order.
#
# TI emits JRLE/JRUC where we emit JAxx/JAUC; both are accepted. TI prints the
# displacements as decimal 1152/64, so 480h/40h are accepted too. A
# displacement only counts inside the load that uses it: a bare 64/1152
# elsewhere in the body (an unrelated local at *FP(64), the constant 0x400,
# an 0x4800 that merely shares a prefix) is not evidence.
#
# Our branch.c is built exactly like the M3 gate: tms-clang -O2 with the
# -replexitval=never optimizer (that rewrite would turn sumto's loop into an
# i33 multiply, which the target cannot select), then llc -O0. Dropping
# -replexitval=never makes this check fail to compile the loop, not pass.
#
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC TI_BRANCH TI_XHELLO
set -eu
ROOT=$(cd "$(dirname "$0")/../../../.." && pwd)
DIR=$ROOT/scripts/tests/m3/diff
SRC=$ROOT/scripts/m3
LLVM_BIN=${LLVM_BIN:-$ROOT/build/llvm-clean-build/bin}
TMS_CLANG=${TMS_CLANG:-$LLVM_BIN/clang}
TMS_OPT=${TMS_OPT:-$LLVM_BIN/opt}
TMS_BITADDR=${TMS_BITADDR:-$ROOT/tools/tms-bitaddr/tms-bitaddr}
TMS_LLC=${TMS_LLC:-$LLVM_BIN/llc}
TI_BRANCH=${TI_BRANCH:-$DIR/ti-branch.s}
TI_XHELLO=${TI_XHELLO:-$DIR/ti-xhello.s}
export TMS_CLANG TMS_OPT TMS_BITADDR
fail() {
echo "M3 diff: FAIL: $*" >&2
exit 1
}
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do
[ -x "$tool" ] || fail "missing tool: $tool"
done
[ -f "$TI_BRANCH" ] || fail "stored TI golden missing: $TI_BRANCH"
[ -f "$TI_XHELLO" ] || fail "stored TI golden missing: $TI_XHELLO"
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m3-diff.XXXXXX")
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
# branch.c is generated through a wrapper so only this file keeps its loop.
OPT_NOREPLEXIT=$TMP/opt-noreplexit
printf '#!/bin/sh\nexec "%s" -replexitval=never "$@"\n' "$TMS_OPT" > "$OPT_NOREPLEXIT"
chmod +x "$OPT_NOREPLEXIT"
echo "== compile our branch.s and xhello.s"
TMS_OPT=$OPT_NOREPLEXIT "$ROOT/scripts/tms-clang" -O2 -I "$SRC" \
-o "$TMP/branch.ll" "$SRC/branch.c"
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/branch.ll" -o "$TMP/branch.s"
"$ROOT/scripts/tms-clang" -O2 -I "$SRC" \
-o "$TMP/xhello.ll" "$SRC/xhello.c"
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/xhello.ll" -o "$TMP/xhello.s"
echo " branch.s: $(grep -c . "$TMP/branch.s") lines"
echo " xhello.s: $(grep -c . "$TMP/xhello.s") lines"
# Our llc output has `;` end-of-line comments (`; %bb.0`). Strip them so a
# comment cannot satisfy a check; TI asm has none.
for f in "$TMP/branch.s" "$TMP/xhello.s"; do
sed 's/;.*//' "$f" > "$f.code"
done
# body FILE FUNC: from `_func:` to the next .globl/.end.
body() {
awk -v want="_$2:" '
$1 == want { infn = 1; next }
infn && ($1 == ".globl" || $1 == ".end") { exit }
infn { print }
' "$1"
}
require() {
_file=$1
_pat=$2
_what=$3
if grep -Eiq "$_pat" "$_file"; then
echo " ok $_what"
return 0
fi
echo "M3 diff: FAIL: missing $_what" >&2
echo " pattern: $_pat" >&2
sed 's/^/ /' "$_file" >&2
exit 1
}
# require_last_rets2 FILE WHAT: the last RETS instruction in the body (the
# epilogue) is RETS 2. Matching any RETS 2 elsewhere would let a wrong
# epilogue through.
require_last_rets2() {
if awk '
/^[[:space:]]*RETS([[:space:]]|$)/ { last = $0; seen = 1 }
END {
if (!seen)
exit 1
exit (last ~ /^[[:space:]]*RETS[[:space:]]+2[[:space:]]*$/) ? 0 : 1
}
' "$1"; then
echo " ok $2"
return 0
fi
echo "M3 diff: FAIL: $2 (last RETS is not RETS 2)" >&2
sed 's/^/ /' "$1" >&2
exit 1
}
# A conditional branch: JA/JR plus a condition suffix. JAUC/JRUC/JRU do not
# match because their suffix is U/UC.
COND='^[[:space:]]*J[A-Z]*(EQ|NE|LT|LE|GT|GE|V|NV|P|N|Z|NZ|PL|MI)[[:space:]]'
# An unconditional branch: our JAUC, TI's JRUC (and bare JRU).
UNCOND='^[[:space:]]*J(AUC|RUC|RU|UMP)[[:space:]]'
# has_backedge FILE: true when a branch targets a label defined earlier in the
# same function body. TI closes the loop with the conditional JRLE L4; we
# close it with the unconditional JAUC LBB0_1, so either form counts.
has_backedge() {
awk '
/^[A-Za-z_][A-Za-z0-9_]*:/ {
lbl = $1
sub(/:$/, "", lbl)
pos[lbl] = NR
next
}
/^[[:space:]]*J/ {
if ($2 in pos && pos[$2] < NR)
found = 1
}
END { exit found ? 0 : 1 }
' "$1"
}
check_branch() { # check_branch FILE LABEL
_f=$1
body "$_f" sumto > "$TMP/sumto.body"
body "$_f" max2 > "$TMP/max2.body"
require "$TMP/sumto.body" "$COND" "$2: sumto has a conditional branch"
require "$TMP/sumto.body" "$UNCOND" "$2: sumto has an unconditional branch"
if ! has_backedge "$TMP/sumto.body"; then
echo "M3 diff: FAIL: $2: sumto has no loop back-edge" >&2
sed 's/^/ /' "$TMP/sumto.body" >&2
exit 1
fi
echo " ok $2: sumto branches back to the loop"
require_last_rets2 "$TMP/sumto.body" "$2: sumto ends with RETS 2"
require "$TMP/max2.body" "$COND" "$2: max2 has a conditional branch"
require_last_rets2 "$TMP/max2.body" "$2: max2 ends with RETS 2"
}
echo "== ABI shape: TI golden"
check_branch "$TI_BRANCH" "TI"
echo "== ABI shape: our compiler"
check_branch "$TMP/branch.s.code" "ours"
# Count the argument pushes since the previous CALLA, then require nine for
# XCreateSimpleWindow. Both compilers push the last argument first: display,
# root, x, y, width, height, border_width, border, background.
count_xcreate_pushes() {
awk '
/^_xhello:/ { infn = 1; n = 0; next }
!infn { next }
/CALLA[[:space:]]+_XCreateSimpleWindow/ { print n; exit }
/CALLA/ { n = 0; next }
/MOVE[[:space:]]+[^,]+,[[:space:]]*\*(STK|A14)\+/ { n++ }
' "$1"
}
for pair in "$TI_XHELLO:TI" "$TMP/xhello.s.code:ours"; do
f=${pair%%:*}
who=${pair##*:}
pushes=$(count_xcreate_pushes "$f")
[ -n "$pushes" ] || fail "$who xhello has no CALLA _XCreateSimpleWindow"
[ "$pushes" = "9" ] \
|| fail "$who XCreateSimpleWindow has $pushes argument pushes, want 9"
echo " ok $who: XCreateSimpleWindow takes nine argument pushes"
done
# TI: Display.screens is the indexed load `MOVE *A7(1152),A7,1`; Screen.root
# is loaded by `MOVE *A7(64),*FP(32),1`. The closing paren and the comma keep
# the boundary exact, so 0x4800/0x400 and decimal 11520/640 do not match; the
# register names may vary.
TI_SCREENS='^[[:space:]]*MOVE[[:space:]]+\*(A[0-9]+|FP|SP)\((1152|0x480|480h)\),'
TI_ROOT='^[[:space:]]*MOVE[[:space:]]+\*(A[0-9]+|FP|SP)\((64|0x40|40h)\),[[:space:]]*\*(A[0-9]+|FP|SP)\([0-9]+\)'
# has_addi_load FILE screens|root: our displacement is materialised by
# `ADDI <disp>, A<n>` and then used by a load through that same register
# (`MOVE *A<n>,...`). Only that pairing counts, not any ADDI in the body.
has_addi_load() {
awk -v kind="$2" '
function disp_ok(d) {
if (kind == "screens")
return d == "1152" || d == "0x480" || d == "480h"
return d == "64" || d == "0x40" || d == "40h"
}
/^[[:space:]]*ADDI[[:space:]]/ {
line = $0
sub(/^[[:space:]]*ADDI[[:space:]]+/, "", line)
split(line, f, /[[:space:]]*,[[:space:]]*/)
disp = f[1]
reg = f[2]
if (getline nxt <= 0)
next
while (nxt ~ /^[[:space:]]*$/) {
if (getline nxt <= 0)
next
}
if (disp_ok(disp) &&
nxt ~ ("^[[:space:]]*MOVE[[:space:]]+\\*" reg "([[:space:],]|$)"))
found = 1
}
END { exit found ? 0 : 1 }
' "$1"
}
check_offsets() { # check_offsets FILE WHO ti|ours
_f=$1
_who=$2
_side=$3
body "$_f" xhello > "$TMP/xhello.body"
if [ "$_side" = ti ]; then
require "$TMP/xhello.body" "$TI_SCREENS" \
"$_who: Display.screens is loaded at bit 0x480 (*A?(1152))"
require "$TMP/xhello.body" "$TI_ROOT" \
"$_who: Screen.root is loaded at bit 0x40 (*A?(64),*A?(...))"
return 0
fi
if has_addi_load "$TMP/xhello.body" screens; then
echo " ok $_who: Display.screens is loaded at bit 0x480 (ADDI 1152 + load)"
else
echo "M3 diff: FAIL: missing $_who: Display.screens load at bit 0x480" >&2
sed 's/^/ /' "$TMP/xhello.body" >&2
exit 1
fi
if has_addi_load "$TMP/xhello.body" root; then
echo " ok $_who: Screen.root is loaded at bit 0x40 (ADDI 64 + load)"
else
echo "M3 diff: FAIL: missing $_who: Screen.root load at bit 0x40" >&2
sed 's/^/ /' "$TMP/xhello.body" >&2
exit 1
fi
}
echo "== ROM bit displacements"
check_offsets "$TI_XHELLO" "TI" ti
check_offsets "$TMP/xhello.s.code" "ours" ours
echo "M3 diff: PASS"