From 2d5ed90fb26f9910cf750873645e848801a68803 Mon Sep 17 00:00:00 2001 From: arti Date: Thu, 12 Nov 2026 11:49:32 +0000 Subject: [PATCH] tests: M4 Xlib images, events and the termtris integration --- README.md | 303 ++++++++++++++---- scripts/tests/m4/diff/check.sh | 507 +++++++++++++++++++++++++++++++ scripts/tests/m4/diff/ti-fnptr.c | 33 ++ scripts/tests/m4/diff/ti-fnptr.s | 95 ++++++ scripts/tests/m4/diff/ti-image.c | 86 ++++++ scripts/tests/m4/diff/ti-image.s | 127 ++++++++ symbols/README.md | 49 ++- symbols/vt1200-v22.syms | 4 +- 8 files changed, 1128 insertions(+), 76 deletions(-) create mode 100755 scripts/tests/m4/diff/check.sh create mode 100644 scripts/tests/m4/diff/ti-fnptr.c create mode 100644 scripts/tests/m4/diff/ti-fnptr.s create mode 100644 scripts/tests/m4/diff/ti-image.c create mode 100644 scripts/tests/m4/diff/ti-image.s diff --git a/README.md b/README.md index 28fb9be..373d9f4 100644 --- a/README.md +++ b/README.md @@ -3,43 +3,112 @@ A modern C toolchain for the DEC VT1200 / TI TMS34010, built as a small fork of upstream LLVM. -**Status: M3.** `llc -march=tms34010` emits GSPA-compatible assembly using the +**Status: M4.** `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). M3 adds conditional/unconditional branches, -ordinary `while`/`for` 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. +(`RETS 0` without arguments). M4 adds function pointers and `CALL Rs`, 32-bit +multiply/divide, zero-extending narrow loads, module-unique private labels, +`i8`/`i16` fixed arguments and the contiguous-stack varargs form, the X11R4 +`XImage`/`XEvent` layouts, the full image/event client, the corrected 30-entry +ROM symbol table, and the pinned termtris full-game integration. It is still +**assembly-only**: no object writer, no `clang -c`, and no native COFF; +GSPA/GSPLNK under DOSBox produce the COFF. Floats, `long long`, general +varargs, bitfields, structs by value, narrow returns and native COFF are M5. + +Branch `m4` is the current milestone snapshot in both repositories; the +default branch is `m3` until the controller/user updates it after the final +review. Branch `m3` remains the previous snapshot, `m2` is the M2 tip and +`m1` the reproducible M1 snapshot. - 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) +- M4 plan: [`docs/superpowers/plans/2026-11-12-tms34010-m4-xlib-image-events-and-termtris.md`](docs/superpowers/plans/2026-11-12-tms34010-m4-xlib-image-events-and-termtris.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 `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. + (branch `m4`) — the full buildable LLVM 23.1.5 tree with the TMS34010 + target, clang bit-address changes and the M2/M3/M4 backend. Branch `m3` + (the repository default) is the previous snapshot; branch `m2` is the M2 + tip and branch `tms34010` is the M1 tip and keeps the original M1 backend + and suite. - **This project:** - (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. + (branch `m4`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`, + `tms-clang`, symbols, the M2/M3/M4 harnesses and the spike. Branch `m3` + (the repository default) is the previous snapshot; branch `m2` 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/M3 -do not change it. +here is a **frozen M0 mirror** used by the default upstream+patches build; +M2/M3/M4 do not change it. -## What M3 provides +## What M4 provides + +M4 closes the remaining Xlib path to a real image/event client and a full-game +integration, still assembly-only: + +- **Function pointers and `CALL Rs`.** A function pointer is a 32-bit bit + address; an indirect call lowers to the one-word register call `CALL Rs` + (GSPA `0x0920`, `dis34` `call aN`), with the same old-STK save, argument + pushes, `SETF` fixup and `RETS 2` as a direct call. Direct calls remain + `CALLA _sym`. `m4-fnptr.ll` + `scripts/tests/m4/fnptr.sh`. +- **32-bit multiply/divide.** `MPYS`/`MPYU`/`DIVS`/`DIVU` with an odd + destination (the even form is the `Rd:Rd+1` pair and would clobber the next + register); remainder is quotient + `MPYS` + `SUB`. No integer + `_mul`/`_div`/`_mod` exists in `RTS.LIB`. `m4-mul.ll` + + `scripts/tests/m4/mul.sh`. +- **Zero-extending and extending narrow loads.** `ZEXTLOAD`/`EXTLOAD` i8/i16 + lower to the sign-extending load plus `ANDI 255`/`ANDI 65535`; an i1 load is + a byte load masked to one bit. `m4-zext.ll`, `m4-anyext.ll`, + `m4-i1load.ll`. +- **Module-unique private labels.** Every local symbol gets an FNV-1a token + from the module identifier, so two TUs with literals no longer collide in + GSPA's flat table; public names are unchanged. `m4-private.ll` and the + two-TU `scripts/tests/m4/private.sh`. +- **`i8`/`i16` fixed arguments.** Promoted to a 4-byte stack slot; the caller + sign/zero-extends per the parameter attribute and the callee truncates. + `i8`/`i16`/`i64`/float/aggregate returns remain rejected. `m4-arg8.ll`. +- **Contiguous-stack varargs.** Only the `&last_fixed` walk used by termtris' + `sprintf`; no `va_list`/`va_start`/`va_arg`. Zero-fixed and i8/i16-anchor + definitions are rejected. Static variadic definitions can lose `...` under + `-O2` dead-argument elimination; external termtris `sprintf` is unaffected. + `m4-varargs.ll`, `m4-reject-vararg0.ll`, + `m4-reject-vararg-narrow{,16}.ll`, `scripts/tests/m4/varargs.sh`. +- **Driver policies.** `scripts/tms-clang` passes `-replexitval=never` (the + i33 exit-value multiply is unselectable on this i64-less target) and + `-switch-to-lookup=false` (a late byte-offset GEP after `--scale` would be + read as a bit offset, an 8x address bug). Both are driver policies because + `opt` has no target machine. +- **X11R4 `XImage`/`XEvent`/`XErrorEvent` layouts and the image client.** The + real 32-bit packed offsets: `XImage` data 16, byte_order 20, bytes_per_line + 40, bits_per_pixel 44, f 64, `sizeof` 88; `XEvent` 96; `XErrorEvent` 20 + (with the explicit packed-ABI pad); `Display.screens` byte 0x90 / bit + 0x480, `Screen.root` byte 0x8 / bit 0x40. The client draws a 1-bit LSBFirst + checker through an aligned pixmap + `XCopyArea` (12-pixel phase) and 15 + two-row bands (the ROM request buffer is 16 KiB), with `.bss` at + `0x01A00000` and `prog` at `0x018A0000`. It asserts the checker pixels, the + real LK401 `q` (keycode 193 / keysym `0x71`), window focus and zero X + errors in MAME. +- **Corrected 30-entry v2.2 symbol table.** `XMapWindow` is `0x3FD64F60` + (MapWindow opcode 8); `0x3FD645B0` is `XRaiseWindow` (ConfigureWindow + opcode 12, `CWStackMode=Above`). The XG block and the image/error-handler + entries are validated by `scripts/m4/validate-syms.sh` and + `scripts/tests/m4/symbols.sh`. +- **Pinned termtris.** The full game builds with this compiler and the 1987 + GSPA/GSPLNK + `rts.lib`, at the pinned `vt1200-termtris` commit, and runs + board/move/panels/quit in MAME with real key injection, the `0x40000`-bit + `prog` region and the required thresholds. The tree is test-only (GPLv3). +- **Differential goldens.** The 1987 TI compiler's function-pointer and + image/struct-offset assembly is stored under `scripts/tests/m4/diff/`; the + check compares ABI shape without running DOSBox. + +## What M3 provides (branch `m3`) M3 adds integer control flow and the first compiled C Xlib client, still assembly-only: @@ -62,8 +131,9 @@ assembly-only: - 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. -- Nine `m3-*.ll` lit tests, for a total of 46 with the M2 suite; the M3 - end-to-end, dis34, layout and differential gates (below). +- Nine `m3-*.ll` lit tests on the `m3` branch (46 with the M2 suite); M4 + removes the obsolete `m3-reject-zext.ll` and adds 18 `m4-*` tests for 58 + total with the M2 suite. M3 remains an `i32`/pointer milestone. `setcc` is supported, but narrow (`i8`/`i16`) parameters and returns are still rejected. Branches on `i64`, @@ -80,9 +150,9 @@ still emits assembly only. - **`-O2` loop exit-value rewriting is not lowered.** IndVarSimplify can turn `sumto`'s loop into a closed-form `i33` multiply, which M3 cannot select. The M3 harness therefore builds `branch.c` with `-replexitval=never`. Ordinary - `while`/`for` loops that keep a real back-edge and phi work. M4/M5 must add - multiply/`i64` lowering, a target-level pass policy, or a target diagnostic - instead of a raw abort. + `while`/`for` loops that keep a real back-edge and phi work. M4 adds + multiply/divide and moves the `-replexitval=never` policy into + `scripts/tms-clang`; see "What M4 provides". ## What M2 provides (branch `m2`) @@ -117,7 +187,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/M3) | +| | M1 test convention (branch `m1`) | TI ABI (M2/M3/M4) | |---|---|---| | 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)`, … | @@ -136,7 +206,91 @@ 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 M3 gates +## Run the M4 gates + +These are the documented commands on branch `m4`; all must pass. The MAME +gates are slow and are run once. + +```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: 58 passed (32 m2-* + 8 m3-* + 18 m4-*) +build/llvm-clean-build/bin/llvm-lit -v \ + build/llvm-clean/llvm/test/CodeGen/TMS34010 --filter='(m2|m3|m4)-' + +# 3. M4 image/event client in MAME (also runs layout.sh + validate-syms.sh) +scripts/tests/m4/run.sh +# -> m4 xshow: PASS (1-bit image + real q key + window focus + 0 X errors) + +# 4. Linked .text decoded with dis34 (ROM stubs, CALL A?, SETF, RETS, checker) +scripts/tests/m4/dis-roundtrip.sh +# -> M4 dis34 round-trip: PASS + +# 5. Layouts, the 30-entry symbol table, and the correction negative control +scripts/tests/m4/layout.sh +scripts/tests/m4/symbols.sh + +# 6. The pinned termtris full-game integration +scripts/tests/m4/termtris.sh +# -> m4 termtris: PASS (board/move/panels/quit; .../termtris_*_3200.png) + +# 7. Differential TI-compiler function-pointer/image ABI check (no DOSBox) +scripts/tests/m4/diff/check.sh +# -> M4 diff: PASS (goldens + negative controls) + +# 8. The smaller M4 runtime gates +scripts/tests/m4/unsigned.sh +scripts/tests/m4/private.sh +scripts/tests/m4/mul.sh +scripts/tests/m4/fnptr.sh +scripts/tests/m4/varargs.sh + +# 9. M2/M3 regressions and the GEP tool tests +scripts/tests/m3/run.sh +scripts/tests/m3/dis-roundtrip.sh +scripts/tests/m3/layout.sh +scripts/tests/m2/run.sh +scripts/tests/m2/dis-roundtrip.sh +scripts/tests/m2/diff/check.sh +tools/tms-bitaddr/tests/run.sh +# -> tms-bitaddr: PASS +``` + +`scripts/tests/m4/run.sh` first runs `layout.sh` and +`scripts/m4/validate-syms.sh`, then compiles `scripts/m4/xshow.c` and +`scripts/m4/xgfix.c` with `scripts/tms-clang -O2`, runs +`llc -march=tms34010 -O0 -filetype=asm`, generates the ROM import stubs, +concatenates `scripts/m4/xshow-entry.asm`, assembles/links with GSPA/GSPLNK +2.00 under DOSBox, loads the COFF in MAME, injects the real LK401 `Q`, and +requires the checker pixels, `quit=1`, keycode 193 / keysym `0x71`, +`xerrors=0`, focus `0x400001` and the last event KeyPress. The 12-pixel +prefix is checked as the preceding checker cell in bands 0 and 1. + +`scripts/tests/m4/termtris.sh` runs the pinned tree through +`scripts/m4/termtris/build.sh` and `play_keys.sh`; it enforces `loops >= 100` +for all four modes, `events >= 6` for move/panels/quit (`>= 2` for board), +`quit = 1` only for quit, focus = the created termtris window, +`status=0 (OK)` and a non-empty screenshot each time. The vendored tree is +test-only (GPLv3). + +`scripts/tests/m4/diff/check.sh` is the new CI golden check. It compiles our +function-pointer and image equivalents with `scripts/tms-clang` + `llc` and +compares ABI shape with `scripts/tests/m4/diff/ti-fnptr.s` and +`ti-image.s`: an indirect `CALL A?`, the old-STK save, argument/return/`RETS +2`, ten `XCreateImage` and ten `XPutImage` pushes (the plan's 10/9 is a typo; +`XPutImage` takes display, drawable, gc, image, src_x, src_y, dest_x, dest_y, +width, height), nine `XCreateSimpleWindow` pushes, the leading +constant/Display-last order, and the Display/Screen/XImage bit displacements. +It then mutates copies of the goldens (removing the indirect call and +reversing the `XPutImage` argument pushes) and requires the check to fail; +the stored goldens are not modified. The goldens were generated once with +GSP C Compiler 2.01 under DOSBox; CI never runs DOSBox. + +Paths are overridable with `TMS_CLANG`, `TMS_OPT`, `TMS_BITADDR`, `TMS_LLC`, +`MAME`, `DOSBOX`, `SDK_IMG`, the per-gate work/COFF variables and `M4_SYMS`. + +## Run the M3 gates (branch `m3`) These are the documented commands on branch `m3`; all must pass. @@ -217,7 +371,7 @@ DOSBox, it greps the stored files. As in the MAME gate, `branch.c` must be built with `-replexitval=never`; the default exit-value rewrite turns `sumto`'s loop into an unselectable `i33` multiply. -## Run the M2 gates (default branch) +## Run the M2 gates (branch `m2`) These are the M2 commands; they also pass on `m3` (verified on branch `m3` on 2026-11-11: `M2 MAME: PASS`, `M2 dis34 round-trip: PASS`, `M2 diff: PASS`). The @@ -357,32 +511,43 @@ M3 deliberately stops at the first window and ordinary integer control flow: The `JR`/branch-relaxation code-size pass is M5 as well. - **Varargs, `musttail`, narrow ABI parameters/returns:** unchanged from M2. -## M4 prerequisites (carry-forwards) +### M3 carry-forwards delivered in M4 -The final M3 review left these concrete prerequisites for M4: +All five final-M3 prerequisites are implemented: zero-extending loads replaced +the reject test, private labels are module-unique, unsigned/`<=` runtime +coverage exists, the fragile harness plumbing was replaced, and the +`m3-cmpi`/`layout.sh` checks were tightened. See the M4 feature list above. -- **Add zero-extending narrow loads.** M3 rejects `unsigned char`, - `unsigned short` and `_Bool` loads (pinned by `m3-reject-zext.ll`); M4 must - add the `ZEXTLOAD i8`/`i16` patterns and replace the reject test. -- **Namespace every private label.** `scripts/tests/m3/run.sh` prefixes - `LBB_` per translation unit, but deterministic private string labels - are not renamed. Two TUs with literals therefore collide in GSPA's flat - symbol table. M4 must namespace all private labels or make compiler private - names module-unique. -- **Add unsigned/`<=` runtime coverage.** `JALE`, `JALO`, `JAHI` and `JALS`, - `JAHS`, `JANE`, `JAGE` are covered by lit tests only and do not appear in the - compiled M3 image. M4 should add an unsigned/`<=` runtime marker. -- **Replace fragile test plumbing.** The `always_inline` root helper and the - register-hardcoded nine-argument dis34 check are test-strength aids that will - not survive real M4 code; make them operand- and register-independent. -- **Tighten test strength.** `m3-cmpi`'s `CHECK-DAG` for 0/1 does not pin their - order, and `layout.sh` pairs the root probe with fixed constants; neither - weakens the current pass, but both should be tightened. +## M4 non-goals and the M5 boundary + +M4 deliberately stops before: + +- **Native object output:** no `MCCodeEmitter`, object writer, + `llc -filetype=obj`, `clang -c` or native COFF; GSPA/GSPLNK still produce + the COFF. The `JR`/branch-relaxation code-size pass and MMTM/MMFM + register-list saves are M5 (the current per-register system-stack pushes + have the identical ABI layout). +- **Floating point and `long long`:** no float/double or `i64` arguments, + returns, arithmetic or branches. +- **General varargs:** no `va_list`, `va_start` or `va_arg`; only the + contiguous `&last_fixed` pattern used by termtris `sprintf`. +- **Bitfields and aggregates by value:** no structs/unions by value, `sret`, + `byval`, bitfields or aggregate returns. Struct fields through pointers are + supported. +- **Narrow returns:** `i8`/`i16` returns remain rejected; fixed i8/i16 + arguments are supported. +- **Xlib beyond the tested calls:** the client uses the validated image, + event, GC, window and error-handler entries; it does not implement the whole + Xlib surface, `XGetImage`, or `XDestroyImage` on a non-heap image. +- **Deeper libc/runtime coverage:** M5 owns the broader `RTS.LIB` integration. + +The pinned termtris tree is test-only (GPLv3) and is not part of the compiler +or project distribution. ## Repository layout ```text -build/llvm-clean/ compiler fork checkout used for M2/M3 (ignored) +build/llvm-clean/ compiler fork checkout used for M2/M3/M4 (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 @@ -390,10 +555,15 @@ 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/m4/ M4 harness: unsigned/private/mul/fnptr/varargs, XImage client, + XG fix, termtris build, symbols validator 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, differential check + negative controls +scripts/tests/m4/ M4 gates (run/dis-roundtrip/layout/symbols/termtris and the + unsigned/private/mul/fnptr/varargs gates) +scripts/tests/m4/diff/ TI function-pointer/image goldens + 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 @@ -408,11 +578,12 @@ 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/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 -DOSBox/MAME/SDK; it needs only the built `clang`, `opt`, `llc` and +Python 3, and about 20 GB of disk for the LLVM source and build. The M2/M3/M4 +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 termtris gate also needs the pinned +`vendor/vt1200-termtris` tree (test-only). The differential golden check needs +no DOSBox/MAME/SDK; it needs only the built `clang`, `opt`, `llc` and `tools/tms-bitaddr/tms-bitaddr`. On Debian: ```sh @@ -430,13 +601,13 @@ Xlib call sequence, `as34`, `dis34`, SDKs, manuals). It is not vendored: ## Build the compiler -### Compiler fork (M3, the normal path) +### Compiler fork (M4, the normal path) -The fork checkout already exists at `build/llvm-clean` on `m3`. Build it in +The fork checkout already exists at `build/llvm-clean` on `m4`. Build it in place: ```sh -LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m3 ./scripts/build-llvm.sh +LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m4 ./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 ``` @@ -445,7 +616,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=m3 ./scripts/build-llvm.sh +LLVM_BRANCH=m4 ./scripts/build-llvm.sh ``` Fork mode **never** checks out, resets, cleans or patches the source tree; it @@ -462,22 +633,23 @@ 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/M3 fork), it -refuses rather than destroying it. Use fork mode for that checkout. +if `build/llvm-clean` has some other origin (for example the M2/M3/M4 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/M3. +`git diff` (including new files via `git add -N`); it is not used for +M1/M2/M3/M4. ## Build the M0 support tools -The M2/M3 pipeline uses `tms-bitaddr` and `tms-clang`: +The M2/M3/M4 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/M3): +M0 regression test (optional in M2/M3/M4): ```sh TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \ @@ -513,7 +685,8 @@ object file or a usable COFF for this target. ## Later milestones -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. +M5 adds the native COFF path (`MCCodeEmitter`/object writer), floats, +`long long`, general varargs (`va_list`), bitfields, structs by value and the +`JR`/branch-relaxation code-size pass, plus MMTM/MMFM register-list saves and +deeper libc/runtime coverage. 2ine r5.01 is a later speed-up for the +GSPA/GSPLNK step. diff --git a/scripts/tests/m4/diff/check.sh b/scripts/tests/m4/diff/check.sh new file mode 100755 index 0000000..41e72fb --- /dev/null +++ b/scripts/tests/m4/diff/check.sh @@ -0,0 +1,507 @@ +#!/bin/sh +# M4 differential TI-compiler check: function pointers and the Xlib image ABI. +# +# scripts/tests/m4/diff/check.sh +# +# The stored ti-fnptr.s/ti-image.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: +# +# apply the old-STK save, one program-stack argument push, a call through +# an A register (TI CALL, ours CALL; CALLR is accepted too), and a +# last RETS of RETS 2. A direct CALLA in apply is a failure. +# test the three direct CALLA _apply calls (function-pointer arguments). +# image XCreateImage and XPutImage take ten argument pushes, XCreatePixmap +# five, and XCreateSimpleWindow nine. Every call saves old STK. +# The first pushes are the last arguments, so the leading constants +# are XCreateImage 80,32,480,640; XPutImage 32,640; XCreatePixmap +# 1,480,640; XCreateSimpleWindow 0,0,0,480,640. The final push is +# the Display pointer, not a constant. This is what makes the +# reversed-argument negative control fail. +# offsets Display.screens (bit 0x480, byte 0x90), Screen.root (bit 0x40, +# byte 0x8), and XImage data/byte_order/bytes_per_line/bits_per_pixel/ +# f (bits 0x80/0xA0/0x140/0x160/0x200). TI uses the packed direct +# displacements; ours materialises an ADDI and the matching load or +# store through the same register. +# +# The plan's "XCreateImage/XPutImage 10/9" is a typo for 10/10: XPutImage is +# (display, drawable, gc, image, src_x, src_y, dest_x, dest_y, width, height), +# ten arguments. Nine is XCreateSimpleWindow. +# +# Negative controls run at the end: the check is re-run on a copy of the +# fnptr golden with the indirect call removed, and on a copy of the image +# golden with the XPutImage argument pushes reversed. Both must fail. The +# stored goldens are never modified. +# +# Our fnptr source is scripts/m4/fnptr.c (the runtime gate fixture). The image +# equivalent is generated here because it needs only the layout declarations. +# +# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC TI_FNPTR TI_IMAGE +# OUR_FNPTR; M4_DIFF_POSITIVE_ONLY=1 skips the negative controls. +set -eu + +ROOT=$(cd "$(dirname "$0")/../../../.." && pwd) +DIR=$ROOT/scripts/tests/m4/diff + +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_FNPTR=${TI_FNPTR:-$DIR/ti-fnptr.s} +TI_IMAGE=${TI_IMAGE:-$DIR/ti-image.s} +OUR_FNPTR=${OUR_FNPTR:-$ROOT/scripts/m4/fnptr.c} +export TMS_CLANG TMS_OPT TMS_BITADDR + +fail() { + echo "M4 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_FNPTR" ] || fail "stored TI golden missing: $TI_FNPTR" +[ -f "$TI_IMAGE" ] || fail "stored TI golden missing: $TI_IMAGE" +[ -f "$OUR_FNPTR" ] || fail "our fnptr source missing: $OUR_FNPTR" + +TMP=$(mktemp -d "${TMPDIR:-/tmp}/m4-diff.XXXXXX") +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +echo "== compile our fnptr.s and image.s" +TMS_CLANG="$TMS_CLANG" TMS_OPT="$TMS_OPT" TMS_BITADDR="$TMS_BITADDR" \ + "$ROOT/scripts/tms-clang" -O2 -o "$TMP/fnptr.ll" "$OUR_FNPTR" +"$TMS_LLC" -march=tms34010 -O0 -filetype=asm \ + "$TMP/fnptr.ll" -o "$TMP/fnptr.s" + +cat > "$TMP/image.c" <<'EOF' +struct display { unsigned char pad[0x90]; void *screens; }; +struct screen { unsigned char pad[0x8]; unsigned int root; }; +struct ximage_funcs { + void *create_image; void *destroy_image; void *get_pixel; + void *put_pixel; void *sub_image; void *add_pixel; +}; +struct ximage { + int width, height; int xoffset; int format; char *data; int byte_order; + int bitmap_unit; int bitmap_bit_order; int bitmap_pad; int depth; + int bytes_per_line; int bits_per_pixel; + unsigned long red_mask, green_mask, blue_mask; + void *obdata; + struct ximage_funcs f; +}; +extern struct display *XOpenDisplay(const char *); +extern int XCreateSimpleWindow(struct display *, unsigned int, int, int, + unsigned int, unsigned int, unsigned int, + unsigned long, unsigned long); +extern unsigned int XCreatePixmap(struct display *, unsigned int, unsigned int, + unsigned int, unsigned int); +extern struct ximage *XCreateImage(struct display *, void *, unsigned int, int, + int, char *, unsigned int, unsigned int, + int, int); +extern int XPutImage(struct display *, unsigned int, void *, struct ximage *, + int, int, int, int, unsigned int, unsigned int); +extern int XMapWindow(struct display *, unsigned int); +extern int XSync(struct display *, int); + +__attribute__((noinline)) +unsigned int image(struct display *d, unsigned int w, void *gc, char *data) { + struct ximage *img; + unsigned int root; + unsigned int pm; + + root = ((struct screen *)d->screens)->root; + img = XCreateImage(d, 0, 1, 0, 0, data, 640, 480, 32, 80); + img->byte_order = 0; + img->bytes_per_line = 80; + img->bits_per_pixel = 1; + img->f.create_image = 0; + pm = XCreatePixmap(d, w, 640, 480, 1); + XPutImage(d, pm, gc, img, 0, 0, 0, 0, 640, 32); + img->data = data; + XMapWindow(d, w); + XSync(d, 0); + return root + pm; +} + +__attribute__((noinline)) +unsigned int window(struct display *d) { + unsigned int root = ((struct screen *)d->screens)->root; + return XCreateSimpleWindow(d, root, 20, 20, 640, 480, 0, 0, 0); +} +EOF + +TMS_CLANG="$TMS_CLANG" TMS_OPT="$TMS_OPT" TMS_BITADDR="$TMS_BITADDR" \ + "$ROOT/scripts/tms-clang" -O2 -o "$TMP/image.ll" "$TMP/image.c" +"$TMS_LLC" -march=tms34010 -O0 -filetype=asm \ + "$TMP/image.ll" -o "$TMP/image.s" +echo " fnptr.s: $(grep -c . "$TMP/fnptr.s") lines, image.s: $(grep -c . "$TMP/image.s") lines" + +# Our llc output has `;` end-of-line comments. Strip them so a comment cannot +# satisfy a check; TI asm has none. +for f in "$TMP/fnptr.s" "$TMP/image.s"; do + sed 's/;.*//' "$f" > "$f.code" +done + +# body FILE FUNC: from `_func:` to the next function label or section directive. +body() { + awk -v want="_$2:" ' + $1 == want { infn = 1; next } + infn { + if ($0 ~ /^[[:space:]]*\.(globl|sect|text|bss|data|end|file)/) exit + print + } + ' "$1" +} + +require() { + _file=$1 + _pat=$2 + _what=$3 + if grep -Eiq "$_pat" "$_file"; then + echo " ok $_what" + return 0 + fi + echo "M4 diff: FAIL: missing $_what" >&2 + echo " pattern: $_pat" >&2 + sed 's/^/ /' "$_file" >&2 + exit 1 +} + +require_last_rets2() { + if awk ' + /^[[:space:]]*RETS([[:space:]]|$)/ { last = $0; seen = 1 } + END { + if (!seen) + exit 1 + exit (last ~ /^[[:space:]]*RETS[[:space:]]+2[[:space:]]*$/) ? 0 : 1 + } + ' "$1"; then + echo " ok $2" + return 0 + fi + echo "M4 diff: FAIL: $2 (last RETS is not RETS 2)" >&2 + sed 's/^/ /' "$1" >&2 + exit 1 +} + +# call_info FILE FUNC CALLEE: one line "N PUSHES OLDSTK TOKENS..." for the +# first call to CALLEE in the function body. Each TOKENS element is a resolved +# decimal constant for a push whose source register last held a constant, or +# X for anything else (memory/argument load, symbol, unknown register). +call_info() { + body "$1" "$2" | awk -v want="$3" ' + function isreg(x) { return x ~ /^A([0-9]|1[0-5])$/ } + function trim(x) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", x); return x } + function unknown(r) { has[r] = 0 } + function operands(line, a, n, i) { + n = split(line, a, ",") + for (i = 1; i <= n; i++) + a[i] = trim(a[i]) + return n + } + /^[[:space:]]*MOVE[[:space:]]/ { + line = $0 + sub(/^[[:space:]]*MOVE[[:space:]]+/, "", line) + n = operands(line, a) + if (n >= 3 && a[2] ~ /^\*(STK|A14)\+$/ && a[3] == "1") { + npush++ + if (isreg(a[1]) && has[a[1]]) + consts = consts " " val[a[1]] + else + consts = consts " X" + next + } + if (n >= 3 && a[2] ~ /^-\*SP$/ && (a[1] == "STK" || a[1] == "A14")) { + oldstk = 1 + next + } + if (n >= 2 && isreg(a[2])) { + if (a[1] ~ /^-?[0-9]+$/) { + val[a[2]] = a[1]; has[a[2]] = 1 + } else if (isreg(a[1]) && has[a[1]]) { + val[a[2]] = val[a[1]]; has[a[2]] = 1 + } else { + unknown(a[2]) + } + } + next + } + /^[[:space:]]*(MOVI|MOVK)[[:space:]]/ { + line = $0 + sub(/^[[:space:]]*(MOVI|MOVK)[[:space:]]+/, "", line) + n = operands(line, a) + if (n >= 2 && isreg(a[2]) && a[1] ~ /^-?[0-9]+$/) { + val[a[2]] = a[1]; has[a[2]] = 1 + } else if (n >= 2 && isreg(a[2])) { + unknown(a[2]) + } + next + } + /^[[:space:]]*CLR[[:space:]]/ { + line = $0 + sub(/^[[:space:]]*CLR[[:space:]]+/, "", line) + if (isreg(trim(line))) { val[trim(line)] = 0; has[trim(line)] = 1 } + next + } + /^[[:space:]]*SUB[[:space:]]/ { + line = $0 + sub(/^[[:space:]]*SUB[[:space:]]+/, "", line) + n = operands(line, a) + if (n >= 2 && isreg(a[2]) && a[1] == a[2]) { + val[a[2]] = 0; has[a[2]] = 1 + } else if (n >= 2 && isreg(a[2])) { + unknown(a[2]) + } + next + } + /^[[:space:]]*(ADDK|SUBK)[[:space:]]/ { + op = $1 + line = $0 + sub(/^[[:space:]]*(ADDK|SUBK)[[:space:]]+/, "", line) + n = operands(line, a) + if (n >= 2 && isreg(a[2]) && a[1] ~ /^-?[0-9]+$/ && has[a[2]]) { + val[a[2]] = val[a[2]] + (op == "ADDK" ? a[1] : -a[1]) + } else if (n >= 2 && isreg(a[2])) { + unknown(a[2]) + } + next + } + /^[[:space:]]*ADDI[[:space:]]/ { + line = $0 + sub(/^[[:space:]]*ADDI[[:space:]]+/, "", line) + n = operands(line, a) + if (n >= 2 && isreg(a[2])) + unknown(a[2]) + next + } + /^[[:space:]]*CALLA[[:space:]]/ { + callee = $2 + sub(/^_/, "", callee) + if (callee == want) { + print npush, oldstk, consts + exit + } + npush = 0; oldstk = 0; consts = "" + next + } + ' +} + +# check_call FILE FUNC CALLEE NPUSH "CONST PREFIX" +check_call() { + _file=$1 + _func=$2 + _callee=$3 + _wantn=$4 + _wantc=$5 + _info=$(call_info "$_file" "$_func" "$_callee") + [ -n "$_info" ] || fail "$_func has no CALLA $_callee" + set -- $_info + _n=$1 + _stk=$2 + shift 2 + _tokens="$*" + [ "$_n" = "$_wantn" ] \ + || fail "$_func/$_callee has $_n argument pushes, want $_wantn" + [ "$_stk" = 1 ] || fail "$_func/$_callee does not save old STK" + case "$_tokens" in + "$_wantc "*) ;; + *) + echo "M4 diff: FAIL: $_func/$_callee leading constants are '$_tokens', want '$_wantc ...'" >&2 + exit 1 + ;; + esac + _last=${_tokens##* } + [ "$_last" = X ] \ + || fail "$_func/$_callee last push looks like a constant, not the Display argument" + echo " ok $_func/$_callee: $_n pushes, old STK, leading [$_wantc], Display last" +} + +# check_fnptr FILE WHO TESTFUNC +check_fnptr() { + _f=$1 + _who=$2 + _test=$3 + body "$_f" apply > "$TMP/apply.body" + require "$TMP/apply.body" '^[[:space:]]*CALL(R)?[[:space:]]+A[0-9]+' \ + "$_who: apply calls through an A register" + if grep -Eq '^[[:space:]]*CALLA[[:space:]]' "$TMP/apply.body"; then + sed 's/^/ /' "$TMP/apply.body" >&2 + fail "$_who: apply still contains a direct CALLA" + fi + require "$TMP/apply.body" '^[[:space:]]*MOVE[[:space:]]+(STK|A14),[[:space:]]*-\*SP' \ + "$_who: apply saves the old STK" + require "$TMP/apply.body" \ + '^[[:space:]]*MOVE[[:space:]]+[^,]+,[[:space:]]*\*(STK|A14)\+,[[:space:]]*1' \ + "$_who: apply pushes the callback argument" + require_last_rets2 "$TMP/apply.body" "$_who: apply ends with RETS 2" + + body "$_f" "$_test" > "$TMP/$_test.body" + _calls=$(grep -Ec '^[[:space:]]*CALLA[[:space:]]+_apply' "$TMP/$_test.body" || true) + [ "$_calls" -ge 3 ] \ + || { sed 's/^/ /' "$TMP/$_test.body" >&2; fail "$_who: $_test makes $_calls direct CALLA _apply calls, want 3"; } + echo " ok $_who: $_test makes three direct CALLA _apply calls" +} + +# has_addi_access FILE KIND DISP: an ADDI DISP,REG immediately followed by a +# MOVE that uses *REG as a load (KIND=load) or store destination (KIND=store). +has_addi_access() { + awk -v kind="$2" -v disp="$3" ' + function disp_ok(d) { + if (d == disp) return 1 + if (disp == "1152" && (d == "0x480" || d == "480h")) return 1 + if (disp == "64" && (d == "0x40" || d == "40h")) return 1 + if (disp == "128" && (d == "0x80" || d == "80h")) return 1 + if (disp == "160" && (d == "0xA0" || d == "0a0h")) return 1 + if (disp == "320" && (d == "0x140" || d == "140h")) return 1 + if (disp == "352" && (d == "0x160" || d == "160h")) return 1 + if (disp == "512" && (d == "0x200" || d == "200h")) return 1 + return 0 + } + /^[[:space:]]*ADDI[[:space:]]/ { + line = $0 + sub(/^[[:space:]]*ADDI[[:space:]]+/, "", line) + split(line, f, /[[:space:]]*,[[:space:]]*/) + d = f[1] + reg = f[2] + if (!disp_ok(d)) + next + if (getline nxt <= 0) + next + while (nxt ~ /^[[:space:]]*$/) { + if (getline nxt <= 0) + next + } + if (kind == "load" && + nxt ~ ("^[[:space:]]*MOVE[[:space:]]+\\*" reg "([,\\(]|[[:space:]]|$)")) + found = 1 + if (kind == "store" && + nxt ~ ("^[[:space:]]*MOVE[[:space:]]+[^,]+, ?\\*" reg "([,\\(]|[[:space:]]|$)")) + found = 1 + } + END { exit found ? 0 : 1 } + ' "$1" +} + +check_offsets() { + _f=$1 + _who=$2 + _side=$3 + body "$_f" image > "$TMP/image.body" + if [ "$_side" = ti ]; then + require "$TMP/image.body" \ + '^[[:space:]]*MOVE[[:space:]]+\*(A[0-9]+|FP|SP)\((1152|0x480|480h)\),' \ + "$_who: Display.screens is loaded at bit 0x480 (byte 0x90)" + require "$TMP/image.body" \ + '^[[:space:]]*MOVE[[:space:]]+\*(A[0-9]+|FP|SP)\((64|0x40|40h)\),[[:space:]]*\*(A[0-9]+|FP|SP)\([0-9]+\)' \ + "$_who: Screen.root is loaded at bit 0x40 (byte 0x8)" + for pair in 128:data 160:byte_order 320:bytes_per_line 352:bits_per_pixel 512:f; do + d=${pair%%:*} + name=${pair##*:} + case "$d" in + 128) pat='(128|0x80|80h)' ;; + 160) pat='(160|0xA0|0a0h)' ;; + 320) pat='(320|0x140|140h)' ;; + 352) pat='(352|0x160|160h)' ;; + 512) pat='(512|0x200|200h)' ;; + esac + require "$TMP/image.body" \ + "^[[:space:]]*MOVE[[:space:]]+.*\\*A[0-9]+\\($pat\\)," \ + "$_who: XImage.$name is at bit $d" + done + return 0 + fi + for pair in load:1152:screens load:64:root store:128:data store:160:byte_order \ + store:320:bytes_per_line store:352:bits_per_pixel store:512:f; do + kind=${pair%%:*} + rest=${pair#*:} + d=${rest%%:*} + name=${rest##*:} + if has_addi_access "$TMP/image.body" "$kind" "$d"; then + echo " ok $_who: XImage/Display/Screen $name access at bit $d (ADDI + $kind)" + else + sed 's/^/ /' "$TMP/image.body" >&2 + fail "$_who: no $kind through bit displacement $d for $name" + fi + done +} + +# check_image FILE WHO TI|ours +check_image() { + _f=$1 + _who=$2 + _side=$3 + check_call "$_f" image XCreateImage 10 "80 32 480 640" + check_call "$_f" image XCreatePixmap 5 "1 480 640" + check_call "$_f" image XPutImage 10 "32 640" + check_call "$_f" window XCreateSimpleWindow 9 "0 0 0 480 640" + body "$_f" image > "$TMP/rets.body" + require_last_rets2 "$TMP/rets.body" "$_who: image ends with RETS 2" + body "$_f" window > "$TMP/rets2.body" + require_last_rets2 "$TMP/rets2.body" "$_who: window ends with RETS 2" + check_offsets "$_f" "$_who" "$_side" +} + +echo "== ABI shape: TI fnptr golden" +check_fnptr "$TI_FNPTR" "TI" test +echo "== ABI shape: our fnptr" +check_fnptr "$TMP/fnptr.s.code" "ours" fnptr_test + +echo "== ABI shape: TI image golden" +check_image "$TI_IMAGE" "TI" ti +echo "== ABI shape: our image" +check_image "$TMP/image.s.code" "ours" ours + +if [ "${M4_DIFF_POSITIVE_ONLY:-0}" = 1 ]; then + echo "M4 diff: PASS" + exit 0 +fi + +echo "== negative controls" +# 1. Remove the indirect call from the fnptr golden: apply must then fail. +sed 's/^[[:space:]]*CALL[[:space:]].*/ CALLA _dec/' "$TI_FNPTR" \ + > "$TMP/fnptr-no-indirect.s" +grep -q 'CALLA _dec' "$TMP/fnptr-no-indirect.s" \ + || fail "could not construct the no-indirect-call fnptr golden" +if M4_DIFF_POSITIVE_ONLY=1 TI_FNPTR="$TMP/fnptr-no-indirect.s" \ + "$0" > "$TMP/neg-fnptr.out" 2>&1; then + sed 's/^/ /' "$TMP/neg-fnptr.out" >&2 + fail "check accepted a fnptr golden without the indirect call" +fi +echo " ok negative control: fnptr without the indirect call rejected" +grep 'M4 diff: FAIL' "$TMP/neg-fnptr.out" | head -1 | sed 's/^/ /' + +# 2. Reverse the ten XPutImage argument pushes: Display must no longer be the +# last push and the leading constants must change. +awk ' + { lines[NR] = $0 } + /^[[:space:]]*MOVE[[:space:]]+[^,]+,[[:space:]]*\*STK\+,[[:space:]]*1[[:space:]]*$/ { + npush++ + pos[npush] = NR + push[npush] = $0 + next + } + /^[[:space:]]*CALLA[[:space:]]+_XPutImage/ { + for (i = 1; i <= npush; i++) + lines[pos[i]] = push[npush - i + 1] + npush = 0 + next + } + /^[[:space:]]*CALLA[[:space:]]/ { npush = 0; next } + END { for (i = 1; i <= NR; i++) print lines[i] } +' "$TI_IMAGE" > "$TMP/image-reversed.s" +if cmp -s "$TI_IMAGE" "$TMP/image-reversed.s"; then + fail "reversed-argument mutation did not change the image golden" +fi +if M4_DIFF_POSITIVE_ONLY=1 TI_IMAGE="$TMP/image-reversed.s" \ + "$0" > "$TMP/neg-image.out" 2>&1; then + sed 's/^/ /' "$TMP/neg-image.out" >&2 + fail "check accepted the reversed XPutImage argument order" +fi +echo " ok negative control: reversed XPutImage arguments rejected" +grep 'M4 diff: FAIL' "$TMP/neg-image.out" | head -1 | sed 's/^/ /' + +echo "M4 diff: PASS" diff --git a/scripts/tests/m4/diff/ti-fnptr.c b/scripts/tests/m4/diff/ti-fnptr.c new file mode 100644 index 0000000..030691a --- /dev/null +++ b/scripts/tests/m4/diff/ti-fnptr.c @@ -0,0 +1,33 @@ +/* K&R source for the M4 differential function-pointer golden. + * + * The 1987 TI compiler (GSP C Compiler 2.01) does not accept a typedef for a + * function pointer, nor the volatile qualifier on the local, so the callback + * is spelled as a plain `int (*f)()` parameter and the local pointer is + * unqualified. `apply` is the call-through-pointer proof: the caller pushes + * `x` and the callback, saves STK, and calls through the loaded pointer (TI + * `CALL A7`, ours `CALL A?`). The direct calls to `apply` from `test()` stay + * `CALLA _apply`. + */ +int inc(x) int x; { return x + 1; } +int dec(x) int x; { return x - 1; } + +int apply(f, x) +int (*f)(); +int x; +{ + return (*f)(x); +} + +int (*g_dec)() = dec; + +int test() +{ + int (*vf)(); + int a; + int b; + + vf = inc; + a = apply(inc, 41); + b = apply(vf, 41); + return apply(g_dec, 43) + a + b; +} diff --git a/scripts/tests/m4/diff/ti-fnptr.s b/scripts/tests/m4/diff/ti-fnptr.s new file mode 100644 index 0000000..a86b883 --- /dev/null +++ b/scripts/tests/m4/diff/ti-fnptr.s @@ -0,0 +1,95 @@ +******************************************************* +* GSP C COMPILER, Version 2.01, 87.300 * +******************************************************* +FP .set A13 +STK .set A14 + .globl _inc + .file "TI-FNPTR.c" +****************************************************** +* FUNCTION DEF : _inc +****************************************************** +_inc: + MMTM SP,A7,FP + MOVE STK,FP + MOVE *FP(-32),A7,1 + ADDK 1,A7 + MOVE A7,A8 +EPI0_1: + MOVE *SP(96),STK,1 + MMFM SP,A7,FP + RETS 2 + .globl _dec +****************************************************** +* FUNCTION DEF : _dec +****************************************************** +_dec: + MMTM SP,A7,FP + MOVE STK,FP + MOVE *FP(-32),A7,1 + SUBK 1,A7 + MOVE A7,A8 +EPI0_2: + MOVE *SP(96),STK,1 + MMFM SP,A7,FP + RETS 2 + .globl _apply +****************************************************** +* FUNCTION DEF : _apply +****************************************************** +_apply: + MMTM SP,A7,FP + MOVE STK,FP + MOVE STK,-*SP,1 + MOVE *FP(-64),*STK+,1 + MOVE *FP(-32),A7,1 + CALL A7 +EPI0_3: + MOVE *SP(96),STK,1 + MMFM SP,A7,FP + RETS 2 + + .sect ".cinit" + .word IS1 + .long _g_dec+0 + .field _dec+0,32 +IS1 .set 2 + .text + .globl _g_dec + .bss _g_dec,32,16 + .globl _test +****************************************************** +* FUNCTION DEF : _test +****************************************************** +_test: + MMTM SP,A0,A5,A7,A12,FP + MOVE STK,FP + ADDI 96,STK + MOVI _inc,A0 + MOVE A0,*FP,1 + MOVE STK,-*SP,1 + MOVI 41,A12 + MOVE A12,*STK+,1 + MOVE A0,*STK+,1 + CALLA _apply + MOVE A8,*FP(32),1 + MOVE STK,-*SP,1 + MOVE A12,*STK+,1 + MOVE *FP(0),*STK+,1 + CALLA _apply + MOVE A8,*FP(64),1 + MOVE STK,-*SP,1 + ADDK 2,A12 + MOVE A12,*STK+,1 + MOVE @_g_dec,*STK+,1 + CALLA _apply + MOVE A8,A7 + MOVE *FP(32),A5,1 + ADD A5,A7 + MOVE *FP(64),A5,1 + ADD A5,A7 + MOVE A7,A8 +EPI0_4: + ADDI -96,STK + MMFM SP,A0,A5,A7,A12,FP + RETS 0 + .end diff --git a/scripts/tests/m4/diff/ti-image.c b/scripts/tests/m4/diff/ti-image.c new file mode 100644 index 0000000..59e3e21 --- /dev/null +++ b/scripts/tests/m4/diff/ti-image.c @@ -0,0 +1,86 @@ +/* K&R source for the M4 differential Xlib-image golden. + * + * It models the X11R4 32-bit XImage/Display/Screen layout and the ten-argument + * XCreateImage/XPutImage, five-argument XCreatePixmap and nine-argument + * XCreateSimpleWindow calls. The 1987 TI compiler packs struct members and + * its &-of-member is already a bit address, so the golden carries the ROM + * bit displacements directly: Display.screens 1152 (byte 0x90), Screen.root + * 64 (byte 0x8), and XImage data/byte_order/bytes_per_line/bits_per_pixel/f + * 128/160/320/352/512 (bytes 16/20/40/44/64). + */ +struct display { + char pad[0x90]; + void *screens; +}; + +struct screen { + char pad[0x8]; + unsigned int root; +}; + +struct ximage_funcs { + void *create_image; + void *destroy_image; + void *get_pixel; + void *put_pixel; + void *sub_image; + void *add_pixel; +}; + +struct ximage { + int width, height; + int xoffset; + int format; + char *data; + int byte_order; + int bitmap_unit; + int bitmap_bit_order; + int bitmap_pad; + int depth; + int bytes_per_line; + int bits_per_pixel; + unsigned long red_mask, green_mask, blue_mask; + void *obdata; + struct ximage_funcs f; +}; + +extern struct display *XOpenDisplay(); +extern int XCreateSimpleWindow(); +extern unsigned int XCreatePixmap(); +extern struct ximage *XCreateImage(); +extern int XPutImage(); +extern int XMapWindow(); +extern int XSync(); + +int image(d, w, gc, data) +struct display *d; +unsigned int w; +void *gc; +char *data; +{ + struct ximage *img; + unsigned int root; + unsigned int pm; + + root = ((struct screen *)d->screens)->root; + img = XCreateImage(d, 0, 1, 0, 0, data, 640, 480, 32, 80); + img->byte_order = 0; + img->bytes_per_line = 80; + img->bits_per_pixel = 1; + img->f.create_image = 0; + pm = XCreatePixmap(d, w, 640, 480, 1); + XPutImage(d, pm, gc, img, 0, 0, 0, 0, 640, 32); + img->data = data; + XMapWindow(d, w); + XSync(d, 0); + return root + pm; +} + +int window(d) +struct display *d; +{ + unsigned int root; + + root = ((struct screen *)d->screens)->root; + return XCreateSimpleWindow(d, root, 20, 20, 640, 480, 0, 0, 0); +} diff --git a/scripts/tests/m4/diff/ti-image.s b/scripts/tests/m4/diff/ti-image.s new file mode 100644 index 0000000..c874a3e --- /dev/null +++ b/scripts/tests/m4/diff/ti-image.s @@ -0,0 +1,127 @@ +******************************************************* +* GSP C COMPILER, Version 2.01, 87.300 * +******************************************************* +FP .set A13 +STK .set A14 + .globl _XOpenDisplay + .globl _XCreateSimpleWindow + .globl _XCreatePixmap + .globl _XCreateImage + .globl _XPutImage + .globl _XMapWindow + .globl _XSync + .globl _image + .file "TI-IMAGE.c" +****************************************************** +* FUNCTION DEF : _image +****************************************************** +_image: + MMTM SP,A5,A7,A12,FP + MOVE STK,FP + ADDI 96,STK + MOVE *FP(-32),A7,1 + MOVE *A7(1152),A7,1 + MOVE *A7(64),*FP(32),1 + MOVE STK,-*SP,1 + MOVI 80,A7 + MOVE A7,*STK+,1 + MOVK 32,A7 + MOVE A7,*STK+,1 + MOVI 480,A7 + MOVE A7,*STK+,1 + MOVI 640,A7 + MOVE A7,*STK+,1 + MOVE *FP(-128),*STK+,1 + CLR A7 + MOVE A7,*STK+,1 + MOVE A7,*STK+,1 + ADDK 1,A7 + MOVE A7,*STK+,1 + SUB A7,A7 + MOVE A7,*STK+,1 + MOVE *FP(-32),*STK+,1 + CALLA _XCreateImage + MOVE A8,*FP,1 + MOVE *FP,A7,1 + CLR A12 + MOVE A12,*A7(160),1 + MOVI 80,A12 + MOVE A12,*A7(320),1 + MOVK 1,A12 + MOVE A12,*A7(352),1 + SUB A12,A12 + MOVE A12,*A7(512),1 + MOVE STK,-*SP,1 + ADDK 1,A12 + MOVE A12,*STK+,1 + MOVI 480,A12 + MOVE A12,*STK+,1 + MOVI 640,A12 + MOVE A12,*STK+,1 + MOVE *FP(-64),*STK+,1 + MOVE *FP(-32),*STK+,1 + CALLA _XCreatePixmap + MOVE A8,*FP(64),1 + MOVE STK,-*SP,1 + MOVK 32,A12 + MOVE A12,*STK+,1 + MOVI 640,A12 + MOVE A12,*STK+,1 + CLR A12 + MOVE A12,*STK+,1 + MOVE A12,*STK+,1 + MOVE A12,*STK+,1 + MOVE A12,*STK+,1 + MOVE A7,*STK+,1 + MOVE *FP(-96),*STK+,1 + MOVE *FP(64),*STK+,1 + MOVE *FP(-32),*STK+,1 + CALLA _XPutImage + MOVE *FP(-128),*A7(128),1 + MOVE STK,-*SP,1 + MOVE *FP(-64),*STK+,1 + MOVE *FP(-32),*STK+,1 + CALLA _XMapWindow + MOVE STK,-*SP,1 + MOVE A12,*STK+,1 + MOVE *FP(-32),*STK+,1 + CALLA _XSync + MOVE *FP(32),A7,1 + MOVE *FP(64),A5,1 + ADD A5,A7 + MOVE A7,A8 +EPI0_1: + MOVE *SP(160),STK,1 + MMFM SP,A5,A7,A12,FP + RETS 2 + .globl _window +****************************************************** +* FUNCTION DEF : _window +****************************************************** +_window: + MMTM SP,A7,FP + MOVE STK,FP + ADDK 32,STK + MOVE *FP(-32),A7,1 + MOVE *A7(1152),A7,1 + MOVE *A7(64),*FP(0),1 + MOVE STK,-*SP,1 + CLR A7 + MOVE A7,*STK+,1 + MOVE A7,*STK+,1 + MOVE A7,*STK+,1 + MOVI 480,A7 + MOVE A7,*STK+,1 + MOVI 640,A7 + MOVE A7,*STK+,1 + MOVK 20,A7 + MOVE A7,*STK+,1 + MOVE A7,*STK+,1 + MOVE *FP(0),*STK+,1 + MOVE *FP(-32),*STK+,1 + CALLA _XCreateSimpleWindow +EPI0_2: + MOVE *SP(96),STK,1 + MMFM SP,A7,FP + RETS 2 + .end diff --git a/symbols/README.md b/symbols/README.md index 3a926f4..59e5cbf 100644 --- a/symbols/README.md +++ b/symbols/README.md @@ -12,13 +12,21 @@ runs the gate and also pins the `XMapWindow`/`XRaiseWindow` correction. The table is consumed through `scripts/m2/gen-rom-imports.sh`, which emits an ABI-preserving `jauc
h` stub for every -` ` line. M3 uses the four connection/window entries; M4 -uses the drawing, event, `XImage`, and error-handler entries. +` ` line. The **30-entry v2.2 table** is the M4 table: the +four M3 connection/window entries plus the drawing, event, `XImage`, XG, +blitter and image-client entries. `scripts/m2/gen-rom-imports.sh` emits every +entry; each client links only the stubs it calls. Do not assume an address here is validated just because it is listed. Each entry below names its evidence. Nothing is guessed: `XCreateImage`, -`XPutImage`, `XGetImage`, and the two error-handler setters were extracted from -the ROM disassembly before being added. +`XPutImage`, `XGetImage`, `XDestroyImage`, the two error-handler setters, +`XCreatePixmap`, `XSetInputFocus` and `blit_copy` were extracted from the ROM +disassembly before being added. + +`XLoadFont` and `XCopyArea` are **not** in the pinned termtris `xlib.h` +(that header has 15 entries). Their addresses come from the M4 task brief's +ROM-entry list and the ROM request-opcode evidence below; they are not +termtris-proven. Do not cite the termtris header for them. ## `XMapWindow` correction @@ -34,14 +42,26 @@ opcode **8** (`MapWindow`). The termtris VT1200 `xlib.h` already used `XMapWindow` to `0x3FD645B0` (reported as `XRaiseWindow`/wrong). A client that wants map-and-raise calls `XMapWindow` and then `XRaiseWindow`. +The vendored DWT reverse-engineering notes (`vendor/vt1200/dwt/notes/GUI_APPS.md`, +`vendor/vt1200/dwt/notes/CROSS.md`, `vendor/vt1200/dwt/gui/xhello.asm`) still +label `0x3FD645B0` as `XMapWindow`. Those are **historical notes** and were +deliberately left untouched: the address is `XRaiseWindow`, and the corrected +table here is authoritative. `scripts/m4/validate-syms.sh` and +`scripts/tests/m4/symbols.sh` pin the correction. + ## v2.2 entries validated +The table below has **30 entries**: 12 connection/window/event, 1 blitter +helper, 8 drawing/GC, 6 `XImage`/error-handler and 3 C runtime entries. +`scripts/m4/validate-syms.sh` walks all 30 and prints the count. + ### Connection, windows, events | entry | bit address | evidence | |---|---|---| | `XOpenDisplay` | `0x3F9C2570` | termtris `xlib.h`; `xhello.asm` calls it; MAME child connects | | `XCreateSimpleWindow` | `0x3FD41B90` | termtris `xlib.h`; nine-argument create, window id nonzero | +| `XCreatePixmap` | `0x3FD416E0` | CreatePixmap request opcode `0x35`; five arguments (display, drawable, width, height, depth); M4 image client's aligned 1-bit pixmap | | `XMapWindow` | `0x3FD64F60` | request opcode 8 (`MapWindow`); termtris `xlib.h` | | `XRaiseWindow` | `0x3FD645B0` | request opcode 12 (`ConfigureWindow`), `CWStackMode=Above` | | `XSync` | `0x3FD9F6B0` | termtris `xlib.h`; called after mapping | @@ -50,6 +70,13 @@ wants map-and-raise calls `XMapWindow` and then `XRaiseWindow`. | `XNextEvent` | `0x3F9C1CF0` | termtris `xlib.h`; event loop proof | | `XSelectInput` | `0x3FD97D50` | termtris `xlib.h` (`ChangeWindowAttributes`) | | `XGetInputFocus` | `0x3FD55210` | termtris `xlib.h` | +| `XSetInputFocus` | `0x3FD536B0` | SetInputFocus request opcode `0x2a`; the ROM body reads `0xffe0`/`0xffc0`/`0xffa0` and supplies CurrentTime; sibling of `XGetInputFocus` opcode `0x2b` | + +### Blitter copy helper + +| entry | bit address | evidence | +|---|---|---| +| `blit_copy` | `0x3FB44400` | `_XPutImage`'s copy entry (`0x3F9CBFE0` calls `0x3FB44400`); TMS34010 blitter memcpy with the ROM order `(source, destination, byte count)` and a normal `MMTM` prologue. The M4 image client primes it once per draw with a 4-byte self-copy because a loader-created process starts with a zeroed blitter window/pitch context. Validated by `scripts/m4/validate-syms.sh` and the `_blit_copy` `JAUC`/`CALLA` checks in `dis-roundtrip.sh`. | ### Drawing and GC @@ -59,8 +86,8 @@ wants map-and-raise calls `XMapWindow` and then `XRaiseWindow`. | `XSetForeground` | `0x3FD99A60` | termtris `xlib.h`; caches in GC | | `XFillRectangle` | `0x3F99E8F0` | termtris `xlib.h`; request opcode 70 | | `XDrawString` | `0x3FDA0000` | termtris `xlib.h`; request opcode 74 (`PolyText8`) | -| `XLoadFont` | `0x3FD4AE80` | termtris `xlib.h`; request opcode 45 (`OpenFont`) | -| `XCopyArea` | `0x3FD3E220` | termtris `xlib.h`; request opcode 62 | +| `XLoadFont` | `0x3FD4AE80` | M4 task brief ROM-entry list; request opcode 45 (`OpenFont`); **not** in the termtris `xlib.h` | +| `XCopyArea` | `0x3FD3E220` | M4 task brief ROM-entry list; request opcode 62 (`CopyArea`); **not** in the termtris `xlib.h`; the M4 image client copies its aligned pixmap with it | | `XLookupString` | `0x3F9A9C30` | termtris `xlib.h`; key translation | | `sleep` | `0x3FB33A20` | termtris `xlib.h`; called directly | @@ -88,15 +115,19 @@ reconstructed ROM image starts at bit `0x3F200000`. | `malloc` | `0x3F701230` | ROM allocator; called by `XCreateImage`/`XGetImage` | | `free` | `0x3F702440` | ROM free; called by `XDestroyImage` | -## M3 regression check +## M3/M4 regression checks `scripts/m3/validate-syms.sh` independently checks the four M3 entries (`XOpenDisplay`, `XCreateSimpleWindow`, `XMapWindow`, `XSync`) and now expects `XMapWindow 0x3FD64F60`. `scripts/tests/m3/run.sh` runs it before every MAME run, so the compiled client cannot pass with a stale stub. The M3 and M4 validators are separate scripts (the M3 one is a four-entry regression; the M4 -one walks the whole table), but they use the same range/alignment/prologue -rules and the same corrected address. +one walks the whole 30-entry table), but they use the same +range/alignment/prologue rules and the same corrected address. The M4 table +also carries `XCreatePixmap`, `XSetInputFocus`, `blit_copy`, `XImage`, +`XGetImage`/`XDestroyImage` and both error-handler setters; `symbols.sh` +rejects the historical `XMapWindow 0x3FD645B0` mapping and an unaligned +`XPutImage`. 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. diff --git a/symbols/vt1200-v22.syms b/symbols/vt1200-v22.syms index 5730f82..36f2fd9 100644 --- a/symbols/vt1200-v22.syms +++ b/symbols/vt1200-v22.syms @@ -40,8 +40,8 @@ XCreateGC 0x3F997000 termtris xlib.h XSetForeground 0x3FD99A60 termtris xlib.h XFillRectangle 0x3F99E8F0 termtris xlib.h XDrawString 0x3FDA0000 termtris xlib.h -XLoadFont 0x3FD4AE80 termtris xlib.h -XCopyArea 0x3FD3E220 termtris xlib.h +XLoadFont 0x3FD4AE80 M4 brief entry; OpenFont opcode 45 +XCopyArea 0x3FD3E220 M4 brief entry; CopyArea opcode 62 XLookupString 0x3F9A9C30 termtris xlib.h sleep 0x3FB33A20 termtris xlib.h #