docs: M3 control flow and first Xlib window

This commit is contained in:
arti committed 2026-11-11 19:56:19 +00:00
1 parent 09db9e22e9
commit ed58040fbd
7 files changed
+532 -48

No files matched your search

+164 -39
View File
@@ -3,38 +3,73 @@
A modern C toolchain for the DEC VT1200 / TI TMS34010, built as a small fork of
upstream LLVM.
**Status: M2.** `llc -march=tms34010` emits GSPA-compatible assembly using the
**Status: M3.** `llc -march=tms34010` emits GSPA-compatible assembly using the
TI ABI: integer/pointer arguments are pushed on the program stack, the result is
returned in `A8`, frames use `A13`/`A14`/`A15`, and functions end in `RETS 2`
(`RETS 0` without arguments). M2 can call a ROM entry point (`strlen`) and emit
initialized `.data` and string literals. It is still **assembly-only**: no
object writer, no `clang -c`, and no native COFF; GSPA/GSPLNK under DOSBox
produce the COFF. Control flow, floats, structs, varargs and function pointers
remain M3+.
(`RETS 0` without arguments). M3 adds conditional/unconditional branches,
loops, integer ALU/shift operations, and the first compiled C Xlib client: a
four-call window opened with `XOpenDisplay`, created with
`XCreateSimpleWindow`, mapped with `XMapWindow` and synchronized with `XSync`.
It is still **assembly-only**: no object writer, no `clang -c`, and no native
COFF; GSPA/GSPLNK under DOSBox produce the COFF. XImage/XEvent, the real client,
structs by value, function pointers, floats and `i64` remain M4/M5.
- Design: [`docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md`](docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md)
- M0 plan: [`docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md`](docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md)
- M1 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md`](docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md)
- M2 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md`](docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md)
- M3 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m3-control-flow-and-xlib-window.md`](docs/superpowers/plans/2026-11-11-tms34010-m3-control-flow-and-xlib-window.md)
- Spike: [`spike/REPORT-23.md`](spike/REPORT-23.md) (current, LLVM 23.1.5);
[`spike/REPORT.md`](spike/REPORT.md) (historical, 19.1.7)
## Repositories
- **Compiler fork:** <https://git.wut.ee/opencode.siil.arti.ee/llvm-tms34010>
(branch `m2`) — the full buildable LLVM 23.1.5 tree with the TMS34010
target, clang bit-address changes and the M2 backend. Branch `tms34010` is
the M1 tip and keeps the original M1 backend and suite.
(branch `m3`) — the full buildable LLVM 23.1.5 tree with the TMS34010
target, clang bit-address changes and the M2/M3 backend. Branch `m2` (the
repository default) is the M2 snapshot; branch `tms34010` is the M1 tip and
keeps the original M1 backend and suite.
- **This project:** <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>
(branch `m2`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
`tms-clang`, symbols, the M2 harness and the spike. Branch `m1` is the
reproducible M1 snapshot, including the retired M1 harness.
(branch `m3`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
`tms-clang`, symbols, the M2/M3 harnesses and the spike. Branch `m2` (the
repository default) is the M2 snapshot and branch `m1` is the reproducible M1
snapshot, including the retired M1 harness.
The compiler fork is the canonical source for all target code. `patches/llvm`
here is a **frozen M0 mirror** used by the default upstream+patches build; M2
does not change it.
here is a **frozen M0 mirror** used by the default upstream+patches build; M2/M3
do not change it.
## What M2 provides
## What M3 provides
M3 adds integer control flow and the first compiled C Xlib client, still
assembly-only:
- Conditional and unconditional branches for `i32` and pointer values:
`CMP`/`CMPI` plus `JAEQ`/`JANE`, the signed `JALT`/`JALE`/`JAGT`/`JAGE`, the
unsigned `JALO`/`JALS`/`JAHI`/`JAHS`, and `JAUC` for an unconditional jump.
Loops, if/else and `?:` work; `?:` expands to a compare plus a two-block
diamond (`SELECTCC`), and `setcc` becomes that diamond over 1/0.
- Integer ALU and shift operations: `SUB`, `AND`, `OR`, `XOR`, `NEG`, `NOT`,
`SLL`, `SRL`, `SRA`, plus `ADDI`/`ANDI`/`SLLK`/`SRAK`/`SRLK` immediates.
- The first compiled C Xlib window: `XOpenDisplay`, `XCreateSimpleWindow`
(nine arguments in the ROM's order: display, root, x, y, width, height,
border width, border, background), `XMapWindow` and `XSync`. All four go
through validated ROM import stubs.
- The ROM's `Display`/`Screen` field displacements are **bit** offsets
`0x480` (`Display.screens`) and `0x40` (`Screen.root`), i.e. byte offsets
`0x90` and `0x8` in `scripts/m3/vt1200_xlib.h`.
- Misaligned accesses: `MOVE`/`MOVB` and the FS0/FS1 field-size forms have no
alignment requirement on the TMS34010, so packed and bit-addressed struct
fields select directly instead of being split into byte-wide accesses.
- Eight `m3-*.ll` lit tests, for a total of 45 with the M2 suite; the M3
end-to-end, dis34, layout and differential gates (below).
M3 remains an `i32`/pointer milestone. Narrow integer loads/stores and `setcc`
are supported, but narrow (`i8`/`i16`) parameters and returns are still
rejected. Branches on `i64`, floats, aggregates, varargs and indirect calls are
not supported. The backend still emits assembly only.
## What M2 provides (branch `m2`)
M2 replaces the M1 test convention with the TI ABI and stops at assembly:
@@ -67,7 +102,7 @@ M2 replaces the M1 test convention with the TI ABI and stops at assembly:
## The TI ABI
| | M1 test convention (branch `m1`) | TI ABI (M2) |
| | M1 test convention (branch `m1`) | TI ABI (M2/M3) |
|---|---|---|
| Integer/pointer arguments | `A0`–`A3`, four maximum | program stack via `A14` (STK), pushed in reverse |
| Argument slots | registers | first at `FP(-32)`, second at `FP(-64)`, … |
@@ -86,9 +121,76 @@ immediately after every `CALLA` (`SETF 16,0,0` then `SETF 32,0,1`), because
ROM code may change either field size. FS0 drives `short` loads/stores and FS1
drives 32-bit words.
## Run the M2 gates
## Run the M3 gates
These are the documented commands; all must pass. The M1 harness and suite are
These are the documented commands on branch `m3`; all must pass.
```sh
# 1. Build llc, opt, clang and the test tools
ninja -C build/llvm-clean-build llc opt clang llvm-config FileCheck count not
# 2. LLVM codegen tests: 45 passed (37 m2-* + 8 m3-*)
build/llvm-clean-build/bin/llvm-lit -v \
build/llvm-clean-build/test/CodeGen/TMS34010 --filter='(m2|m3)-'
# 3. Layout/symbols, then compile, assemble, link and run in MAME
scripts/tests/m3/run.sh
# -> layout.sh: PASS (Display.screens=0x480, Screen.root=0x40; ...)
# -> M3 MAME: PASS
# (display/root/window nonzero, sum=55 max=22 lt=1, screens=0x480 root=0x40)
# 4. Rebuild the COFF and check the GSPA encodings with dis34
scripts/tests/m3/dis-roundtrip.sh
# -> M3 dis34 round-trip: PASS
# 5. Differential TI-compiler control-flow/Xlib check (no DOSBox)
scripts/tests/m3/diff/check.sh
# -> M3 diff: PASS
# 6. GEP scaling/normalization tool tests
tools/tms-bitaddr/tests/run.sh
# -> tms-bitaddr: PASS
```
`scripts/tests/m3/run.sh` first runs `scripts/tests/m3/layout.sh` (the ROM's
`0x480`/`0x40` bit displacements in both our asm and the 1987 golden) and
`scripts/m3/validate-syms.sh` (the four Xlib imports), then compiles
`scripts/m3/branch.c`, `layout.c` and `xhello.c` with `scripts/tms-clang -O2`,
runs `llc -march=tms34010 -O0 -filetype=asm`, generates the per-ROM import
stubs, concatenates the result with `scripts/m3/entry.asm`, assembles/links
with GSPA/GSPLNK 2.00 under DOSBox, and loads the COFF in MAME. `branch.c` is
built with `opt -replexitval=never`, because the default exit-value rewrite
turns `sumto`'s loop into an `i33` multiply that this target cannot select;
the loop itself is the point of the test.
The MAME run pins `VT1200_MONITOR=VR319` (the real 2048-bit row pitch) and
checks the ABI markers — nonzero display/root/window ids, `sumto(10) == 55`,
`max2(20, 22) == 22`, `lt(5, 7) == 1`, `layout_screens() == 0x480`,
`layout_root() == 0x40` — and, decisively, two all-white 32-bit client words in
VRAM at (150,150) and (250,200) inside the mapped window. A markers-only run or
a root-window bit is a failure. Paths are overridable with `TMS_CLANG`,
`TMS_OPT`, `TMS_BITADDR`, `TMS_LLC`, `MAME`, `DOSBOX`, `SDK_IMG`, `M3_WORK`,
`M3_COFF` and `M3_SDK`.
`scripts/tests/m3/dis-roundtrip.sh` rebuilds through the normal GSPA/GSPLNK
path and uses `dis34` to check the linked Xlib stubs (`JAUC` to the ROM bit
addresses), the nine-argument `XCreateSimpleWindow` push order, the branch code
(`CMP`, `JALT`, `JAGT`, `JAEQ`, `JAUC`, `LBB…` labels), the old-STK restore
offsets, and the `"0::0"` `.data` bytes.
`scripts/tests/m3/diff/check.sh` is the CI golden check. It compares the ABI
shape of our assembly with stored output from the 1987 TI compiler for the same
K&R sources: a conditional branch and an unconditional loop back-edge in
`sumto`, a conditional branch in `max2`, `RETS 2`, nine
`XCreateSimpleWindow` argument pushes, and the `0x480`/`0x40` bit
displacements. It does not compare instruction selection byte-for-byte. The
stored `ti-branch.s` and `ti-xhello.s` were generated once with
`gspcpp`/`gspcc`/`gspcg` (GSP C Compiler 2.01) under DOSBox; CI never runs
DOSBox, it greps the stored files.
## Run the M2 gates (default branch)
These are the M2 commands; they also pass on `m3`. The M1 harness and suite are
on branch `m1` and are **not** M2 gates: they encode the retired M1 register
convention.
@@ -175,8 +277,11 @@ _strlen:
```
`JAUC` is the TMS34010 unconditional absolute jump; GSPA 2.00 rejects bare
`ja`. M2 uses only `strlen`; later milestones extend and validate the table and
add the Xlib call sequence. Do not add Xlib dependencies to M2.
`ja`. M2 uses only `strlen`. M3 adds the four Xlib entries
(`XOpenDisplay`, `XCreateSimpleWindow`, `XMapWindow`, `XSync`) and validates
their stubs in `scripts/m3/validate-syms.sh` and in MAME; `symbols/README.md`
records the evidence. Do not add other Xlib dependencies until they are
validated.
## M2 non-goals
@@ -190,11 +295,10 @@ M2 deliberately rejects or omits:
- **Varargs:** `...` functions and calls (M5).
- **Function pointers and indirect calls** (M4).
- **Tail calls:** ordinary `tail` markers are demoted to ordinary calls;
`musttail` is not implemented (M2 C does not emit it). Real tail calls are
M3.
- **Narrow ABI types:** `i8`/`i16` parameters, `i64`, and narrow returns; M3
adds the widen/truncate rules. Narrow loads/stores inside a function remain
supported.
`musttail` is not implemented (M2 C does not emit it). Real tail calls remain
a later milestone.
- **Narrow ABI types:** `i8`/`i16` parameters, `i64`, and narrow returns remain
rejected; narrow loads/stores inside a function are supported.
- **Object output:** no `MCCodeEmitter`, object writer, `llc -filetype=obj`,
`clang -c` or native COFF. GSPA assembles and GSPLNK links the asm.
- **Xlib and clients:** no X11 headers, Xlib calls or real VT1200 clients in
@@ -203,18 +307,39 @@ M2 deliberately rejects or omits:
The optimizer's `returned` argument attribute is accepted because it does not
affect the stack-slot ABI; `sret`/`byval`/`inreg`/`nest` remain rejected.
## M3 non-goals
M3 deliberately stops at the first window and ordinary integer control flow:
- **XImage/XEvent and the real client:** no image transfer, event handling,
full X11R4 headers or real VT1200 client (M4). M3 uses exactly the four Xlib
calls above and no other Xlib entry.
- **Aggregates by value:** structs/unions passed or returned by value, `sret`,
`byval`, bitfields (M4/M5). Struct *fields* through pointers are supported.
- **Function pointers and indirect calls:** the callee must be a direct global
symbol (M4).
- **Floating point and `i64`:** no float/double or 64-bit integer arguments,
returns, arithmetic or branches (M5).
- **Object output:** still no `MCCodeEmitter`, object writer,
`llc -filetype=obj`, `clang -c` or native COFF; GSPA/GSPLNK produce the COFF.
The `JR`/branch-relaxation code-size pass is M5 as well.
- **Varargs, `musttail`, narrow ABI parameters/returns:** unchanged from M2.
## Repository layout
```text
build/llvm-clean/ compiler fork checkout used for M2 (ignored)
build/llvm-clean/ compiler fork checkout used for M2/M3 (ignored)
build/llvm-clean-build/ its Ninja build tree (ignored)
build/llvm-src/ M0 working fork (ignored)
patches/llvm/ frozen M0 patch series against llvmorg-23.1.5
tools/tms-bitaddr/ GEP scale/normalize tool
scripts/tms-clang IR pipeline wrapper (clang + tms-bitaddr + opt)
scripts/m2/ M2 harness: C inputs, entry.asm, link.cmd, stubs, checks
scripts/m3/ M3 harness: branch/xhello/layout inputs, Xlib header, entry/link
scripts/tests/m2/ M2 gates (run.sh, dis-roundtrip.sh)
scripts/tests/m2/diff/ TI golden asm and the differential ABI check
scripts/tests/m3/ M3 gates (run.sh, dis-roundtrip.sh, layout.sh)
scripts/tests/m3/diff/ TI branch/Xlib goldens and the differential check
scripts/m1/ M1 harness (historical; also on branch `m1`, not an M2 gate)
scripts/tests/m1/ M1 gates (historical; also on branch `m1`, not an M2 gate)
scripts/tests/m0/ M0 integration tests
@@ -229,7 +354,7 @@ docs/ design and plans
## Prerequisites
`cmake`, `ninja-build`, `ccache`, a C++17 host compiler (GCC 14 is used here),
Python 3, and about 20 GB of disk for the LLVM source and build. The M2 MAME
Python 3, and about 20 GB of disk for the LLVM source and build. The M2/M3 MAME
and dis34 gates additionally need `dosbox`, `mtools` (`mcopy`), the VT1200 SDK
image, the `dwt` COFF loader, `dis34`, and a built MAME `vt1200` binary
(`build/mame-src/vt1200`). The differential golden check needs no
@@ -251,13 +376,13 @@ Xlib call sequence, `as34`, `dis34`, SDKs, manuals). It is not vendored:
## Build the compiler
### Compiler fork (M2, the normal path)
### Compiler fork (M3, the normal path)
The fork checkout already exists at `build/llvm-clean` on `m2`. Build it in
The fork checkout already exists at `build/llvm-clean` on `m3`. Build it in
place:
```sh
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m2 ./scripts/build-llvm.sh
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m3 ./scripts/build-llvm.sh
# binaries: build/llvm-clean-build/bin/{llc,clang,opt,llvm-config,FileCheck}
# lit driver: build/llvm-clean-build/bin/llvm-lit
```
@@ -266,7 +391,7 @@ To clone the fork first instead of using an existing checkout:
```sh
LLVM_REPO=gitea@git.wut.ee:opencode.siil.arti.ee/llvm-tms34010.git \
LLVM_BRANCH=m2 ./scripts/build-llvm.sh
LLVM_BRANCH=m3 ./scripts/build-llvm.sh
```
Fork mode **never** checks out, resets, cleans or patches the source tree; it
@@ -283,22 +408,22 @@ TMS34010; a first build takes roughly 20–40 minutes.
This mode clones `llvmorg-23.1.5`, applies `patches/llvm/*.patch`, and builds
`build/llvm-clean-build`. It owns and resets only a plain `llvm-project` clone:
if `build/llvm-clean` has some other origin (for example the M2 fork), it
if `build/llvm-clean` has some other origin (for example the M2/M3 fork), it
refuses rather than destroying it. Use fork mode for that checkout.
`scripts/export-patches.sh` regenerates the committed M0 patch series with
`git diff` (including new files via `git add -N`); it is not used for M1/M2.
`git diff` (including new files via `git add -N`); it is not used for M1/M2/M3.
## Build the M0 support tools
The M2 pipeline uses `tms-bitaddr` and `tms-clang`:
The M2/M3 pipeline uses `tms-bitaddr` and `tms-clang`:
```sh
LLVM_CONFIG=$PWD/build/llvm-clean-build/bin/llvm-config \
tools/tms-bitaddr/build.sh
```
M0 regression test (optional in M2):
M0 regression test (optional in M2/M3):
```sh
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
@@ -334,7 +459,7 @@ object file or a usable COFF for this target.
## Later milestones
M3 adds branches/control flow, the Xlib call sequence and a wider validated ROM
symbol table. M4 adds the real client plus structs and function pointers. M5
adds the native COFF path, floats, varargs, bitfields and `long long`; 2ine
r5.01 is a later speed-up for the GSPA/GSPLNK step.
M4 adds the real Xlib client (`XImage`/`XEvent`, the full X11R4 headers),
structs by value and function pointers. M5 adds the native COFF path, floats,
varargs, bitfields, `long long` and the `JR`/branch-relaxation code-size pass;
2ine r5.01 is a later speed-up for the GSPA/GSPLNK step.
+191
View File
@@ -0,0 +1,191 @@
#!/bin/sh
# M3 differential TI-compiler control-flow/Xlib check.
#
# scripts/tests/m3/diff/check.sh
#
# The stored ti-branch.s/ti-xhello.s are one-time outputs of the 1987 TI
# compiler (GSP C Compiler 2.01) under DOSBox. CI never runs DOSBox: this
# script greps the stored golden files and compares their ABI shape with the
# assembly our compiler emits for the equivalent C.
#
# The oracle is the ABI shape, not instruction selection. Each requirement
# accepts the TI spellings and ours:
#
# sumto a conditional branch and an unconditional loop back-edge,
# result in A8, RETS 2
# max2 a conditional branch, result in A8, RETS 2
# xhello nine argument pushes for XCreateSimpleWindow (background first,
# display last), and the ROM bit displacements 0x480 (Display.screens)
# / 0x40 (Screen.root)
#
# TI emits JRLE/JRUC where we emit JAxx/JAUC; both are accepted. TI prints the
# displacements as decimal 1152/64, so 480h/40h are accepted too.
#
# Our branch.c is built exactly like the M3 gate: tms-clang -O2 with the
# -replexitval=never optimizer (that rewrite would turn sumto's loop into an
# i33 multiply, which the target cannot select), then llc -O0.
#
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC TI_BRANCH TI_XHELLO
set -eu
ROOT=$(cd "$(dirname "$0")/../../../.." && pwd)
DIR=$ROOT/scripts/tests/m3/diff
SRC=$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}
TI_BRANCH=${TI_BRANCH:-$DIR/ti-branch.s}
TI_XHELLO=${TI_XHELLO:-$DIR/ti-xhello.s}
export TMS_CLANG TMS_OPT TMS_BITADDR
fail() {
echo "M3 diff: FAIL: $*" >&2
exit 1
}
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do
[ -x "$tool" ] || fail "missing tool: $tool"
done
[ -f "$TI_BRANCH" ] || fail "stored TI golden missing: $TI_BRANCH"
[ -f "$TI_XHELLO" ] || fail "stored TI golden missing: $TI_XHELLO"
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m3-diff.XXXXXX")
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
# branch.c is generated through a wrapper so only this file keeps its loop.
OPT_NOREPLEXIT=$TMP/opt-noreplexit
printf '#!/bin/sh\nexec "%s" -replexitval=never "$@"\n' "$TMS_OPT" > "$OPT_NOREPLEXIT"
chmod +x "$OPT_NOREPLEXIT"
echo "== compile our branch.s and xhello.s"
TMS_OPT=$OPT_NOREPLEXIT "$ROOT/scripts/tms-clang" -O2 -I "$SRC" \
-o "$TMP/branch.ll" "$SRC/branch.c"
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/branch.ll" -o "$TMP/branch.s"
"$ROOT/scripts/tms-clang" -O2 -I "$SRC" \
-o "$TMP/xhello.ll" "$SRC/xhello.c"
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/xhello.ll" -o "$TMP/xhello.s"
echo " branch.s: $(grep -c . "$TMP/branch.s") lines"
echo " xhello.s: $(grep -c . "$TMP/xhello.s") lines"
# Our llc output has `;` end-of-line comments (`; %bb.0`). Strip them so a
# comment cannot satisfy a check; TI asm has none.
for f in "$TMP/branch.s" "$TMP/xhello.s"; do
sed 's/;.*//' "$f" > "$f.code"
done
# body FILE FUNC: from `_func:` to the next .globl/.end.
body() {
awk -v want="_$2:" '
$1 == want { infn = 1; next }
infn && ($1 == ".globl" || $1 == ".end") { exit }
infn { print }
' "$1"
}
require() {
_file=$1
_pat=$2
_what=$3
if grep -Eiq "$_pat" "$_file"; then
echo " ok $_what"
return 0
fi
echo "M3 diff: FAIL: missing $_what" >&2
echo " pattern: $_pat" >&2
sed 's/^/ /' "$_file" >&2
exit 1
}
require_rets2() {
require "$1" '^[[:space:]]*RETS[[:space:]]+2([[:space:]]|$)' "$2"
}
# A conditional branch: JA/JR plus a condition suffix. JAUC/JRUC/JRU do not
# match because their suffix is U/UC.
COND='^[[:space:]]*J[A-Z]*(EQ|NE|LT|LE|GT|GE|V|NV|P|N|Z|NZ|PL|MI)[[:space:]]'
# An unconditional branch: our JAUC, TI's JRUC (and bare JRU).
UNCOND='^[[:space:]]*J(AUC|RUC|RU|UMP)[[:space:]]'
# has_backedge FILE: true when a branch targets a label defined earlier in the
# same function body. TI closes the loop with the conditional JRLE L4; we
# close it with the unconditional JAUC LBB0_1, so either form counts.
has_backedge() {
awk '
/^[A-Za-z_][A-Za-z0-9_]*:/ {
lbl = $1
sub(/:$/, "", lbl)
pos[lbl] = NR
next
}
/^[[:space:]]*J/ {
if ($2 in pos && pos[$2] < NR)
found = 1
}
END { exit found ? 0 : 1 }
' "$1"
}
check_branch() { # check_branch FILE LABEL
_f=$1
body "$_f" sumto > "$TMP/sumto.body"
body "$_f" max2 > "$TMP/max2.body"
require "$TMP/sumto.body" "$COND" "$2: sumto has a conditional branch"
require "$TMP/sumto.body" "$UNCOND" "$2: sumto has an unconditional branch"
if ! has_backedge "$TMP/sumto.body"; then
echo "M3 diff: FAIL: $2: sumto has no loop back-edge" >&2
sed 's/^/ /' "$TMP/sumto.body" >&2
exit 1
fi
echo " ok $2: sumto branches back to the loop"
require_rets2 "$TMP/sumto.body" "$2: sumto ends with RETS 2"
require "$TMP/max2.body" "$COND" "$2: max2 has a conditional branch"
require_rets2 "$TMP/max2.body" "$2: max2 ends with RETS 2"
}
echo "== ABI shape: TI golden"
check_branch "$TI_BRANCH" "TI"
echo "== ABI shape: our compiler"
check_branch "$TMP/branch.s.code" "ours"
# Count the argument pushes since the previous CALLA, then require nine for
# XCreateSimpleWindow. Both compilers push the last argument first: display,
# root, x, y, width, height, border_width, border, background.
count_xcreate_pushes() {
awk '
/^_xhello:/ { infn = 1; n = 0; next }
!infn { next }
/CALLA[[:space:]]+_XCreateSimpleWindow/ { print n; exit }
/CALLA/ { n = 0; next }
/MOVE[[:space:]]+[^,]+,[[:space:]]*\*(STK|A14)\+/ { n++ }
' "$1"
}
for pair in "$TI_XHELLO:TI" "$TMP/xhello.s.code:ours"; do
f=${pair%%:*}
who=${pair##*:}
pushes=$(count_xcreate_pushes "$f")
[ -n "$pushes" ] || fail "$who xhello has no CALLA _XCreateSimpleWindow"
[ "$pushes" = "9" ] \
|| fail "$who XCreateSimpleWindow has $pushes argument pushes, want 9"
echo " ok $who: XCreateSimpleWindow takes nine argument pushes"
done
HEX480='(0x480|480h|[^0-9a-f]1152[^0-9a-f])'
HEX40='(0x40|40h|[^0-9a-f]64[^0-9a-f])'
check_offsets() { # check_offsets FILE WHO
_f=$1
_who=$2
body "$_f" xhello > "$TMP/xhello.body"
require "$TMP/xhello.body" "$HEX480" "$_who: Display.screens at bit 0x480"
require "$TMP/xhello.body" "$HEX40" "$_who: Screen.root at bit 0x40"
}
echo "== ROM bit displacements"
check_offsets "$TI_XHELLO" "TI"
check_offsets "$TMP/xhello.s.code" "ours"
echo "M3 diff: PASS"
+4
View File
@@ -0,0 +1,4 @@
/* K&R source for the M3 differential golden: the same two functions as
scripts/m3/branch.c, compiled by the 1987 TI compiler. */
int sumto(n) int n; { int s; int i; s = 0; for (i = 1; i <= n; i++) s = s + i; return s; }
int max2(a, b) int a; int b; { if (a > b) return a; return b; }
+56
View File
@@ -0,0 +1,56 @@
*******************************************************
* GSP C COMPILER, Version 2.01, 87.300 *
*******************************************************
FP .set A13
STK .set A14
.globl _sumto
.file "BRANCH.c"
******************************************************
* FUNCTION DEF : _sumto
******************************************************
_sumto:
MMTM SP,A5,A7,A12,FP
MOVE STK,FP
ADDI 64,STK
CLR A12
MOVE A12,*FP,1
ADDK 1,A12
MOVE A12,*FP(32),1
JRUC L1
L4:
MOVE *FP(32),A7,1
MOVE *FP,A5,1
ADD A7,A5
MOVE A5,*FP,1
ADDK 1,A7
MOVE A7,*FP(32),1
L1:
MOVE *FP(32),A7,1
MOVE *FP(-32),A5,1
CMP A5,A7
JRLE L4
MOVE *FP,A8,1
EPI0_1:
MOVE *SP(160),STK,1
MMFM SP,A5,A7,A12,FP
RETS 2
.globl _max2
******************************************************
* FUNCTION DEF : _max2
******************************************************
_max2:
MMTM SP,A5,A7,FP
MOVE STK,FP
MOVE *FP(-32),A7,1
MOVE *FP(-64),A5,1
CMP A5,A7
JRLE L5
MOVE A7,A8
JRUC EPI0_2
L5:
MOVE A5,A8
EPI0_2:
MOVE *SP(128),STK,1
MMFM SP,A5,A7,FP
RETS 2
.end
+37
View File
@@ -0,0 +1,37 @@
/* K&R source for the M3 differential golden: the same four-call Xlib client
as scripts/m3/xhello.c, compiled by the 1987 TI compiler.
The ROM's Display/Screen fields are at bit displacements 0x480/0x40, i.e.
byte offsets 0x90/0x8; the 1987 compiler's &-of-member arithmetic is in
bits, so it emits 1152 and 64 for these pads. */
struct display {
char pad[0x90];
void *screens;
};
struct screen {
char pad[0x8];
unsigned int root;
};
extern struct display *XOpenDisplay();
extern int XCreateSimpleWindow();
extern int XMapWindow();
extern int XSync();
int xhello()
{
struct display *d;
unsigned int root;
int w;
int s;
d = XOpenDisplay("0::0");
if (!d)
return -1;
root = ((struct screen *)d->screens)->root;
w = XCreateSimpleWindow(d, root, 100, 100, 400, 200, 2, 0, 1);
XMapWindow(d, w);
s = XSync(d, 0);
return w;
}
+69
View File
@@ -0,0 +1,69 @@
*******************************************************
* GSP C COMPILER, Version 2.01, 87.300 *
*******************************************************
FP .set A13
STK .set A14
.globl _XOpenDisplay
.globl _XCreateSimpleWindow
.globl _XMapWindow
.globl _XSync
.globl _xhello
.file "XHELLO.c"
******************************************************
* FUNCTION DEF : _xhello
******************************************************
_xhello:
MMTM SP,A7,FP
MOVE STK,FP
ADDI 128,STK
MOVE STK,-*SP,1
MOVI SL1,A8
MOVE A8,*STK+,1
CALLA _XOpenDisplay
MOVE A8,*FP,1
MOVE *FP,A7,1
JRNZ L1
MOVI -1,A8
JRUC EPI0_1
L1:
MOVE *A7(1152),A7,1
MOVE *A7(64),*FP(32),1
MOVE STK,-*SP,1
MOVK 1,A7
MOVE A7,*STK+,1
SUB A7,A7
MOVE A7,*STK+,1
ADDK 2,A7
MOVE A7,*STK+,1
MOVI 200,A7
MOVE A7,*STK+,1
MOVI 400,A7
MOVE A7,*STK+,1
MOVI 100,A7
MOVE A7,*STK+,1
MOVE A7,*STK+,1
MOVE *FP(32),*STK+,1
MOVE *FP(0),*STK+,1
CALLA _XCreateSimpleWindow
MOVE A8,*FP(64),1
MOVE STK,-*SP,1
MOVE *FP(64),*STK+,1
MOVE *FP(0),*STK+,1
CALLA _XMapWindow
MOVE STK,-*SP,1
CLR A7
MOVE A7,*STK+,1
MOVE *FP(0),*STK+,1
CALLA _XSync
MOVE A8,*FP(96),1
MOVE *FP(64),A8,1
EPI0_1:
ADDI -128,STK
MMFM SP,A7,FP
RETS 0
******************************************************
* DEFINE STRING CONSTANTS *
******************************************************
.text
SL1: .string "0::0",0
.end
+11 -9
View File
@@ -7,13 +7,13 @@ stable across firmware revisions; v2.2 is the first target.
payloads in `vendor/vt1200/dwt/gui/xhello.asm` and the README. **M2 consumes
only `strlen`**: `scripts/m2/gen-rom-imports.sh` emits an ABI-preserving
`jauc` stub for every entry, and the M2 harness calls `_strlen` at
`0x3FB458D0`. The other entries are recorded for later milestones and are not
yet exercised by compiled C.
M3 owns the wider table: it extends the entries from the Ghidra project /
`ptr_constants.py`, adds the Xlib call sequence, and validates each entry on
MAME, then on the real unit. Do not assume an address here is validated just
because it is listed.
`0x3FB458D0`. **M3 consumes exactly the four Xlib entries below** through the
same stub generator; the compiled `scripts/m3/xhello.c` is the end-to-end proof.
The table has not been widened beyond those five entries. M4 extends it for the
real client (`XImage`/`XEvent` and the rest of the X11R4 calls) from the Ghidra
project / `ptr_constants.py`, and validates each new entry on MAME, then on the
real unit. Do not assume an address here is validated just because it is
listed.
## v2.2 Xlib entries validated
@@ -30,8 +30,10 @@ against the live DWT X server):
`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`.
plausible function prologue (`MOVE`/`MMTM`/`SETF`/`CMP`/`ADDI`).
`scripts/tests/m3/run.sh` runs it before every MAME run, so the compiled client
cannot pass with a stale stub. It is not a re-extraction; M4 extends the table
for `XImage`/`XEvent` and the rest of the real client.
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.