569 lines
21 KiB
Bash
Executable File
569 lines
21 KiB
Bash
Executable File
#!/bin/sh
|
|
# M4 differential TI-compiler check: function pointers and the Xlib image ABI.
|
|
#
|
|
# scripts/tests/m4/diff/check.sh
|
|
#
|
|
# The stored ti-fnptr.s/ti-image.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:
|
|
#
|
|
# apply the old-STK save, one program-stack argument push, a call through
|
|
# an A register (TI CALL, ours CALL; CALLR is accepted too), and a
|
|
# last RETS of RETS 2. A direct CALLA in apply is a failure.
|
|
# test the direct CALLA _apply calls (at least three; TI has three,
|
|
# ours has four because the fixture calls apply again in the return).
|
|
# image XCreateImage and XPutImage take ten argument pushes, XCreatePixmap
|
|
# five, and XCreateSimpleWindow nine. Every call saves old STK.
|
|
# The first pushes are the last arguments, so the leading constants
|
|
# are XCreateImage 80,32,480,640; XPutImage 32,640; XCreatePixmap
|
|
# 1,480,640; XCreateSimpleWindow 0,0,0,480,640. The final push is
|
|
# the Display pointer, not a constant. This is what makes the
|
|
# reversed-argument negative control fail.
|
|
# offsets Display.screens (bit 0x480, byte 0x90), Screen.root (bit 0x40,
|
|
# byte 0x8), and XImage data/byte_order/bytes_per_line/bits_per_pixel/
|
|
# f (bits 0x80/0xA0/0x140/0x160/0x200). TI uses the packed direct
|
|
# displacements; ours materialises an ADDI and the matching load or
|
|
# store through the same register.
|
|
#
|
|
# The plan's "XCreateImage/XPutImage 10/9" is a typo for 10/10: XPutImage is
|
|
# (display, drawable, gc, image, src_x, src_y, dest_x, dest_y, width, height),
|
|
# ten arguments. Nine is XCreateSimpleWindow.
|
|
#
|
|
# Negative controls run at the end: the check is re-run on a copy of the
|
|
# fnptr golden with the indirect call removed, on a copy of the image golden
|
|
# with the XPutImage argument pushes fully reversed, and on a copy with only
|
|
# the Display and drawable pushes swapped. All must fail. The stored goldens
|
|
# are never modified.
|
|
#
|
|
# Our fnptr source is scripts/m4/fnptr.c (the runtime gate fixture). The image
|
|
# equivalent is generated here because it needs only the layout declarations.
|
|
#
|
|
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC TI_FNPTR TI_IMAGE
|
|
# OUR_FNPTR; M4_DIFF_POSITIVE_ONLY=1 skips the negative controls.
|
|
set -eu
|
|
|
|
ROOT=$(cd "$(dirname "$0")/../../../.." && pwd)
|
|
DIR=$ROOT/scripts/tests/m4/diff
|
|
|
|
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_FNPTR=${TI_FNPTR:-$DIR/ti-fnptr.s}
|
|
TI_IMAGE=${TI_IMAGE:-$DIR/ti-image.s}
|
|
OUR_FNPTR=${OUR_FNPTR:-$ROOT/scripts/m4/fnptr.c}
|
|
export TMS_CLANG TMS_OPT TMS_BITADDR
|
|
|
|
fail() {
|
|
echo "M4 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_FNPTR" ] || fail "stored TI golden missing: $TI_FNPTR"
|
|
[ -f "$TI_IMAGE" ] || fail "stored TI golden missing: $TI_IMAGE"
|
|
[ -f "$OUR_FNPTR" ] || fail "our fnptr source missing: $OUR_FNPTR"
|
|
|
|
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m4-diff.XXXXXX")
|
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
|
|
|
echo "== compile our fnptr.s and image.s"
|
|
TMS_CLANG="$TMS_CLANG" TMS_OPT="$TMS_OPT" TMS_BITADDR="$TMS_BITADDR" \
|
|
"$ROOT/scripts/tms-clang" -O2 -o "$TMP/fnptr.ll" "$OUR_FNPTR"
|
|
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm \
|
|
"$TMP/fnptr.ll" -o "$TMP/fnptr.s"
|
|
|
|
cat > "$TMP/image.c" <<'EOF'
|
|
struct display { unsigned char pad[0x90]; void *screens; };
|
|
struct screen { unsigned char pad[0x8]; unsigned int root; };
|
|
struct ximage_funcs {
|
|
void *create_image; void *destroy_image; void *get_pixel;
|
|
void *put_pixel; void *sub_image; void *add_pixel;
|
|
};
|
|
struct ximage {
|
|
int width, height; int xoffset; int format; char *data; int byte_order;
|
|
int bitmap_unit; int bitmap_bit_order; int bitmap_pad; int depth;
|
|
int bytes_per_line; int bits_per_pixel;
|
|
unsigned long red_mask, green_mask, blue_mask;
|
|
void *obdata;
|
|
struct ximage_funcs f;
|
|
};
|
|
extern struct display *XOpenDisplay(const char *);
|
|
extern int XCreateSimpleWindow(struct display *, unsigned int, int, int,
|
|
unsigned int, unsigned int, unsigned int,
|
|
unsigned long, unsigned long);
|
|
extern unsigned int XCreatePixmap(struct display *, unsigned int, unsigned int,
|
|
unsigned int, unsigned int);
|
|
extern struct ximage *XCreateImage(struct display *, void *, unsigned int, int,
|
|
int, char *, unsigned int, unsigned int,
|
|
int, int);
|
|
extern int XPutImage(struct display *, unsigned int, void *, struct ximage *,
|
|
int, int, int, int, unsigned int, unsigned int);
|
|
extern int XMapWindow(struct display *, unsigned int);
|
|
extern int XSync(struct display *, int);
|
|
|
|
__attribute__((noinline))
|
|
unsigned int image(struct display *d, unsigned int w, void *gc, char *data) {
|
|
struct ximage *img;
|
|
unsigned int root;
|
|
unsigned int pm;
|
|
|
|
root = ((struct screen *)d->screens)->root;
|
|
img = XCreateImage(d, 0, 1, 0, 0, data, 640, 480, 32, 80);
|
|
img->byte_order = 0;
|
|
img->bytes_per_line = 80;
|
|
img->bits_per_pixel = 1;
|
|
img->f.create_image = 0;
|
|
pm = XCreatePixmap(d, w, 640, 480, 1);
|
|
XPutImage(d, pm, gc, img, 0, 0, 0, 0, 640, 32);
|
|
img->data = data;
|
|
XMapWindow(d, w);
|
|
XSync(d, 0);
|
|
return root + pm;
|
|
}
|
|
|
|
__attribute__((noinline))
|
|
unsigned int window(struct display *d) {
|
|
unsigned int root = ((struct screen *)d->screens)->root;
|
|
return XCreateSimpleWindow(d, root, 20, 20, 640, 480, 0, 0, 0);
|
|
}
|
|
EOF
|
|
|
|
TMS_CLANG="$TMS_CLANG" TMS_OPT="$TMS_OPT" TMS_BITADDR="$TMS_BITADDR" \
|
|
"$ROOT/scripts/tms-clang" -O2 -o "$TMP/image.ll" "$TMP/image.c"
|
|
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm \
|
|
"$TMP/image.ll" -o "$TMP/image.s"
|
|
echo " fnptr.s: $(grep -c . "$TMP/fnptr.s") lines, image.s: $(grep -c . "$TMP/image.s") lines"
|
|
|
|
# Our llc output has `;` end-of-line comments. Strip them so a comment cannot
|
|
# satisfy a check; TI asm has none.
|
|
for f in "$TMP/fnptr.s" "$TMP/image.s"; do
|
|
sed 's/;.*//' "$f" > "$f.code"
|
|
done
|
|
|
|
# body FILE FUNC: from `_func:` to the next function label or section directive.
|
|
body() {
|
|
awk -v want="_$2:" '
|
|
$1 == want { infn = 1; next }
|
|
infn {
|
|
if ($0 ~ /^[[:space:]]*\.(globl|sect|text|bss|data|end|file)/) exit
|
|
print
|
|
}
|
|
' "$1"
|
|
}
|
|
|
|
require() {
|
|
_file=$1
|
|
_pat=$2
|
|
_what=$3
|
|
if grep -Eiq "$_pat" "$_file"; then
|
|
echo " ok $_what"
|
|
return 0
|
|
fi
|
|
echo "M4 diff: FAIL: missing $_what" >&2
|
|
echo " pattern: $_pat" >&2
|
|
sed 's/^/ /' "$_file" >&2
|
|
exit 1
|
|
}
|
|
|
|
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 "M4 diff: FAIL: $2 (last RETS is not RETS 2)" >&2
|
|
sed 's/^/ /' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
# call_info FILE FUNC CALLEE: one line
|
|
# "N PUSHES OLDSTK LASTDISP TOKENS..." for the first call to CALLEE in the
|
|
# function body. Each TOKENS element is a resolved decimal constant for a
|
|
# push whose source register last held a constant, or X for anything else.
|
|
# LASTDISP is 1 when the final push's source is the first-argument slot: the
|
|
# TI golden's `*FP(-32)`, or our register loaded from the ADDI -32 slot.
|
|
call_info() {
|
|
body "$1" "$2" | awk -v want="$3" '
|
|
function isreg(x) { return x ~ /^A([0-9]|1[0-5])$/ }
|
|
function trim(x) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", x); return x }
|
|
function unknown(r) { has[r] = 0 }
|
|
function operands(line, a, n, i) {
|
|
n = split(line, a, ",")
|
|
for (i = 1; i <= n; i++)
|
|
a[i] = trim(a[i])
|
|
return n
|
|
}
|
|
/^[[:space:]]*MOVE[[:space:]]/ {
|
|
line = $0
|
|
sub(/^[[:space:]]*MOVE[[:space:]]+/, "", line)
|
|
n = operands(line, a)
|
|
if (n >= 3 && a[2] ~ /^\*(STK|A14)\+$/ && a[3] == "1") {
|
|
npush++
|
|
if (isreg(a[1]) && has[a[1]])
|
|
consts = consts " " val[a[1]]
|
|
else
|
|
consts = consts " X"
|
|
if (a[1] == "*FP(-32)" ||
|
|
(isreg(a[1]) && hasdisp[a[1]]))
|
|
lastdisp = 1
|
|
else
|
|
lastdisp = 0
|
|
next
|
|
}
|
|
if (n >= 3 && a[2] ~ /^-\*SP$/ && (a[1] == "STK" || a[1] == "A14")) {
|
|
oldstk = 1
|
|
next
|
|
}
|
|
if (n >= 2 && isreg(a[2])) {
|
|
dsrc = 0
|
|
if (a[1] == "*FP(-32)")
|
|
dsrc = 1
|
|
else if (a[1] ~ /^\*A[0-9]+$/ && firstaddr[substr(a[1], 2)])
|
|
dsrc = 1
|
|
else if (isreg(a[1]) && hasdisp[a[1]])
|
|
dsrc = 1
|
|
if (a[1] ~ /^-?[0-9]+$/) {
|
|
val[a[2]] = a[1]; has[a[2]] = 1; hasdisp[a[2]] = 0
|
|
} else if (isreg(a[1]) && has[a[1]]) {
|
|
val[a[2]] = val[a[1]]; has[a[2]] = 1; hasdisp[a[2]] = dsrc
|
|
} else {
|
|
has[a[2]] = 0; hasdisp[a[2]] = dsrc
|
|
}
|
|
}
|
|
next
|
|
}
|
|
/^[[:space:]]*(MOVI|MOVK)[[:space:]]/ {
|
|
line = $0
|
|
sub(/^[[:space:]]*(MOVI|MOVK)[[:space:]]+/, "", line)
|
|
n = operands(line, a)
|
|
if (n >= 2 && isreg(a[2]) && a[1] ~ /^-?[0-9]+$/) {
|
|
val[a[2]] = a[1]; has[a[2]] = 1; hasdisp[a[2]] = 0
|
|
} else if (n >= 2 && isreg(a[2])) {
|
|
unknown(a[2])
|
|
}
|
|
next
|
|
}
|
|
/^[[:space:]]*CLR[[:space:]]/ {
|
|
line = $0
|
|
sub(/^[[:space:]]*CLR[[:space:]]+/, "", line)
|
|
if (isreg(trim(line))) {
|
|
val[trim(line)] = 0; has[trim(line)] = 1
|
|
hasdisp[trim(line)] = 0
|
|
}
|
|
next
|
|
}
|
|
/^[[:space:]]*SUB[[:space:]]/ {
|
|
line = $0
|
|
sub(/^[[:space:]]*SUB[[:space:]]+/, "", line)
|
|
n = operands(line, a)
|
|
if (n >= 2 && isreg(a[2]) && a[1] == a[2]) {
|
|
val[a[2]] = 0; has[a[2]] = 1; hasdisp[a[2]] = 0
|
|
} else if (n >= 2 && isreg(a[2])) {
|
|
unknown(a[2])
|
|
}
|
|
next
|
|
}
|
|
/^[[:space:]]*(ADDK|SUBK)[[:space:]]/ {
|
|
op = $1
|
|
line = $0
|
|
sub(/^[[:space:]]*(ADDK|SUBK)[[:space:]]+/, "", line)
|
|
n = operands(line, a)
|
|
if (n >= 2 && isreg(a[2]) && a[1] ~ /^-?[0-9]+$/ && has[a[2]]) {
|
|
val[a[2]] = val[a[2]] + (op == "ADDK" ? a[1] : -a[1])
|
|
hasdisp[a[2]] = 0
|
|
} else if (n >= 2 && isreg(a[2])) {
|
|
unknown(a[2])
|
|
}
|
|
next
|
|
}
|
|
/^[[:space:]]*ADDI[[:space:]]/ {
|
|
line = $0
|
|
sub(/^[[:space:]]*ADDI[[:space:]]+/, "", line)
|
|
n = operands(line, a)
|
|
if (n >= 2 && isreg(a[2])) {
|
|
unknown(a[2])
|
|
if (a[1] == "-32")
|
|
firstaddr[a[2]] = 1
|
|
else
|
|
firstaddr[a[2]] = 0
|
|
}
|
|
next
|
|
}
|
|
/^[[:space:]]*CALLA[[:space:]]/ {
|
|
callee = $2
|
|
sub(/^_/, "", callee)
|
|
if (callee == want) {
|
|
print npush, oldstk, lastdisp, consts
|
|
exit
|
|
}
|
|
npush = 0; oldstk = 0; lastdisp = 0; consts = ""
|
|
next
|
|
}
|
|
'
|
|
}
|
|
|
|
# check_call FILE FUNC CALLEE NPUSH "CONST PREFIX"
|
|
check_call() {
|
|
_file=$1
|
|
_func=$2
|
|
_callee=$3
|
|
_wantn=$4
|
|
_wantc=$5
|
|
_info=$(call_info "$_file" "$_func" "$_callee")
|
|
[ -n "$_info" ] || fail "$_func has no CALLA $_callee"
|
|
set -- $_info
|
|
_n=$1
|
|
_stk=$2
|
|
_disp=$3
|
|
shift 3
|
|
_tokens="$*"
|
|
[ "$_n" = "$_wantn" ] \
|
|
|| fail "$_func/$_callee has $_n argument pushes, want $_wantn"
|
|
[ "$_stk" = 1 ] || fail "$_func/$_callee does not save old STK"
|
|
case "$_tokens" in
|
|
"$_wantc "*) ;;
|
|
*)
|
|
echo "M4 diff: FAIL: $_func/$_callee leading constants are '$_tokens', want '$_wantc ...'" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
_last=${_tokens##* }
|
|
[ "$_last" = X ] \
|
|
|| fail "$_func/$_callee last push looks like a constant, not the Display argument"
|
|
[ "$_disp" = 1 ] \
|
|
|| fail "$_func/$_callee last push is not the Display argument (first-argument slot)"
|
|
echo " ok $_func/$_callee: $_n pushes, old STK, leading [$_wantc], Display last"
|
|
}
|
|
|
|
# check_fnptr FILE WHO TESTFUNC
|
|
check_fnptr() {
|
|
_f=$1
|
|
_who=$2
|
|
_test=$3
|
|
body "$_f" apply > "$TMP/apply.body"
|
|
require "$TMP/apply.body" '^[[:space:]]*CALL(R)?[[:space:]]+A[0-9]+' \
|
|
"$_who: apply calls through an A register"
|
|
if grep -Eq '^[[:space:]]*CALLA[[:space:]]' "$TMP/apply.body"; then
|
|
sed 's/^/ /' "$TMP/apply.body" >&2
|
|
fail "$_who: apply still contains a direct CALLA"
|
|
fi
|
|
require "$TMP/apply.body" '^[[:space:]]*MOVE[[:space:]]+(STK|A14),[[:space:]]*-\*SP' \
|
|
"$_who: apply saves the old STK"
|
|
require "$TMP/apply.body" \
|
|
'^[[:space:]]*MOVE[[:space:]]+[^,]+,[[:space:]]*\*(STK|A14)\+,[[:space:]]*1' \
|
|
"$_who: apply pushes the callback argument"
|
|
require_last_rets2 "$TMP/apply.body" "$_who: apply ends with RETS 2"
|
|
|
|
body "$_f" "$_test" > "$TMP/$_test.body"
|
|
_calls=$(grep -Ec '^[[:space:]]*CALLA[[:space:]]+_apply' "$TMP/$_test.body" || true)
|
|
[ "$_calls" -ge 3 ] \
|
|
|| { sed 's/^/ /' "$TMP/$_test.body" >&2; fail "$_who: $_test makes $_calls direct CALLA _apply calls, want at least 3"; }
|
|
echo " ok $_who: $_test makes $_calls direct CALLA _apply calls (want >= 3)"
|
|
}
|
|
|
|
# has_addi_access FILE KIND DISP: an ADDI DISP,REG immediately followed by a
|
|
# MOVE that uses *REG as a load (KIND=load) or store destination (KIND=store).
|
|
has_addi_access() {
|
|
awk -v kind="$2" -v disp="$3" '
|
|
function disp_ok(d) {
|
|
if (d == disp) return 1
|
|
if (disp == "1152" && (d == "0x480" || d == "480h")) return 1
|
|
if (disp == "64" && (d == "0x40" || d == "40h")) return 1
|
|
if (disp == "128" && (d == "0x80" || d == "80h")) return 1
|
|
if (disp == "160" && (d == "0xA0" || d == "0a0h")) return 1
|
|
if (disp == "320" && (d == "0x140" || d == "140h")) return 1
|
|
if (disp == "352" && (d == "0x160" || d == "160h")) return 1
|
|
if (disp == "512" && (d == "0x200" || d == "200h")) return 1
|
|
return 0
|
|
}
|
|
/^[[:space:]]*ADDI[[:space:]]/ {
|
|
line = $0
|
|
sub(/^[[:space:]]*ADDI[[:space:]]+/, "", line)
|
|
split(line, f, /[[:space:]]*,[[:space:]]*/)
|
|
d = f[1]
|
|
reg = f[2]
|
|
if (!disp_ok(d))
|
|
next
|
|
if (getline nxt <= 0)
|
|
next
|
|
while (nxt ~ /^[[:space:]]*$/) {
|
|
if (getline nxt <= 0)
|
|
next
|
|
}
|
|
if (kind == "load" &&
|
|
nxt ~ ("^[[:space:]]*MOVE[[:space:]]+\\*" reg "([,\\(]|[[:space:]]|$)"))
|
|
found = 1
|
|
if (kind == "store" &&
|
|
nxt ~ ("^[[:space:]]*MOVE[[:space:]]+[^,]+, ?\\*" reg "([,\\(]|[[:space:]]|$)"))
|
|
found = 1
|
|
}
|
|
END { exit found ? 0 : 1 }
|
|
' "$1"
|
|
}
|
|
|
|
check_offsets() {
|
|
_f=$1
|
|
_who=$2
|
|
_side=$3
|
|
body "$_f" image > "$TMP/image.body"
|
|
if [ "$_side" = ti ]; then
|
|
require "$TMP/image.body" \
|
|
'^[[:space:]]*MOVE[[:space:]]+\*(A[0-9]+|FP|SP)\((1152|0x480|480h)\),' \
|
|
"$_who: Display.screens is loaded at bit 0x480 (byte 0x90)"
|
|
require "$TMP/image.body" \
|
|
'^[[:space:]]*MOVE[[:space:]]+\*(A[0-9]+|FP|SP)\((64|0x40|40h)\),[[:space:]]*\*(A[0-9]+|FP|SP)\([0-9]+\)' \
|
|
"$_who: Screen.root is loaded at bit 0x40 (byte 0x8)"
|
|
for pair in 128:data 160:byte_order 320:bytes_per_line 352:bits_per_pixel 512:f; do
|
|
d=${pair%%:*}
|
|
name=${pair##*:}
|
|
case "$d" in
|
|
128) pat='(128|0x80|80h)' ;;
|
|
160) pat='(160|0xA0|0a0h)' ;;
|
|
320) pat='(320|0x140|140h)' ;;
|
|
352) pat='(352|0x160|160h)' ;;
|
|
512) pat='(512|0x200|200h)' ;;
|
|
esac
|
|
require "$TMP/image.body" \
|
|
"^[[:space:]]*MOVE[[:space:]]+.*\\*A[0-9]+\\($pat\\)," \
|
|
"$_who: XImage.$name is at bit $d"
|
|
done
|
|
return 0
|
|
fi
|
|
for pair in load:1152:screens load:64:root store:128:data store:160:byte_order \
|
|
store:320:bytes_per_line store:352:bits_per_pixel store:512:f; do
|
|
kind=${pair%%:*}
|
|
rest=${pair#*:}
|
|
d=${rest%%:*}
|
|
name=${rest##*:}
|
|
if has_addi_access "$TMP/image.body" "$kind" "$d"; then
|
|
echo " ok $_who: XImage/Display/Screen $name access at bit $d (ADDI + $kind)"
|
|
else
|
|
sed 's/^/ /' "$TMP/image.body" >&2
|
|
fail "$_who: no $kind through bit displacement $d for $name"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# check_image FILE WHO TI|ours
|
|
check_image() {
|
|
_f=$1
|
|
_who=$2
|
|
_side=$3
|
|
check_call "$_f" image XCreateImage 10 "80 32 480 640"
|
|
check_call "$_f" image XCreatePixmap 5 "1 480 640"
|
|
check_call "$_f" image XPutImage 10 "32 640"
|
|
check_call "$_f" window XCreateSimpleWindow 9 "0 0 0 480 640"
|
|
body "$_f" image > "$TMP/rets.body"
|
|
require_last_rets2 "$TMP/rets.body" "$_who: image ends with RETS 2"
|
|
body "$_f" window > "$TMP/rets2.body"
|
|
require_last_rets2 "$TMP/rets2.body" "$_who: window ends with RETS 2"
|
|
check_offsets "$_f" "$_who" "$_side"
|
|
}
|
|
|
|
echo "== ABI shape: TI fnptr golden"
|
|
check_fnptr "$TI_FNPTR" "TI" test
|
|
echo "== ABI shape: our fnptr"
|
|
check_fnptr "$TMP/fnptr.s.code" "ours" fnptr_test
|
|
|
|
echo "== ABI shape: TI image golden"
|
|
check_image "$TI_IMAGE" "TI" ti
|
|
echo "== ABI shape: our image"
|
|
check_image "$TMP/image.s.code" "ours" ours
|
|
|
|
if [ "${M4_DIFF_POSITIVE_ONLY:-0}" = 1 ]; then
|
|
echo "M4 diff: PASS"
|
|
exit 0
|
|
fi
|
|
|
|
echo "== negative controls"
|
|
# 1. Remove the indirect call from the fnptr golden: apply must then fail.
|
|
sed 's/^[[:space:]]*CALL[[:space:]].*/ CALLA _dec/' "$TI_FNPTR" \
|
|
> "$TMP/fnptr-no-indirect.s"
|
|
grep -q 'CALLA _dec' "$TMP/fnptr-no-indirect.s" \
|
|
|| fail "could not construct the no-indirect-call fnptr golden"
|
|
if M4_DIFF_POSITIVE_ONLY=1 TI_FNPTR="$TMP/fnptr-no-indirect.s" \
|
|
"$0" > "$TMP/neg-fnptr.out" 2>&1; then
|
|
sed 's/^/ /' "$TMP/neg-fnptr.out" >&2
|
|
fail "check accepted a fnptr golden without the indirect call"
|
|
fi
|
|
echo " ok negative control: fnptr without the indirect call rejected"
|
|
grep 'M4 diff: FAIL' "$TMP/neg-fnptr.out" | head -1 | sed 's/^/ /'
|
|
|
|
# 2. Reverse the ten XPutImage argument pushes: Display must no longer be the
|
|
# last push and the leading constants must change.
|
|
awk '
|
|
{ lines[NR] = $0 }
|
|
/^[[:space:]]*MOVE[[:space:]]+[^,]+,[[:space:]]*\*STK\+,[[:space:]]*1[[:space:]]*$/ {
|
|
npush++
|
|
pos[npush] = NR
|
|
push[npush] = $0
|
|
next
|
|
}
|
|
/^[[:space:]]*CALLA[[:space:]]+_XPutImage/ {
|
|
for (i = 1; i <= npush; i++)
|
|
lines[pos[i]] = push[npush - i + 1]
|
|
npush = 0
|
|
next
|
|
}
|
|
/^[[:space:]]*CALLA[[:space:]]/ { npush = 0; next }
|
|
END { for (i = 1; i <= NR; i++) print lines[i] }
|
|
' "$TI_IMAGE" > "$TMP/image-reversed.s"
|
|
if cmp -s "$TI_IMAGE" "$TMP/image-reversed.s"; then
|
|
fail "reversed-argument mutation did not change the image golden"
|
|
fi
|
|
if M4_DIFF_POSITIVE_ONLY=1 TI_IMAGE="$TMP/image-reversed.s" \
|
|
"$0" > "$TMP/neg-image.out" 2>&1; then
|
|
sed 's/^/ /' "$TMP/neg-image.out" >&2
|
|
fail "check accepted the reversed XPutImage argument order"
|
|
fi
|
|
echo " ok negative control: reversed XPutImage arguments rejected"
|
|
grep 'M4 diff: FAIL' "$TMP/neg-image.out" | head -1 | sed 's/^/ /'
|
|
|
|
# 3. Swap only the last two XPutImage pushes (Display and drawable). The
|
|
# leading constants and the non-constant last-token rule still hold, so
|
|
# only the first-argument (Display) check can reject this.
|
|
awk '
|
|
{ lines[NR] = $0 }
|
|
/^[[:space:]]*MOVE[[:space:]]+[^,]+,[[:space:]]*\*STK\+,[[:space:]]*1[[:space:]]*$/ {
|
|
npush++
|
|
pos[npush] = NR
|
|
next
|
|
}
|
|
/^[[:space:]]*CALLA[[:space:]]+_XPutImage/ {
|
|
if (npush >= 2) {
|
|
tmp = lines[pos[npush]]
|
|
lines[pos[npush]] = lines[pos[npush - 1]]
|
|
lines[pos[npush - 1]] = tmp
|
|
}
|
|
npush = 0
|
|
next
|
|
}
|
|
/^[[:space:]]*CALLA[[:space:]]/ { npush = 0; next }
|
|
END { for (i = 1; i <= NR; i++) print lines[i] }
|
|
' "$TI_IMAGE" > "$TMP/image-swapped.s"
|
|
if cmp -s "$TI_IMAGE" "$TMP/image-swapped.s"; then
|
|
fail "Display/drawable swap mutation did not change the image golden"
|
|
fi
|
|
if M4_DIFF_POSITIVE_ONLY=1 TI_IMAGE="$TMP/image-swapped.s" \
|
|
"$0" > "$TMP/neg-swap.out" 2>&1; then
|
|
sed 's/^/ /' "$TMP/neg-swap.out" >&2
|
|
fail "check accepted the Display/drawable push swap"
|
|
fi
|
|
echo " ok negative control: Display swapped with the drawable rejected"
|
|
grep 'M4 diff: FAIL' "$TMP/neg-swap.out" | head -1 | sed 's/^/ /'
|
|
|
|
echo "M4 diff: PASS"
|