diff --git a/README.md b/README.md index 695e7c1..2f04f61 100644 --- a/README.md +++ b/README.md @@ -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:** - (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:** - (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. diff --git a/scripts/tests/m3/diff/check.sh b/scripts/tests/m3/diff/check.sh new file mode 100755 index 0000000..b02021d --- /dev/null +++ b/scripts/tests/m3/diff/check.sh @@ -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" diff --git a/scripts/tests/m3/diff/ti-branch.c b/scripts/tests/m3/diff/ti-branch.c new file mode 100644 index 0000000..ca601a5 --- /dev/null +++ b/scripts/tests/m3/diff/ti-branch.c @@ -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; } diff --git a/scripts/tests/m3/diff/ti-branch.s b/scripts/tests/m3/diff/ti-branch.s new file mode 100644 index 0000000..735a85a --- /dev/null +++ b/scripts/tests/m3/diff/ti-branch.s @@ -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 diff --git a/scripts/tests/m3/diff/ti-xhello.c b/scripts/tests/m3/diff/ti-xhello.c new file mode 100644 index 0000000..277c289 --- /dev/null +++ b/scripts/tests/m3/diff/ti-xhello.c @@ -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; +} diff --git a/scripts/tests/m3/diff/ti-xhello.s b/scripts/tests/m3/diff/ti-xhello.s new file mode 100644 index 0000000..a88feba --- /dev/null +++ b/scripts/tests/m3/diff/ti-xhello.s @@ -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 diff --git a/symbols/README.md b/symbols/README.md index 6b3abe1..6c1de6b 100644 --- a/symbols/README.md +++ b/symbols/README.md @@ -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.