diff --git a/scripts/m1/char-test.asm b/scripts/m1/char-test.asm new file mode 100644 index 0000000..9cda719 --- /dev/null +++ b/scripts/m1/char-test.asm @@ -0,0 +1,22 @@ +; char-test.asm -- run the compiled char helpers and publish their results. +; +; Appended after char.s so _loadb/_storeb (and every earlier function) keep +; their linked addresses. _start calls this instead of calling _second +; directly, which is the same one-word-size calla; _char_test calls _second +; and returns its result in A0 so the existing second() marker still works. +; +; storeb(018A8080, 0x81) writes the byte 0x81 +; loadb(018A8080) returns the sign-extended 0xFFFFFF81 + .text + .globl _char_test +_char_test: + calla _second ; A0 = &arr[1] + move a0, a3 ; save it for the second() marker + movi 018A8080h, a0 ; p = storeb/loadb marker byte + movi 81h, a1 ; v = 0x81 (low byte stored by MOVB) + calla _storeb + calla _loadb ; A0 is still 018A8080 + movi 018A8060h, a2 + move a0, *a2, 1 ; 32-bit loadb marker + move a3, a0 ; restore &arr[1] + rets diff --git a/scripts/m1/char.c b/scripts/m1/char.c new file mode 100644 index 0000000..5e344cf --- /dev/null +++ b/scripts/m1/char.c @@ -0,0 +1,2 @@ +int loadb(char *p) { return *p; } +void storeb(char *p, int v) { *p = v; } diff --git a/scripts/m1/dis-roundtrip.sh b/scripts/m1/dis-roundtrip.sh index 355c61e..b2f735e 100755 --- a/scripts/m1/dis-roundtrip.sh +++ b/scripts/m1/dis-roundtrip.sh @@ -12,6 +12,8 @@ # _load MOVE *A0, A0, 1 ; RETS 0 # _late ADDI 24, A0 ; RETS 0 # _second MOVI _arr+32, A0 ; RETS 0 +# _loadb MOVB *A0, A0 ; RETS 0 +# _storeb MOVB A1, *A0 ; RETS 0 # # This verifies the 1987 GSPA assembler's encoding, not a dormant LLVM Inst # encoding. Overridable: M1_COFF DIS34 @@ -121,31 +123,49 @@ ADD=$(get_sym _add) LOAD=$(get_sym _load) LATE=$(get_sym _late) SECOND=$(get_sym _second) +LOADB=$(get_sym _loadb) +STOREB=$(get_sym _storeb) ADD_HEX=$(printf '%08X' "$ADD") LOAD_HEX=$(printf '%08X' "$LOAD") LATE_HEX=$(printf '%08X' "$LATE") SECOND_HEX=$(printf '%08X' "$SECOND") +LOADB_HEX=$(printf '%08X' "$LOADB") +STOREB_HEX=$(printf '%08X' "$STOREB") [ "$ADD_HEX" = "018A0240" ] || fail "_add linked at $ADD_HEX, want 018A0240" [ "$LOAD_HEX" = "018A0270" ] || fail "_load linked at $LOAD_HEX, want 018A0270" [ "$LATE_HEX" = "018A0290" ] || fail "_late linked at $LATE_HEX, want 018A0290" [ "$SECOND_HEX" = "018A02C0" ] || fail "_second linked at $SECOND_HEX, want 018A02C0" +[ "$LOADB_HEX" = "018A0300" ] || fail "_loadb linked at $LOADB_HEX, want 018A0300" +[ "$STOREB_HEX" = "018A0320" ] || fail "_storeb linked at $STOREB_HEX, want 018A0320" echo " _add=$ADD_HEX _load=$LOAD_HEX _late=$LATE_HEX _second=$SECOND_HEX" +echo " _loadb=$LOADB_HEX _storeb=$STOREB_HEX (appended after _second)" 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 +# dis34 -l prints "018a0240:\tadd a0, a1\t; 4001". The operand must be +# followed by optional whitespace and then the comment/end-of-line marker, so +# "rets 1" cannot satisfy a "rets" pattern. +match_at() { + _file=$1 + _addr=$2 + _pat=$3 + _laddr=$(printf '%08x' "$_addr") + grep -Eiq "^${_laddr}:[[:space:]]+${_pat}([[:space:]]*(;|$))" "$_file" +} + # expect_at LABEL DECIMAL_ADDR EXPECTED_TEXT ERE -# dis34 -l prints "018a0240:\tadd a0, a1\t; 4001". expect_at() { _label=$1 _addr=$2 _want=$3 _pat=$4 _laddr=$(printf '%08x' "$_addr") - if grep -Eiq "^${_laddr}:[[:space:]]+${_pat}([[:space:]]|;|$)" "$TMP/dis34.txt"; then + if match_at "$TMP/dis34.txt" "$_addr" "$_pat"; then echo " ok $_label @ 0x$_laddr: $_want" return 0 fi @@ -191,16 +211,18 @@ expect_label "$SECOND" expect_at "_second" "$SECOND" "MOVI _arr+32, A0" 'movi[[:space:]]+(0x)?18a9020,[[:space:]]*a0' expect_at "_second+48" $((SECOND + 48)) "RETS 0" 'rets([[:space:]]+0)?' -# The char helpers are lit-only in M1, so the harness does not link them. -# If a later harness does, do not silently skip their encoding. -MOVB_SEEN=0 -for name in _loadb _storeb; do - if addr=$(sym_addr "$name"); then - MOVB_SEEN=1 - expect_at "$name" "$addr" "MOVB" 'movb[[:space:]]+' - fi -done -[ "$MOVB_SEEN" -eq 1 ] \ - || echo " movb: no _loadb/_storeb linked (char helpers are lit-only)" +# Negative control for the RETS operand boundary: "rets 1" must not match. +printf '018a0260:\trets\t1\t; 0960\n' > "$TMP/rets-neg.txt" +if match_at "$TMP/rets-neg.txt" $((ADD + 32)) 'rets([[:space:]]+0)?'; then + fail "negative control: the RETS pattern accepted 'rets 1'" +fi +echo " ok negative control @ 0x$(printf '%08x' $((ADD + 32))): 'rets 1' rejected" + +expect_label "$LOADB" +expect_at "_loadb" "$LOADB" "MOVB *A0, A0" 'movb[[:space:]]+\*a0,[[:space:]]*a0' +expect_at "_loadb+16" $((LOADB + 16)) "RETS 0" 'rets([[:space:]]+0)?' + +expect_at "_storeb" "$STOREB" "MOVB A1, *A0" 'movb[[:space:]]+a1,[[:space:]]*\*a0' +expect_at "_storeb+16" $((STOREB + 16)) "RETS 0" 'rets([[:space:]]+0)?' echo "M1 dis34 round-trip: PASS" diff --git a/scripts/m1/entry.asm b/scripts/m1/entry.asm index 514ce8d..a3e7c60 100644 --- a/scripts/m1/entry.asm +++ b/scripts/m1/entry.asm @@ -7,9 +7,16 @@ ; 018A8000 add(20, 22) ; 018A8020 load(018A8100) ; 018A8040 second() == &arr[1] +; 018A8060 loadb(018A8080) == 0xFFFFFF81 (sign-extended 0x81) +; 018A8080 storeb(018A8080, 0x81) leaves the byte 0x81 ; ; _late is linked but not called: it is a compile-time check that a surviving ; GEP offset is 24 bits, not 192. +; +; The char helpers are exercised by _char_test (char-test.asm, appended after +; the compiled functions). Calling it here instead of calling _second keeps +; this entry's size -- and therefore every function's linked address -- the +; same; _char_test calls _second and returns its result in A0. .text .globl _start _start: @@ -29,7 +36,8 @@ _start: movi 018A8020h, a2 move a0, *a2, 1 - calla _second + calla _char_test + movi 018A8040h, a2 move a0, *a2, 1 diff --git a/scripts/m1/m1-check.lua b/scripts/m1/m1-check.lua index c205c38..64e5027 100644 --- a/scripts/m1/m1-check.lua +++ b/scripts/m1/m1-check.lua @@ -4,6 +4,8 @@ -- M1_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua -- M1_EXPECT_LOAD expected load() result (default 0x12345678) -- M1_EXPECT_SECOND expected &arr[1] in bits +-- M1_EXPECT_LOADB expected sign-extended byte load (default 0xFFFFFF81) +-- M1_EXPECT_STOREB expected byte store (default 0x81) -- -- The marker read runs one frame before loader.lua's inspection/exit frame. @@ -12,6 +14,8 @@ dofile(os.getenv("M1_LOADER_LUA")) local ENDF = tonumber(os.getenv("LOADER_END") or "2700") local EXPECT_LOAD = tonumber(os.getenv("M1_EXPECT_LOAD") or "0x12345678") local EXPECT_SECOND = tonumber(os.getenv("M1_EXPECT_SECOND") or "0x018A9020") +local EXPECT_LOADB = tonumber(os.getenv("M1_EXPECT_LOADB") or "0xFFFFFF81") +local EXPECT_STOREB = tonumber(os.getenv("M1_EXPECT_STOREB") or "0x81") local frames = 0 local checked = false @@ -23,14 +27,18 @@ emu.register_frame_done(function() local add = prog:read_u32(0x018A8000) local load = prog:read_u32(0x018A8020) local second = prog:read_u32(0x018A8040) - print(string.format("M1: add=%d load=0x%08X second=0x%08X", - add, load, second)) - if add == 42 and load == EXPECT_LOAD and second == EXPECT_SECOND then + local loadb = prog:read_u32(0x018A8060) + local storeb = prog:read_u8(0x018A8080) + print(string.format( + "M1: add=%d load=0x%08X second=0x%08X loadb=0x%08X storeb=0x%02X", + add, load, second, loadb, storeb)) + if add == 42 and load == EXPECT_LOAD and second == EXPECT_SECOND + and loadb == EXPECT_LOADB and storeb == EXPECT_STOREB then print("M1 MAME: PASS") else print(string.format( - "M1 MAME: FAIL (want add=42 load=0x%08X second=0x%08X)", - EXPECT_LOAD, EXPECT_SECOND)) + "M1 MAME: FAIL (want add=42 load=0x%08X second=0x%08X loadb=0x%08X storeb=0x%02X)", + EXPECT_LOAD, EXPECT_SECOND, EXPECT_LOADB, EXPECT_STOREB)) end end end, "m1-check") diff --git a/scripts/m1/run.sh b/scripts/m1/run.sh index 51a45bb..728a25b 100755 --- a/scripts/m1/run.sh +++ b/scripts/m1/run.sh @@ -3,16 +3,18 @@ # # scripts/m1/run.sh # -# Compiles four C functions with scripts/tms-clang + llc -O0, assembles and +# Compiles six C functions with scripts/tms-clang + llc -O0, assembles and # links them with the 1987 GSPA/GSPLNK 2.00 under DOSBox, then loads the COFF # into MAME through dwt/loader/loader.lua and checks the marker words: # # add(20, 22) == 42 # load(&word) == 0x12345678 # second() == &arr[1] (arr + 32 bits) +# loadb(&byte) == 0xFFFFFF81 (sign-extended 0x81) +# storeb(&byte, 0x81) leaves the byte 0x81 # # Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC MAME DOSBOX DOSBOX_CONF -# SDK_IMG M1_WORK M1_SDK LOADER_AT LOADER_END +# SDK_IMG M1_WORK M1_COFF M1_SDK LOADER_AT LOADER_END set -eu ROOT=$(cd "$(dirname "$0")/../.." && pwd) @@ -30,6 +32,7 @@ DOSBOX_CONF=${DOSBOX_CONF:-$ROOT/vendor/vt1200/harness/sdk-scratch/dosbox-bench. 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=${M1_WORK:-$ROOT/build/m1} +COFF=${M1_COFF:-$WORK/m1.coff} SDK=${M1_SDK:-$ROOT/build/m1-sdk} ASM=$WORK/asm LOADER_LUA=$ROOT/vendor/vt1200/dwt/loader/loader.lua @@ -47,10 +50,11 @@ for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do done mkdir -p "$ASM" "$WORK/evidence" "$WORK/run/nvram" "$WORK/run/cfg" +mkdir -p "$(dirname "$COFF")" echo "== compile and select" export TMS_CLANG TMS_OPT TMS_BITADDR -for name in add load late const_gep; do +for name in add load late const_gep char; do "$ROOT/scripts/tms-clang" -O2 -o "$ASM/$name.ll" "$DIR/$name.c" "$TMS_LLC" -march=tms34010 -O0 -filetype=asm \ "$ASM/$name.ll" -o "$ASM/$name.s" \ @@ -82,7 +86,7 @@ fi echo "== assemble and link with GSPA/GSPLNK under DOSBox" cat "$DIR/entry.asm" "$ASM/add.s" "$ASM/load.s" "$ASM/late.s" \ - "$ASM/const_gep.s" > "$SDK/M1.ASM" + "$ASM/const_gep.s" "$ASM/char.s" "$DIR/char-test.asm" > "$SDK/M1.ASM" # The linker command file must keep CRLF line endings. sed 's/\r$//' "$DIR/link.cmd" | sed 's/$/\r/' > "$SDK/M1.CMD" rm -f "$SDK/M1.OBJ" "$SDK/M1.OUT" "$SDK/M1.MAP" "$SDK/M1.LST" @@ -102,7 +106,7 @@ grep -q 'No Errors' "$WORK/m1.lst" \ || { grep -i -B2 -A2 'error' "$WORK/m1.lst" >&2 || true; fail "GSPA reported errors"; } [ -f "$SDK/M1.OUT" ] || fail "GSPLNK produced no M1.OUT" sed 's/\r$//' "$SDK/M1.MAP" > "$WORK/m1.map" -cp "$SDK/M1.OUT" "$WORK/m1.coff" +cp "$SDK/M1.OUT" "$COFF" cp "$SDK/M1.ASM" "$WORK/m1.asm" ENTRY=$(awk '$2 == "_start" { print $1; exit }' "$WORK/m1.map") @@ -133,7 +137,7 @@ echo "== run the COFF in MAME" VT1200_SKIP_LEVEL1=1 \ LOADER_AT="$LOADER_AT" LOADER_END="$LOADER_END" \ -LOADER_BIN="$LOADER_BIN" LOADER_COFF="$WORK/m1.coff" \ +LOADER_BIN="$LOADER_BIN" LOADER_COFF="$COFF" \ LOADER_OUT="$WORK/evidence" \ M1_LOADER_LUA="$LOADER_LUA" \ M1_EXPECT_LOAD=0x12345678 M1_EXPECT_SECOND="$EXPECT_SECOND" \ diff --git a/scripts/tests/m1/dis-roundtrip.sh b/scripts/tests/m1/dis-roundtrip.sh index ba7a1de..e580a21 100755 --- a/scripts/tests/m1/dis-roundtrip.sh +++ b/scripts/tests/m1/dis-roundtrip.sh @@ -5,6 +5,12 @@ set -eu ROOT=$(cd "$(dirname "$0")/../../.." && pwd) # Always rebuild: a stale build/m1/m1.coff would let an asm-encoding -# regression pass without ever touching the compiler or assembler. +# regression pass without ever touching the compiler or assembler. Keep +# dis-roundtrip.sh pointed at the file this run produces, including when +# M1_WORK/M1_COFF are overridden. +WORK=${M1_WORK:-$ROOT/build/m1} +M1_COFF=${M1_COFF:-$WORK/m1.coff} +export M1_COFF + "$ROOT/scripts/m1/run.sh" "$@" exec "$ROOT/scripts/m1/dis-roundtrip.sh" "$@"