tests: M4 Xlib images, events and the termtris integration

This commit is contained in:
arti committed 2026-11-12 11:49:32 +00:00
1 parent 1a91329864
commit 2d5ed90fb2
8 files changed
+1128 -76

No files matched your search

+238 -65
View File
@@ -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:** <https://git.wut.ee/opencode.siil.arti.ee/llvm-tms34010>
(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:** <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>
(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<n>_<m>` 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.
+507
View File
@@ -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"
+33
View File
@@ -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;
}
+95
View File
@@ -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
+86
View File
@@ -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);
}
+127
View File
@@ -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
+40 -9
View File
@@ -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 <address>h` stub for every
`<name> <bit-address>` line. M3 uses the four connection/window entries; M4
uses the drawing, event, `XImage`, and error-handler entries.
`<name> <bit-address>` 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.
+2 -2
View File
@@ -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
#