tests: M3 branches and first Xlib window in MAME

This commit is contained in:
arti committed 2026-11-11 19:46:00 +00:00
1 parent b34016a9f3
commit 09db9e22e9
14 files changed
+980 -1

No files matched your search

+23
View File
@@ -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;
}
+463
View File
@@ -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"
+25
View File
@@ -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
+5
View File
@@ -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); }
+16
View File
@@ -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
}
+79
View File
@@ -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")
+223
View File
@@ -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"
+61
View File
@@ -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)"
+6 -1
View File
@@ -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;
}
+18
View File
@@ -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;
}
+17
View File
@@ -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" "$@"
+11
View File
@@ -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' |
+15
View File
@@ -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" "$@"
+18
View File
@@ -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.