tests: M3 branches and first Xlib window in MAME
This commit is contained in:
1 parent
b34016a9f3
commit
09db9e22e9
14 files changed
+980
-1
No files matched your search
@@ -0,0 +1,23 @@
|
||||
#include "vt1200_xlib.h"
|
||||
|
||||
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n) * 0x20u))
|
||||
|
||||
int sumto(int n) {
|
||||
int s = 0;
|
||||
int i;
|
||||
for (i = 1; i <= n; i++)
|
||||
s += i;
|
||||
return s;
|
||||
}
|
||||
|
||||
int max2(int a, int b) { return a > b ? a : b; }
|
||||
int lt(int a, int b) { return a < b; }
|
||||
|
||||
int branch_test(void) {
|
||||
MARK(4) = (unsigned int)sumto(10);
|
||||
MARK(5) = (unsigned int)max2(20, 22);
|
||||
MARK(6) = (unsigned int)lt(5, 7);
|
||||
MARK(7) = (unsigned int)layout_screens();
|
||||
MARK(8) = (unsigned int)layout_root();
|
||||
return 0;
|
||||
}
|
||||
Executable
+463
@@ -0,0 +1,463 @@
|
||||
#!/bin/sh
|
||||
# M3 dis34 round-trip.
|
||||
#
|
||||
# scripts/m3/dis-roundtrip.sh
|
||||
#
|
||||
# Parses build/m3/m3.coff, disassembles its linked .text at 018A0000 with the
|
||||
# VT1200 tree's dis34, and checks that GSPA encoded the compiler output:
|
||||
#
|
||||
# _XOpenDisplay/_XCreateSimpleWindow/_XMapWindow/_XSync JAUC <ROM bit addr>
|
||||
# _xhello: 1 push -> CALLA _XOpenDisplay, 9 pushes -> CALLA
|
||||
# _XCreateSimpleWindow (arguments 1,0,2,200,400,100,100,root,
|
||||
# display in that order, display pushed last), 2 -> _XMapWindow,
|
||||
# 2 -> _XSync, each followed by the ABI's SETF 16/32 pair
|
||||
# branch code: CMP, JALT, JAGT, JAEQ, JAUC and LBB… labels (never .LBB)
|
||||
# .data: 30 3A 3A 30 00 ("0::0")
|
||||
# old-STK restore offsets: *SP((pushes+1)*32),A14
|
||||
#
|
||||
# Overridable: M3_COFF M3_ASM DIS34
|
||||
set -eu
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
WORK=${M3_WORK:-$ROOT/build/m3}
|
||||
COFF=${M3_COFF:-$WORK/m3.coff}
|
||||
ASM=${M3_ASM:-$WORK/m3.asm}
|
||||
DIS34=${DIS34:-$ROOT/vendor/vt1200/tools/smart34010/dis34}
|
||||
|
||||
fail() {
|
||||
echo "M3 dis34 round-trip: FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ -f "$COFF" ] || fail "linked COFF not found: $COFF (run scripts/m3/run.sh)"
|
||||
[ -f "$ASM" ] || fail "linked assembly not found: $ASM (run scripts/m3/run.sh)"
|
||||
[ -x "$DIS34" ] || fail "dis34 not found or not executable: $DIS34"
|
||||
|
||||
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m3-dis34.XXXXXX")
|
||||
FAIL_LOG=${M3_DIS_FAIL_LOG:-${TMPDIR:-/tmp}/m3-dis34-failure.txt}
|
||||
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||
|
||||
# Read little-endian fields out of the COFF. od keeps this dependency-free.
|
||||
read_u8s() {
|
||||
od -An -v -tu1 -j "$1" -N "$2" "$COFF"
|
||||
}
|
||||
u16() {
|
||||
read_u8s "$1" 2 | awk '{ print $1 + 256 * $2 }'
|
||||
}
|
||||
u32() {
|
||||
read_u8s "$1" 4 | awk '{ print $1 + 256 * $2 + 65536 * $3 + 16777216 * $4 }'
|
||||
}
|
||||
|
||||
echo "== parse the COFF section table ($COFF)"
|
||||
nscns=$(u16 2)
|
||||
opthdr=$(u16 16)
|
||||
[ "$nscns" -gt 0 ] 2>/dev/null || fail "COFF has no sections (nscns='$nscns')"
|
||||
[ "$opthdr" -gt 0 ] 2>/dev/null || fail "COFF has no optional header (opthdr='$opthdr')"
|
||||
|
||||
TEXT_ADDR=
|
||||
TEXT_BITS=
|
||||
TEXT_PTR=
|
||||
DATA_ADDR=
|
||||
DATA_BITS=
|
||||
DATA_PTR=
|
||||
sh_base=$((20 + opthdr))
|
||||
i=0
|
||||
while [ "$i" -lt "$nscns" ]; do
|
||||
o=$((sh_base + i * 40))
|
||||
name=$(dd if="$COFF" bs=1 skip="$o" count=8 2>/dev/null | tr -d '\000')
|
||||
if [ "$name" = ".text" ]; then
|
||||
TEXT_ADDR=$(u32 $((o + 8)))
|
||||
TEXT_BITS=$(u32 $((o + 16)))
|
||||
TEXT_PTR=$(u32 $((o + 20)))
|
||||
elif [ "$name" = ".data" ]; then
|
||||
DATA_ADDR=$(u32 $((o + 8)))
|
||||
DATA_BITS=$(u32 $((o + 16)))
|
||||
DATA_PTR=$(u32 $((o + 20)))
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ -n "$TEXT_ADDR" ] || fail "COFF has no .text section"
|
||||
[ "$TEXT_BITS" -gt 0 ] 2>/dev/null || fail ".text is empty"
|
||||
[ $((TEXT_BITS % 8)) -eq 0 ] || fail ".text size $TEXT_BITS bits is not whole bytes"
|
||||
[ -n "$DATA_ADDR" ] || fail "COFF has no .data section"
|
||||
[ "$DATA_BITS" -gt 0 ] 2>/dev/null || fail ".data is empty"
|
||||
[ $((DATA_BITS % 8)) -eq 0 ] || fail ".data size $DATA_BITS bits is not whole bytes"
|
||||
|
||||
TEXT_HEX=$(printf '%08X' "$TEXT_ADDR")
|
||||
ENTRY=$(u32 $((20 + 16)))
|
||||
ENTRY_HEX=$(printf '%08X' "$ENTRY")
|
||||
TEXT_BYTES=$((TEXT_BITS / 8))
|
||||
DATA_BYTES=$((DATA_BITS / 8))
|
||||
|
||||
[ "$TEXT_HEX" = "018A0000" ] \
|
||||
|| fail ".text is linked at $TEXT_HEX, want 018A0000"
|
||||
[ "$ENTRY_HEX" = "018A0000" ] \
|
||||
|| fail "COFF entry point is $ENTRY_HEX, want 018A0000"
|
||||
[ "$TEXT_PTR" -gt 0 ] || fail ".text has no raw data (scnptr=0)"
|
||||
[ "$DATA_PTR" -gt 0 ] || fail ".data has no raw data (scnptr=0)"
|
||||
|
||||
echo " .text at $TEXT_HEX: $TEXT_BITS bits ($TEXT_BYTES bytes), scnptr=$TEXT_PTR"
|
||||
echo " .data at $(printf '%08X' "$DATA_ADDR"): $DATA_BITS bits ($DATA_BYTES bytes), scnptr=$DATA_PTR"
|
||||
echo " entry point $ENTRY_HEX"
|
||||
|
||||
dd if="$COFF" of="$TMP/text.bin" bs=1 skip="$TEXT_PTR" count="$TEXT_BYTES" 2>/dev/null
|
||||
[ "$(wc -c < "$TMP/text.bin")" -eq "$TEXT_BYTES" ] \
|
||||
|| fail "extracted $(wc -c < "$TMP/text.bin") .text bytes, want $TEXT_BYTES"
|
||||
dd if="$COFF" of="$TMP/data.bin" bs=1 skip="$DATA_PTR" count="$DATA_BYTES" 2>/dev/null
|
||||
[ "$(wc -c < "$TMP/data.bin")" -eq "$DATA_BYTES" ] \
|
||||
|| fail "extracted $(wc -c < "$TMP/data.bin") .data bytes, want $DATA_BYTES"
|
||||
|
||||
echo "== resolve the linked symbols from the COFF symbol table"
|
||||
symptr=$(u32 8)
|
||||
nsyms=$(u32 12)
|
||||
[ "$symptr" -gt 0 ] 2>/dev/null || fail "COFF has no symbol table"
|
||||
[ "$nsyms" -gt 0 ] 2>/dev/null || fail "COFF symbol table is empty"
|
||||
|
||||
sym_addr() {
|
||||
_want=$1
|
||||
_strtab=$((symptr + nsyms * 18))
|
||||
_i=0
|
||||
while [ "$_i" -lt "$nsyms" ]; do
|
||||
_o=$((symptr + _i * 18))
|
||||
# COFF inline names are 8 bytes; longer names are 0,0,0,0 + a
|
||||
# string-table offset (the table starts with its own 4-byte size).
|
||||
if [ "$(u32 "$_o")" -eq 0 ]; then
|
||||
_soff=$(u32 $((_o + 4)))
|
||||
_n=$(dd if="$COFF" bs=1 skip=$((_strtab + _soff)) count=64 2>/dev/null |
|
||||
tr '\000' '\n' | head -1)
|
||||
else
|
||||
_n=$(dd if="$COFF" bs=1 skip="$_o" count=8 2>/dev/null | tr -d '\000')
|
||||
fi
|
||||
if [ "$_n" = "$_want" ]; then
|
||||
u32 $((_o + 8))
|
||||
return 0
|
||||
fi
|
||||
_aux=$(od -An -v -tu1 -j $((_o + 17)) -N 1 "$COFF" | awk '{ print $1 }')
|
||||
_i=$((_i + 1 + _aux))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
get_sym() {
|
||||
_name=$1
|
||||
_addr=$(sym_addr "$_name") || fail "COFF symbol table has no $_name"
|
||||
[ -n "$_addr" ] || fail "COFF symbol table has no $_name"
|
||||
printf '%s' "$_addr"
|
||||
}
|
||||
|
||||
XOPEN=$(get_sym _XOpenDisplay)
|
||||
XCREATE=$(get_sym _XCreateSimpleWindow)
|
||||
XMAP=$(get_sym _XMapWindow)
|
||||
XSYNC=$(get_sym _XSync)
|
||||
XHELLO=$(get_sym _xhello)
|
||||
SUMTO=$(get_sym _sumto)
|
||||
MAX2=$(get_sym _max2)
|
||||
LT=$(get_sym _lt)
|
||||
BRANCH=$(get_sym _branch_test)
|
||||
ROOTPROBE=$(get_sym _root_probe)
|
||||
XOPEN_HEX=$(printf '%08X' "$XOPEN")
|
||||
XCREATE_HEX=$(printf '%08X' "$XCREATE")
|
||||
XMAP_HEX=$(printf '%08X' "$XMAP")
|
||||
XSYNC_HEX=$(printf '%08X' "$XSYNC")
|
||||
XHELLO_HEX=$(printf '%08X' "$XHELLO")
|
||||
XOPEN_L=$(printf '%x' "$XOPEN")
|
||||
XCREATE_L=$(printf '%x' "$XCREATE")
|
||||
XMAP_L=$(printf '%x' "$XMAP")
|
||||
XSYNC_L=$(printf '%x' "$XSYNC")
|
||||
echo " _XOpenDisplay=$XOPEN_HEX _XCreateSimpleWindow=$XCREATE_HEX"
|
||||
echo " _XMapWindow=$XMAP_HEX _XSync=$XSYNC_HEX"
|
||||
echo " _xhello=$XHELLO_HEX _sumto=$(printf '%08X' "$SUMTO") _max2=$(printf '%08X' "$MAX2") _lt=$(printf '%08X' "$LT")"
|
||||
echo " _branch_test=$(printf '%08X' "$BRANCH") _root_probe=$(printf '%08X' "$ROOTPROBE")"
|
||||
|
||||
echo "== disassemble the emitted .text with dis34"
|
||||
"$DIS34" -b "$TEXT_HEX" -l -o "$TMP/text.bin" > "$TMP/dis34.txt" 2> "$TMP/dis34.err" \
|
||||
|| { cat "$TMP/dis34.err" >&2; fail "dis34 failed"; }
|
||||
[ -s "$TMP/dis34.txt" ] || fail "dis34 produced no output"
|
||||
|
||||
# match_at FILE DECIMAL_ADDR ERE
|
||||
match_at() {
|
||||
_file=$1
|
||||
_addr=$2
|
||||
_pat=$3
|
||||
_laddr=$(printf '%08x' "$_addr")
|
||||
grep -Eiq "^${_laddr}:[[:space:]]+${_pat}([[:space:]]*(;|$))" "$_file"
|
||||
}
|
||||
|
||||
expect_at() {
|
||||
_label=$1
|
||||
_addr=$2
|
||||
_want=$3
|
||||
_pat=$4
|
||||
_laddr=$(printf '%08x' "$_addr")
|
||||
if match_at "$TMP/dis34.txt" "$_addr" "$_pat"; then
|
||||
echo " ok $_label @ 0x$_laddr: $_want"
|
||||
return 0
|
||||
fi
|
||||
{
|
||||
echo "M3 dis34 round-trip: FAIL"
|
||||
echo " $_label at bit 0x$_laddr: expected $_want"
|
||||
echo " dis34 output near 0x$_laddr:"
|
||||
awk -v a="$_laddr" '
|
||||
{ low = tolower($0) }
|
||||
low ~ ("^" a ":") { seen = 1 }
|
||||
seen { print " " $0; n++ }
|
||||
seen && n >= 4 { exit }
|
||||
' "$TMP/dis34.txt"
|
||||
if cp "$TMP/dis34.txt" "$FAIL_LOG" 2>/dev/null; then
|
||||
echo " full disassembly preserved at: $FAIL_LOG"
|
||||
else
|
||||
echo " full disassembly: $TMP/dis34.txt (temporary; removed on exit)"
|
||||
fi
|
||||
} >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
func_body() {
|
||||
_laddr=$(printf '%08x' "$1")
|
||||
awk -v a="$_laddr" '
|
||||
{ low = tolower($0) }
|
||||
low ~ ("^" a ":") { on = 1 }
|
||||
on { print }
|
||||
on && low ~ /^[0-9a-f]+:[[:space:]]+rets([[:space:]]|;|$)/ { exit }
|
||||
' "$TMP/dis34.txt"
|
||||
}
|
||||
|
||||
expect_in() {
|
||||
_file=$1
|
||||
_want=$2
|
||||
_pat=$3
|
||||
if grep -Eiq "$_pat" "$_file"; then
|
||||
echo " ok $_want"
|
||||
return 0
|
||||
fi
|
||||
echo "M3 dis34 round-trip: FAIL: missing $_want" >&2
|
||||
sed 's/^/ /' "$_file" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# count_between FILE START_RE END_RE PUSH_RE: count push encodings after the
|
||||
# START_RE line up to (not including) the END_RE line.
|
||||
count_between() {
|
||||
_file=$1
|
||||
_start=$2
|
||||
_end=$3
|
||||
_push=$4
|
||||
awk -v s="$_start" -v e="$_end" -v p="$_push" '
|
||||
{ low = tolower($0) }
|
||||
on && low ~ e { exit }
|
||||
on && low ~ p { n++ }
|
||||
low ~ s { on = 1 }
|
||||
END { print n + 0 }
|
||||
' "$_file"
|
||||
}
|
||||
|
||||
echo "== check the ROM import stubs"
|
||||
expect_at "_XOpenDisplay" "$XOPEN" "JAUC 3F9C2570" \
|
||||
'jauc[[:space:]]+0x3f9c2570'
|
||||
expect_at "_XCreateSimpleWindow" "$XCREATE" "JAUC 3FD41B90" \
|
||||
'jauc[[:space:]]+0x3fd41b90'
|
||||
expect_at "_XMapWindow" "$XMAP" "JAUC 3FD645B0" \
|
||||
'jauc[[:space:]]+0x3fd645b0'
|
||||
expect_at "_XSync" "$XSYNC" "JAUC 3FD9F6B0" \
|
||||
'jauc[[:space:]]+0x3fd9f6b0'
|
||||
|
||||
echo "== check the compiled Xlib call sequence"
|
||||
func_body "$XHELLO" > "$TMP/xhello.txt"
|
||||
expect_in "$TMP/xhello.txt" "CALLA _XOpenDisplay ($XOPEN_HEX)" \
|
||||
"calla[[:space:]]+l${XOPEN_L}"
|
||||
expect_in "$TMP/xhello.txt" "CALLA _XCreateSimpleWindow ($XCREATE_HEX)" \
|
||||
"calla[[:space:]]+l${XCREATE_L}"
|
||||
expect_in "$TMP/xhello.txt" "CALLA _XMapWindow ($XMAP_HEX)" \
|
||||
"calla[[:space:]]+l${XMAP_L}"
|
||||
expect_in "$TMP/xhello.txt" "CALLA _XSync ($XSYNC_HEX)" \
|
||||
"calla[[:space:]]+l${XSYNC_L}"
|
||||
|
||||
# The arguments are pushed last-first onto the program stack; the first
|
||||
# argument (background=1 for XCreateSimpleWindow) is pushed last.
|
||||
P1=$(count_between "$TMP/xhello.txt" '^[0-9a-f]+:[[:space:]]+setf[[:space:]]+16' \
|
||||
"calla[[:space:]]+l${XOPEN_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
||||
P2=$(count_between "$TMP/xhello.txt" "calla[[:space:]]+l${XOPEN_L}" \
|
||||
"calla[[:space:]]+l${XCREATE_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
||||
P3=$(count_between "$TMP/xhello.txt" "calla[[:space:]]+l${XCREATE_L}" \
|
||||
"calla[[:space:]]+l${XMAP_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
||||
P4=$(count_between "$TMP/xhello.txt" "calla[[:space:]]+l${XMAP_L}" \
|
||||
"calla[[:space:]]+l${XSYNC_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
||||
[ "$P1" -eq 1 ] || fail "XOpenDisplay has $P1 argument pushes, want 1"
|
||||
[ "$P2" -eq 9 ] || fail "XCreateSimpleWindow has $P2 argument pushes, want 9"
|
||||
[ "$P3" -eq 2 ] || fail "XMapWindow has $P3 argument pushes, want 2"
|
||||
[ "$P4" -eq 2 ] || fail "XSync has $P4 argument pushes, want 2"
|
||||
echo " ok argument pushes: XOpenDisplay=1 XCreateSimpleWindow=9 XMapWindow=2 XSync=2"
|
||||
|
||||
# Counting pushes is not enough: the nine words must reach the callee in the
|
||||
# ABI order. build/m3/m3.lst records background 1, border 0, border width 2,
|
||||
# height 200, width 400, y 100, then x 100 (x reuses y's register, so there are
|
||||
# six MOVIs for the seven constants), followed by the root (A2) and display
|
||||
# (A0) pushes. Assert the immediates and push registers as one sequence, so a
|
||||
# swapped pair fails even though the count stays nine, and require the final
|
||||
# push before CALLA to be the display register.
|
||||
awk '
|
||||
tolower($0) ~ /calla[[:space:]]+l'"$XOPEN_L"'/ { on = 1; next }
|
||||
tolower($0) ~ /calla[[:space:]]+l'"$XCREATE_L"'/ { exit }
|
||||
!on { next }
|
||||
tolower($0) ~ /move[[:space:]]+a14,[[:space:]]*-\*sp,[[:space:]]*1/ { started = 1; next }
|
||||
!started { next }
|
||||
tolower($0) ~ /movi[[:space:]]+/ {
|
||||
line = $0
|
||||
sub(/^[^:]*:[[:space:]]+movi[[:space:]]+/, "", line)
|
||||
sub(/[[:space:]]*;.*/, "", line)
|
||||
sub(/[[:space:]]+/, " ", line)
|
||||
split(line, p, /,[[:space:]]*/)
|
||||
printf "IMM %s %s\n", tolower(p[1]), tolower(p[2])
|
||||
next
|
||||
}
|
||||
tolower($0) ~ /move[[:space:]]+a[0-9]+,[[:space:]]*\*a14\+,[[:space:]]*1/ {
|
||||
line = $0
|
||||
sub(/^[^:]*:[[:space:]]+move[[:space:]]+/, "", line)
|
||||
sub(/,.*/, "", line)
|
||||
printf "PUSH %s\n", tolower(line)
|
||||
next
|
||||
}
|
||||
' "$TMP/xhello.txt" > "$TMP/xcreate.seq"
|
||||
cat > "$TMP/xcreate.want" <<'EOF'
|
||||
IMM 0x1 a1
|
||||
PUSH a1
|
||||
IMM 0x0 a1
|
||||
PUSH a1
|
||||
IMM 0x2 a3
|
||||
PUSH a3
|
||||
IMM 0xc8 a3
|
||||
PUSH a3
|
||||
IMM 0x190 a3
|
||||
PUSH a3
|
||||
IMM 0x64 a3
|
||||
PUSH a3
|
||||
PUSH a3
|
||||
PUSH a2
|
||||
PUSH a0
|
||||
EOF
|
||||
if ! cmp -s "$TMP/xcreate.want" "$TMP/xcreate.seq"; then
|
||||
{
|
||||
echo "M3 dis34 round-trip: FAIL: XCreateSimpleWindow argument order"
|
||||
echo " expected (background,border,border_width,height,width,y,x,root,display):"
|
||||
sed 's/^/ /' "$TMP/xcreate.want"
|
||||
echo " got:"
|
||||
sed 's/^/ /' "$TMP/xcreate.seq"
|
||||
} >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " ok XCreateSimpleWindow argument order: 1,0,2,200,400,100,100,root,display (display pushed last)"
|
||||
|
||||
# Xlib clobbers SETF; the compiler must re-establish 16/FS0 and 32/FS1 after
|
||||
# every call. Check in the linked assembly, where CALLA symbols survive.
|
||||
awk '
|
||||
/^_xhello:/ { on = 1; next }
|
||||
on && /-- End function/ { exit }
|
||||
on && /^[[:space:]]*CALLA/ { pending = 1; next }
|
||||
on && pending && /SETF[[:space:]]+16,[[:space:]]*0,[[:space:]]*0/ { saw16 = 1; next }
|
||||
on && pending && saw16 && /SETF[[:space:]]+32,[[:space:]]*0,[[:space:]]*1/ {
|
||||
n++; pending = 0; saw16 = 0; next
|
||||
}
|
||||
END { print n + 0 }
|
||||
' "$ASM" > "$TMP/setf_count"
|
||||
SETF_COUNT=$(cat "$TMP/setf_count")
|
||||
[ "$SETF_COUNT" -eq 4 ] \
|
||||
|| fail "found $SETF_COUNT post-call SETF 16/32 pairs in _xhello, want 4"
|
||||
echo " ok four post-call SETF 16,0,0 + SETF 32,0,1 pairs"
|
||||
|
||||
# _xhello takes no arguments of its own (the nine-argument call is inside it),
|
||||
# so it returns with RETS 0; the argument-taking branch functions use RETS 2.
|
||||
func_body "$XHELLO" > "$TMP/xhello.txt"
|
||||
expect_in "$TMP/xhello.txt" "_xhello RETS 0" 'rets([[:space:]]+0)?([[:space:]]*(;|$))'
|
||||
for pair in "$SUMTO:_sumto" "$MAX2:_max2" "$LT:_lt"; do
|
||||
addr=${pair%%:*}
|
||||
label=${pair##*:}
|
||||
func_body "$addr" > "$TMP/fn.txt"
|
||||
expect_in "$TMP/fn.txt" "$label RETS 2" 'rets[[:space:]]+2([[:space:]]*(;|$))'
|
||||
done
|
||||
|
||||
echo "== check the branch code"
|
||||
for pair in "$SUMTO:_sumto" "$MAX2:_max2" "$LT:_lt"; do
|
||||
addr=${pair%%:*}
|
||||
label=${pair##*:}
|
||||
func_body "$addr" > "$TMP/fn.txt"
|
||||
if ! grep -Eiq 'cmp' "$TMP/fn.txt"; then
|
||||
sed 's/^/ /' "$TMP/fn.txt" >&2
|
||||
fail "$label has no CMP"
|
||||
fi
|
||||
if ! grep -Eiq '\bjauc\b' "$TMP/fn.txt"; then
|
||||
sed 's/^/ /' "$TMP/fn.txt" >&2
|
||||
fail "$label has no JAUC"
|
||||
fi
|
||||
echo " ok $label: CMP + JAUC"
|
||||
done
|
||||
for pair in "$XHELLO:_xhello"; do
|
||||
addr=${pair%%:*}
|
||||
label=${pair##*:}
|
||||
func_body "$addr" > "$TMP/fn.txt"
|
||||
if ! grep -Eiq '\bjauc\b' "$TMP/fn.txt"; then
|
||||
sed 's/^/ /' "$TMP/fn.txt" >&2
|
||||
fail "$label has no JAUC"
|
||||
fi
|
||||
echo " ok $label: JAUC"
|
||||
done
|
||||
func_body "$BRANCH" > "$TMP/branch.txt"
|
||||
for callee in _sumto _max2 _lt _layout_screens _layout_root; do
|
||||
grep -Eiq "calla[[:space:]]+L$(get_sym "$callee" | xargs printf '%X')" "$TMP/branch.txt" \
|
||||
|| fail "_branch_test does not call $callee"
|
||||
done
|
||||
echo " ok _branch_test calls sumto, max2, lt, layout_screens, layout_root"
|
||||
# Conditional branch opcodes emitted by this compiled image. JALE/JALO/JAHI
|
||||
# coverage lives in the m3-loop/m3-unsigned/m3-cmpi lit tests; this image has
|
||||
# the signed compare, loop-exit and smax branches.
|
||||
for op in jalt jagt jaeq; do
|
||||
grep -Eiq "^[0-9a-f]+:[[:space:]]+$op([[:space:]]|;|$)" "$TMP/dis34.txt" \
|
||||
|| fail "no $op instruction in the linked .text"
|
||||
echo " ok $op present"
|
||||
done
|
||||
|
||||
# GSPA rejects dotted .L labels; the harness gives each TU an LBB prefix.
|
||||
grep -q 'LBB' "$ASM" || fail "linked assembly has no LBB block labels"
|
||||
if grep -q '\.LBB' "$ASM"; then
|
||||
fail "linked assembly contains dotted .LBB labels (GSPA rejects them)"
|
||||
fi
|
||||
echo " ok LBB… block labels (no dot)"
|
||||
|
||||
echo "== check the old-STK restore offsets"
|
||||
check_stk() {
|
||||
_label=$1
|
||||
_addr=$2
|
||||
func_body "$_addr" > "$TMP/func.txt"
|
||||
_pushes=$(awk '
|
||||
tolower($0) ~ /move[[:space:]]+[a-z0-9]+,[[:space:]]*-\*sp,[[:space:]]*1/ { n++ }
|
||||
tolower($0) ~ /move[[:space:]]+a14,[[:space:]]*a13/ { print n; exit }
|
||||
' "$TMP/func.txt")
|
||||
[ -n "$_pushes" ] || fail "$_label has no SETFP (MOVE A14,A13)"
|
||||
_operand=$(grep -Eio '\*sp\(0x[0-9a-f]+\)' "$TMP/func.txt" | head -1 \
|
||||
| sed 's/.*0x//; s/).*//')
|
||||
[ -n "$_operand" ] || fail "$_label has no LOADSTK (*SP(n),A14)"
|
||||
_want=$(( (_pushes + 1) * 32 ))
|
||||
_got=$((0x$_operand))
|
||||
[ "$_got" -eq "$_want" ] \
|
||||
|| fail "$_label restores A14 from *SP(0x$_operand) ($_got bits) but $_pushes pushes need $_want bits"
|
||||
echo " ok $_label: $_pushes pushes -> *SP($(printf '0x%x' "$_want")), A14, 1"
|
||||
}
|
||||
check_stk "_sumto" "$SUMTO"
|
||||
check_stk "_max2" "$MAX2"
|
||||
check_stk "_lt" "$LT"
|
||||
check_stk "_root_probe" "$ROOTPROBE"
|
||||
|
||||
# Negative control for the RETS operand boundary: "rets 1" must not match.
|
||||
printf '%08x:\trets\t1\t; 0961\n' "$SUMTO" > "$TMP/rets-neg.txt"
|
||||
if match_at "$TMP/rets-neg.txt" "$SUMTO" 'rets([[:space:]]+0)?'; then
|
||||
fail "negative control: the RETS pattern accepted 'rets 1'"
|
||||
fi
|
||||
echo " ok negative control @ 0x$(printf '%08x' "$SUMTO"): 'rets 1' rejected"
|
||||
|
||||
echo "== check the copied .data bytes"
|
||||
DATA_HEX=$(od -An -v -tx1 "$TMP/data.bin" | tr -d ' \n')
|
||||
case "$DATA_HEX" in
|
||||
*303a3a3000*) echo " ok .data contains 30 3A 3A 30 00 (\"0::0\")" ;;
|
||||
*) fail ".data does not contain 30 3A 3A 30 00 (got $DATA_HEX)" ;;
|
||||
esac
|
||||
|
||||
echo "M3 dis34 round-trip: PASS"
|
||||
@@ -0,0 +1,25 @@
|
||||
; entry.asm -- M3 entry, linked at bit 018A0000.
|
||||
;
|
||||
; Calls the compiled branch/loop tests and then the first compiled C Xlib
|
||||
; client. Markers (0x20 bits apart):
|
||||
; 018A8000 Display pointer (xhello)
|
||||
; 018A8020 root window (xhello)
|
||||
; 018A8040 window id (xhello)
|
||||
; 018A8060 XSync diagnostic (xhello)
|
||||
; 018A8080 sumto(10) == 55 (branch_test)
|
||||
; 018A80A0 max2(20,22) == 22 (branch_test)
|
||||
; 018A80C0 lt(5,7) == 1 (branch_test)
|
||||
; 018A80E0 layout_screens() == 0x480
|
||||
; 018A8100 layout_root() == 0x40
|
||||
.text
|
||||
.globl _start
|
||||
_start:
|
||||
setf 16, 0, 0
|
||||
setf 32, 0, 1
|
||||
movi _cstack, a14
|
||||
move a14, a13
|
||||
calla _branch_test
|
||||
calla _xhello
|
||||
rets
|
||||
|
||||
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
|
||||
@@ -14,3 +14,8 @@ int layout_screens(void) {
|
||||
int layout_root(void) {
|
||||
return (int)__builtin_offsetof(Screen, root) * 8;
|
||||
}
|
||||
|
||||
/* Exercise the real access path: vt1200_root() follows Display.screens (bit
|
||||
0x480) and reads Screen.root (bit 0x40). The align-1 loads this generates
|
||||
must select MOVE directly (see m3-unaligned.ll). */
|
||||
unsigned int root_probe(Display *d) { return vt1200_root(d); }
|
||||
@@ -0,0 +1,16 @@
|
||||
-a
|
||||
-o M3.OUT
|
||||
-m M3.MAP
|
||||
-e _start
|
||||
M3.obj
|
||||
MEMORY
|
||||
{
|
||||
prog: origin = 018A0000h, length = 8000h
|
||||
ram: origin = 018A9000h, length = 40000h
|
||||
}
|
||||
SECTIONS
|
||||
{
|
||||
.text: {} > prog
|
||||
.data: {} > prog
|
||||
.bss: {} > ram
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
-- m3-check.lua -- load the M3 COFF with dwt/loader/loader.lua, read the
|
||||
-- compiled branch/Xlib marker words, and prove a window was mapped by reading
|
||||
-- the white background pixel in VRAM.
|
||||
--
|
||||
-- M3_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua
|
||||
-- LOADER_END loader inspection/exit frame
|
||||
--
|
||||
-- The marker read runs one frame before loader.lua's inspection/exit frame.
|
||||
-- XSync is diagnostic only; the pass requires the visible white pixel.
|
||||
--
|
||||
-- The DWT root window is a fine monochrome weave, so one set bit at (150,150)
|
||||
-- cannot distinguish the mapped client window from the root. Require the
|
||||
-- whole 32-bit VRAM word containing (150,150) to be 0xFFFFFFFF, and check a
|
||||
-- second interior word at (250,200) so the client area is proven solid, not
|
||||
-- just a lucky white stripe.
|
||||
|
||||
dofile(os.getenv("M3_LOADER_LUA"))
|
||||
|
||||
local ENDF = tonumber(os.getenv("LOADER_END") or "3400")
|
||||
|
||||
-- Window: XCreateSimpleWindow(display, root, 100, 100, 400, 200, 2, 0, 1).
|
||||
-- The client area starts at (102,102); sample well inside it.
|
||||
local VRAM_BASE = 0x04000000
|
||||
local VRAM_PITCH = 2048 -- VR319: 2048 bits per row
|
||||
local WHITE_WORD = 0xFFFFFFFF
|
||||
local PX, PY = 150, 150
|
||||
local PX2, PY2 = 250, 200
|
||||
|
||||
local frames = 0
|
||||
local checked = false
|
||||
|
||||
local function vram_word(prog, x, y)
|
||||
local bit = VRAM_BASE + y * VRAM_PITCH + x
|
||||
local word = bit - (bit % 32)
|
||||
return prog:read_u32(word), word, bit
|
||||
end
|
||||
|
||||
emu.register_frame_done(function()
|
||||
frames = frames + 1
|
||||
if frames == ENDF - 1 and not checked then
|
||||
checked = true
|
||||
local prog = manager.machine.devices[':maincpu'].spaces['program']
|
||||
local display = prog:read_u32(0x018A8000)
|
||||
local root = prog:read_u32(0x018A8020)
|
||||
local window = prog:read_u32(0x018A8040)
|
||||
local sync = prog:read_u32(0x018A8060)
|
||||
local sum = prog:read_u32(0x018A8080)
|
||||
local maxv = prog:read_u32(0x018A80A0)
|
||||
local ltv = prog:read_u32(0x018A80C0)
|
||||
local screens = prog:read_u32(0x018A80E0)
|
||||
local rootoff = prog:read_u32(0x018A8100)
|
||||
|
||||
-- The full words containing each sample pixel.
|
||||
local pix, word, bit = vram_word(prog, PX, PY)
|
||||
local pix2, word2, bit2 = vram_word(prog, PX2, PY2)
|
||||
local solid = pix == WHITE_WORD
|
||||
local solid2 = pix2 == WHITE_WORD
|
||||
|
||||
print(string.format(
|
||||
"M3: display=%u root=%u window=%u sync=%d sum=%d max=%d lt=%d screens=0x%x root_off=0x%x",
|
||||
display, root, window, sync, sum, maxv, ltv, screens, rootoff))
|
||||
print(string.format(
|
||||
"M3: pixel(%d,%d) bit=0x%08X word=0x%08X value=0x%08X %s",
|
||||
PX, PY, bit, word, pix, solid and "SOLID-WHITE" or "not-solid-white"))
|
||||
print(string.format(
|
||||
"M3: pixel(%d,%d) bit=0x%08X word=0x%08X value=0x%08X %s",
|
||||
PX2, PY2, bit2, word2, pix2, solid2 and "SOLID-WHITE" or "not-solid-white"))
|
||||
|
||||
local ok = display ~= 0 and root ~= 0 and window ~= 0
|
||||
and sum == 55 and maxv == 22 and ltv == 1
|
||||
and screens == 0x480 and rootoff == 0x40
|
||||
and solid and solid2
|
||||
if ok then
|
||||
print("M3 MAME: PASS")
|
||||
else
|
||||
print("M3 MAME: FAIL")
|
||||
end
|
||||
end
|
||||
end, "m3-check")
|
||||
Executable
+223
@@ -0,0 +1,223 @@
|
||||
#!/bin/sh
|
||||
# M3 end-to-end test: compiled branches/loops plus the first compiled C Xlib
|
||||
# client, linked with the 1987 GSPA/GSPLNK toolchain and run in MAME.
|
||||
#
|
||||
# scripts/m3/run.sh
|
||||
#
|
||||
# Compiles xhello.c, branch.c and layout.c with scripts/tms-clang -O2 and
|
||||
# llc -O0, assembles/links them with the SDK under DOSBox, loads the COFF
|
||||
# through dwt/loader/loader.lua, and checks:
|
||||
#
|
||||
# 018A8000 Display pointer (nonzero)
|
||||
# 018A8020 root window id (nonzero)
|
||||
# 018A8040 window id (nonzero)
|
||||
# 018A8060 XSync result (diagnostic only)
|
||||
# 018A8080 sumto(10) == 55
|
||||
# 018A80A0 max2(20,22)== 22
|
||||
# 018A80C0 lt(5,7) == 1
|
||||
# 018A80E0 layout_screens() == 0x480
|
||||
# 018A8100 layout_root() == 0x40
|
||||
#
|
||||
# and, decisively, the full all-white VRAM word at (150,150) and another at
|
||||
# (250,200) inside the mapped window. A markers-only or root-window bit is a
|
||||
# failure.
|
||||
#
|
||||
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC MAME DOSBOX DOSBOX_CONF
|
||||
# SDK_IMG M3_WORK M3_COFF M3_SDK LOADER_AT LOADER_END
|
||||
set -eu
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
DIR=$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}
|
||||
|
||||
MAME=${MAME:-$ROOT/build/mame-src/vt1200}
|
||||
DOSBOX=${DOSBOX:-dosbox}
|
||||
DOSBOX_CONF=${DOSBOX_CONF:-$ROOT/vendor/vt1200/harness/sdk-scratch/dosbox-bench.conf}
|
||||
SDK_IMG=${SDK_IMG:-$ROOT/vendor/vt1200/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img}
|
||||
|
||||
WORK=${M3_WORK:-$ROOT/build/m3}
|
||||
COFF=${M3_COFF:-$WORK/m3.coff}
|
||||
SDK=${M3_SDK:-$ROOT/build/m3-sdk}
|
||||
ASM=$WORK/asm
|
||||
LOADER_LUA=$ROOT/vendor/vt1200/dwt/loader/loader.lua
|
||||
LOADER_BIN=$ROOT/vendor/vt1200/dwt/loader/build/loader.bin
|
||||
LOADER_AT=${LOADER_AT:-2500}
|
||||
LOADER_END=${LOADER_END:-3400}
|
||||
|
||||
SYMS=${M3_SYMS:-$ROOT/symbols/vt1200-v22.syms}
|
||||
|
||||
fail() {
|
||||
echo "m3: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do
|
||||
[ -x "$tool" ] || fail "missing tool: $tool"
|
||||
done
|
||||
|
||||
mkdir -p "$ASM" "$WORK/evidence" "$WORK/run/nvram" "$WORK/run/cfg"
|
||||
mkdir -p "$(dirname "$COFF")"
|
||||
|
||||
# IndVarSimplify's exit-value replacement turns sumto's loop into an i33
|
||||
# multiply (n*(n+1)/2), and this target has no multiply or i64 support: llc
|
||||
# then dies selecting umul_lohi. Keep the loop (which is the point of the M3
|
||||
# branch test) by disabling only that rewrite, and only for branch.c; xhello
|
||||
# and layout use the normal optimizer pipeline. branch.c itself is unchanged.
|
||||
OPT_NOREPLEXIT=$WORK/opt-keep-loops
|
||||
printf '#!/bin/sh\nexec "%s" -replexitval=never "$@"\n' "$TMS_OPT" > "$OPT_NOREPLEXIT"
|
||||
chmod +x "$OPT_NOREPLEXIT"
|
||||
|
||||
echo "== compile and select"
|
||||
export TMS_CLANG TMS_BITADDR
|
||||
for name in xhello branch layout; do
|
||||
opt=$TMS_OPT
|
||||
[ "$name" = branch ] && opt=$OPT_NOREPLEXIT
|
||||
TMS_OPT=$opt "$ROOT/scripts/tms-clang" -O2 -I "$DIR" -o "$ASM/$name.ll" "$DIR/$name.c"
|
||||
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm \
|
||||
"$ASM/$name.ll" -o "$ASM/$name.s" \
|
||||
|| fail "llc failed for $name.c"
|
||||
echo " $name.s: $(grep -c . "$ASM/$name.s") lines"
|
||||
done
|
||||
|
||||
# The Xlib calls must be real ROM imports, and the window must use background
|
||||
# pixel 1 (white).
|
||||
grep -q 'CALLA _XOpenDisplay' "$ASM/xhello.s" \
|
||||
|| fail "xhello.s has no 'CALLA _XOpenDisplay'"
|
||||
grep -q 'CALLA _XCreateSimpleWindow' "$ASM/xhello.s" \
|
||||
|| fail "xhello.s has no 'CALLA _XCreateSimpleWindow'"
|
||||
grep -q 'CALLA _XMapWindow' "$ASM/xhello.s" \
|
||||
|| fail "xhello.s has no 'CALLA _XMapWindow'"
|
||||
grep -q 'CALLA _XSync' "$ASM/xhello.s" \
|
||||
|| fail "xhello.s has no 'CALLA _XSync'"
|
||||
# 9 arguments: background=1, border=0, width=2, height=200, width=400, y=100,
|
||||
# x=100, root, display. The first argument (background) is the last pushed,
|
||||
# i.e. the first MOVI before the nine '*A14+' stores.
|
||||
PUSHES=$(awk '
|
||||
/MOVE .*, *\*A14\+, *1/ { n++ }
|
||||
/CALLA/ { if ($0 ~ /_XCreateSimpleWindow/) print n; n = 0 }
|
||||
' "$ASM/xhello.s")
|
||||
[ "$PUSHES" = "9" ] || fail "XCreateSimpleWindow has $PUSHES pushes, want 9"
|
||||
grep -q 'MOVI 1, ' "$ASM/xhello.s" \
|
||||
|| fail "xhello.s has no background pixel 1 (white)"
|
||||
echo " XCreateSimpleWindow: 9 argument pushes, background pixel 1"
|
||||
|
||||
echo "== generate the per-ROM import stubs"
|
||||
"$ROOT/scripts/m2/gen-rom-imports.sh" "$SYMS" "$WORK/rom-imports.asm"
|
||||
for sym in XOpenDisplay XCreateSimpleWindow XMapWindow XSync; do
|
||||
grep -q "^_$sym:" "$WORK/rom-imports.asm" \
|
||||
|| fail "rom-imports.asm has no _$sym stub"
|
||||
done
|
||||
grep -A1 '^_XOpenDisplay:' "$WORK/rom-imports.asm" | grep -qi 'jauc 3F9C2570h' \
|
||||
|| fail "rom-imports.asm _XOpenDisplay stub does not jump to 3F9C2570h"
|
||||
echo " _XOpenDisplay: $(grep -A1 '^_XOpenDisplay:' "$WORK/rom-imports.asm" | tail -1 | sed 's/^[[:space:]]*//')"
|
||||
|
||||
echo "== extract the 1987 SDK"
|
||||
if [ ! -f "$SDK/GSPA.EXE" ]; then
|
||||
command -v mcopy >/dev/null 2>&1 || fail "mtools (mcopy) is required"
|
||||
[ -f "$SDK_IMG" ] || fail "SDK image not found: $SDK_IMG"
|
||||
mkdir -p "$SDK"
|
||||
mcopy -s -n -i "$SDK_IMG" "::/A2/*" "$SDK/"
|
||||
mcopy -s -n -i "$SDK_IMG" "::/C2/*" "$SDK/"
|
||||
fi
|
||||
[ -f "$SDK/GSPA.EXE" ] || fail "GSPA.EXE not extracted into $SDK"
|
||||
|
||||
echo "== assemble and link with GSPA/GSPLNK under DOSBox"
|
||||
# GSPA 2.00 has one flat symbol table per assembly, and llc names basic blocks
|
||||
# LBB0_1, LBB1_2, ... restarting in every translation unit. Concatenating the
|
||||
# three TUs would therefore collide (DUPLICATE DEFINITION). Give each TU its
|
||||
# own LBB prefix; the labels stay LBB… (GSPA rejects dotted .L labels).
|
||||
for pair in branch:B layout:L xhello:X; do
|
||||
src=${pair%%:*}
|
||||
tag=${pair##*:}
|
||||
sed "s/LBB\([0-9][0-9]*_[0-9][0-9]*\)/LBB$tag\1/g" \
|
||||
"$ASM/$src.s" > "$ASM/$src.unique.s"
|
||||
done
|
||||
cat "$DIR/entry.asm" "$ASM/branch.unique.s" "$ASM/layout.unique.s" \
|
||||
"$ASM/xhello.unique.s" "$WORK/rom-imports.asm" > "$SDK/M3.ASM"
|
||||
# The linker command file must keep CRLF line endings.
|
||||
sed 's/\r$//' "$DIR/link.cmd" | sed 's/$/\r/' > "$SDK/M3.CMD"
|
||||
rm -f "$SDK/M3.OBJ" "$SDK/M3.OUT" "$SDK/M3.MAP" "$SDK/M3.LST"
|
||||
|
||||
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||
timeout "${DOSBOX_TIMEOUT:-300}" "$DOSBOX" -conf "$DOSBOX_CONF" \
|
||||
-noprimaryconf -nolocalconf \
|
||||
-c "mount c $SDK" -c "c:" \
|
||||
-c "gspa M3 M3.obj M3.lst -l" \
|
||||
-c "gsplnk M3.cmd" \
|
||||
-c "exit" -exit > "$WORK/dosbox.log" 2>&1 \
|
||||
|| { tail -20 "$WORK/dosbox.log" >&2; fail "DOSBox failed"; }
|
||||
|
||||
[ -f "$SDK/M3.LST" ] || fail "GSPA produced no listing (see $WORK/dosbox.log)"
|
||||
sed 's/\r$//' "$SDK/M3.LST" > "$WORK/m3.lst"
|
||||
grep -q 'No Errors' "$WORK/m3.lst" \
|
||||
|| { grep -i -B2 -A2 'error' "$WORK/m3.lst" >&2 || true; fail "GSPA reported errors"; }
|
||||
grep -q 'No Warnings' "$WORK/m3.lst" \
|
||||
|| { grep -i -B2 -A2 'warning' "$WORK/m3.lst" >&2 || true; fail "GSPA reported warnings"; }
|
||||
[ -f "$SDK/M3.OUT" ] || fail "GSPLNK produced no M3.OUT"
|
||||
sed 's/\r$//' "$SDK/M3.MAP" > "$WORK/m3.map"
|
||||
cp "$SDK/M3.OUT" "$COFF"
|
||||
cp "$SDK/M3.ASM" "$WORK/m3.asm"
|
||||
|
||||
ENTRY=$(awk '$2 == "_start" { print $1; exit }' "$WORK/m3.map")
|
||||
[ "$ENTRY" = "018A0000" ] || fail "linker entry _start is '$ENTRY', want 018A0000"
|
||||
grep -q 'ENTRY POINT SYMBOL: "_start"' "$WORK/m3.map" \
|
||||
|| fail "linker map has no _start entry point"
|
||||
|
||||
BSS_ORIGIN=$(awk '$1 == ".bss" && $2 == "0" { print $3; exit }' "$WORK/m3.map")
|
||||
BSS_LENGTH=$(awk '$1 == ".bss" && $2 == "0" { print $4; exit }' "$WORK/m3.map")
|
||||
[ -n "$BSS_ORIGIN" ] || fail "linker map has no .bss section"
|
||||
[ "$BSS_ORIGIN" = "018A9000" ] \
|
||||
|| fail "linker .bss origin is $BSS_ORIGIN, want 018A9000 (link.cmd ram origin)"
|
||||
[ "$BSS_LENGTH" = "00008000" ] \
|
||||
|| fail ".bss is $BSS_LENGTH bits, expected 00008000 (_cstack)"
|
||||
DATA_ORIGIN=$(awk '$1 == ".data" && $2 == "0" { print $3; exit }' "$WORK/m3.map")
|
||||
DATA_LENGTH=$(awk '$1 == ".data" && $2 == "0" { print $4; exit }' "$WORK/m3.map")
|
||||
[ -n "$DATA_LENGTH" ] || fail "linker map has no .data section"
|
||||
[ "$DATA_LENGTH" != "00000000" ] || fail ".data is empty (no \"0::0\" literal)"
|
||||
echo " linked .text at 018A0000, .data at $DATA_ORIGIN ($DATA_LENGTH bits), .bss at $BSS_ORIGIN ($BSS_LENGTH bits)"
|
||||
|
||||
echo "== run the COFF in MAME"
|
||||
[ -x "$MAME" ] || fail "MAME binary not built: $MAME"
|
||||
[ -f "$LOADER_LUA" ] || fail "COFF loader missing: $LOADER_LUA"
|
||||
[ -f "$LOADER_BIN" ] || fail "COFF loader payload missing: $LOADER_BIN"
|
||||
|
||||
# Start from empty NVRAM and pin the monitor: the pixel check assumes the real
|
||||
# VR319 2048-bit row pitch, and stale NVRAM must not change the boot state.
|
||||
rm -rf "$WORK/run/nvram"
|
||||
mkdir -p "$WORK/run/nvram"
|
||||
|
||||
VT1200_MONITOR=VR319 \
|
||||
VT1200_SKIP_LEVEL1=1 \
|
||||
LOADER_AT="$LOADER_AT" LOADER_END="$LOADER_END" \
|
||||
LOADER_BIN="$LOADER_BIN" LOADER_COFF="$COFF" \
|
||||
LOADER_OUT="$WORK/evidence" \
|
||||
M3_LOADER_LUA="$LOADER_LUA" \
|
||||
timeout "${MAME_TIMEOUT:-900}" "$MAME" vt1200 \
|
||||
-rompath "$ROOT/vendor/vt1200/harness/mame/roms" \
|
||||
-video none -sound none -nothrottle -skip_gameinfo \
|
||||
-nvram_directory "$WORK/run/nvram" \
|
||||
-cfg_directory "$WORK/run/cfg" \
|
||||
-debug -debugger none \
|
||||
-autoboot_script "$DIR/m3-check.lua" -autoboot_delay 0 \
|
||||
> "$WORK/mame.log" 2>&1 \
|
||||
|| { tail -30 "$WORK/mame.log" >&2; fail "MAME run failed"; }
|
||||
|
||||
MARKERS=$(grep -E '^M3: display=' "$WORK/mame.log" | tail -1 || true)
|
||||
[ -n "$MARKERS" ] || { tail -30 "$WORK/mame.log" >&2; fail "MAME produced no M3 markers"; }
|
||||
echo " $MARKERS"
|
||||
|
||||
grep -q 'LOADER: result status=0 (OK)' "$WORK/mame.log" \
|
||||
|| { grep 'LOADER: result' "$WORK/mame.log" >&2 || true; fail "COFF loader did not report status 0"; }
|
||||
grep -q 'placed \.text .* 0 differing' "$WORK/mame.log" \
|
||||
|| fail "COFF loader could not verify the placed .text bytes"
|
||||
grep -q 'placed \.data .* 0 differing' "$WORK/mame.log" \
|
||||
|| fail "COFF loader could not verify the placed .data bytes"
|
||||
grep -q '^M3 MAME: PASS' "$WORK/mame.log" \
|
||||
|| { tail -30 "$WORK/mame.log" >&2; fail "M3 marker/window check failed"; }
|
||||
|
||||
echo "M3 MAME: PASS"
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/bin/sh
|
||||
# M3 ROM symbol validation: the four Xlib entry points used by the compiled
|
||||
# client must exist in the v2.2 table, sit in the ROM's bit range, be 16-bit
|
||||
# aligned, and disassemble to a plausible function prologue.
|
||||
#
|
||||
# This is a regression check, not a re-extraction: dwt/gui/xhello.asm is the
|
||||
# evidence that these entries work. M4 extends the table for XImage/XEvent.
|
||||
#
|
||||
# scripts/m3/validate-syms.sh [symbols/vt1200-v22.syms]
|
||||
#
|
||||
# Overridable: M3_SYMS DIS34 ROM
|
||||
set -eu
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
SYMS=${1:-${M3_SYMS:-$ROOT/symbols/vt1200-v22.syms}}
|
||||
DIS34=${DIS34:-$ROOT/vendor/vt1200/tools/smart34010/dis34}
|
||||
ROM=${ROM:-$ROOT/vendor/vt1200/vt1200_v22.bin}
|
||||
|
||||
ROM_LO=$((0x3F200000))
|
||||
ROM_HI=$((0x3FFFFFFF))
|
||||
|
||||
fail() {
|
||||
echo "m3 validate-syms: FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ -f "$SYMS" ] || fail "symbol file not found: $SYMS"
|
||||
[ -x "$DIS34" ] || fail "dis34 not found or not executable: $DIS34"
|
||||
[ -f "$ROM" ] || fail "ROM image not found: $ROM"
|
||||
|
||||
check_sym() { # check_sym NAME EXPECTED_HEX
|
||||
name=$1
|
||||
expected=$2
|
||||
got=$(awk -v n="$name" '$1 == n { print $2; exit }' "$SYMS")
|
||||
[ -n "$got" ] || fail "$SYMS has no $name entry"
|
||||
[ "$got" = "$expected" ] \
|
||||
|| fail "$name is $got, expected $expected (the xhello.asm-proven entry)"
|
||||
|
||||
addr=$((got))
|
||||
[ "$addr" -ge "$ROM_LO" ] && [ "$addr" -le "$ROM_HI" ] \
|
||||
|| fail "$name $got is outside the ROM bit range 3F200000h..3FFFFFFFh"
|
||||
[ $((addr % 16)) -eq 0 ] \
|
||||
|| fail "$name $got is not 16-bit aligned"
|
||||
|
||||
off=$(( (addr - ROM_LO) / 8 ))
|
||||
first=$("$DIS34" -b "$got" -s "$off" "$ROM" 2>/dev/null |
|
||||
awk 'NF && $1 !~ /^L[0-9A-Fa-f]+:$/ { print tolower($1); exit }')
|
||||
[ -n "$first" ] || fail "$name $got disassembled to nothing"
|
||||
case " move mmtm setf cmp addi " in
|
||||
*" $first "*) ;;
|
||||
*) fail "$name $got starts with '$first', not a plausible function prologue" ;;
|
||||
esac
|
||||
echo " ok $name $got: $first ..."
|
||||
}
|
||||
|
||||
check_sym XOpenDisplay 0x3F9C2570
|
||||
check_sym XCreateSimpleWindow 0x3FD41B90
|
||||
check_sym XMapWindow 0x3FD645B0
|
||||
check_sym XSync 0x3FD9F6B0
|
||||
|
||||
echo "m3 validate-syms: PASS (four v2.2 Xlib entries in ROM, aligned, prologues valid)"
|
||||
@@ -28,7 +28,12 @@ extern Window XCreateSimpleWindow(Display *display, Window parent,
|
||||
extern int XMapWindow(Display *display, Window window);
|
||||
extern int XSync(Display *display, int discard);
|
||||
|
||||
static unsigned int vt1200_root(Display *d) {
|
||||
/* clang is invoked at -O0 (the optimizer runs separately), which marks every
|
||||
definition noinline. Keep this helper always-inline so layout.c and the
|
||||
Xlib client do not each emit a local _vt1200_root for GSPA/GSPLNK to
|
||||
collide on. */
|
||||
static inline __attribute__((always_inline)) unsigned int
|
||||
vt1200_root(Display *d) {
|
||||
return ((Screen *)((Display *)d)->screens)->root;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "vt1200_xlib.h"
|
||||
|
||||
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n) * 0x20u))
|
||||
|
||||
int xhello(void) {
|
||||
Display *d = XOpenDisplay("0::0");
|
||||
MARK(0) = (unsigned int)d;
|
||||
if (!d)
|
||||
return -1;
|
||||
unsigned int root = vt1200_root(d);
|
||||
MARK(1) = root;
|
||||
Window w = XCreateSimpleWindow(d, root, 100, 100, 400, 200, 2, 0, 1);
|
||||
MARK(2) = (unsigned int)w;
|
||||
XMapWindow(d, w);
|
||||
int s = XSync(d, 0);
|
||||
MARK(3) = (unsigned int)s;
|
||||
return (int)w;
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
# M3 test gate: rebuild the linked COFF through the normal GSPA/GSPLNK path,
|
||||
# then verify dis34 decodes the Xlib call sequence, branch code and old-STK
|
||||
# restore offsets at the linked addresses.
|
||||
set -eu
|
||||
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||
|
||||
# Always rebuild: a stale build/m3/m3.coff would let an assembler/compiler
|
||||
# regression pass without ever being reassembled. Keep dis-roundtrip.sh
|
||||
# pointed at the file this run produces, including when M3_WORK/M3_COFF are
|
||||
# overridden.
|
||||
WORK=${M3_WORK:-$ROOT/build/m3}
|
||||
M3_COFF=${M3_COFF:-$WORK/m3.coff}
|
||||
export M3_COFF
|
||||
|
||||
"$ROOT/scripts/m3/run.sh" "$@"
|
||||
exec "$ROOT/scripts/m3/dis-roundtrip.sh" "$@"
|
||||
@@ -69,6 +69,17 @@ check "$WORK/layout.s" layout_root "$HEX40" "0x40 (Screen.root)"
|
||||
check "$GOLDEN" layout_screens "$HEX480" "0x480 (Display.screens)"
|
||||
check "$GOLDEN" layout_root "$HEX40" "0x40 (Screen.root)"
|
||||
|
||||
# root_probe exercises the real access path: vt1200_root() follows
|
||||
# Display.screens (bit 0x480) and reads Screen.root (bit 0x40). The helper is
|
||||
# always_inline, so the incoming align-1 loads select MOVE directly in
|
||||
# root_probe (see m3-unaligned.ll). Require both displacements and reject a
|
||||
# MOVB byte split.
|
||||
check "$WORK/layout.s" root_probe "$HEX480" "0x480 (Display.screens)"
|
||||
check "$WORK/layout.s" root_probe "$HEX40" "0x40 (Screen.root)"
|
||||
if body "$WORK/layout.s" root_probe | grep -q 'MOVB'; then
|
||||
fail "root_probe contains MOVB: align-1 field loads did not select MOVE"
|
||||
fi
|
||||
|
||||
# Guard against the old byte-pad mistake: 0x480*8 / 0x40*8 (9216 / 512)
|
||||
# means the struct was padded in bytes past the ROM's bit displacements.
|
||||
if body "$GOLDEN" layout_screens | tr 'A-Z' 'a-z' |
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
# M3 test gate: validate the Display/Screen layout and ROM entries, compile the
|
||||
# branch/loop tests and the first C Xlib client, assemble/link with GSPA/GSPLNK,
|
||||
# load the COFF in MAME, and require the branch markers plus a solid white
|
||||
# window area.
|
||||
set -eu
|
||||
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||
|
||||
# These two checks are part of the gate, not manual steps: layout.sh proves the
|
||||
# 0x480/0x40 root probe still selects the ROM displacements, and
|
||||
# validate-syms.sh proves the four Xlib imports are still the v2.2 entries.
|
||||
"$ROOT/scripts/tests/m3/layout.sh"
|
||||
"$ROOT/scripts/m3/validate-syms.sh"
|
||||
|
||||
exec "$ROOT/scripts/m3/run.sh" "$@"
|
||||
@@ -15,5 +15,23 @@ M3 owns the wider table: it extends the entries from the Ghidra project /
|
||||
MAME, then on the real unit. Do not assume an address here is validated just
|
||||
because it is listed.
|
||||
|
||||
## v2.2 Xlib entries validated
|
||||
|
||||
The four Xlib entries used by the compiled client are **validated** by
|
||||
`vendor/vt1200/dwt/gui/xhello.asm` (the hand-written payload that already runs
|
||||
against the live DWT X server):
|
||||
|
||||
| entry | bit address | evidence |
|
||||
|---|---|---|
|
||||
| `XOpenDisplay` | `0x3F9C2570` | `xhello.asm` calls it; `CALLA _XOpenDisplay` reaches the stub in MAME |
|
||||
| `XCreateSimpleWindow` | `0x3FD41B90` | nine-argument window create; window id marker is nonzero |
|
||||
| `XMapWindow` | `0x3FD645B0` | mapped window produces the white VRAM pixel |
|
||||
| `XSync` | `0x3FD9F6B0` | called after mapping; return value is diagnostic only |
|
||||
|
||||
`scripts/m3/validate-syms.sh` is the regression check: each entry must be in
|
||||
the ROM bit range `3F200000h..3FFFFFFFh`, 16-bit aligned, and disassemble to a
|
||||
plausible function prologue (`MOVE`/`MMTM`/`SETF`/`CMP`/`ADDI`). It is not a
|
||||
re-extraction; M4 extends the table for `XImage`/`XEvent`.
|
||||
|
||||
The build resolves these as absolute symbols (or ABI-preserving stubs). Do not
|
||||
bake them into the compiler: keep them in a per-ROM linker script.
|
||||
Reference in new issue
Block a user