Files
vt1200/harness/udpstream/build-stream.sh
T

203 lines
8.1 KiB
Bash
Executable File

#!/bin/sh
# Build the video-stream payload (C + ROM-API thunks).
#
# harness/udpstream/build-stream.sh
# STREAM_POOL_BYTES=0x7FFF000 harness/udpstream/build-stream.sh # fail the malloc
#
# .text at bit 0x018C0000, .bss at 0x01C00000 (both inside the firmware's large
# free block), entry _start, no .cinit (the runtime's autoinit pass does not
# run -- see harness/c-hello/README.md).
#
# POOL_BYTES is compile-time: the script writes poolcfg.h from
# STREAM_POOL_BYTES (default 249856 = 244 KB: two 24 KB slots + 160 KB
# framebuffer + 32 KB dictionary + 4 KB slack).
#
# Outputs: build/vidstream.bin (raw .text, what vidstream.lua loads),
# build/vidstream.coff, vidstream.map, vidstream.lst, entry.lst,
# vidstream.env (POOL_BYTES, the linked entry point and the address of
# the static fallback pool, which the MAME check asserts against).
set -eu
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
DIR=$(cd "$(dirname "$0")" && pwd)
WORK="$DIR/work"
OUT="$DIR/build"
IMG="$ROOT/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img"
DOSBOX_CONF="$ROOT/harness/sdk-scratch/dosbox-bench.conf"
TEXT=018C0000
BSS=01C00000
POOL_BYTES=${STREAM_POOL_BYTES:-249856}
mkdir -p "$WORK" "$OUT"
if [ ! -x "$WORK/GSPCG.EXE" ]; then
mcopy -s -n -i "$IMG" "::/A2/*" "$WORK/"
mcopy -s -n -i "$IMG" "::/C2/*" "$WORK/"
fi
SRC=${STREAM_SRC:-$DIR/vidstream.c}
cp "$SRC" "$WORK/VIDSTRM.C"
cp "$DIR/romapi.h" "$WORK/ROMAPI.H"
# The frame parser is a second translation unit: vidstream.c calls it and the
# host unit test (tests/test_frame_parse.c) compiles the same file. The 1987
# preprocessor cannot open a name with an 11-character base (Task 3 lost a
# build to VIDSTREAM.C the same way), so the work copies use 8.3 names and the
# C sources include "frmpsr.h" -- a symlink to frame_parse.h in the repo.
cp "$DIR/frame_parse.c" "$WORK/FRMPSR.C"
cp "$DIR/frame_parse.h" "$WORK/FRMPSR.H"
cp "$DIR/entry.asm" "$WORK/ENTRY.ASM"
cp "$DIR/vram.asm" "$WORK/VRAM.ASM"
# vram_b.asm is Task 5's PIXBLT B,XY primitive. Link it when it exists so the
# build does not have to be edited again that day; a Task 3 tree has only
# vram.asm and links exactly what it uses.
VRAMB_OBJ=
VRAMB_LST=
if [ -f "$DIR/vram_b.asm" ]; then
cp "$DIR/vram_b.asm" "$WORK/VRAMB.ASM"
VRAMB_OBJ=VRAMB.obj
VRAMB_LST=VRAMB.LST
fi
# The allocator request size is a compile-time constant (the 1987 compiler has
# no -D we rely on). One generated header keeps the default in one place.
printf '#define POOL_BYTES %s\n' "$POOL_BYTES" > "$WORK/POOLCFG.H"
{
printf '%s\n' '-a'
printf '%s\n' '-o VIDSTRM.out'
printf '%s\n' '-m VIDSTRM.map'
printf '%s\n' '-e _start'
printf '%s\n' 'VIDSTRM.obj'
printf '%s\n' 'FRMPSR.obj'
printf '%s\n' 'ENTRY.obj'
printf '%s\n' 'VRAM.obj'
[ -z "$VRAMB_OBJ" ] || printf '%s\n' "$VRAMB_OBJ"
printf '%s\n' '-l RTS.LIB'
printf '%s\n' 'MEMORY'
printf '%s\n' '{'
printf '%s\n' " prog: origin = ${TEXT}h, length = 20000h"
printf '%s\n' " ram: origin = ${BSS}h, length = 200000h"
printf '%s\n' '}'
printf '%s\n' 'SECTIONS'
printf '%s\n' '{'
printf '%s\n' ' .text: {} > prog'
printf '%s\n' ' .data: {} > prog'
printf '%s\n' ' .cinit: {} > prog'
printf '%s\n' ' .bss: {} > ram'
printf '%s\n' '}'
} > "$WORK/VIDSTRM.CMD"
sed -i 's/$/\r/' "$WORK/VIDSTRM.CMD"
rm -f "$WORK"/VIDSTRM.OBJ "$WORK"/VIDSTRM.OUT "$WORK"/VIDSTRM.LST \
"$WORK"/VIDSTRM.MAP "$WORK"/VIDSTRM.ASM "$WORK"/VIDSTRM.CPP \
"$WORK"/VIDSTRM.IF "$WORK"/ENTRY.OBJ "$WORK"/ENTRY.LST \
"$WORK"/FRMPSR.OBJ "$WORK"/FRMPSR.LST "$WORK"/FRMPSR.ASM \
"$WORK"/FRMPSR.CPP "$WORK"/FRMPSR.IF \
"$WORK"/VRAM.OBJ "$WORK"/VRAM.LST "$WORK"/VRAMB.OBJ "$WORK"/VRAMB.LST
# The toolchain runs from a batch file, not from one -c per command. Once the
# build grew to a second translation unit the -c list stopped completing:
# FRMPSR.OBJ and ENTRY.OBJ were written, VRAM.OBJ and VIDSTRM.OUT never were,
# and DOSBox then sat at the prompt until the 240 s timeout (reproduced twice,
# with nothing in dosbox.log -- the DOS tools write to the screen, not stdout).
# DOSBox feeds each -c line through the keyboard buffer, which is the likeliest
# cause; a .BAT has no such limit, completes the same steps in ~2 s, and makes
# the step list one readable block. CRLF, because DOS.
{
printf 'gspcpp VIDSTRM\r\n'
printf 'gspcc VIDSTRM\r\n'
printf 'gspcg VIDSTRM\r\n'
printf 'gspa VIDSTRM VIDSTRM.obj VIDSTRM.lst -l\r\n'
printf 'gspcpp FRMPSR\r\n'
printf 'gspcc FRMPSR\r\n'
printf 'gspcg FRMPSR\r\n'
printf 'gspa FRMPSR FRMPSR.obj FRMPSR.lst -l\r\n'
printf 'gspa ENTRY ENTRY.obj ENTRY.lst -l\r\n'
printf 'gspa VRAM VRAM.obj VRAM.lst -l\r\n'
[ -z "$VRAMB_OBJ" ] || printf 'gspa VRAMB VRAMB.obj VRAMB.lst -l\r\n'
printf 'gsplnk VIDSTRM.cmd\r\n'
printf 'exit\r\n'
} > "$WORK/BUILD.BAT"
run_dosbox() {
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout 240 dosbox -conf "$DOSBOX_CONF" -noprimaryconf -nolocalconf \
-c "mount c $WORK" -c "c:" \
-c "BUILD.BAT" \
-c "exit" -exit > "$WORK/dosbox.log" 2>&1
}
run_dosbox
if [ ! -f "$WORK/VIDSTRM.OUT" ]; then
echo "build-stream.sh: GSPLNK produced no VIDSTRM.OUT" >&2
sed 's/\r$//' "$WORK/VIDSTRM.MAP" 2>/dev/null | tail -20 >&2 || true
for f in VIDSTRM.LST FRMPSR.LST ENTRY.LST VRAM.LST $VRAMB_LST; do
sed 's/\r$//' "$WORK/$f" 2>/dev/null | grep -i 'error' | grep -vi 'no errors' >&2 || true
done
exit 1
fi
for f in VIDSTRM.LST FRMPSR.LST ENTRY.LST VRAM.LST $VRAMB_LST; do
if sed 's/\r$//' "$WORK/$f" | grep -i 'error' | grep -qvi 'no errors'; then
echo "build-stream.sh: $f diagnostics:" >&2
sed 's/\r$//' "$WORK/$f" | grep -in 'error' | grep -vi 'no errors' >&2
exit 1
fi
done
cp "$WORK/VIDSTRM.OUT" "$OUT/vidstream.coff"
sed 's/\r$//' "$WORK/VIDSTRM.MAP" > "$OUT/vidstream.map"
sed 's/\r$//' "$WORK/VIDSTRM.LST" > "$OUT/vidstream.lst"
sed 's/\r$//' "$WORK/FRMPSR.LST" > "$OUT/frmpsr.lst"
sed 's/\r$//' "$WORK/ENTRY.LST" > "$OUT/entry.lst"
sed 's/\r$//' "$WORK/VRAM.LST" > "$OUT/vram.lst"
if [ -n "$VRAMB_LST" ]; then
sed 's/\r$//' "$WORK/VRAMB.LST" > "$OUT/vramb.lst"
fi
# Guard: the marker block (MARK in the source) must sit clear of .text, or the
# payload overwrites its own code with marker writes (an illegal-opcode crash).
python3 - "$SRC" "$OUT/vidstream.map" <<'PYCHK'
import re, sys
src, mp = sys.argv[1], sys.argv[2]
m = re.search(r'#define\s+MARK\s+(0x[0-9A-Fa-f]+)', open(src).read())
if m:
mark = int(m.group(1), 16)
for ln in open(mp):
f = ln.split()
if len(f) >= 4 and f[0] == '.text':
start, size = int(f[2], 16), int(f[3], 16)
end = start + size
if end > mark:
sys.stderr.write("build: ERROR .text ends at 0x%X, past MARK 0x%X\n" % (end, mark))
sys.exit(1)
print("build: .text ends 0x%X, MARK 0x%X (%d bytes clear)" % (end, mark, (mark - end) // 8))
PYCHK
# The raw .text image is what the debugger loads (vidstream.lua).
python3 "$ROOT/harness/hello-world/cofextract.py" \
"$WORK/VIDSTRM.OUT" "$OUT/vidstream.bin"
# The static-fallback address and the entry point are linked, not guessed:
# publish them (bits) for the MAME check, which enters at _start and asserts
# that a failed malloc lands exactly on the static buffer.
STATIC_PTR=$(sed 's/\r$//' "$WORK/VIDSTRM.MAP" \
| awk '$2 == "_pool_static" { print $1; exit }')
if [ -z "$STATIC_PTR" ]; then
echo "build-stream.sh: _pool_static missing from the map (fallback cannot be checked)" >&2
exit 1
fi
ENTRY_PTR=$(sed 's/\r$//' "$WORK/VIDSTRM.MAP" \
| awk '/ENTRY POINT SYMBOL/ { print $NF; exit }')
if [ -z "$ENTRY_PTR" ]; then
echo "build-stream.sh: no entry point in the map" >&2
exit 1
fi
printf 'POOL_BYTES=%s\nSTATIC_PTR=0x%s\nENTRY_PTR=0x%s\n' \
"$POOL_BYTES" "$STATIC_PTR" "$ENTRY_PTR" > "$OUT/vidstream.env"
echo "build-stream.sh: $OUT/vidstream.bin ($(wc -c < "$OUT/vidstream.bin") bytes)"
grep -E "ENTRY POINT|^\.(text|cinit|bss)" "$OUT/vidstream.map" | head -8
echo "build-stream.sh: POOL_BYTES=$POOL_BYTES entry bit 0x$ENTRY_PTR static fallback bit 0x$STATIC_PTR"