16 Commits
m3 ... m4
Author SHA1 Message Date
arti 5d980dc6e3 tests/docs: final M4 review fixes 2026-11-12 12:23:04 +00:00
arti 1511919437 tests/docs: fix M4 review findings in bands, symbols and diff check 2026-11-12 12:04:36 +00:00
arti 2d5ed90fb2 tests: M4 Xlib images, events and the termtris integration 2026-11-12 11:49:32 +00:00
arti 1a91329864 tests: M4 termtris integration and varargs ABI gate
Build the pinned termtris branch (be5b323) with this compiler, assemble
and link it with the TI 1987 GSPA/GSPLNK and RTS.LIB, and run the proven
play_keys board/move/panels/quit paths in MAME with the M4 loader.  Add
the contiguous-stack varargs MAME probe and the switch-to-lookup driver
regression check.  The termtris build found the i8/i16 argument, i1/
anyext load, nested-data, MULHS and late-GEP gaps fixed in the compiler.
2026-11-12 10:51:15 +00:00
arti fb0159620e tests: validate the image blitter copy and assert window focus 2026-11-12 09:40:45 +00:00
arti 26697b9585 tests: M4 1-bit XImage, real key events, and the MAME image gate 2026-11-12 09:21:17 +00:00
arti 7946519828 symbols: correct XMapWindow, add XImage/XG entries and validation 2026-11-12 07:19:42 +00:00
arti 8cb060cdb4 tests: M4 X11R4 XImage/XEvent/error-event layout 2026-11-12 06:53:29 +00:00
arti 7a89a1fe4c tests: M4 function-pointer/indirect-call gate
Add the fnptr C callback test (direct symbol, volatile local pointer and
global pointer), its GSPA entry and marker script, and scripts/tests/m4/
fnptr.sh.  The gate compiles at the default -O2 pipeline, requires all four
markers to be 42 in MAME, and checks the linked assembly: _apply calls through
an A register (CALL, never CALLA) after the argument push, while _fnptr_test
keeps the direct CALLA and materialises inc with MOVI.  It also pins the GSPA
0x0920 encoding and requires dis34 to decode the call as 'call aN'.
2026-11-12 06:40:12 +00:00
arti d8efad2479 tests: use an unsigned helper for the wide multiply marker
The full-width marker called the signed mul helper with a constant whose
product overflows int, which is UB and made the assertion rely on wrap-around.
Add mulu(unsigned, unsigned) for marker 10; the low-32 product and MPYS
selection are unchanged.  Check the optimized IR has no nsw on mulu.
2026-11-12 06:21:31 +00:00
arti 00522b56fc tests: exercise wide 32-bit products and unsigned umul16
Add runtime markers for a full-width 32x32 multiply (0x12345678 * 0x9abcdef0
= 0x242d2080) and for a wide-dividend remainder, so a 16-bit-truncated MPYS or
a wrong FS1 cannot still produce the expected small results.  Compute the 16x16
unsigned product in unsigned arithmetic to remove the signed-overflow
annotation, and reject a regressed nuw nsw multiply in the optimized IR.
2026-11-12 06:17:29 +00:00
arti 7cd9a48ee3 tests: M4 multiply/divide and -O2 loop gates
Add a runtime marker test for 32x32 multiply, the 16x16 MPYU path, signed and
unsigned divide/remainder, and for sumto/squares compiled through the ordinary
tms-clang -O2 driver.  The linked assembly is also checked for MPYS/MPYU/
DIVS/DIVU and for a surviving sumto loop.
2026-11-12 06:04:56 +00:00
arti cfe751ccab driver: keep -O2 loops for the i64-less target
Scripts/tms-clang runs the generic opt pipeline without a target machine, so
the target cannot veto IndVarSimplify's exit-value replacement through TTI.
That rewrite turns sumto's closed form into an i33 multiply, which llc cannot
select.  Disable only that rewrite with -replexitval=never and document why.
2026-11-12 06:04:56 +00:00
arti 57ea80a5c0 tests: exercise every signed and unsigned predicate at runtime 2026-11-12 05:46:38 +00:00
arti 28c9ee7e3c tests: M4 unsigned runtime and private-label gates 2026-11-12 05:38:22 +00:00
arti dbf71bdc5f docs: M4 Xlib images, events and termtris integration plan 2026-11-12 05:23:33 +00:00
60 changed files with 5909 additions and 111 deletions

No files matched your search

+248 -65
View File
@@ -3,43 +3,114 @@
A modern C toolchain for the DEC VT1200 / TI TMS34010, built as a small fork of A modern C toolchain for the DEC VT1200 / TI TMS34010, built as a small fork of
upstream LLVM. 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 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` returned in `A8`, frames use `A13`/`A14`/`A15`, and functions end in `RETS 2`
(`RETS 0` without arguments). M3 adds conditional/unconditional branches, (`RETS 0` without arguments). M4 adds function pointers and `CALL Rs`, 32-bit
ordinary `while`/`for` loops, integer ALU/shift operations, and the first multiply/divide, zero-extending narrow loads, module-unique private labels,
compiled C Xlib client: a four-call window opened with `XOpenDisplay`, created with `i8`/`i16` fixed arguments and the contiguous-stack varargs form, the X11R4
`XCreateSimpleWindow`, mapped with `XMapWindow` and synchronized with `XSync`. `XImage`/`XEvent` layouts, the full image/event client, the corrected 30-entry
It is still **assembly-only**: no object writer, no `clang -c`, and no native ROM symbol table, and the pinned termtris full-game integration. It is still
COFF; GSPA/GSPLNK under DOSBox produce the COFF. XImage/XEvent, the real client, **assembly-only**: no object writer, no `clang -c`, and no native COFF;
structs by value, function pointers, floats and `i64` remain M4/M5. 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) - 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) - 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) - 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) - 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) - 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: [`spike/REPORT-23.md`](spike/REPORT-23.md) (current, LLVM 23.1.5);
[`spike/REPORT.md`](spike/REPORT.md) (historical, 19.1.7) [`spike/REPORT.md`](spike/REPORT.md) (historical, 19.1.7)
## Repositories ## Repositories
- **Compiler fork:** <https://git.wut.ee/opencode.siil.arti.ee/llvm-tms34010> - **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 (branch `m4`) — the full buildable LLVM 23.1.5 tree with the TMS34010
target, clang bit-address changes and the M2/M3 backend. Branch `m2` (the target, clang bit-address changes and the M2/M3/M4 backend. Branch `m3`
repository default) is the M2 snapshot; branch `tms34010` is the M1 tip and (the repository default) is the previous snapshot; branch `m2` is the M2
keeps the original M1 backend and suite. 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> - **This project:** <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>
(branch `m3`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`, (branch `m4`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
`tms-clang`, symbols, the M2/M3 harnesses and the spike. Branch `m2` (the `tms-clang`, symbols, the M2/M3/M4 harnesses and the spike. Branch `m3`
repository default) is the M2 snapshot and branch `m1` is the reproducible M1 (the repository default) is the previous snapshot; branch `m2` is the M2
snapshot, including the retired M1 harness. 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` 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 here is a **frozen M0 mirror** used by the default upstream+patches build;
do not change it. 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`. There is no compiler
multiply/divide helper in `RTS.LIB`; `_div` is only the C `div()` wrapper.
`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 a custom 64-bit
string-hash token (FNV-style, not canonical FNV-1a), 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 X11R4 object-size compatibility 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 bands of 32 rows (the ROM request buffer is 16 KiB), with
`.bss` at `0x01A00000` and `prog` at `0x018A0000`. `XCopyArea` copies
`IMG_W - 12` = 628 columns, so the rightmost 12 source columns (the last
visible cell's final 12 px) are not displayed; the last visible cell is
20 px, not 32. 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 M3 adds integer control flow and the first compiled C Xlib client, still
assembly-only: assembly-only:
@@ -62,8 +133,11 @@ assembly-only:
- Misaligned accesses: `MOVE`/`MOVB` and the FS0/FS1 field-size forms have no - Misaligned accesses: `MOVE`/`MOVB` and the FS0/FS1 field-size forms have no
alignment requirement on the TMS34010, so packed and bit-addressed struct alignment requirement on the TMS34010, so packed and bit-addressed struct
fields select directly instead of being split into byte-wide accesses. 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 - Nine `m3-*.ll` lit tests on the `m3` branch (46 with the M2 suite); M4
end-to-end, dis34, layout and differential gates (below). removes five obsolete M2 refusal tests (`m2-reject-arg8`,
`m2-reject-call-arg8`, `m2-reject-call-indirect`, `m2-reject-call-vararg`,
`m2-reject-vararg`) and `m3-reject-zext.ll`, and adds 18 `m4-*` tests:
46 - 5 - 1 + 18 = 58 total with the M2 suite.
M3 remains an `i32`/pointer milestone. `setcc` is supported, but narrow M3 remains an `i32`/pointer milestone. `setcc` is supported, but narrow
(`i8`/`i16`) parameters and returns are still rejected. Branches on `i64`, (`i8`/`i16`) parameters and returns are still rejected. Branches on `i64`,
@@ -80,9 +154,9 @@ still emits assembly only.
- **`-O2` loop exit-value rewriting is not lowered.** IndVarSimplify can turn - **`-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 `sumto`'s loop into a closed-form `i33` multiply, which M3 cannot select. The
M3 harness therefore builds `branch.c` with `-replexitval=never`. Ordinary 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 `while`/`for` loops that keep a real back-edge and phi work. M4 adds
multiply/`i64` lowering, a target-level pass policy, or a target diagnostic multiply/divide and moves the `-replexitval=never` policy into
instead of a raw abort. `scripts/tms-clang`; see "What M4 provides".
## What M2 provides (branch `m2`) ## What M2 provides (branch `m2`)
@@ -117,7 +191,7 @@ M2 replaces the M1 test convention with the TI ABI and stops at assembly:
## The TI ABI ## 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 | | 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)`, … | | Argument slots | registers | first at `FP(-32)`, second at `FP(-64)`, … |
@@ -136,7 +210,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 ROM code may change either field size. FS0 drives `short` loads/stores and FS1
drives 32-bit words. 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. These are the documented commands on branch `m3`; all must pass.
@@ -217,7 +375,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 built with `-replexitval=never`; the default exit-value rewrite turns
`sumto`'s loop into an unselectable `i33` multiply. `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 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 2026-11-11: `M2 MAME: PASS`, `M2 dis34 round-trip: PASS`, `M2 diff: PASS`). The
@@ -357,32 +515,49 @@ M3 deliberately stops at the first window and ordinary integer control flow:
The `JR`/branch-relaxation code-size pass is M5 as well. The `JR`/branch-relaxation code-size pass is M5 as well.
- **Varargs, `musttail`, narrow ABI parameters/returns:** unchanged from M2. - **Varargs, `musttail`, narrow ABI parameters/returns:** unchanged from M2.
## M4 prerequisites (carry-forwards) ### M3 carry-forwards in M4
The final M3 review left these concrete prerequisites for M4: M4 closes three of the five final-M3 prerequisites: zero-extending loads
replaced the reject test, private labels are module-unique, and unsigned/`<=`
runtime coverage exists. The new M4 harness and
`scripts/tests/m4/dis-roundtrip.sh` are operand/register-independent, and
`layout.sh` was tightened. The M3 regression is deliberately unchanged:
`m3-cmpi.ll` still uses `CHECK-DAG: MOVI 0`/`MOVI 1` (the 1/0 order is not
pinned), and `scripts/m3/dis-roundtrip.sh` still hardcodes the nine
`XCreateSimpleWindow` push registers. Those M3-strength fixes remain
test-hardening follow-ups; the M3 gates pass as-is. See the M4 feature list
above.
- **Add zero-extending narrow loads.** M3 rejects `unsigned char`, ## M4 non-goals and the M5 boundary
`unsigned short` and `_Bool` loads (pinned by `m3-reject-zext.ll`); M4 must
add the `ZEXTLOAD i8`/`i16` patterns and replace the reject test. M4 deliberately stops before:
- **Namespace every private label.** `scripts/tests/m3/run.sh` prefixes
`LBB<n>_<m>` per translation unit, but deterministic private string labels - **Native object output:** no `MCCodeEmitter`, object writer,
are not renamed. Two TUs with literals therefore collide in GSPA's flat `llc -filetype=obj`, `clang -c` or native COFF; GSPA/GSPLNK still produce
symbol table. M4 must namespace all private labels or make compiler private the COFF. The `JR`/branch-relaxation code-size pass and MMTM/MMFM
names module-unique. register-list saves are M5 (the current per-register system-stack pushes
- **Add unsigned/`<=` runtime coverage.** `JALE`, `JALO`, `JAHI` and `JALS`, have the identical ABI layout).
`JAHS`, `JANE`, `JAGE` are covered by lit tests only and do not appear in the - **Floating point and `long long`:** no float/double or `i64` arguments,
compiled M3 image. M4 should add an unsigned/`<=` runtime marker. returns, arithmetic or branches.
- **Replace fragile test plumbing.** The `always_inline` root helper and the - **General varargs:** no `va_list`, `va_start` or `va_arg`; only the
register-hardcoded nine-argument dis34 check are test-strength aids that will contiguous `&last_fixed` pattern used by termtris `sprintf`.
not survive real M4 code; make them operand- and register-independent. - **Bitfields and aggregates by value:** no structs/unions by value, `sret`,
- **Tighten test strength.** `m3-cmpi`'s `CHECK-DAG` for 0/1 does not pin their `byval`, bitfields or aggregate returns. Struct fields through pointers are
order, and `layout.sh` pairs the root probe with fixed constants; neither supported.
weakens the current pass, but both should be tightened. - **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 ## Repository layout
```text ```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-clean-build/ its Ninja build tree (ignored)
build/llvm-src/ M0 working fork (ignored) build/llvm-src/ M0 working fork (ignored)
patches/llvm/ frozen M0 patch series against llvmorg-23.1.5 patches/llvm/ frozen M0 patch series against llvmorg-23.1.5
@@ -390,10 +565,15 @@ tools/tms-bitaddr/ GEP scale/normalize tool
scripts/tms-clang IR pipeline wrapper (clang + tms-bitaddr + opt) scripts/tms-clang IR pipeline wrapper (clang + tms-bitaddr + opt)
scripts/m2/ M2 harness: C inputs, entry.asm, link.cmd, stubs, checks 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/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/ M2 gates (run.sh, dis-roundtrip.sh)
scripts/tests/m2/diff/ TI golden asm and the differential ABI check 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/ M3 gates (run.sh, dis-roundtrip.sh, layout.sh)
scripts/tests/m3/diff/ TI branch/Xlib goldens, differential check + negative controls 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/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/m1/ M1 gates (historical; also on branch `m1`, not an M2 gate)
scripts/tests/m0/ M0 integration tests scripts/tests/m0/ M0 integration tests
@@ -408,11 +588,12 @@ docs/ design and plans
## Prerequisites ## Prerequisites
`cmake`, `ninja-build`, `ccache`, a C++17 host compiler (GCC 14 is used here), `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 Python 3, and about 20 GB of disk for the LLVM source and build. The M2/M3/M4
and dis34 gates additionally need `dosbox`, `mtools` (`mcopy`), the VT1200 SDK MAME and dis34 gates additionally need `dosbox`, `mtools` (`mcopy`), the
image, the `dwt` COFF loader, `dis34`, and a built MAME `vt1200` binary VT1200 SDK image, the `dwt` COFF loader, `dis34`, and a built MAME `vt1200`
(`build/mame-src/vt1200`). The differential golden check needs no binary (`build/mame-src/vt1200`). The termtris gate also needs the pinned
DOSBox/MAME/SDK; it needs only the built `clang`, `opt`, `llc` and `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: `tools/tms-bitaddr/tms-bitaddr`. On Debian:
```sh ```sh
@@ -430,13 +611,13 @@ Xlib call sequence, `as34`, `dis34`, SDKs, manuals). It is not vendored:
## Build the compiler ## 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: place:
```sh ```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} # binaries: build/llvm-clean-build/bin/{llc,clang,opt,llvm-config,FileCheck}
# lit driver: build/llvm-clean-build/bin/llvm-lit # lit driver: build/llvm-clean-build/bin/llvm-lit
``` ```
@@ -445,7 +626,7 @@ To clone the fork first instead of using an existing checkout:
```sh ```sh
LLVM_REPO=gitea@git.wut.ee:opencode.siil.arti.ee/llvm-tms34010.git \ 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 Fork mode **never** checks out, resets, cleans or patches the source tree; it
@@ -462,22 +643,23 @@ TMS34010; a first build takes roughly 20–40 minutes.
This mode clones `llvmorg-23.1.5`, applies `patches/llvm/*.patch`, and builds 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: `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 if `build/llvm-clean` has some other origin (for example the M2/M3/M4 fork),
refuses rather than destroying it. Use fork mode for that checkout. it refuses rather than destroying it. Use fork mode for that checkout.
`scripts/export-patches.sh` regenerates the committed M0 patch series with `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 ## 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 ```sh
LLVM_CONFIG=$PWD/build/llvm-clean-build/bin/llvm-config \ LLVM_CONFIG=$PWD/build/llvm-clean-build/bin/llvm-config \
tools/tms-bitaddr/build.sh tools/tms-bitaddr/build.sh
``` ```
M0 regression test (optional in M2/M3): M0 regression test (optional in M2/M3/M4):
```sh ```sh
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \ TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
@@ -513,7 +695,8 @@ object file or a usable COFF for this target.
## Later milestones ## Later milestones
M4 adds the real Xlib client (`XImage`/`XEvent`, the full X11R4 headers), M5 adds the native COFF path (`MCCodeEmitter`/object writer), floats,
structs by value and function pointers. M5 adds the native COFF path, floats, `long long`, general varargs (`va_list`), bitfields, structs by value and the
varargs, bitfields, `long long` and the `JR`/branch-relaxation code-size pass; `JR`/branch-relaxation code-size pass, plus MMTM/MMFM register-list saves and
2ine r5.01 is a later speed-up for the GSPA/GSPLNK step. deeper libc/runtime coverage. 2ine r5.01 is a later speed-up for the
GSPA/GSPLNK step.
@@ -21,7 +21,7 @@
- **Flags are modeled with a reserved `ST` pseudo-register:** `CMP`/`CMPI` define it, conditional branches and `SETCC` sequences use it. Do not silently drop the compare-to-branch dependency. - **Flags are modeled with a reserved `ST` pseudo-register:** `CMP`/`CMPI` define it, conditional branches and `SETCC` sequences use it. Do not silently drop the compare-to-branch dependency.
- **CMP operand order:** for `icmp <pred> a, b` the compare must compute `a - b` (the branch condition reads the flags for `a <pred> b`). The signed/unsigned conditions map to the TMS34010 condition codes in `dis34`: `uc/p/ls/hi/lt/ge/le/gt/c/nc/eq/ne/v/nv/n/nn` (0–15). - **CMP operand order:** for `icmp <pred> a, b` the compare must compute `a - b` (the branch condition reads the flags for `a <pred> b`). The signed/unsigned conditions map to the TMS34010 condition codes in `dis34`: `uc/p/ls/hi/lt/ge/le/gt/c/nc/eq/ne/v/nv/n/nn` (0–15).
- M3 scalar control flow: i32/pointer values, signed and unsigned integer compares, `and`/`or`/`xor`/`shl`/`lshr`/`ashr`/`sub`/`neg`/`not`; no switch tables, indirect branches, floats, i64, or function pointers in M3. - M3 scalar control flow: i32/pointer values, signed and unsigned integer compares, `and`/`or`/`xor`/`shl`/`lshr`/`ashr`/`sub`/`neg`/`not`; no switch tables, indirect branches, floats, i64, or function pointers in M3.
- **Xlib is ROM-v2.2 only.** Use the four known-good entries from `symbols/vt1200-v22.syms`: `XOpenDisplay 0x3F9C2570`, `XCreateSimpleWindow 0x3FD41B90`, `XMapWindow 0x3FD645B0`, `XSync 0x3FD9F6B0`. The stubs are `JAUC <address>h`; `CALLA _XOpenDisplay` reaches the ROM function. - **Xlib is ROM-v2.2 only.** Use the four known-good entries from `symbols/vt1200-v22.syms`: `XOpenDisplay 0x3F9C2570`, `XCreateSimpleWindow 0x3FD41B90`, `XMapWindow 0x3FD64F60`, `XSync 0x3FD9F6B0`. (`0x3FD645B0` is `XRaiseWindow`, not `XMapWindow`; the M4 correction verified it.) The stubs are `JAUC <address>h`; `CALLA _XOpenDisplay` reaches the ROM function.
- **Display/Screen layout is ROM-specific:** xhello's `*a11(0480h)`/`*a10(040h)` are **bit displacements**, so `Display.screens` is the 32-bit pointer at **byte 0x90** (bit `0x480`) and `Screen.root` is the 32-bit root id at **byte 0x8** (bit `0x40`). M3 uses a minimal `scripts/m3/vt1200_xlib.h` with byte pads 0x90/0x8 and `_Static_assert`s, returning the ROM's bit offsets `0x480`/`0x40`; do not assume the host X11R7 `_XDisplay` layout matches the ROM. The full X11R4 `XImage`/`XEvent` headers are M4. - **Display/Screen layout is ROM-specific:** xhello's `*a11(0480h)`/`*a10(040h)` are **bit displacements**, so `Display.screens` is the 32-bit pointer at **byte 0x90** (bit `0x480`) and `Screen.root` is the 32-bit root id at **byte 0x8** (bit `0x40`). M3 uses a minimal `scripts/m3/vt1200_xlib.h` with byte pads 0x90/0x8 and `_Static_assert`s, returning the ROM's bit offsets `0x480`/`0x40`; do not assume the host X11R7 `_XDisplay` layout matches the ROM. The full X11R4 `XImage`/`XEvent` headers are M4.
- `XOpenDisplay("0::0")` must run in the loader-created `create_process` child, not the rdebug dispatcher; the M2 `_start`/loader path satisfies this. - `XOpenDisplay("0::0")` must run in the loader-created `create_process` child, not the rdebug dispatcher; the M2 `_start`/loader path satisfies this.
- `XCreateSimpleWindow` takes 9 arguments: `display, parent, x, y, width, height, border_width, border, background`; the first argument is at `FP(-32)`, the ninth at `FP(-288)`; `RETS 2`. - `XCreateSimpleWindow` takes 9 arguments: `display, parent, x, y, width, height, border_width, border, background`; the first argument is at `FP(-32)`, the ninth at `FP(-288)`; `RETS 2`.
@@ -588,7 +588,7 @@ Then read the VRAM pixel inside the window: the window is at `(100,100)`, 400×2
`scripts/m3/dis-roundtrip.sh` adapts the M2 COFF parser and symbol lookup. It must require: `scripts/m3/dis-roundtrip.sh` adapts the M2 COFF parser and symbol lookup. It must require:
- the `_XOpenDisplay`/`_XCreateSimpleWindow`/`_XMapWindow`/`_XSync` `JAUC` stubs at `3F9C2570`/`3FD41B90`/`3FD645B0`/`3FD9F6B0`; - the `_XOpenDisplay`/`_XCreateSimpleWindow`/`_XMapWindow`/`_XSync` `JAUC` stubs at `3F9C2570`/`3FD41B90`/`3FD64F60`/`3FD9F6B0`;
- a `CALLA _XOpenDisplay` with one argument push; `CALLA _XCreateSimpleWindow` with **nine** `MOVE ...,*A14+,1` pushes in order (the first argument at the top of the program stack); `CALLA _XMapWindow` with two; `CALLA _XSync` with two; - a `CALLA _XOpenDisplay` with one argument push; `CALLA _XCreateSimpleWindow` with **nine** `MOVE ...,*A14+,1` pushes in order (the first argument at the top of the program stack); `CALLA _XMapWindow` with two; `CALLA _XSync` with two;
- the `SETF 16,0,0` + `SETF 32,0,1` after each call; `RETS 2` for the nine-argument function; - the `SETF 16,0,0` + `SETF 32,0,1` after each call; `RETS 2` for the nine-argument function;
- the branch code: `CMP`, `JALT`/`JAGT`/`JALE`/`JALO`/`JAHI`, `JAUC`, and the `LBB…` labels (no dot); - the branch code: `CMP`, `JALT`/`JAGT`/`JALE`/`JALO`/`JAHI`, `JAUC`, and the `LBB…` labels (no dot);
@@ -0,0 +1,548 @@
# TMS34010 M4 — Xlib Images, Events, and the termtris Integration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** A compiled C program displays a 1-bit `XImage` through the ROM Xlib, handles real X events and keyboard input, and exits cleanly; the project also builds the WIP `termtris` VT1200 client with this compiler and runs its move/panels/quit acceptance in MAME.
**Architecture:** M4 keeps the M3 asm-only path and TI ABI. It fills the scalar gaps real clients need (zero-extending narrow loads, module-unique private labels, unsigned/`<=` runtime coverage, 32-bit multiply/divide through `rts.lib`), adds function pointers/indirect `CALL`, adds the X11R4-compatible `XImage`/`XEvent`/`XErrorEvent` layouts, corrects the ROM symbol table, and implements the mandatory per-process XG block so a loader child can receive events. Two small clients prove the image and event paths; the pinned `termtris` branch is a third, full-game integration test.
**Tech Stack:** LLVM/Clang 23.1.5 fork (`llvm-tms34010`, branch `m4`), C++17, CMake/Ninja, `llc`, GSPA/GSPLNK 2.00 under DOSBox, COFF loader, MAME, `dis34`, `llvm-lit`/FileCheck, 1987 TI compiler, the pinned `termtris` branch.
**Spec:** `docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md` (§4.2, §7.3.1, §7.4–§7.7, §7.9, M4, M5)
## Global Constraints
- Compiler work happens in **`llvm-tms34010`**, branch `m4`, created from `m3` at `73c85350a401`. Use `build/llvm-clean` and `build/llvm-clean-build`; do not clone another LLVM tree.
- Project plans/tests live in **`tms-c-compiler`**, branch `m4`, created from `m3` at `24f8773` plus the M4 plan commit. `m3` stays the default snapshot until M4 is reviewed; do not merge M2/M3 into `m0`.
- The M3 TI ABI, `.data`, branch/loop, FS0/FS1 and old-STK rules are unchanged. M4 adds: function pointers/indirect calls, zero-extending narrow loads, module-unique private labels, 32-bit multiply/divide, XImage/XEvent/error-handler layouts, the XG block, and the wider Xlib.
- Output remains **assembly only**: `llc -march=tms34010 -filetype=asm`; GSPA/GSPLNK 2.00 under DOSBox; the existing COFF loader. No LLVM object writer/MCCodeEmitter (M5).
- **ROM v2.2 only.** Use the termtris `xlib.h` table and the ROM disassembly. Correct the table: **`XMapWindow = 0x3FD64F60`** (request opcode 8); **`0x3FD645B0` is `XRaiseWindow`** (ConfigureWindow, opcode 12, value-mask `0x40`/`CWStackMode`, value 0/`Above`), not a mapping entry. The M3 table/`xhello` used `0x3FD645B0` under the `XMapWindow` name; its MAME gate still saw a solid-white client area, so re-run that gate with `0x3FD64F60` and record whether the old pass depended on server-side create/configure behavior. The M3 harness/docs/dis34 expectations are updated.
- **Per-process XG block is mandatory before `XOpenDisplay` in a loader child.** The termtris fix: `proc = *(u32*)0x001B3900; pp = *(u32*)(proc+0x100); *(u32*)pp = (u32)zeroed_1KiB;`. Without it `_XEnq` sees `XG == NULL` and `XNextEvent` delivers garbage/nothing. The ROM is untouched.
- Known text/event/GC entries (termtris `xlib.h`): `XOpenDisplay 0x3F9C2570`, `XCreateSimpleWindow 0x3FD41B90`, `XSelectInput 0x3FD97D50`, `XSync 0x3FD9F6B0`, `XFlush 0x3F9B1030`, `XNextEvent 0x3F9C1CF0`, `XLookupString 0x3F9A9C30`, `XCreateGC 0x3F997000`, `XSetForeground 0x3FD99A60`, `XFillRectangle 0x3F99E8F0`, `XDrawString 0x3FDA0000`, `XPending 0x3FD67620`, `XGetInputFocus 0x3FD55210`, `XLoadFont 0x3FD4AE80`, `XCopyArea 0x3FD3E220`, `sleep 0x3FB33A20`. The `XImage` entries (`XCreateImage`, `XPutImage`, `XDestroyImage`, optional `XGetImage`) and `XSetErrorHandler`/`XSetIOErrorHandler` must be extracted/validated before use; do not guess.
- `XMapWindow` is the mapping API for new clients; the M4 image/event clients use the true entry. The termtris client uses `XMapWindow 0x3FD64F60`.
- The **termtris additional integration** is pinned at branch `termtris`, commit `be5b323f5d2cc4f7a7d25e7143c67bd9828ccbdf`. Its `apps/termtris` tree is GPLv3; vendor/read it with `COPYING` intact and test only. Build it with **this compiler**, not its TI r5.01/2ine path. Its acceptance is the proven `play_keys` run: `move`/`panels`/`quit`, `loops >= 100`, `events >= 6`, `focus` = the window, `quit = 1` for `q`, and the captured frames.
- The **primary M4 acceptance** is the image/event client: a 1-bit `XImage` (LSBFirst) appears in MAME, no X errors are reported by the handler, and a real key press is delivered and processed. The termtris run is the additional, full-game integration test.
- `XImage` data is `unsigned char`; zero-extending `unsigned char`/`unsigned short` loads must work. The program stack is A14 (grows up); the system stack A15; `RETS 2/0` and FS0/FS1 apply to indirect calls too.
- X11 headers: use an X11R4-compatible subset (system Xlib/Xutil or a vendored minimal header). `Display.screens` remains byte `0x90` (bit `0x480`); `Screen.root` byte `0x8` (bit `0x40`). Verify the `XImage`/`XEvent`/`XErrorEvent` field offsets with `_Static_assert`s and a TI/host golden before relying on them.
- The M2/M3 tests must stay green. Add `m4-*` lit tests and `scripts/tests/m4/*` gates.
- No XImage/XEvent/the full Xlib beyond the listed calls; no floats/i64/varargs/bitfields/structs by value/native COFF in M4. The termtris compile may expose a gap; fix it or rule it out explicitly in the report.
## Review Focus
1. **Function pointer/indirect call ABI:** `CALL Rs`; the call-site old-STK save iff args; argument order; A8 return; FS0/FS1; the callee `RETS`. A wrong `CALL` target or stack leaves the caller corrupted. Tests in Task 3 (`m4-fnptr`, a C callback) and Task 7 (termtris callbacks).
2. **XImage/XEvent/GC/XErrorEvent layout and semantics:** `XImage.byte_order`/`bitmap_bit_order` LSBFirst, `data`, `bytes_per_line`; `XEvent.type`/`xkey` fields; `XErrorEvent` fields. A wrong field or offset yields a black/garbled image or no key. Tests in Task 4 (static offsets) and Task 6 (image pixel/no-X-errors/key).
3. **The XG block/event loop:** without `vt_install_xg()` before `XOpenDisplay` the loader child receives no events; the termtris tree documents the exact failure. Tests in Task 5 (XG gate) and Tasks 6/7 (keys/focus/quit).
4. **`XMapWindow` vs `XRaiseWindow`:** the M3 table's `XMapWindow` (`0x3FD645B0`) is actually `XRaiseWindow` (ConfigureWindow opcode 12); the real `XMapWindow` is `0x3FD64F60` (opcode 8). Use it for new clients and correct the M3 harness/docs. Tests in Task 5 and the M3 re-run.
5. **Narrow loads/unsigned/multiply/loop prerequisites:** `unsigned char` XImage data, `unsigned short`, `<=`/unsigned branches, and the `-O2` closed-form loop/multiply. A missing feature aborts a real client. Tests in Tasks 1/2 and the termtris run.
6. **The termtris integration:** the pinned branch, the function-pointer callbacks, the event loop, the keys/panels/quit, the screenshots, the `loops/events/focus/quit` markers. Test in Task 7.
7. **X11R4 layout vs the ROM:** the byte/bit `Display`/`Screen` and the `XImage`/`XEvent` fields; a header mismatch produces plausible but wrong data. Tests in Task 4 (static asserts) and Task 6.
---
### Task 1: M4 branches, zero-extending narrow loads, private labels, and unsigned runtime
**Files:**
- Modify: `build/llvm-clean/llvm/lib/Target/TMS34010/TMS34010ISelLowering.cpp`, `TMS34010InstrInfo.td`
- Modify: `TMS34010TargetMachine.cpp` / `TMS34010AsmPrinter.cpp` (private symbol naming)
- Test: `build/llvm-clean/llvm/test/CodeGen/TMS34010/m4-zext.ll`, `m4-private.ll` (or a two-TU shell test)
- Create: `scripts/m4/unsigned.c`, `scripts/m4/private-a.c`, `private-b.c`, `scripts/tests/m4/{unsigned,private}.sh`
**Interfaces:**
- Consumes: M3 target and tests.
- Produces: `ZEXTLOAD i8/i16` selection; module-unique private symbols; runtime `JALS`/`JAHS`/`JANE`/`JAGE`/`JALE`/`JALO`/`JAHI` coverage.
- [ ] **Step 1: Write the failing tests**
`m4-zext.ll`:
```llvm
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @zext8(ptr %p) {
%v = load i8, ptr %p, align 1
%r = zext i8 %v to i32
ret i32 %r
}
define i32 @zext16(ptr %p) {
%v = load i16, ptr %p, align 1
%r = zext i16 %v to i32
ret i32 %r
}
; CHECK-LABEL: _zext8:
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: ANDI 255
; CHECK-LABEL: _zext16:
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK: ANDI 65535
```
The `ANDI` forms are the 1's-complement immediates; the masks must be exact. `m4-private` compiles two TUs with the same private string literal and requires the GSPA link to succeed (no duplicate private label).
`scripts/m4/unsigned.c`:
```c
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n)*0x20u))
int le(int a, int b) { return a <= b; }
unsigned ule(unsigned a, unsigned b) { return a <= b; }
int sum_le(int n) { int s = 0, i; for (i = 0; i <= n; i++) s += i; return s; }
int unsigned_test(void) {
MARK(0) = (unsigned)le(5, 7);
MARK(1) = (unsigned)ule(7u, 5u);
MARK(2) = (unsigned)sum_le(10);
return 0;
}
```
`scripts/m4/private-a.c` and `private-b.c` each define a string literal and a function returning it, e.g. `char *a(void) { return "same"; }`; the linker must resolve both without a duplicate label.
- [ ] **Step 2: Run them red**
```sh
build/llvm-clean-build/bin/llvm-lit -v build/llvm-clean-build/test/CodeGen/TMS34010 --filter='m4-(zext|private)'
```
Expected: FAIL — `Cannot select: ... zext from i8/i16`, and the two-TU private link collides.
- [ ] **Step 3: Implement the zext loads and private naming**
- Set `ZEXTLOAD i8/i16` legal and add patterns: `MOVB` (which sign-extends) + `ANDI 255`, and FS0 `MOVE16` + `ANDI 65535`; reuse `ANDI` (the M4 `.td` fix already complements the immediate). Add the `ANDI` immediate 1's complement if needed.
- Make private/string-literal symbols module-unique so two TUs with the same `.str`/private name do not collide in GSPA's flat symbol table. Keep public names unchanged; the sanitizer remains GSPA-valid. The two-TU test must assemble and link with `No Errors, No Warnings`.
- [ ] **Step 4: Run the MAME unsigned test**
Build `unsigned.c` with `tms-clang -O2` + `llc -O0`, link with the M3 `scripts/m4` harness/entry (a minimal entry calling `_unsigned_test`), and check `le(5,7)=1`, `ule(7,5)=0`, `sum_le(10)=55`. The assembly must contain `JALE`/`JALS` (or the correct unsigned `<=` form) and `JAHS`/`JAHI`.
- [ ] **Step 5: Run the suite and commit**
```sh
build/llvm-clean-build/bin/llvm-lit -v build/llvm-clean-build/test/CodeGen/TMS34010 --filter='(m2|m3|m4)-'
```
Expected: all green (the M3 suite plus the new tests). Commit the compiler changes and the project harness/tests; the report.
### Task 2: 32-bit multiply/divide and the `-O2` loop pipeline
**Files:**
- Modify: `TMS34010InstrInfo.td`, `TMS34010ISelLowering.cpp/.h`, `TMS34010TargetMachine.cpp` (TTI/pass policy)
- Create: `scripts/m4/mul.c`, `scripts/m4/loop.c`, `scripts/tests/m4/mul.sh`
- Link: the 1987 `RTS.LIB` (the `_mul`/`_div`/`_mod` entries) or a target sequence
**Interfaces:**
- Consumes: Task 1 and M3's `JAcc`/`ST`.
- Produces: `MPYS`/`MPYU` 16×16; 32×32 multiply/divide via `rts.lib` libcalls or a sequence; an `-O2` loop policy that does not produce an unsupported i33 multiply.
- [ ] **Step 1: Write the failing tests**
`m4-mul.ll` (or C through `tms-clang`):
```llvm
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @mul(i32 %a, i32 %b) {
%r = mul i32 %a, %b
ret i32 %r
}
; CHECK: MPYS
; CHECK: MPYU
```
`scripts/m4/loop.c`:
```c
int sumto(int n) { int s = 0, i; for (i = 1; i <= n; i++) s += i; return s; }
int squares(int n) { int s = 0, i; for (i = 0; i < n; i++) s += i * i; return s; }
```
Compile with the **default** `tms-clang -O2` (no `-replexitval=never`) and require success + correct MAME results (`sumto(10)=55`, `squares(5)=30`).
- [ ] **Step 2: Run it red**
```sh
scripts/tms-clang -O2 -o /tmp/opencode/m4-mul.ll scripts/m4/mul.c
build/llvm-clean-build/bin/llc -march=tms34010 -O0 -filetype=asm ...
```
Expected: `Cannot select: mul` / `umul_lohi` / the i33 multiply / missing `_mul`.
- [ ] **Step 3: Implement the arithmetic and loop policy**
- Add `MPYS`/`MPYU` 16×16 and the 32×32 multiply/divide/remainder libcalls. Prefer linking the 1987 `RTS.LIB` `_mul`/`_div`/`_mod` entries (reuse the TI runtime; no new libc); wire the `TargetLowering` libcall names and the M4 `link.cmd` (`-l RTS.LIB`). If the runtime entry names differ, extract them from the SDK `RTS.LIB`/map and record them.
- Address the `IndVarSimplify` exit-value i33 multiply: add a target `TargetTransformInfo`/pass policy that makes the closed-form replacement unprofitable for this target, or lower the i33/i64 multiply through the runtime. The M3 `m3` harness may keep `-replexitval=never`, but M4's ordinary `-O2` loop must compile.
- Keep the M3 `shouldFoldConstantShiftPairToMask` behavior; the multiply/divide tests.
- [ ] **Step 4: Run the MAME multiply/loop tests**
`mul(6,7)=42`, `sumto(10)=55`, `squares(5)=30`; the `-O2` pipeline without the exit-value wrapper. Record the `RTS.LIB` link line and the symbols.
- [ ] **Step 5: Run the suite and commit**
Commit the compiler arithmetic/policy and the M4 tests; the report.
### Task 3: Function pointers and indirect calls
**Files:**
- Modify: `TMS34010InstrInfo.td` (`CALL Rs`), `TMS34010ISelLowering.cpp/.h` (`LowerCall` indirect), `TMS34010MCInstLower.cpp` if needed
- Test: `m4-fnptr.ll`, `scripts/m4/fnptr.c`, `scripts/tests/m4/fnptr.sh`
**Interfaces:**
- Consumes: M3 `LowerCall`/`CALLA`/frames; Task 1/2.
- Produces: `CALL Rs` (1-word indirect call; GSPA `CALL $rs`), function addresses as bit addresses, function-pointer parameters/returns, callback ABI.
- [ ] **Step 1: Write the failing test**
`m4-fnptr.ll`:
```llvm
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @inc(i32 %x) {
%r = add i32 %x, 1
ret i32 %r
}
define i32 @apply(ptr %f, i32 %x) {
%r = call i32 %f(i32 %x)
ret i32 %r
}
; CHECK-LABEL: _apply:
; CHECK: CALL A{{[0-9]+}}
; CHECK: RETS 2
```
`scripts/m4/fnptr.c`:
```c
typedef int (*fn)(int);
int inc(int x) { return x + 1; }
int apply(fn f, int x) { return f(x); }
int fnptr_test(void) { return apply(inc, 41); } /* 42 */
```
- [ ] **Step 2: Run it red**
Expected: `M2/M3 supports only direct calls` fatal for the indirect call.
- [ ] **Step 3: Implement indirect calls**
- Define `CALL` (`CALL $rs`, 1 word, `isCall`, `Uses=[A14]`, `Defs=[A8,A14]`, 2 bytes) and the asm printer.
- Extend `LowerCall`: accept a `RegisterSDNode`/`CopyFromReg` callee; emit the same old-STK/argument/`CALL Rs`/FS0-FS1/`A8` sequence as `CALLA`; reject only unsupported callee shapes. A function pointer is a 32-bit bit address; `MOVI _inc, A?` or `MOVI 0xADDR, A?` materializes it.
- Keep the direct `CALLA` path and the `getCallPreservedMask`.
- Test a function pointer from a global symbol and from an absolute address (`((fn)0x3F...)`), the latter used by the Xlib header.
- [ ] **Step 4: Run the MAME callback test**
`fnptr_test()=42`; the termtris function-pointer callbacks (`term_reset` etc.) are the integration case. Record the `CALL` encoding/`dis34`.
- [ ] **Step 5: Commit**
Commit the compiler/test changes; the report.
### Task 4: X11R4-compatible headers and XImage/XEvent/error-event layouts
**Files:**
- Create: `scripts/m4/x11m4.h` (or vendor an X11R4 subset), `scripts/m4/layout.c`, `scripts/m4/layout-ti.c`
- Create: `scripts/tests/m4/layout.sh`
- Reuse: the Task 3 `vt1200_xlib.h` byte/bit layout
**Interfaces:**
- Consumes: the M3 `Display`/`Screen` layout (byte 0x90/0x8).
- Produces: `XImage`, `XEvent`/`XKeyEvent`/`XExposeEvent`/`XFocusChangeEvent`/`XButtonEvent`, `XErrorEvent`, `GC`/`Visual`, the function prototypes/constants, and verified field offsets.
- [ ] **Step 1: Write the failing layout test**
`scripts/m4/x11m4.h` contains at least:
```c
typedef 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 funcs *f;
} XImage;
typedef union _XEvent {
int type;
struct { int type; unsigned long serial; int send_event; void *display;
unsigned long window, root, subwindow; unsigned long time;
int x, y, x_root, y_root; unsigned state; unsigned keycode;
int same_screen; } xkey;
struct { int type; unsigned long serial; int send_event; void *display;
unsigned long window; int x, y, width, height, count; } xexpose;
/* xfocus/xbutton/... as needed */
} XEvent;
typedef struct _XErrorEvent {
int type;
void *display;
unsigned long resourceid, serial;
unsigned char error_code, request_code, minor_code;
} XErrorEvent;
```
`scripts/m4/layout.c`:
```c
#include "x11m4.h"
_Static_assert(__builtin_offsetof(Display, screens) == 0x90, "...");
_Static_assert(__builtin_offsetof(Screen, root) == 0x8, "...");
_Static_assert(__builtin_offsetof(XImage, data) == 20, "...");
_Static_assert(__builtin_offsetof(XImage, byte_order) == 24, "...");
_Static_assert(__builtin_offsetof(XImage, bytes_per_line) == 40, "...");
_Static_assert(__builtin_offsetof(XEvent, type) == 0, "...");
_Static_assert(sizeof(XEvent) >= 96, "...");
```
Use the actual 32-bit X11R4/system header field offsets; if the system header's `XImage`/`XEvent` match the ROM, assert them; otherwise document the ROM-verified subset. The `XImage` size must cover what `XCreateImage` returns; the `XEvent` buffer is the termtris `vt_u32 evbuf[24]` (96 bytes).
- [ ] **Step 2: Run it red/verify**
Compile with `scripts/tms-clang -O0` + `llc`; the static asserts must pass. If a field offset differs, fix the header (or the target if the packed struct is wrong) and report it. Do not use the host 64-bit layout without the 32-bit type mapping.
- [ ] **Step 3: Build the TI/host golden**
Compile `layout-ti.c` (the `Display`/`Screen`/`XImage`/`XEvent` offset functions) with the 1987 compiler; store the asm; require the same constants. The 1987 compiler's `&` is bit-addressed; convert to byte offsets consistently.
- [ ] **Step 4: Run the layout gate and commit**
`scripts/tests/m4/layout.sh` compiles the static asserts, checks the target and TI golden constants, and fails loudly. Commit the header/layout test.
### Task 5: ROM symbols, `XMapWindow` correction, and the XG block
**Files:**
- Modify: `symbols/vt1200-v22.syms`, `symbols/README.md`
- Modify: `scripts/m3/xhello.c`/`scripts/m3/dis-roundtrip.sh`/M3 docs (the `XMapWindow`/`XRaiseWindow` correction)
- Create: `scripts/m4/xgfix.c`, `scripts/m4/validate-syms.sh`, `scripts/tests/m4/symbols.sh`
**Interfaces:**
- Consumes: the termtris `xlib.h` table and the M3 loader.
- Produces: the corrected/extended v2.2 symbol table; `vt_install_xg()`; a symbol-validation gate.
- [ ] **Step 1: Correct and extend the symbol table**
- `XMapWindow = 0x3FD64F60`; add `XRaiseWindow = 0x3FD645B0` (not `XMapRaised`; the ROM entry sends ConfigureWindow with `CWStackMode=Above`). If a client wants map-and-raise, call `XMapWindow` then `XRaiseWindow`.
- Add the termtris-proven entries: `XSelectInput 0x3FD97D50`, `XCreateGC 0x3F997000`, `XSetForeground 0x3FD99A60`, `XFillRectangle 0x3F99E8F0`, `XDrawString 0x3FDA0000`, `XNextEvent 0x3F9C1CF0`, `XLookupString 0x3F9A9C30`, `XFlush 0x3F9B1030`, `XPending 0x3FD67620`, `XGetInputFocus 0x3FD55210`, `XLoadFont 0x3FD4AE80`, `XCopyArea 0x3FD3E220`, `sleep 0x3FB33A20`.
- Note: `XLoadFont`/`XCopyArea` are **not** in the pinned termtris `xlib.h`; they are ROM-validated by their request builders (OpenFont opcode 45 and CopyArea opcode 62), as `symbols/README.md` records.
- Extract/validate the `XImage` entries (`XCreateImage`, `XPutImage`, `XDestroyImage`, optional `XGetImage`) and `XSetErrorHandler`/`XSetIOErrorHandler` from the ROM (Ghidra/`ptr_constants.py`, the `XPutImage`/`XGetImage` strings at `0x3F9D0E80`/`0x3FD567A0`). Do not guess an address; if an entry cannot be validated, report it and use the fallback (the termtris text client) for that part.
- Update `scripts/m3/xhello.c`/the comments to call the true `XMapWindow` (`0x3FD64F60`), the M3 `dis-roundtrip.sh` stub expectation, and the M3 docs. Do not use `0x3FD645B0` as a mapping call. Re-run the M3 MAME/dis34 gates; the window must still appear, and record whether the old `0x3FD645B0` pass depended on server-side create/configure behavior.
- [ ] **Step 2: Implement the XG fix**
`scripts/m4/xgfix.c` (the termtris fix):
```c
int vt_install_xg(void) {
unsigned int proc = *(volatile unsigned int *)0x001B3900u;
if (proc < 0x100u || proc > 0x01FFFFFFu) return 0;
unsigned int pp = *(volatile unsigned int *)(proc + 0x100u);
if (pp < 0x100u || pp > 0x01FFFFFFu) return 0;
static unsigned int xg[256]; /* 1 KiB zeroed .bss */
*(volatile unsigned int *)pp = (unsigned int)xg;
return 1;
}
```
Call it **before** `XOpenDisplay` in the event/image clients and the termtris build.
- [ ] **Step 3: Validate the symbols**
`scripts/m4/validate-syms.sh` checks the v2.2 entries are in ROM range, 16-bit aligned, and start with a plausible function prologue (`dis34`); the termtris `xlib.h` is the evidence. Add a test that `0x3FD645B0` is reported as `XRaiseWindow` (ConfigureWindow opcode 12), so the correction is pinned.
- [ ] **Step 4: Run the M3 correction and the symbol gate**
Re-run `scripts/tests/m3/run.sh`/`dis-roundtrip.sh`; the M3 window still appears with the true `XMapWindow 0x3FD64F60`. Run `scripts/tests/m4/symbols.sh`; all entries validated. Commit.
### Task 6: The M4 image/event client and the MAME image/key gate
**Files:**
- Create: `scripts/m4/xshow.c`, `scripts/m4/entry.asm`, `scripts/m4/link.cmd`, `scripts/m4/run.sh`, `scripts/m4/m4-check.lua`, `scripts/m4/dis-roundtrip.sh`
- Create: `scripts/tests/m4/run.sh`, `scripts/tests/m4/dis-roundtrip.sh`
- Reuse: `scripts/m4/x11m4.h`, `xgfix.c`, `validate-syms.sh`
**Interfaces:**
- Consumes: Tasks 1–5.
- Produces: `M4 MAME: PASS` — a 1-bit `XImage` displayed, no X errors, a real key press processed, focus/window markers, and the window/image pixel evidence.
- [ ] **Step 1: Write the image/event client**
`scripts/m4/xshow.c` (the `xshow` shape, VT1200 Xlib):
```c
#include "x11m4.h"
static volatile unsigned int *MARK(unsigned n) { return (volatile unsigned int *)(0x018A8000u + n*0x20u); }
static int xerrors;
static int on_error(void *d, XErrorEvent *e) { xerrors++; return 0; }
int xshow(void) {
if (!vt_install_xg()) return -1;
Display *d = XOpenDisplay("0::0");
MARK(0)[0] = (unsigned)d; if (!d) return -1;
Window w = XCreateSimpleWindow(d, vt_root(d), 20, 20, 640, 480, 0, 0, 0);
MARK(1)[0] = (unsigned)w;
XSelectInput(d, w, ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask);
GC gc = XCreateGC(d, w, 0, 0);
XSetForeground(d, gc, 1);
/* 1-bit checker image, LSBFirst */
int bpl = ((640 + 31) / 32) * 4;
static unsigned char data[480 * 80]; /* 640/8 = 80 bytes/row */
for (int y = 0; y < 480; y++) for (int x = 0; x < 640; x++)
if (((x/32) ^ (y/32)) & 1) data[y*80 + (x>>3)] |= 1u << (x & 7);
XImage *img = XCreateImage(d, 0, 1, XYBitmap, 0, (char *)data, 640, 480, 32, bpl);
MARK(2)[0] = (unsigned)img; if (!img) return -1;
img->byte_order = LSBFirst; img->bitmap_bit_order = LSBFirst;
XSetErrorHandler(on_error);
XMapWindow(d, w);
XPutImage(d, w, gc, img, 0, 0, 0, 0, 640, 480);
XSync(d, 0);
for (;;) {
XEvent ev;
while (XPending(d)) {
XNextEvent(d, &ev);
MARK(4)[0]++;
if (ev.type == KeyPress) {
char buf[8]; unsigned long ks = 0;
int n = XLookupString(&ev.xkey, buf, 8, &ks, 0);
MARK(5)[0] = (unsigned)ev.xkey.keycode;
MARK(6)[0] = (unsigned)ks;
if (buf[0] == 'q') { MARK(3)[0] = 1; return 0; }
} else if (ev.type == Expose) {
XPutImage(d, w, gc, img, 0, 0, 0, 0, 640, 480);
}
}
MARK(7)[0] = (unsigned)xerrors;
XFlush(d);
sleep(5); /* the ROM sleep; keep the process alive */
}
}
```
Use the exact Task 4 field names/offsets; `XCreateImage`'s `visual` may be 0 for a 1-bit default; if the ROM requires the default visual, use `DefaultVisual` or the display's default; verify. The window is at (20,20), 640×480; the image checker is LSB-first; the key `q` quits. The `XSetErrorHandler` is a function pointer (Task 3); the `on_error` increments.
- [ ] **Step 2: Entry/link/runner**
`scripts/m4/entry.asm` sets FS0/FS1, STK/FP, calls `_xshow`, `rets`; `_cstack` `.bss` 32768 bits. `link.cmd` places `.text`/`.data` > prog at `018A0000h`, `.bss` > ram at `018A9000h`; link `RTS.LIB` if the compiler/libcall path needs it. `scripts/m4/run.sh` compiles `xshow.c`/`layout.c`/`xgfix.c` with `tms-clang -O2` + `llc -O0`, generates the ROM stubs/function-pointer addresses, GSPA/GSPLNK under DOSBox, loads in MAME with `VT1200_SKIP_LEVEL1=1`, the loader, `LOADER_AT=2500`, `LOADER_END=3400` (adjust), the `VT1200_MONITOR=VR319` pin, and `m4-check.lua`.
`scripts/m4/m4-check.lua` reads the markers: display/window/image nonzero, `xerrors==0`, `events>0`, the keycode/keysym for a `q` injected key, the quit marker, and the image checker at several window pixels (e.g. (30,30) and (50,50) alternate; the checker pattern at 32-pixel cells). Use the VRAM base bit `0x04000000`, VR319 2048-bit pitch, LSB-first; the window is at (20,20). Require `M4 MAME: PASS`; markers alone are not enough.
- [ ] **Step 3: Write the `dis34` round-trip**
`scripts/m4/dis-roundtrip.sh` checks the linked bytes for `CALL`/`CALLA` (the indirect call), the `XOpenDisplay`/`XCreateSimpleWindow`/`XSelectInput`/`XCreateGC`/`XSetForeground`/`XCreateImage`/`XSetErrorHandler`/`XMapWindow`/`XPutImage`/`XSync`/`XNextEvent`/`XLookupString` calls, the 9-argument `XCreateSimpleWindow` order, the `XSetErrorHandler` callback pointer, the `SETF`/`RETS`/old-STK rules, and the checker data in `.data`/`.bss`. The image client's `CALL` encoding is the Task 3 contract.
- [ ] **Step 4: Run the gates**
```sh
scripts/tests/m4/run.sh
scripts/tests/m4/dis-roundtrip.sh
```
Expected: `M4 MAME: PASS` with the image checker, `xerrors=0`, a real key event, and the window; `M4 dis34 round-trip: PASS`.
- [ ] **Step 5: Commit**
Commit the client/harness/tests; the report.
### Task 7: The pinned `termtris` additional integration test
**Files:**
- Fetch: `https://git.wut.ee/opencode.energia.arti.ee/vt1200.git`, branch `termtris`, commit `be5b323f5d2cc4f7a7d25e7143c67bd9828ccbdf`, into `vendor/vt1200-termtris` (test-only, GPLv3 `COPYING` intact)
- Create: `scripts/m4/termtris/build.sh`, `scripts/m4/termtris/play_keys.lua` (adapted from the vendored `apps/termtris/tests/play_keys.lua`), `scripts/m4/termtris/play_keys.sh`
- Create: `scripts/tests/m4/termtris.sh`
**Interfaces:**
- Consumes: Tasks 1–6; the termtris `apps/termtris/src/vt1200/{xlib.h,xgfix.c,main.c,x11.c,compat.c}`, `src/game.c`, `src/pieces.h`, `src/game.h`, `src/term.h`, `src/vt1200/scoredb.c`, `src/vt1200/stdio.h`, `src/vt1200/inttypes.h`.
- Produces: the termtris board, panels, move, and quit in MAME, with the proven `play_keys` marker acceptance.
- [ ] **Step 1: Fetch and pin**
```sh
git clone --depth 1 --branch termtris \
https://git.wut.ee/opencode.energia.arti.ee/vt1200.git \
vendor/vt1200-termtris
git -C vendor/vt1200-termtris rev-parse HEAD # be5b323...
```
Keep `vendor/` ignored; the project test references the pinned checkout. The termtris `VT1200.md`/spec/plan and `apps/termtris/tests/play_keys.{sh,lua}` are the reference.
- [ ] **Step 2: Build termtris with this compiler**
`scripts/m4/termtris/build.sh` compiles the termtris sources with `tms-clang -O2 -I vendor/vt1200-termtris/apps/termtris/src -I .../src/vt1200` + `llc -O0`, concatenates the M4 entry (`_start` calls `_main`; `.data` is copied by the loader; `_cstack`) with the termtris `.s` and the function-pointer Xlib header, links with GSPA/GSPLNK (RTS.LIB for multiply/divide), and emits `build/termtris.coff`/`.map`.
- [ ] **Step 3: Run the play_keys acceptance**
Adapt the termtris `play_keys.lua`/`play_keys.sh`: the `move`/`panels`/`quit` modes, the tablet click + `A/D/S/W/0/P/R/H/Q` injection, the `DIAG_LOOPS/EVENTS/FOCUS/REV/QUIT/TICK` addresses from the M4 `.map`, `LOADER_AT=2500`, `LOADER_END=3200`, `LOADER_SNAP=1`, `VT1200_SKIP_LEVEL1=1`, the loader, and the M4 COFF. Require:
- `loops >= 100` and `events >= 6` for `move`/`panels`;
- `quit = 0` for `move`/`panels`, `quit = 1` for `quit`;
- `focus` = the termtris window (nonzero);
- the board/panels/move/quit screenshots.
The termtris's proven values were `move loops=333 events=18`, `panels loops=323 events=12`, `quit loops=277 events=10 quit=1`; the M4 run may differ in counts, but the thresholds and quit/focus must hold.
- [ ] **Step 4: Record and commit**
The termtris build proves: function pointers (`term_*` callbacks/absolute Xlib pointers), indirect calls, `XSelectInput`/`XNextEvent`/`XLookupString`, the XG block, `XCreateGC`/`XSetForeground`/`XFillRectangle`/`XDrawString`, the timer/sleep, multiply/divide, `sprintf`/compat, and the loader/child. If a termtris feature is genuinely outside M4, report it with a ruling and a specific follow-up; do not weaken the thresholds or fake the run.
Commit the M4 termtris scripts/tests and a short report; keep the vendored tree ignored.
### Task 8: Differential/dis34, docs, and push
**Files:**
- Create: `scripts/tests/m4/diff/{ti-fnptr.c,ti-fnptr.s,ti-image.c,ti-image.s,check.sh}`
- Modify: `README.md` (project), `build/llvm-clean/TMS34010.md` (compiler), `symbols/README.md`
- Push: compiler `m4`, project `m4`
**Interfaces:**
- Consumes: all previous tasks.
- Produces: stored TI goldens for function-pointer/call/image/struct-offset shape, M4 docs, and pushed `m4` branches.
- [ ] **Step 1: Produce the TI goldens**
Compile K&R/ANSI C for a function-pointer apply/callback and the Xlib image-call shape with the 1987 compiler; store `ti-fnptr.s`/`ti-image.s`; no DOSBox in CI. The check compares ABI shape: `CALL Rs`/indirect call, the old-STK push, the argument/return/`RETS 2`, the `XCreateImage`/`XPutImage` 10/9 argument pushes, the `Display`/`Screen`/`XImage` offsets (byte 0x90/0x8; the XImage fields).
- [ ] **Step 2: Write the diff check**
`scripts/tests/m4/diff/check.sh` generates our asm with `tms-clang` + `llc` and greps the stored goldens; accept TI spelling variants; fail loudly. Add negative controls for the `CALL`/the image argument order.
- [ ] **Step 3: Update the docs**
README M4: function pointers/indirect calls; zext narrow loads; multiply/divide/`rts.lib`; the image/event clients; the XG fix; the corrected `XMapWindow`/`XRaiseWindow`; the `XImage`/`XEvent` layouts; the termtris additional integration and its pinned branch; commands; M4 non-goals (XImage/XEvent beyond the tested calls, native COFF, floats/i64/varargs/structs by value; the M4/M5 boundary). TMS34010.md: the `CALL Rs` ABI, function pointers, zext patterns, multiply/divide libcalls, the TTI/loop policy, the XImage/XEvent lowering, the XG/ROM notes. symbols README: the corrected/extended v2.2 entries and the evidence.
- [ ] **Step 4: Run every M4 gate**
```sh
ninja -C build/llvm-clean-build llc opt clang llvm-config FileCheck count not
build/llvm-clean-build/bin/llvm-lit -v build/llvm-clean-build/test/CodeGen/TMS34010 --filter='(m2|m3|m4)-'
scripts/tests/m4/run.sh
scripts/tests/m4/dis-roundtrip.sh
scripts/tests/m4/layout.sh
scripts/tests/m4/symbols.sh
scripts/tests/m4/termtris.sh
scripts/tests/m4/diff/check.sh
tools/tms-bitaddr/tests/run.sh
scripts/tests/m3/run.sh
scripts/tests/m3/dis-roundtrip.sh
```
All green; the M3 gates still pass with the corrected `XMapWindow`/`XRaiseWindow`.
- [ ] **Step 5: Commit and push**
```sh
git -C build/llvm-clean add llvm/lib/Target/TMS34010 llvm/test/CodeGen/TMS34010 TMS34010.md
git -C build/llvm-clean commit -m "tms34010: M4 function pointers, XImage/events and termtris support"
git -C build/llvm-clean push origin m4
git add README.md symbols scripts/m4 scripts/tests/m4 scripts/m3
git commit -m "tests: M4 Xlib images, events and the termtris integration"
git push origin m4
```
Do not force-push, merge into `m0`, or change the default; the controller updates the git.wut.ee default to `m4` after the final review if the user asks.
---
## Execution Handoff
M4 closes the remaining Xlib path to a real image/event client and a full-game integration. M5 owns the native COFF/MCCodeEmitter, floats, varargs, bitfields, `long long`, the `JR`/branch-relaxation code-size pass, MMTM/MMFM, and deeper libc/runtime coverage. The compiler repo is canonical for target code; this project repo holds the plans, wrappers, symbols and integration tests. The pinned `termtris` tree is test-only (GPLv3), not part of the compiler or project distribution.
+3 -2
View File
@@ -7,6 +7,7 @@
# VT1200 tree's dis34, and checks that GSPA encoded the compiler output: # VT1200 tree's dis34, and checks that GSPA encoded the compiler output:
# #
# _XOpenDisplay/_XCreateSimpleWindow/_XMapWindow/_XSync JAUC <ROM bit addr> # _XOpenDisplay/_XCreateSimpleWindow/_XMapWindow/_XSync JAUC <ROM bit addr>
# (XMapWindow is 3FD64F60, MapWindow opcode 8; 3FD645B0 is XRaiseWindow)
# _xhello: 1 push -> CALLA _XOpenDisplay, 9 pushes -> CALLA # _xhello: 1 push -> CALLA _XOpenDisplay, 9 pushes -> CALLA
# _XCreateSimpleWindow (arguments 1,0,2,200,400,100,100,root, # _XCreateSimpleWindow (arguments 1,0,2,200,400,100,100,root,
# display in that order, display pushed last), 2 -> _XMapWindow, # display in that order, display pushed last), 2 -> _XMapWindow,
@@ -256,8 +257,8 @@ expect_at "_XOpenDisplay" "$XOPEN" "JAUC 3F9C2570" \
'jauc[[:space:]]+0x3f9c2570' 'jauc[[:space:]]+0x3f9c2570'
expect_at "_XCreateSimpleWindow" "$XCREATE" "JAUC 3FD41B90" \ expect_at "_XCreateSimpleWindow" "$XCREATE" "JAUC 3FD41B90" \
'jauc[[:space:]]+0x3fd41b90' 'jauc[[:space:]]+0x3fd41b90'
expect_at "_XMapWindow" "$XMAP" "JAUC 3FD645B0" \ expect_at "_XMapWindow" "$XMAP" "JAUC 3FD64F60" \
'jauc[[:space:]]+0x3fd645b0' 'jauc[[:space:]]+0x3fd64f60'
expect_at "_XSync" "$XSYNC" "JAUC 3FD9F6B0" \ expect_at "_XSync" "$XSYNC" "JAUC 3FD9F6B0" \
'jauc[[:space:]]+0x3fd9f6b0' 'jauc[[:space:]]+0x3fd9f6b0'
+3
View File
@@ -18,6 +18,9 @@
# 018A80E0 layout_screens() == 0x480 # 018A80E0 layout_screens() == 0x480
# 018A8100 layout_root() == 0x40 # 018A8100 layout_root() == 0x40
# #
# XMapWindow is the true MapWindow entry 3FD64F60 (request opcode 8);
# 3FD645B0 is XRaiseWindow (ConfigureWindow opcode 12), not a mapping call.
#
# and, decisively, the full all-white VRAM word at (150,150) and another at # and, decisively, the full all-white VRAM word at (150,150) and another at
# (250,200) inside the mapped window. A markers-only or root-window bit is a # (250,200) inside the mapped window. A markers-only or root-window bit is a
# failure. # failure.
+7 -3
View File
@@ -3,8 +3,12 @@
# client must exist in the v2.2 table, sit in the ROM's bit range, be 16-bit # client must exist in the v2.2 table, sit in the ROM's bit range, be 16-bit
# aligned, and disassemble to a plausible function prologue. # aligned, and disassemble to a plausible function prologue.
# #
# This is a regression check, not a re-extraction: dwt/gui/xhello.asm is the # This is a regression check, not a re-extraction: dwt/gui/xhello.asm and the
# evidence that these entries work. M4 extends the table for XImage/XEvent. # termtris xlib.h are the evidence that these entries work. XMapWindow is the
# true map entry (MapWindow request opcode 8); 0x3FD645B0 is XRaiseWindow
# (ConfigureWindow opcode 12), never a mapping call. M4 extends the table for
# XImage/XEvent and validates every entry independently in
# scripts/m4/validate-syms.sh.
# #
# scripts/m3/validate-syms.sh [symbols/vt1200-v22.syms] # scripts/m3/validate-syms.sh [symbols/vt1200-v22.syms]
# #
@@ -55,7 +59,7 @@ check_sym() { # check_sym NAME EXPECTED_HEX
check_sym XOpenDisplay 0x3F9C2570 check_sym XOpenDisplay 0x3F9C2570
check_sym XCreateSimpleWindow 0x3FD41B90 check_sym XCreateSimpleWindow 0x3FD41B90
check_sym XMapWindow 0x3FD645B0 check_sym XMapWindow 0x3FD64F60
check_sym XSync 0x3FD9F6B0 check_sym XSync 0x3FD9F6B0
echo "m3 validate-syms: PASS (four v2.2 Xlib entries in ROM, aligned, prologues valid)" echo "m3 validate-syms: PASS (four v2.2 Xlib entries in ROM, aligned, prologues valid)"
+5
View File
@@ -1,5 +1,10 @@
#include "vt1200_xlib.h" #include "vt1200_xlib.h"
/*
* XMapWindow maps with the true MapWindow entry (0x3FD64F60, request opcode
* 8), not 0x3FD645B0: that address is XRaiseWindow (ConfigureWindow opcode
* 12, CWStackMode=Above).
*/
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n) * 0x20u)) #define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n) * 0x20u))
int xhello(void) { int xhello(void) {
+312
View File
@@ -0,0 +1,312 @@
#!/bin/sh
# M4 dis34 round-trip for the image/event client.
#
# scripts/m4/dis-roundtrip.sh
#
# Parses build/m4-xshow/m4.coff, disassembles its linked .text at 018A0000
# with dis34, and checks that GSPA encoded:
#
# - every ROM Xlib import as JAUC <bit address> (the v2.2 table);
# - the image client's direct calls: XOpenDisplay, XCreateSimpleWindow,
# XSelectInput, XCreateGC, XSetForeground, XCreatePixmap, XCreateImage,
# XSetErrorHandler, XMapWindow, XRaiseWindow, XSetInputFocus, XPutImage,
# XCopyArea, XFillRectangle, XSync, XPending, XNextEvent, XLookupString,
# XFlush, XGetInputFocus;
# - the ROM sleep through a function pointer (CALL Rs, encoded 0x0920), the
# Task 3 indirect-call contract;
# - the nine-argument XCreateSimpleWindow order 0,0,0,480,640,20,20,root,
# display (display pushed last);
# - the XSetErrorHandler callback pointer (MOVI _on_error);
# - SETF 16/32 after every call and the RETS/old-STK rules;
# - the 480x80-byte checker .bss and its word-fill loop.
#
# Overridable: M4_COFF M4_ASM DIS34
set -eu
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
WORK=${M4_WORK:-$ROOT/build/m4-xshow}
COFF=${M4_COFF:-$WORK/m4.coff}
ASM=${M4_ASM:-$WORK/m4.asm}
LST=${M4_LST:-$WORK/m4.lst}
DIS34=${DIS34:-$ROOT/vendor/vt1200/tools/smart34010/dis34}
fail() {
echo "M4 dis34 round-trip: FAIL: $*" >&2
exit 1
}
[ -f "$COFF" ] || fail "linked COFF not found: $COFF (run scripts/m4/run.sh)"
[ -f "$ASM" ] || fail "linked assembly not found: $ASM"
[ -f "$LST" ] || fail "GSPA listing not found: $LST"
[ -x "$DIS34" ] || fail "dis34 not found or not executable: $DIS34"
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m4-dis34.XXXXXX")
FAIL_LOG=${M4_DIS_FAIL_LOG:-${TMPDIR:-/tmp}/m4-dis34-failure.txt}
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
read_u8s() { od -An -v -tu1 -j "$1" -N "$2" "$COFF"; }
u16() { read_u8s "$1" 2 | awk '{ print $1 + 256 * $2 }'; }
u32() { read_u8s "$1" 4 | awk '{ print $1 + 256 * $2 + 65536 * $3 + 16777216 * $4 }'; }
echo "== parse the COFF section table ($COFF)"
nscns=$(u16 2)
opthdr=$(u16 16)
[ "$nscns" -gt 0 ] 2>/dev/null || fail "COFF has no sections"
[ "$opthdr" -gt 0 ] 2>/dev/null || fail "COFF has no optional header"
TEXT_ADDR=; TEXT_BITS=; TEXT_PTR=
DATA_ADDR=; DATA_BITS=; DATA_PTR=
BSS_ADDR=; BSS_BITS=
sh_base=$((20 + opthdr))
i=0
while [ "$i" -lt "$nscns" ]; do
o=$((sh_base + i * 40))
name=$(dd if="$COFF" bs=1 skip="$o" count=8 2>/dev/null | tr -d '\000')
case "$name" in
.text) TEXT_ADDR=$(u32 $((o + 8))); TEXT_BITS=$(u32 $((o + 16))); TEXT_PTR=$(u32 $((o + 20))) ;;
.data) DATA_ADDR=$(u32 $((o + 8))); DATA_BITS=$(u32 $((o + 16))); DATA_PTR=$(u32 $((o + 20))) ;;
.bss) BSS_ADDR=$(u32 $((o + 8))); BSS_BITS=$(u32 $((o + 16))) ;;
esac
i=$((i + 1))
done
[ -n "$TEXT_ADDR" ] || fail "COFF has no .text section"
[ "$TEXT_BITS" -gt 0 ] 2>/dev/null || fail ".text is empty"
[ $((TEXT_BITS % 8)) -eq 0 ] || fail ".text size $TEXT_BITS bits is not whole bytes"
[ -n "$DATA_ADDR" ] || fail "COFF has no .data section"
[ -n "$BSS_ADDR" ] || fail "COFF has no .bss section"
[ "$BSS_BITS" -ge $((0x4B000)) ] 2>/dev/null \
|| fail ".bss is $BSS_BITS bits, too small for the 640x480 image (0x4B000 bits)"
TEXT_HEX=$(printf '%08X' "$TEXT_ADDR")
ENTRY=$(u32 $((20 + 16)))
ENTRY_HEX=$(printf '%08X' "$ENTRY")
TEXT_BYTES=$((TEXT_BITS / 8))
[ "$TEXT_HEX" = "018A0000" ] || fail ".text is linked at $TEXT_HEX, want 018A0000"
[ "$ENTRY_HEX" = "018A0000" ] || fail "COFF entry point is $ENTRY_HEX, want 018A0000"
BSS_HEX=$(printf '%08X' "$BSS_ADDR")
[ "$BSS_HEX" = "01A00000" ] || fail ".bss is at $BSS_HEX, want 01A00000"
echo " .text at $TEXT_HEX: $TEXT_BITS bits ($TEXT_BYTES bytes)"
echo " .data at $(printf '%08X' "$DATA_ADDR"): $DATA_BITS bits"
echo " .bss at $BSS_HEX: $BSS_BITS bits"
echo " entry point $ENTRY_HEX"
dd if="$COFF" of="$TMP/text.bin" bs=1 skip="$TEXT_PTR" count="$TEXT_BYTES" 2>/dev/null
[ "$(wc -c < "$TMP/text.bin")" -eq "$TEXT_BYTES" ] || fail "could not extract .text"
echo "== resolve linked symbols"
symptr=$(u32 8)
nsyms=$(u32 12)
[ "$nsyms" -gt 0 ] 2>/dev/null || fail "COFF has no symbol table"
sym_addr() {
_want=$1
_strtab=$((symptr + nsyms * 18))
_i=0
while [ "$_i" -lt "$nsyms" ]; do
_o=$((symptr + _i * 18))
if [ "$(u32 "$_o")" -eq 0 ]; then
_soff=$(u32 $((_o + 4)))
_n=$(dd if="$COFF" bs=1 skip=$((_strtab + _soff)) count=64 2>/dev/null |
tr '\000' '\n' | head -1)
else
_n=$(dd if="$COFF" bs=1 skip="$_o" count=8 2>/dev/null | tr -d '\000')
fi
if [ "$_n" = "$_want" ]; then
u32 $((_o + 8))
return 0
fi
_aux=$(od -An -v -tu1 -j $((_o + 17)) -N 1 "$COFF" | awk '{ print $1 }')
_i=$((_i + 1 + _aux))
done
return 1
}
get_sym() {
_a=$(sym_addr "$1") || fail "COFF symbol table has no $1"
printf '%s' "$_a"
}
XOPEN=$(get_sym _XOpenDisplay)
XCREATE=$(get_sym _XCreateSimpleWindow)
XSELECT=$(get_sym _XSelectInput)
XGC=$(get_sym _XCreateGC)
XSETFG=$(get_sym _XSetForeground)
XCPM=$(get_sym _XCreatePixmap)
XCI=$(get_sym _XCreateImage)
XERR=$(get_sym _XSetErrorHandler)
XMAP=$(get_sym _XMapWindow)
XRAISE=$(get_sym _XRaiseWindow)
XSETFOCUS=$(get_sym _XSetInputFocus)
XPUT=$(get_sym _XPutImage)
XCOPY=$(get_sym _XCopyArea)
XFILL=$(get_sym _XFillRectangle)
XSYNC=$(get_sym _XSync)
XPEND=$(get_sym _XPending)
XNEXT=$(get_sym _XNextEvent)
XLOOKUP=$(get_sym _XLookupString)
XFLUSH=$(get_sym _XFlush)
XGETFOCUS=$(get_sym _XGetInputFocus)
XSLEEP=$(get_sym _sleep)
XBLIT=$(get_sym _blit_copy)
XSHOW=$(get_sym _xshow)
echo "== disassemble the emitted .text with dis34"
"$DIS34" -b "$TEXT_HEX" -l -o "$TMP/text.bin" > "$TMP/dis34.txt" 2> "$TMP/dis34.err" \
|| { cat "$TMP/dis34.err" >&2; fail "dis34 failed"; }
[ -s "$TMP/dis34.txt" ] || fail "dis34 produced no output"
match_at() {
_file=$1; _addr=$2; _pat=$3
_laddr=$(printf '%08x' "$_addr")
grep -Eiq "^${_laddr}:[[:space:]]+${_pat}([[:space:]]*(;|$))" "$_file"
}
expect_at() {
_label=$1; _addr=$2; _want=$3; _pat=$4
_laddr=$(printf '%08x' "$_addr")
if match_at "$TMP/dis34.txt" "$_addr" "$_pat"; then
echo " ok $_label @ 0x$_laddr: $_want"
return 0
fi
{
echo "M4 dis34 round-trip: FAIL"
echo " $_label at bit 0x$_laddr: expected $_want"
awk -v a="$_laddr" '{ low=tolower($0) } low ~ ("^" a ":") { seen=1 } seen { print " " $0; n++ } seen && n>=4 { exit }' "$TMP/dis34.txt"
cp "$TMP/dis34.txt" "$FAIL_LOG" 2>/dev/null || true
} >&2
exit 1
}
echo "== check the ROM import stubs"
expect_at _XOpenDisplay "$XOPEN" "JAUC 3F9C2570" 'jauc[[:space:]]+0x3f9c2570'
expect_at _XCreateSimpleWindow "$XCREATE" "JAUC 3FD41B90" 'jauc[[:space:]]+0x3fd41b90'
expect_at _XSelectInput "$XSELECT" "JAUC 3FD97D50" 'jauc[[:space:]]+0x3fd97d50'
expect_at _XCreateGC "$XGC" "JAUC 3F997000" 'jauc[[:space:]]+0x3f997000'
expect_at _XSetForeground "$XSETFG" "JAUC 3FD99A60" 'jauc[[:space:]]+0x3fd99a60'
expect_at _XCreatePixmap "$XCPM" "JAUC 3FD416E0" 'jauc[[:space:]]+0x3fd416e0'
expect_at _XCreateImage "$XCI" "JAUC 3F9A0490" 'jauc[[:space:]]+0x3f9a0490'
expect_at _XSetErrorHandler "$XERR" "JAUC 3FD45700" 'jauc[[:space:]]+0x3fd45700'
expect_at _XMapWindow "$XMAP" "JAUC 3FD64F60" 'jauc[[:space:]]+0x3fd64f60'
expect_at _XRaiseWindow "$XRAISE" "JAUC 3FD645B0" 'jauc[[:space:]]+0x3fd645b0'
expect_at _XSetInputFocus "$XSETFOCUS" "JAUC 3FD536B0" 'jauc[[:space:]]+0x3fd536b0'
expect_at _XPutImage "$XPUT" "JAUC 3F9CEC70" 'jauc[[:space:]]+0x3f9cec70'
expect_at _XCopyArea "$XCOPY" "JAUC 3FD3E220" 'jauc[[:space:]]+0x3fd3e220'
expect_at _XFillRectangle "$XFILL" "JAUC 3F99E8F0" 'jauc[[:space:]]+0x3f99e8f0'
expect_at _XSync "$XSYNC" "JAUC 3FD9F6B0" 'jauc[[:space:]]+0x3fd9f6b0'
expect_at _XPending "$XPEND" "JAUC 3FD67620" 'jauc[[:space:]]+0x3fd67620'
expect_at _XNextEvent "$XNEXT" "JAUC 3F9C1CF0" 'jauc[[:space:]]+0x3f9c1cf0'
expect_at _XLookupString "$XLOOKUP" "JAUC 3F9A9C30" 'jauc[[:space:]]+0x3f9a9c30'
expect_at _XFlush "$XFLUSH" "JAUC 3F9B1030" 'jauc[[:space:]]+0x3f9b1030'
expect_at _XGetInputFocus "$XGETFOCUS" "JAUC 3FD55210" 'jauc[[:space:]]+0x3fd55210'
expect_at _sleep "$XSLEEP" "JAUC 3FB33A20" 'jauc[[:space:]]+0x3fb33a20'
expect_at _blit_copy "$XBLIT" "JAUC 3FB44400" 'jauc[[:space:]]+0x3fb44400'
echo "== check the compiled client calls (linked assembly keeps the names)"
for sym in XOpenDisplay XCreateSimpleWindow XSelectInput XCreateGC XSetForeground \
XCreatePixmap XCreateImage XSetErrorHandler XMapWindow XRaiseWindow \
XSetInputFocus XPutImage XCopyArea XFillRectangle XSync XPending \
XNextEvent XLookupString XFlush XGetInputFocus; do
grep -q "CALLA _${sym}" "$ASM" || fail "linked assembly has no CALLA _$sym"
done
echo " ok direct CALLA imports: 20 Xlib entries"
grep -Eq '^[[:space:]]*MOVI _on_error_[A-Za-z0-9_]+, A[0-9]+' "$ASM" \
|| fail "no MOVI _on_error before XSetErrorHandler"
echo " ok XSetErrorHandler callback pointer: MOVI _on_error…"
grep -Eq '^[[:space:]]*CALL A[0-9]+' "$ASM" \
|| fail "no indirect CALL (Task 3 CALL Rs) in the client"
echo " ok indirect sleep call: CALL A?"
grep -q 'CALLA _blit_copy' "$ASM" \
|| fail "linked assembly has no CALLA _blit_copy (ROM copy entry)"
echo " ok ROM blit_copy entry: CALLA _blit_copy (JAUC 3FB44400)"
# XCreateSimpleWindow argument order: push 0,0,0,480,640,20,20,root,display.
LN=$(grep -n 'CALLA _XCreateSimpleWindow' "$ASM" | head -1 | cut -d: -f1)
[ -n "$LN" ] || fail "linked assembly has no CALLA _XCreateSimpleWindow"
sed -n "$((LN - 24)),$((LN))p" "$ASM" > "$TMP/create.block"
awk '
/^[[:space:]]*MOVI[[:space:]]/ {
line=$0; sub(/.*MOVI[[:space:]]+/, "", line); sub(/[[:space:]]*;.*/, "", line);
split(line, p, /,[[:space:]]*/); gsub(/[[:space:]]/, "", p[1]); gsub(/[[:space:]]/, "", p[2]);
val[p[2]] = p[1]; next
}
/^[[:space:]]*MOVE[[:space:]]+A[0-9]+,[[:space:]]*\*A14\+,/ {
line=$0; sub(/.*MOVE[[:space:]]+/, "", line); sub(/,.*/, "", line);
v = val[line]; if (line == "A1") v = "root"; if (line == "A0") v = "display";
print v
}
' "$TMP/create.block" > "$TMP/create.vals"
printf '%s\n' 0 0 0 480 640 20 20 root display > "$TMP/create.want"
if ! cmp -s "$TMP/create.want" "$TMP/create.vals"; then
{
echo "M4 dis34 round-trip: FAIL: XCreateSimpleWindow argument order"
echo " expected (background,border,border_width,height,width,y,x,root,display):"
sed 's/^/ /' "$TMP/create.want"
echo " got:"
sed 's/^/ /' "$TMP/create.vals"
} >&2
exit 1
fi
echo " ok XCreateSimpleWindow order: 0,0,0,480,640,20,20,root,display (display last)"
# Every call must be followed by the SETF 16/32 pair. The hand-written
# _start trampoline (`calla _xshow` + rets) is not a compiler call and is
# checked separately; the scan starts at _xshow: so lower-case call spellings
# inside the compiler output are still matched.
grep -Eq '^[[:space:]]*calla[[:space:]]+_xshow' "$ASM" \
|| fail "entry.asm does not call _xshow"
awk '
{ low = tolower($0) }
/^_xshow:/ { on = 1 }
low ~ /^[[:space:]]*;/ { next }
!on { next }
expect == 1 {
if ($0 ~ /SETF 16, 0, 0/) { expect = 2 }
else { bad++; expect = 0 }
next
}
expect == 2 {
if ($0 ~ /SETF 32, 0, 1/) { ok++; expect = 0 }
else { bad++; expect = 0 }
next
}
low ~ /^[[:space:]]*(calla|call)[[:space:]]/ { calls++; expect = 1 }
END { printf "%d %d %d\n", calls + 0, ok + 0, bad + 0 }
' "$ASM" > "$TMP/setf.count"
read CALLS SETF_OK SETF_BAD < "$TMP/setf.count"
[ "$CALLS" -gt 0 ] || fail "linked assembly has no calls"
[ "$SETF_BAD" -eq 0 ] || fail "$SETF_BAD calls lack the SETF 16/32 pair"
[ "$SETF_OK" -eq "$CALLS" ] || fail "$CALLS calls but $SETF_OK post-call SETF 16/32 pairs"
echo " ok $CALLS calls each followed by SETF 16,0,0 + SETF 32,0,1"
# Old-STK: on_error saves 3 registers; its epilogue restores A14 from
# *SP((3+1)*32) = *SP(0x80). The prologue pushes A13, A0 and A1.
awk '/^_on_error_/ { on=1 } on { print } on && /rets[[:space:]]+2/ { exit }' "$ASM" > "$TMP/onerr.s"
grep -Eq 'MOVE \*SP\((0x80|128)\), A14, 1' "$TMP/onerr.s" \
|| { sed 's/^/ /' "$TMP/onerr.s" >&2; fail "on_error does not restore A14 from *SP(0x80/128)"; }
grep -Eq '^[[:space:]]*RETS 2' "$TMP/onerr.s" || fail "on_error has no RETS 2"
echo " ok on_error: 3 pushes -> *SP(0x80),A14 and RETS 2"
# Task 3 CALL encoding in the GSPA listing.
grep -Eq '^[[:space:]]*[0-9]+[[:space:]]+[0-9A-F]+[[:space:]]+0920[[:space:]]+CALL[[:space:]]+A[0-9]+' "$LST" \
|| fail "GSPA listing has no 0x0920 CALL A?"
echo " ok GSPA encoded the indirect call as 0x0920"
# dis34 must decode the indirect call as `call aN`.
grep -Eq '^[0-9a-f]+:[[:space:]]+call[[:space:]]+a[0-9]+' "$TMP/dis34.txt" \
|| fail "dis34 did not decode any CALL Rs as 'call aN'"
echo " ok dis34 decodes CALL Rs as 'call aN'"
# The checker data lives in .bss: 480*80 bytes, built as 32-bit words.
grep -Eq '\.bss[[:space:]]+_data_[A-Za-z0-9_]+,307200,32' "$ASM" \
|| fail "linked assembly has no .bss _data…,307200,32 (480*80 bytes)"
grep -Eq '^[[:space:]]*MOVI -1, A[0-9]+' "$ASM" \
|| fail "checker fill loop has no MOVI -1 (all-ones cell)"
grep -Eq '^[[:space:]]*NOT A[0-9]+' "$ASM" \
|| fail "checker fill loop has no NOT (all-zero cell)"
echo " ok checker .bss: 480*80 bytes, MOVI -1 / NOT cell fill"
# The client must actually set the XImage byte/bit order to LSBFirst. The
# layout gate pins data=16 (0x80), byte_order=20 (0xA0) and
# bitmap_bit_order=28 (0xE0); here the linked code stores zero at 0xA0/0xE0.
grep -Eq 'ADDI 160, A[0-9]+' "$ASM" || fail "no XImage.byte_order (bit 0xA0) store"
grep -Eq 'ADDI 224, A[0-9]+' "$ASM" || fail "no XImage.bitmap_bit_order (bit 0xE0) store"
echo " ok XImage byte_order/bitmap_bit_order stores (LSBFirst at 0xA0/0xE0)"
echo "M4 dis34 round-trip: PASS"
+17
View File
@@ -0,0 +1,17 @@
; entry.asm -- M4 entry, linked at bit 018A0000.
;
; Calls the compiled unsigned runtime test. Markers (0x20 bits apart):
; 018A8000 le(5,7) == 1
; 018A8020 ule(7,5) == 0 (unsigned <=)
; 018A8040 sum_le(10) == 55
.text
.globl _start
_start:
setf 16, 0, 0
setf 32, 0, 1
movi _cstack, a14
move a14, a13
calla _unsigned_test
rets
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
+43
View File
@@ -0,0 +1,43 @@
-- fnptr-check.lua -- M4 function-pointer/indirect-call markers.
--
-- M4_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua
-- LOADER_END loader inspection/exit frame
--
-- Markers (0x20 bits apart):
-- 018A8000 apply(inc, 41) == 42
-- 018A8020 apply(vf, 41) == 42 (volatile local function pointer)
-- 018A8040 apply(g_fn, 43) == 42 (global function pointer = dec)
-- 018A8060 fnptr_test() == 42 (return value, stored by the entry)
dofile(os.getenv("M4_LOADER_LUA"))
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
local frames = 0
local checked = false
emu.register_frame_done(function()
frames = frames + 1
if frames == ENDF - 1 and not checked then
checked = true
local prog = manager.machine.devices[':maincpu'].spaces['program']
local m = {}
for i = 0, 3 do
m[i] = prog:read_u32(0x018A8000 + i * 0x20)
end
print(string.format("M4: fnptr=%d fnptr_volatile=%d fnptr_global=%d fnptr_ret=%d",
m[0], m[1], m[2], m[3]))
local want = {42, 42, 42, 42}
local ok = true
for i = 0, 3 do
if m[i] ~= want[i + 1] then
ok = false
end
end
if ok then
print("M4 MAME: PASS")
else
print("M4 MAME: FAIL (want fnptr=42 fnptr_volatile=42 fnptr_global=42 fnptr_ret=42)")
end
end
end, "fnptr-check")
+17
View File
@@ -0,0 +1,17 @@
; fnptr-entry.asm -- M4 function-pointer/indirect-call test entry.
;
; Calls the function-pointer translation unit and stores its return value in
; marker 3. Markers live at 0x018A8000 + n*0x20 (bit addresses).
.text
.globl _start
_start:
setf 16, 0, 0
setf 32, 0, 1
movi _cstack, a14
move a14, a13
calla _fnptr_test
movi 018A8060h, a0
move a8, *a0, 1
rets
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
+29
View File
@@ -0,0 +1,29 @@
/* fnptr.c -- M4 function-pointer/indirect-call runtime test.
*
* Markers live at 0x018A8000 + n*0x20 (bit addresses):
* 0 apply(inc, 41) == 42 direct symbol passed as a pointer
* 1 apply(vf, 41) == 42 volatile local function pointer
* 2 apply(g_fn, 43) == 42 global function pointer holding dec
* 3 fnptr_test() == 42 return value, stored by fnptr-entry.asm
*
* apply/inc/dec are noinline so -O2 cannot inline the helper and devirtualise
* the callback into a direct call; the call inside apply is a real `call i32
* %f` and lowers to CALL Rs.
*/
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n) * 0x20u))
typedef int (*fn)(int);
__attribute__((noinline)) int inc(int x) { return x + 1; }
__attribute__((noinline)) int dec(int x) { return x - 1; }
__attribute__((noinline)) int apply(fn f, int x) { return f(x); }
static fn volatile g_fn = dec;
int fnptr_test(void) {
volatile fn vf = inc;
MARK(0) = (unsigned)apply(inc, 41);
MARK(1) = (unsigned)apply(vf, 41);
MARK(2) = (unsigned)apply(g_fn, 43);
return apply(vf, 41);
}
+102
View File
@@ -0,0 +1,102 @@
/*
* Offset-only equivalent of scripts/m4/x11m4.h for the 1987 TI GSP C
* compiler (GSPCC/GSPCG 2.01). The VT1200 C ABI packs struct members, so
* these structs have the same byte layout as the target's x11m4.h. The
* XImage function table is modelled as six 32-bit void* slots: only its
* size and the offset of f are needed here, and that is layout-equivalent
* to the target's six function pointers.
*
* The 1987 compiler's &-of-member is a bit address, so these functions
* return the ROM's bit displacements directly.
*/
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;
};
struct xerror {
int type;
void *display;
unsigned long resourceid;
unsigned long serial;
unsigned char error_code;
unsigned char request_code;
unsigned char minor_code;
unsigned char abi_pad;
};
struct xkey {
int type;
unsigned long serial;
int send_event;
void *display;
unsigned long window;
unsigned long root;
unsigned long subwindow;
unsigned long time;
int x, y;
int x_root, y_root;
unsigned int state;
unsigned int keycode;
int same_screen;
};
struct xexpose {
int type;
unsigned long serial;
int send_event;
void *display;
unsigned long window;
int x, y;
int width, height;
int count;
};
union xevent {
int type;
struct xkey xkey;
struct xexpose xexpose;
long pad[24];
};
int layout_display_screens() { return (int)&((struct display *)0)->screens; }
int layout_screen_root() { return (int)&((struct screen *)0)->root; }
int layout_image_data() { return (int)&((struct ximage *)0)->data; }
int layout_image_byte_order() { return (int)&((struct ximage *)0)->byte_order; }
int layout_image_bytes_per_line() { return (int)&((struct ximage *)0)->bytes_per_line; }
int layout_image_bits_per_pixel() { return (int)&((struct ximage *)0)->bits_per_pixel; }
int layout_image_f() { return (int)&((struct ximage *)0)->f; }
int layout_error_error_code() { return (int)&((struct xerror *)0)->error_code; }
int layout_event_xkey_keycode() { return (int)&((struct xevent *)0)->xkey.keycode; }
int layout_event_size() { return (int)sizeof(union xevent); }
+114
View File
@@ -0,0 +1,114 @@
*******************************************************
* GSP C COMPILER, Version 2.01, 87.300 *
*******************************************************
FP .set A13
STK .set A14
.globl _layout_display_screens
.file "LAYOUT.c"
******************************************************
* FUNCTION DEF : _layout_display_screens
******************************************************
_layout_display_screens:
MOVE A7,-*SP,1
MOVI 1152,A7
MOVE A7,A8
EPI0_1:
MOVE *SP+,A7,1
RETS 0
.globl _layout_screen_root
******************************************************
* FUNCTION DEF : _layout_screen_root
******************************************************
_layout_screen_root:
MOVE A7,-*SP,1
MOVI 64,A7
MOVE A7,A8
EPI0_2:
MOVE *SP+,A7,1
RETS 0
.globl _layout_image_data
******************************************************
* FUNCTION DEF : _layout_image_data
******************************************************
_layout_image_data:
MOVE A7,-*SP,1
MOVI 128,A7
MOVE A7,A8
EPI0_3:
MOVE *SP+,A7,1
RETS 0
.globl _layout_image_byte_order
******************************************************
* FUNCTION DEF : _layout_image_byte_order
******************************************************
_layout_image_byte_order:
MOVE A7,-*SP,1
MOVI 160,A7
MOVE A7,A8
EPI0_4:
MOVE *SP+,A7,1
RETS 0
.globl _layout_image_bytes_per_line
******************************************************
* FUNCTION DEF : _layout_image_bytes_per_line
******************************************************
_layout_image_bytes_per_line:
MOVE A7,-*SP,1
MOVI 320,A7
MOVE A7,A8
EPI0_5:
MOVE *SP+,A7,1
RETS 0
.globl _layout_image_bits_per_pixel
******************************************************
* FUNCTION DEF : _layout_image_bits_per_pixel
******************************************************
_layout_image_bits_per_pixel:
MOVE A7,-*SP,1
MOVI 352,A7
MOVE A7,A8
EPI0_6:
MOVE *SP+,A7,1
RETS 0
.globl _layout_image_f
******************************************************
* FUNCTION DEF : _layout_image_f
******************************************************
_layout_image_f:
MOVE A7,-*SP,1
MOVI 512,A7
MOVE A7,A8
EPI0_7:
MOVE *SP+,A7,1
RETS 0
.globl _layout_error_error_code
******************************************************
* FUNCTION DEF : _layout_error_error_code
******************************************************
_layout_error_error_code:
MOVE A7,-*SP,1
MOVI 128,A7
MOVE A7,A8
EPI0_8:
MOVE *SP+,A7,1
RETS 0
.globl _layout_event_xkey_keycode
******************************************************
* FUNCTION DEF : _layout_event_xkey_keycode
******************************************************
_layout_event_xkey_keycode:
MOVE A7,-*SP,1
MOVI 416,A7
MOVE A7,A8
EPI0_9:
MOVE *SP+,A7,1
RETS 0
.globl _layout_event_size
******************************************************
* FUNCTION DEF : _layout_event_size
******************************************************
_layout_event_size:
MOVI 96,A8
EPI0_10:
RETS 0
.end
+120
View File
@@ -0,0 +1,120 @@
#include "x11m4.h"
/*
* Byte offsets are C offsets; the TMS34010 ROM's indirect displacements are
* bit offsets, so every layout_* function below returns offset * 8. The one
* exception is layout_event_size(), which reports sizeof(XEvent) in bytes
* because the termtris buffer is sized in bytes (vt_u32 evbuf[24]).
*/
_Static_assert(sizeof(void *) == 4, "the VT1200 X11 ABI is 32-bit");
_Static_assert(sizeof(long) == 4, "the VT1200 X11 ABI is 32-bit");
_Static_assert(sizeof(XID) == 4, "XID must be 32-bit");
_Static_assert(sizeof(Time) == 4, "Time must be 32-bit");
_Static_assert(__builtin_offsetof(Display, screens) == 0x90,
"Display.screens must be at byte 0x90 (bit 0x480)");
_Static_assert(__builtin_offsetof(Screen, root) == 0x8,
"Screen.root must be at byte 0x8 (bit 0x40)");
/* 32-bit X11R4 XImage: data 16, byte_order 20, bytes_per_line 40. */
_Static_assert(__builtin_offsetof(XImage, width) == 0, "XImage.width");
_Static_assert(__builtin_offsetof(XImage, height) == 4, "XImage.height");
_Static_assert(__builtin_offsetof(XImage, xoffset) == 8, "XImage.xoffset");
_Static_assert(__builtin_offsetof(XImage, format) == 12, "XImage.format");
_Static_assert(__builtin_offsetof(XImage, data) == 16,
"XImage.data must be at byte 16 (bit 0x80)");
_Static_assert(__builtin_offsetof(XImage, byte_order) == 20,
"XImage.byte_order must be at byte 20 (bit 0xA0)");
_Static_assert(__builtin_offsetof(XImage, bitmap_unit) == 24, "XImage.bitmap_unit");
_Static_assert(__builtin_offsetof(XImage, bitmap_bit_order) == 28,
"XImage.bitmap_bit_order");
_Static_assert(__builtin_offsetof(XImage, bitmap_pad) == 32, "XImage.bitmap_pad");
_Static_assert(__builtin_offsetof(XImage, depth) == 36, "XImage.depth");
_Static_assert(__builtin_offsetof(XImage, bytes_per_line) == 40,
"XImage.bytes_per_line must be at byte 40 (bit 0x140)");
_Static_assert(__builtin_offsetof(XImage, bits_per_pixel) == 44,
"XImage.bits_per_pixel must be at byte 44 (bit 0x160)");
_Static_assert(__builtin_offsetof(XImage, red_mask) == 48, "XImage.red_mask");
_Static_assert(__builtin_offsetof(XImage, green_mask) == 52, "XImage.green_mask");
_Static_assert(__builtin_offsetof(XImage, blue_mask) == 56, "XImage.blue_mask");
_Static_assert(__builtin_offsetof(XImage, obdata) == 60, "XImage.obdata");
_Static_assert(__builtin_offsetof(XImage, f) == 64,
"XImage.f must be at byte 64 (bit 0x200)");
_Static_assert(__builtin_offsetof(struct funcs, destroy_image) == 4,
"XImage.f.destroy_image must be 4 bytes into the inline funcs");
_Static_assert(__builtin_offsetof(XImage, f.destroy_image) == 68,
"XImage.f.destroy_image must be at byte 68 (bit 0x220)");
_Static_assert(sizeof(struct funcs) == 24, "the X11R4 funcs tail is six pointers");
_Static_assert(sizeof(XImage) == 88,
"sizeof(XImage) must cover the ROM's full inline-funcs object");
/* 32-bit X11R4 XErrorEvent: error_code 16, request_code 17, minor_code 18. */
_Static_assert(__builtin_offsetof(XErrorEvent, type) == 0, "XErrorEvent.type");
_Static_assert(__builtin_offsetof(XErrorEvent, display) == 4, "XErrorEvent.display");
_Static_assert(__builtin_offsetof(XErrorEvent, resourceid) == 8,
"XErrorEvent.resourceid");
_Static_assert(__builtin_offsetof(XErrorEvent, serial) == 12, "XErrorEvent.serial");
_Static_assert(__builtin_offsetof(XErrorEvent, error_code) == 16,
"XErrorEvent.error_code must be at byte 16 (bit 0x80)");
_Static_assert(__builtin_offsetof(XErrorEvent, request_code) == 17,
"XErrorEvent.request_code");
_Static_assert(__builtin_offsetof(XErrorEvent, minor_code) == 18,
"XErrorEvent.minor_code");
/* The packed object would be 19 bytes; one X11R4 object-size compatibility
* pad byte keeps the 32-bit object at 20. The client reads only the
* handler-passed fields. */
_Static_assert(sizeof(XErrorEvent) == 20, "sizeof(XErrorEvent) must be 20");
/* XEvent: type 0, xkey.window 16, xkey.keycode 52, xexpose.window 16, x 20. */
_Static_assert(__builtin_offsetof(XEvent, type) == 0, "XEvent.type");
_Static_assert(__builtin_offsetof(XEvent, xkey.window) == 16, "XEvent.xkey.window");
_Static_assert(__builtin_offsetof(XEvent, xkey.keycode) == 52,
"XEvent.xkey.keycode must be at byte 52 (bit 0x1A0)");
_Static_assert(__builtin_offsetof(XEvent, xexpose.window) == 16,
"XEvent.xexpose.window");
_Static_assert(__builtin_offsetof(XEvent, xexpose.x) == 20, "XEvent.xexpose.x");
_Static_assert(__builtin_offsetof(XEvent, xfocus.window) == 16,
"XEvent.xfocus.window");
_Static_assert(__builtin_offsetof(XEvent, xbutton.window) == 16,
"XEvent.xbutton.window");
_Static_assert(__builtin_offsetof(XEvent, xconfigure.window) == 20,
"XEvent.xconfigure.window");
_Static_assert(__builtin_offsetof(XEvent, xclient.window) == 16,
"XEvent.xclient.window");
_Static_assert(sizeof(XEvent) >= 96,
"XEvent must cover the termtris 96-byte vt_u32 evbuf[24]");
_Static_assert(sizeof(XEvent) == 96, "the X11R4 XEvent union is 96 bytes");
int layout_display_screens(void) {
return (int)__builtin_offsetof(Display, screens) * 8;
}
int layout_screen_root(void) {
return (int)__builtin_offsetof(Screen, root) * 8;
}
int layout_image_data(void) {
return (int)__builtin_offsetof(XImage, data) * 8;
}
int layout_image_byte_order(void) {
return (int)__builtin_offsetof(XImage, byte_order) * 8;
}
int layout_image_bytes_per_line(void) {
return (int)__builtin_offsetof(XImage, bytes_per_line) * 8;
}
int layout_image_bits_per_pixel(void) {
return (int)__builtin_offsetof(XImage, bits_per_pixel) * 8;
}
int layout_image_f(void) {
return (int)__builtin_offsetof(XImage, f) * 8;
}
int layout_error_error_code(void) {
return (int)__builtin_offsetof(XErrorEvent, error_code) * 8;
}
int layout_event_xkey_keycode(void) {
return (int)__builtin_offsetof(XEvent, xkey.keycode) * 8;
}
int layout_event_size(void) {
return (int)sizeof(XEvent);
}
/* Exercise the real access path: follows Display.screens and reads Screen.root. */
unsigned int root_probe(Display *d) { return vt_root(d); }
+16
View File
@@ -0,0 +1,16 @@
-a
-o M4.OUT
-m M4.MAP
-e _start
M4.obj
MEMORY
{
prog: origin = 018A0000h, length = 8000h
ram: origin = 01A00000h, length = 80000h
}
SECTIONS
{
.text: {} > prog
.data: {} > prog
.bss: {} > ram
}
+30
View File
@@ -0,0 +1,30 @@
/* m4 loop.c -- an ordinary -O2 loop test. The driver wrapper disables
* IndVarSimplify's exit-value replacement (-replexitval=never): this target has
* no i64, and the closed form of sumto turns into an i33 multiply that llc
* cannot select. The loops themselves must survive and run.
*
* Markers (0x20 bits apart, written by loop_test):
* 018A8100 sumto(10) == 55
* 018A8120 squares(5) == 30
*/
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n)*0x20u))
int sumto(int n) {
int s = 0, i;
for (i = 1; i <= n; i++)
s += i;
return s;
}
int squares(int n) {
int s = 0, i;
for (i = 0; i < n; i++)
s += i * i;
return s;
}
int loop_test(void) {
MARK(8) = (unsigned)sumto(10);
MARK(9) = (unsigned)squares(5);
return 0;
}
+52
View File
@@ -0,0 +1,52 @@
-- m4-check.lua -- load the M4 COFF with dwt/loader/loader.lua and read the
-- marker words written by unsigned.c.
--
-- M4_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua
-- LOADER_END loader inspection/exit frame
--
-- The marker read runs one frame before loader.lua's inspection/exit frame.
-- Markers (0x20 bits apart):
-- 018A8000 le(5,7) == 1
-- 018A8020 ule(7,5) == 0 (unsigned <=)
-- 018A8040 sum_le(10) == 55
-- 018A8060 ge(7,5) == 1 (signed >=)
-- 018A8080 uge(5,7) == 0 (unsigned >=)
-- 018A80A0 gt(5,7) == 0 (signed >)
-- 018A80C0 ugt(7,5) == 1 (unsigned >)
-- 018A80E0 ne(7,5) == 1
-- 018A8100 lt(7,5) == 0 (signed <)
-- 018A8120 ult(5,7) == 1 (unsigned <)
dofile(os.getenv("M4_LOADER_LUA"))
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
local frames = 0
local checked = false
emu.register_frame_done(function()
frames = frames + 1
if frames == ENDF - 1 and not checked then
checked = true
local prog = manager.machine.devices[':maincpu'].spaces['program']
local m = {}
for i = 0, 9 do
m[i] = prog:read_u32(0x018A8000 + i * 0x20)
end
print(string.format(
"M4: le=%d ule=%d sum_le=%d ge=%d uge=%d gt=%d ugt=%d ne=%d lt=%d ult=%d",
m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9]))
local want = {1, 0, 55, 1, 0, 0, 1, 1, 0, 1}
local ok = true
for i = 0, 9 do
if m[i] ~= want[i + 1] then
ok = false
end
end
if ok then
print("M4 MAME: PASS")
else
print("M4 MAME: FAIL (want le=1 ule=0 sum_le=55 ge=1 uge=0 gt=0 ugt=1 ne=1 lt=0 ult=1)")
end
end
end, "m4-check")
+54
View File
@@ -0,0 +1,54 @@
-- mul-check.lua -- M4 multiply/divide and -O2 loop markers.
--
-- M4_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua
-- LOADER_END loader inspection/exit frame
--
-- Markers (0x20 bits apart):
-- 018A8000 mul(6,7) == 42
-- 018A8020 umul16(0xffff,0xffff) == 0xfffe0001
-- 018A8040 sdiv(100,7) == 14
-- 018A8060 udiv(0xfffffffe,7) == 613566756
-- 018A8080 srem(100,7) == 2
-- 018A80A0 urem(100,7) == 2
-- 018A80C0 sdiv(-100,7) == -14
-- 018A80E0 srem(-100,7) == -2
-- 018A8100 sumto(10) == 55
-- 018A8120 squares(5) == 30
-- 018A8140 mul(0x12345678,0x9abcdef0) == 0x242d2080
-- 018A8160 urem(0xfffffffe,7) == 2
dofile(os.getenv("M4_LOADER_LUA"))
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
local frames = 0
local checked = false
emu.register_frame_done(function()
frames = frames + 1
if frames == ENDF - 1 and not checked then
checked = true
local prog = manager.machine.devices[':maincpu'].spaces['program']
local m = {}
for i = 0, 11 do
m[i] = prog:read_u32(0x018A8000 + i * 0x20)
end
print(string.format(
"M4: mul=%d umul16=%d sdiv=%d udiv=%d srem=%d urem=%d sdivneg=%d sremneg=%d sumto=%d squares=%d widemul=%d widerem=%d",
m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9],
m[10], m[11]))
local want = {42, 0xfffe0001, 14, 613566756, 2, 2,
0xfffffff2, 0xfffffffe, 55, 30, 0x242d2080, 2}
local ok = true
for i = 0, 11 do
if m[i] ~= want[i + 1] then
ok = false
end
end
if ok then
print("M4 MAME: PASS")
else
print("M4 MAME: FAIL (want mul=42 umul16=4294836225 sdiv=14 udiv=613566756 srem=2 urem=2 sdivneg=-14 sremneg=-2 sumto=55 squares=30 widemul=606937216 widerem=2)")
end
end
end, "mul-check")
+16
View File
@@ -0,0 +1,16 @@
; mul-entry.asm -- M4 multiply/divide and -O2 loop test entry.
;
; Calls the multiply/divide and loop translation units. Markers written by
; mul.c and loop.c live at 0x018A8000 + n*0x20.
.text
.globl _start
_start:
setf 16, 0, 0
setf 32, 0, 1
movi _cstack, a14
move a14, a13
calla _mul_test
calla _loop_test
rets
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
+49
View File
@@ -0,0 +1,49 @@
/* m4 mul.c -- 32-bit multiply/divide marker test.
*
* Markers (0x20 bits apart, written by mul_test):
* 018A8000 mul(6,7) == 42
* 018A8020 umul16(0xffff,0xffff) == 0xfffe0001 (16x16 MPYU path)
* 018A8040 sdiv(100,7) == 14
* 018A8060 udiv(0xfffffffe,7) == 613566756
* 018A8080 srem(100,7) == 2
* 018A80A0 urem(100,7) == 2
* 018A80C0 sdiv(-100,7) == -14
* 018A80E0 srem(-100,7) == -2
* 018A8140 mulu(0x12345678,0x9ABCDEF0) == 0x242D2080 (wide 32x32 product)
* 018A8160 urem(0xfffffffe,7) == 2 (wide quotient in the remainder)
*/
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n)*0x20u))
int mul(int a, int b) { return a * b; }
/* A full-width unsigned product. The 32x32 low bits are identical for signed
* and unsigned values, so the target selects the same MPYS; using unsigned
* operands keeps a wide marker from being signed-overflow UB. */
unsigned mulu(unsigned a, unsigned b) { return a * b; }
/* A 16x16 unsigned product: both operands are masked to 16 bits, which is the
* form the target selects to MPYU. The product must be computed in unsigned
* arithmetic: 0xffff * 0xffff does not fit in signed int, so `int * int` would
* be signed overflow and the optimizer could narrow or rename the result. */
int umul16(int a, int b) {
return (int)((unsigned)(a & 0xffff) * (unsigned)(b & 0xffff));
}
int sdiv(int a, int b) { return a / b; }
unsigned udiv(unsigned a, unsigned b) { return a / b; }
int srem(int a, int b) { return a % b; }
unsigned urem(unsigned a, unsigned b) { return a % b; }
int mul_test(void) {
MARK(0) = (unsigned)mul(6, 7);
MARK(1) = (unsigned)umul16(0xffff, 0xffff);
MARK(2) = (unsigned)sdiv(100, 7);
MARK(3) = udiv(0xfffffffeu, 7u);
MARK(4) = (unsigned)srem(100, 7);
MARK(5) = urem(100u, 7u);
MARK(6) = (unsigned)sdiv(-100, 7);
MARK(7) = (unsigned)srem(-100, 7);
MARK(10) = mulu(0x12345678u, 0x9ABCDEF0u);
MARK(11) = urem(0xfffffffeu, 7u);
return 0;
}
+1
View File
@@ -0,0 +1 @@
char *a(void) { return "same"; }
+1
View File
@@ -0,0 +1 @@
char *b(void) { return "same"; }
+16
View File
@@ -0,0 +1,16 @@
; private-entry.asm -- M4 two-TU private-symbol link test entry.
;
; Calls both translation units so the linker keeps their functions and the
; string literals that those functions reference.
.text
.globl _start
_start:
setf 16, 0, 0
setf 32, 0, 1
movi _cstack, a14
move a14, a13
calla _a
calla _b
rets
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
+190
View File
@@ -0,0 +1,190 @@
#!/bin/sh
# M4 end-to-end runner: compile translation units, assemble/link with the 1987
# GSPA/GSPLNK toolchain under DOSBox, load the COFF in MAME, and evaluate the
# marker Lua script.
#
# scripts/m4/run.sh
#
# The default is unsigned.c at -O0, linked with entry.asm and checked by
# m4-check.lua. Other M4 tasks can reuse this runner:
#
# M4_SOURCES="foo bar" C basenames under scripts/m4 (default: unsigned)
# M4_ENTRY=/path/entry.asm assembly entry file (default: scripts/m4/entry.asm)
# M4_CHECK_LUA=/path.lua MAME marker script (default: scripts/m4/m4-check.lua)
# M4_CLANG_OPT=-O2 tms-clang optimization level (default: -O0)
# M4_LLC_OPT=-O0 llc optimization level (default: -O0)
# M4_ROM_SYMBOLS=1 append the per-ROM import stubs from symbols
# M4_EXTRA_ASM="/a.asm /b.asm" extra assembly to concatenate
# M4_SKIP_MAME=1 stop after a clean GSPA/GSPLNK link
#
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC MAME DOSBOX DOSBOX_CONF
# SDK_IMG M4_WORK M4_COFF M4_SDK M4_SYMS LOADER_AT LOADER_END
set -eu
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
DIR=$ROOT/scripts/m4
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}
MAME=${MAME:-$ROOT/build/mame-src/vt1200}
DOSBOX=${DOSBOX:-dosbox}
DOSBOX_CONF=${DOSBOX_CONF:-$ROOT/vendor/vt1200/harness/sdk-scratch/dosbox-bench.conf}
SDK_IMG=${SDK_IMG:-$ROOT/vendor/vt1200/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img}
WORK=${M4_WORK:-$ROOT/build/m4}
COFF=${M4_COFF:-$WORK/m4.coff}
SDK=${M4_SDK:-$ROOT/build/m4-sdk}
ASM=$WORK/asm
LOADER_LUA=$ROOT/vendor/vt1200/dwt/loader/loader.lua
LOADER_BIN=$ROOT/vendor/vt1200/dwt/loader/build/loader.bin
LOADER_AT=${LOADER_AT:-2500}
LOADER_END=${LOADER_END:-2700}
SYMS=${M4_SYMS:-$ROOT/symbols/vt1200-v22.syms}
SOURCES=${M4_SOURCES:-unsigned}
ENTRY=${M4_ENTRY:-$DIR/entry.asm}
CHECK_LUA=${M4_CHECK_LUA:-$DIR/m4-check.lua}
CLANG_OPT=${M4_CLANG_OPT:--O0}
LLC_OPT=${M4_LLC_OPT:--O0}
fail() {
echo "m4: $*" >&2
exit 1
}
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do
[ -x "$tool" ] || fail "missing tool: $tool"
done
[ -f "$ENTRY" ] || fail "missing entry: $ENTRY"
mkdir -p "$ASM" "$WORK/evidence" "$WORK/run/nvram" "$WORK/run/cfg"
mkdir -p "$(dirname "$COFF")"
echo "== compile and select ($CLANG_OPT / llc $LLC_OPT)"
export TMS_CLANG TMS_OPT TMS_BITADDR
for name in $SOURCES; do
src=$DIR/$name.c
[ -f "$src" ] || fail "missing source: $src"
"$ROOT/scripts/tms-clang" "$CLANG_OPT" -o "$ASM/$name.ll" "$src"
"$TMS_LLC" -march=tms34010 "$LLC_OPT" -filetype=asm \
"$ASM/$name.ll" -o "$ASM/$name.s" \
|| fail "llc failed for $name.c"
echo " $name.s: $(grep -c . "$ASM/$name.s") lines"
done
echo "== extract the 1987 SDK"
if [ ! -f "$SDK/GSPA.EXE" ]; then
command -v mcopy >/dev/null 2>&1 || fail "mtools (mcopy) is required"
[ -f "$SDK_IMG" ] || fail "SDK image not found: $SDK_IMG"
mkdir -p "$SDK"
mcopy -s -n -i "$SDK_IMG" "::/A2/*" "$SDK/"
mcopy -s -n -i "$SDK_IMG" "::/C2/*" "$SDK/"
fi
[ -f "$SDK/GSPA.EXE" ] || fail "GSPA.EXE not extracted into $SDK"
# .bss is placed at 0x01A00000 (link.cmd). The 640x480 1-bit image is
# 38400 bytes; with the 4 KiB C stack and 1 KiB XG block it cannot fit the old
# 32 KiB region at 0x018A9000. 0x01A00000 is the region the termtris loader
# uses for its 125984-bit .bss (termtris.map 0x01A00000..0x01A1EC20), while the
# X server's request buffer lives in low RAM (0x00988FD0..0x0098CFD0).
echo "== assemble and link with GSPA/GSPLNK under DOSBox"
# GSPA 2.00 has one flat symbol table per assembly, and llc names basic blocks
# LBB0_1, LBB1_2, ... restarting in every translation unit. Give each TU its
# own LBB prefix, as the M3 runner does, so a multi-TU client (xshow + xgfix)
# cannot collide. The per-TU .s files keep their original labels.
tag=0
for name in $SOURCES; do
tag=$((tag + 1))
sed "s/LBB\([0-9][0-9]*_[0-9][0-9]*\)/LBB$tag\1/g" \
"$ASM/$name.s" > "$ASM/$name.unique.s"
done
cat "$ENTRY" > "$SDK/M4.ASM"
for name in $SOURCES; do
cat "$ASM/$name.unique.s" >> "$SDK/M4.ASM"
done
if [ -n "${M4_EXTRA_ASM:-}" ]; then
for extra in $M4_EXTRA_ASM; do
[ -f "$extra" ] || fail "missing extra assembly: $extra"
cat "$extra" >> "$SDK/M4.ASM"
done
fi
if [ -n "${M4_ROM_SYMBOLS:-}" ]; then
"$ROOT/scripts/m2/gen-rom-imports.sh" "$SYMS" "$WORK/rom-imports.asm"
cat "$WORK/rom-imports.asm" >> "$SDK/M4.ASM"
fi
# The linker command file must keep CRLF line endings.
sed 's/\r$//' "$DIR/link.cmd" | sed 's/$/\r/' > "$SDK/M4.CMD"
rm -f "$SDK/M4.OBJ" "$SDK/M4.OUT" "$SDK/M4.MAP" "$SDK/M4.LST"
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout "${DOSBOX_TIMEOUT:-300}" "$DOSBOX" -conf "$DOSBOX_CONF" \
-noprimaryconf -nolocalconf \
-c "mount c $SDK" -c "c:" \
-c "gspa M4 M4.obj M4.lst -l" \
-c "gsplnk M4.cmd" \
-c "exit" -exit > "$WORK/dosbox.log" 2>&1 \
|| { tail -20 "$WORK/dosbox.log" >&2; fail "DOSBox failed"; }
[ -f "$SDK/M4.LST" ] || fail "GSPA produced no listing (see $WORK/dosbox.log)"
sed 's/\r$//' "$SDK/M4.LST" > "$WORK/m4.lst"
grep -q 'No Errors' "$WORK/m4.lst" \
|| { grep -i -B2 -A2 'error' "$WORK/m4.lst" >&2 || true; fail "GSPA reported errors"; }
grep -q 'No Warnings' "$WORK/m4.lst" \
|| { grep -i -B2 -A2 'warning' "$WORK/m4.lst" >&2 || true; fail "GSPA reported warnings"; }
[ -f "$SDK/M4.OUT" ] || fail "GSPLNK produced no M4.OUT"
sed 's/\r$//' "$SDK/M4.MAP" > "$WORK/m4.map"
cp "$SDK/M4.OUT" "$COFF"
cp "$SDK/M4.ASM" "$WORK/m4.asm"
ENTRY_ADDR=$(awk '$2 == "_start" { print $1; exit }' "$WORK/m4.map")
[ "$ENTRY_ADDR" = "018A0000" ] || fail "linker entry _start is '$ENTRY_ADDR', want 018A0000"
grep -q 'ENTRY POINT SYMBOL: "_start"' "$WORK/m4.map" \
|| fail "linker map has no _start entry point"
echo " linked .text at 018A0000, M4.OUT -> $COFF"
if [ -n "${M4_SKIP_MAME:-}" ]; then
echo "M4 LINK: PASS"
exit 0
fi
echo "== run the COFF in MAME"
[ -x "$MAME" ] || fail "MAME binary not built: $MAME"
[ -f "$LOADER_LUA" ] || fail "COFF loader missing: $LOADER_LUA"
[ -f "$LOADER_BIN" ] || fail "COFF loader payload missing: $LOADER_BIN"
[ -f "$CHECK_LUA" ] || fail "marker Lua missing: $CHECK_LUA"
rm -rf "$WORK/run/nvram"
mkdir -p "$WORK/run/nvram"
VT1200_MONITOR=VR319 \
VT1200_SKIP_LEVEL1=1 \
LOADER_AT="$LOADER_AT" LOADER_END="$LOADER_END" \
LOADER_BIN="$LOADER_BIN" LOADER_COFF="$COFF" \
LOADER_OUT="$WORK/evidence" \
M4_LOADER_LUA="$LOADER_LUA" \
timeout "${MAME_TIMEOUT:-900}" "$MAME" vt1200 \
-rompath "$ROOT/vendor/vt1200/harness/mame/roms" \
-video none -sound none -nothrottle -skip_gameinfo \
-nvram_directory "$WORK/run/nvram" \
-cfg_directory "$WORK/run/cfg" \
-debug -debugger none \
-autoboot_script "$CHECK_LUA" -autoboot_delay 0 \
> "$WORK/mame.log" 2>&1 \
|| { tail -30 "$WORK/mame.log" >&2; fail "MAME run failed"; }
MARKERS=$(grep -E '^M4: ' "$WORK/mame.log" | tail -1 || true)
[ -n "$MARKERS" ] || { tail -30 "$WORK/mame.log" >&2; fail "MAME produced no M4 markers"; }
echo " $MARKERS"
grep -q 'LOADER: result status=0 (OK)' "$WORK/mame.log" \
|| { grep 'LOADER: result' "$WORK/mame.log" >&2 || true; fail "COFF loader did not report status 0"; }
grep -q 'placed \.text .* 0 differing' "$WORK/mame.log" \
|| fail "COFF loader could not verify the placed .text bytes"
grep -q '^M4 MAME: PASS' "$WORK/mame.log" \
|| { tail -30 "$WORK/mame.log" >&2; fail "M4 marker check failed"; }
echo "M4 MAME: PASS"
+37
View File
@@ -0,0 +1,37 @@
/*
* switch.c -- a dense char switch, used by tests/m4/termtris.sh to pin the
* driver's late-GEP policy.
*
* The optimizer's switch-to-lookup-table transform creates a new i8 GEP after
* tms-bitaddr --scale; --normalize cannot tell that index apart from an
* already-scaled bit offset, so the table would be indexed one bit per slot.
* tms-clang passes -switch-to-lookup=false; this source makes the hazard
* reachable at -O2 (termtris' init_game and runesc switches are the real
* cases).
*/
int sw_tile(int c)
{
switch(c) {
case '#': return 2;
case '.': return 1;
case '{': return 11;
case '}': return 12;
case '[': return 13;
case ']': return 14;
case '>': return 15;
case '<': return 16;
case '-': return 17;
case '(': return 18;
case ')': return 19;
default: return 0;
}
}
int sw_sum(const char *s)
{
int r = 0;
while(*s) {
r += sw_tile(*s++);
}
return r;
}
+226
View File
@@ -0,0 +1,226 @@
#!/bin/sh
# build.sh -- build the pinned termtris COFF with this compiler (tms-clang +
# llc), then assemble/link with the TI GSPA/GSPLNK toolchain.
#
# scripts/m4/termtris/build.sh
#
# Inputs: the test-only pinned checkout vendor/vt1200-termtris (branch
# `termtris`, commit be5b323...). Output: build/m4-termtris/termtris.coff and
# termtris.map, plus the generated TERMTRIS.ASM/LST for the dis34-style checks.
#
# Overridable: VT1200_TERMTRIS TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC
# TERMTRIS_WORK TERMTRIS_SDK DOSBOX DOSBOX_CONF SDK_IMG
# TERMTRIS_CC_OPT TERMTRIS_LLC_OPT
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
DIR=$ROOT/scripts/m4/termtris
VENDOR=${VT1200_TERMTRIS:-$ROOT/vendor/vt1200-termtris}
SRC=$VENDOR/apps/termtris/src
ENTRY_SRC=$SRC/vt1200
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}
MAME=${MAME:-$ROOT/build/mame-src/vt1200}
DOSBOX=${DOSBOX:-dosbox}
DOSBOX_CONF=${DOSBOX_CONF:-$ROOT/vendor/vt1200/harness/sdk-scratch/dosbox-bench.conf}
SDK_IMG=${SDK_IMG:-$ROOT/vendor/vt1200/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img}
WORK=${TERMTRIS_WORK:-$ROOT/build/m4-termtris}
SDK=${TERMTRIS_SDK:-$ROOT/build/m4-termtris-sdk}
ASM=$WORK/asm
CC_OPT=${TERMTRIS_CC_OPT:--O2}
LLC_OPT=${TERMTRIS_LLC_OPT:--O0}
SOURCES="main game scoredb x11 compat xgfix"
fail() {
echo "termtris build: $*" >&2
exit 1
}
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do
[ -x "$tool" ] || fail "missing tool: $tool"
done
[ -d "$SRC" ] || fail "missing pinned termtris source: $SRC"
# The pinned commit matters: a newer branch may have moved the DIAG markers or
# changed the game loop. Fail loudly rather than silently test another tree.
if git -C "$VENDOR" rev-parse --git-dir >/dev/null 2>&1; then
rev=$(git -C "$VENDOR" rev-parse HEAD)
[ "$rev" = "be5b323f5d2cc4f7a7d25e7143c67bd9828ccbdf" ] \
|| fail "vendor tree is $rev, want the pinned be5b323f5d2cc4f7a7d25e7143c67bd9828ccbdf"
fi
mkdir -p "$ASM" "$WORK"
echo "== compile the pinned sources with this compiler ($CC_OPT) and llc ($LLC_OPT)"
export TMS_CLANG TMS_OPT TMS_BITADDR
# -I order: quoted game headers, then the vt1200 shims (stdio.h/inttypes.h),
# then the small local shims for stdlib/string/time. tms-clang passes
# -nostdinc, so every angle-bracket header must resolve here.
INCLUDES="-I $SRC -I $SRC/vt1200 -I $DIR/include"
for name in $SOURCES; do
case $name in
game) src=$SRC/game.c ;;
main|x11|compat|xgfix) src=$SRC/vt1200/$name.c ;;
scoredb) src=$SRC/vt1200/scoredb.c ;;
*) fail "unknown source $name" ;;
esac
[ -f "$src" ] || fail "missing source: $src"
# shellcheck disable=SC2086
"$ROOT/scripts/tms-clang" "$CC_OPT" $INCLUDES -o "$ASM/$name.ll" "$src" \
|| fail "tms-clang failed for $src"
"$TMS_LLC" -march=tms34010 "$LLC_OPT" -filetype=asm \
"$ASM/$name.ll" -o "$ASM/$name.s" \
|| fail "llc failed for $src"
echo " $name.s: $(grep -c . "$ASM/$name.s") lines"
done
echo "== extract the TI 1987 SDK"
if [ ! -f "$SDK/GSPA.EXE" ]; then
command -v mcopy >/dev/null 2>&1 || fail "mtools (mcopy) is required"
[ -f "$SDK_IMG" ] || fail "SDK image not found: $SDK_IMG"
mkdir -p "$SDK"
mcopy -s -n -i "$SDK_IMG" "::/A2/*" "$SDK/"
mcopy -s -n -i "$SDK_IMG" "::/C2/*" "$SDK/"
fi
[ -f "$SDK/GSPA.EXE" ] || fail "GSPA.EXE not extracted into $SDK"
[ -f "$SDK/RTS.LIB" ] || fail "RTS.LIB not extracted into $SDK"
# GSPA has one flat symbol table per assembly, and llc restarts LBB0_1 in every
# translation unit. Give each TU its own LBB prefix (the M4 run.sh pattern).
tag=0
for name in $SOURCES; do
tag=$((tag + 1))
sed "s/LBB\([0-9][0-9]*_[0-9][0-9]*\)/LBB$tag\1/g" \
"$ASM/$name.s" > "$ASM/$name.unique.s"
done
# The body (entry, code, libc) without the .cinit member: the linker takes the
# empty CINIT.asm first, and the caller relinks with CINITZ.asm when the map
# says .cinit is empty.
# The vendored entry/cinit files end with `.end`, which stops GSPA at that
# point when the pieces are concatenated into one assembly (the vendored
# build assembles each file separately). Drop the terminator; GSPA's own
# `.end` is appended by the cinit member.
strip_end() {
sed '/^[[:space:]]*\.end[[:space:]]*$/d' "$1"
}
: > "$WORK/code.asm"
strip_end "$ENTRY_SRC/entry.asm" >> "$WORK/code.asm"
for name in $SOURCES; do
cat "$ASM/$name.unique.s" >> "$WORK/code.asm"
done
# GSPA needs an explicit `.globl` declaration for a symbol the linker will
# resolve from another object (rts.lib); llc prints the call/reference but no
# declaration. Collect the CALLA/MOVI references and declare the ones this
# assembly does not define itself.
sed -n \
-e 's/.*CALLA[[:space:]]*\(_[A-Za-z0-9_][A-Za-z0-9_]*\).*/\1/p' \
-e 's/.*MOVI[[:space:]]*\(_[A-Za-z0-9_][A-Za-z0-9_]*\).*/\1/p' \
"$WORK/code.asm" | sort -u > "$WORK/refs.txt"
sed -n 's/^\(_[A-Za-z0-9_][A-Za-z0-9_]*\):.*/\1/p' "$WORK/code.asm" \
| sort -u > "$WORK/defs.txt"
: > "$WORK/externs.asm"
while read -r sym; do
[ -n "$sym" ] || continue
grep -qxF "$sym" "$WORK/defs.txt" || printf '\t.globl\t%s\n' "$sym" >> "$WORK/externs.asm"
done < "$WORK/refs.txt"
: > "$WORK/body.asm"
cat "$WORK/externs.asm" "$WORK/code.asm" > "$WORK/body.asm"
strip_end "$ENTRY_SRC/cinit.asm" > "$WORK/cinit.asm"
strip_end "$ENTRY_SRC/cinitz.asm" > "$WORK/cinitz.asm"
# The link command file must keep CRLF line endings for the DOS tool.
write_cmd() {
{
printf '%s\n' '-a'
printf '%s\n' '-o TERMTRIS.OUT'
printf '%s\n' '-m TERMTRIS.MAP'
printf '%s\n' '-e _start'
printf '%s\n' 'TERMTRIS.obj'
printf '%s\n' '-l rts.lib'
printf '%s\n' 'MEMORY'
printf '%s\n' '{'
# The vintage r5.01 build's text+data is 10.6+0.6 KiB and fits the
# 20000h-bit program region. This compiler's -O2/llc -O0 output is
# about twice as large (see the report), so the program region is
# widened to 40000h bits (32 KiB). It ends at 018E0000, below the
# loader's COFF buffer at 01900000 and the .bss region at 01A00000.
printf '%s\n' ' prog: origin = 018A0000h, length = 40000h'
printf '%s\n' ' ram: origin = 01A00000h, length = 40000h'
printf '%s\n' '}'
printf '%s\n' 'SECTIONS'
printf '%s\n' '{'
printf '%s\n' ' .text: {} > prog'
printf '%s\n' ' .data: {} > prog'
printf '%s\n' ' .cinit: {} > prog'
printf '%s\n' ' .bss: {} > ram'
printf '%s\n' '}'
} > "$WORK/termtris.cmd"
sed 's/$/\r/' "$WORK/termtris.cmd" > "$SDK/TERMTRIS.CMD"
}
assemble_link() {
cinit=$1
cat "$WORK/body.asm" "$WORK/$cinit" > "$SDK/TERMTRIS.ASM"
rm -f "$SDK/TERMTRIS.OBJ" "$SDK/TERMTRIS.LST" \
"$SDK/TERMTRIS.OUT" "$SDK/TERMTRIS.MAP"
write_cmd
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout "${DOSBOX_TIMEOUT:-300}" "$DOSBOX" -conf "$DOSBOX_CONF" \
-noprimaryconf -nolocalconf \
-c "mount c $SDK" -c "c:" \
-c "gspa TERMTRIS TERMTRIS.obj TERMTRIS.lst -l" \
-c "gsplnk TERMTRIS.cmd" \
-c "exit" -exit > "$WORK/dosbox.log" 2>&1 \
|| { tail -30 "$WORK/dosbox.log" >&2; fail "DOSBox failed ($cinit)"; }
[ -f "$SDK/TERMTRIS.LST" ] || fail "GSPA produced no listing (see $WORK/dosbox.log)"
sed 's/\r$//' "$SDK/TERMTRIS.LST" > "$WORK/termtris.lst"
grep -q 'No Errors' "$WORK/termtris.lst" \
|| { grep -i -B2 -A2 'error' "$WORK/termtris.lst" >&2 || true; \
fail "GSPA reported an error"; }
# GSPA's symbol table caps names at 31 characters. llc's path-hashed
# static/switch-table names can exceed that; the truncation is consistent
# between definition and reference and the map below is checked for
# duplicates. Any other warning is fatal.
nwarn=$(grep -c 'WARNING' "$WORK/termtris.lst" || true)
ntrunc=$(grep -c 'WARNING - SYMBOL TRUNCATED' "$WORK/termtris.lst" || true)
if [ "$nwarn" -ne "$ntrunc" ]; then
grep -i -B2 -A2 'warning' "$WORK/termtris.lst" >&2 || true
fail "GSPA reported a non-truncation warning"
fi
[ "$nwarn" -eq 0 ] || echo " GSPA: $nwarn long symbol name(s) truncated to 31 chars"
[ -f "$SDK/TERMTRIS.OUT" ] || fail "GSPLNK produced no TERMTRIS.OUT"
sed 's/\r$//' "$SDK/TERMTRIS.MAP" > "$WORK/termtris.map"
}
echo "== assemble and link with GSPA/GSPLNK under DOSBox"
assemble_link cinit.asm
CSIZE=$(awk '$1 == ".cinit" {print $4; exit}' "$WORK/termtris.map")
if [ "$CSIZE" = "00000000" ]; then
# An empty .cinit makes the linker point `cinit` at .text; the entry's
# walker would read an instruction as a record count. Relink with the
# one-word terminator, exactly as the vendored build.sh does.
echo " .cinit is empty; relinking with the CINITZ terminator"
assemble_link cinitz.asm
fi
ENTRY_ADDR=$(awk '$2 == "_start" { print $1; exit }' "$WORK/termtris.map")
[ -n "$ENTRY_ADDR" ] || fail "link map has no _start"
[ "$ENTRY_ADDR" = "018a0000" ] || [ "$ENTRY_ADDR" = "018A0000" ] \
|| fail "linker _start is '$ENTRY_ADDR', want 018a0000"
cp "$SDK/TERMTRIS.OUT" "$WORK/termtris.coff"
cp "$SDK/TERMTRIS.ASM" "$WORK/termtris.asm"
echo " linked .text at $ENTRY_ADDR, TERMTRIS.OUT -> $WORK/termtris.coff"
awk '/^\.text|^\.data|^\.cinit|^\.bss/ || /_dbg_loops|_dbg_events|_dbg_last|_dbg_focus|_dbg_rev|_quit|_tick_interval/ {print}' \
"$WORK/termtris.map" | sed -n '1,40p'
+12
View File
@@ -0,0 +1,12 @@
/*
* stdlib.h -- the subset of <stdlib.h> termtris' game.c uses, for the
* freestanding tms-clang build. The implementations come from the TI 1987
* RTS.LIB (rand/srand); there is no heap allocator in the game.
*/
#ifndef VT_TERMTRIS_STDLIB_H_
#define VT_TERMTRIS_STDLIB_H_
int rand(void);
void srand(unsigned seed);
#endif
+15
View File
@@ -0,0 +1,15 @@
/*
* string.h -- the subset of <string.h> termtris uses, for the freestanding
* tms-clang build. The implementations come from the TI 1987 RTS.LIB.
*/
#ifndef VT_TERMTRIS_STRING_H_
#define VT_TERMTRIS_STRING_H_
void *memcpy(void *dst, const void *src, unsigned n);
void *memset(void *dst, int c, unsigned n);
unsigned strlen(const char *s);
char *strcpy(char *dst, const char *src);
char *strncpy(char *dst, const char *src, unsigned n);
int strcmp(const char *a, const char *b);
#endif
+8
View File
@@ -0,0 +1,8 @@
/*
* time.h -- game.c includes <time.h>, but the VT1200 port replaces time(0)
* with the ROM's free-running tick (vt_rand_seed); no time function is used.
*/
#ifndef VT_TERMTRIS_TIME_H_
#define VT_TERMTRIS_TIME_H_
#endif
+393
View File
@@ -0,0 +1,393 @@
-- play_keys.lua -- M4 termtris acceptance harness (adapted from the pinned
-- apps/termtris/tests/play_keys.lua).
--
-- Boots the firmware with VT1200_SKIP_LEVEL1=1 (the documented divergence) to
-- the OS, then at frame LOADER_AT pokes:
-- * the loader payload at bit 0x01880000 (raw dwt/loader/build/loader.bin)
-- * a GSPLNK COFF image at LOADER_BUF (default bit 0x01900000)
-- * the loader descriptor at 0x01882C00
-- and sets PC = 0x01880000. It does not use the network, the rdebug service
-- or any firmware transport: this is the debugger-assisted milestone.
--
-- The loader itself copies the COFF sections to their linked bit addresses and
-- calls create_process(entry, "LOADER", 0x6000, 1, 0). This script only pokes
-- the input and reads the result; it never writes the app's code or markers.
--
-- Env:
-- LOADER_BIN loader payload (default dwt/loader/build/loader.bin)
-- LOADER_COFF app COFF (default dwt/loader/build/marker.coff)
-- LOADER_BUF COFF buffer bit address (default 0x01900000)
-- LOADER_AT injection frame (default 2500)
-- LOADER_END inspection/exit frame (default 2700)
-- LOADER_OUT evidence directory (default dwt/loader/evidence)
-- LOADER_SNAP 1 = write a PNG at LOADER_END (default 0)
-- LOADER_VERBOSE 1 = print the result block every 25 frames
local function hex(v, w) return string.format("%0" .. (w or 8) .. "X", v) end
local machine = manager.machine
local PLAY_MODE = os.getenv("PLAY_MODE") or "move"
print("PLAYKEYS: mode="..PLAY_MODE)
local DL = tonumber(os.getenv("DIAG_LOOPS") or "0")
local DE = tonumber(os.getenv("DIAG_EVENTS") or "0")
local DT = tonumber(os.getenv("DIAG_LAST") or "0")
local DF = tonumber(os.getenv("DIAG_FOCUS") or "0")
local DR = tonumber(os.getenv("DIAG_REV") or "0")
local DQ = tonumber(os.getenv("DIAG_QUIT") or "0")
local DTICK = tonumber(os.getenv("DIAG_TICK") or "0")
local DW = tonumber(os.getenv("DIAG_WIN") or "0")
local ports = machine.ioport.ports
local key_a = ports[":lk401:K26"].fields["A"]
local key_d = ports[":lk401:K28"].fields["D"]
local key_s = ports[":lk401:K27"].fields["S"]
local key_w = ports[":lk401:K14"].fields["W"]
local key_0 = ports[":lk401:K10"].fields["0 )"]
local key_p = ports[":lk401:K22"].fields["P"]
local key_r = ports[":lk401:K16"].fields["R"]
local key_h = ports[":lk401:K31"].fields["H"]
local key_q = ports[":lk401:K13"].fields["Q"]
local tabx = ports[":tablet:TABLET_X"].fields["Tablet X"]
local taby = ports[":tablet:TABLET_Y"].fields["Tablet Y"]
local tabb = ports[":tablet:TABLET_BTN"].fields["P1 Button 1"]
local cpu = machine.devices[':maincpu']
local dbg = machine.debugger
local prog = cpu.spaces['program']
if not dbg then
print("LOADER: debugger not enabled; pass -debug")
machine:exit()
return
end
local AT = tonumber(os.getenv("LOADER_AT") or "2500")
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
local BIN = os.getenv("LOADER_BIN") or "dwt/loader/build/loader.bin"
local COFF = os.getenv("LOADER_COFF") or "dwt/loader/build/marker.coff"
local BUF = tonumber(os.getenv("LOADER_BUF") or "0x01900000")
local OUT = os.getenv("LOADER_OUT") or "dwt/loader/evidence"
local SNAP = os.getenv("LOADER_SNAP") == "1"
local MODE = tonumber(os.getenv("LOADER_MODE") or "0")
local A8DESC = os.getenv("LOADER_A8_DESC") == "1"
local VERBOSE = os.getenv("LOADER_VERBOSE") == "1"
local LOADER_BASE = 0x01880000
local DESC = 0x01882C00
local RESULT = 0x01882D00
local RETURN_STUB = 0x01883100
local RETURN_SP = 0x01883110
local function rd(p) return io.open(p, "rb") end
local function slurp(p)
local f, err = rd(p)
if not f then error("cannot open " .. p .. ": " .. tostring(err)) end
local s = f:read("*a")
f:close()
return s
end
local function u16(s, o)
return s:byte(o + 1) + s:byte(o + 2) * 256
end
local function u32(s, o)
return s:byte(o + 1) + s:byte(o + 2) * 256
+ s:byte(o + 3) * 65536 + s:byte(o + 4) * 16777216
end
local function parse_coff(d)
local c = { data = d }
c.magic = u16(d, 0)
c.nscns = u16(d, 2)
c.opthdr = u16(d, 16)
c.sections = {}
if c.opthdr > 0 then
c.optmagic = u16(d, 20)
c.entry = u32(d, 20 + 16)
c.textstart = u32(d, 20 + 20)
c.datastart = u32(d, 20 + 24)
end
local base = 20 + c.opthdr
for i = 0, c.nscns - 1 do
local o = base + i * 40
local name = d:sub(o + 1, o + 8):gsub("%z", "")
c.sections[#c.sections + 1] = {
name = name,
paddr = u32(d, o + 8),
size = u32(d, o + 16), -- bits
scnptr = u32(d, o + 20),
flags = u32(d, o + 36),
}
end
return c
end
local function poke_bytes(bitaddr, data)
local n = math.floor((#data + 1) / 2)
for i = 0, n - 1 do
local lo = data:byte(i * 2 + 1) or 0
local hi = data:byte(i * 2 + 2) or 0
prog:write_u16(bitaddr + i * 16, lo + hi * 256)
end
return n
end
local function verify_bytes(bitaddr, data)
local n = math.floor((#data + 1) / 2)
local bad = 0
for i = 0, n - 1 do
local lo = data:byte(i * 2 + 1) or 0
local hi = data:byte(i * 2 + 2) or 0
if prog:read_u16(bitaddr + i * 16) ~= lo + hi * 256 then
bad = bad + 1
end
end
return n, bad
end
local function w32(a, v) prog:write_u32(a, v) end
local function r32(a)
local ok, v = pcall(function() return prog:read_u32(a) end)
if ok then return v end
return 0xDEADBEEF
end
local loader = slurp(BIN)
local coff = slurp(COFF)
local app = parse_coff(coff)
print(string.format("LOADER: payload %s (%d B) -> bit %s", BIN, #loader, hex(LOADER_BASE)))
print(string.format("LOADER: COFF %s (%d B) -> bit %s", COFF, #coff, hex(BUF)))
print(string.format("LOADER: file magic=%04X nscns=%d opthdr=%d optmagic=%s entry=%s textstart=%s",
app.magic, app.nscns, app.opthdr,
app.optmagic and string.format("%04X", app.optmagic) or "none",
hex(app.entry or 0), hex(app.textstart or 0)))
for _, s in ipairs(app.sections) do
print(string.format("LOADER: sec %-8s paddr=%s size=%d bits (%d B) scnptr=%#x flags=%#x",
s.name, hex(s.paddr), s.size, math.floor(s.size / 8), s.scnptr, s.flags))
end
local TRACE = os.getenv("LOADER_TRACE") == "1"
local frames = 0
local injected = false
local done = false
local last_count = -1
local function status_name(v)
local t = {
[0] = "OK",
[1] = "bad descriptor",
[2] = "truncated COFF / section headers",
[3] = "bad COFF magic",
[4] = "section size not whole words",
[5] = "target outside RAM",
[6] = "target overlaps loader page or COFF buffer",
[7] = "relocation records present (unsupported)",
[8] = "target not in a free-list block",
[9] = "no entry point",
}
return t[v] or ("error " .. tostring(v))
end
local function result_dump()
local st = r32(RESULT + 0x000)
print(string.format(
"LOADER: result status=%d (%s) entry=%s text=%s/%d bits data=%s/%d bits bss=%s/%d bits relocs=%d pcb=%s nscns=%d len=%d",
st, status_name(st), hex(r32(RESULT + 0x020)),
hex(r32(RESULT + 0x040)), r32(RESULT + 0x060),
hex(r32(RESULT + 0x080)), r32(RESULT + 0x0A0),
hex(r32(RESULT + 0x0C0)), r32(RESULT + 0x0E0),
r32(RESULT + 0x100), hex(r32(RESULT + 0x120)),
r32(RESULT + 0x140), r32(RESULT + 0x160)))
end
local function inject()
-- 1. loader itself
local n, bad = 0, 0
poke_bytes(LOADER_BASE, loader)
n, bad = verify_bytes(LOADER_BASE, loader)
print(string.format("LOADER: loader poked %d words, readback %d bad at %s",
n, bad, hex(LOADER_BASE)))
-- 2. COFF image
poke_bytes(BUF, coff)
n, bad = verify_bytes(BUF, coff)
print(string.format("LOADER: COFF poked %d words, readback %d bad at %s",
n, bad, hex(BUF)))
-- 3. pre-fill every .bss section with A5A5, so a loaded zero is meaningful
for _, s in ipairs(app.sections) do
if s.flags & 0x80 ~= 0 then
for i = 0, math.floor(s.size / 16) - 1 do
prog:write_u16(s.paddr + i * 16, 0xA5A5)
end
print(string.format("LOADER: pre-filled .bss %s..%s with A5A5",
hex(s.paddr), hex(s.paddr + s.size)))
end
end
-- 4. descriptor: magic "LD" "R1", buf, len, mode=0 (spin)
w32(DESC + 0x00, 0)
prog:write_u16(DESC + 0x00, 0x4C44)
prog:write_u16(DESC + 0x10, 0x5231)
w32(DESC + 0x20, BUF)
w32(DESC + 0x40, #coff)
w32(DESC + 0x60, MODE)
print(string.format("LOADER: descriptor @%s magic=%04X/%04X buf=%s len=%d mode=%d",
hex(DESC), prog:read_u16(DESC + 0x00), prog:read_u16(DESC + 0x10),
hex(r32(DESC + 0x20)), r32(DESC + 0x40), r32(DESC + 0x60)))
-- 5. return stub. mode 0 ignores it (the loader jumps to ORIG_PC
-- itself); mode 1 returns here, and the stub resumes the interrupted
-- PC so the caller yields and the scheduler can run the child.
local orig_pc = cpu.state['PC'].value
if MODE == 1 then
prog:write_u16(RETURN_STUB, 0xC080) -- jauc <orig_pc>
prog:write_u16(RETURN_STUB + 0x10, orig_pc & 0xFFFF)
prog:write_u16(RETURN_STUB + 0x20, math.floor(orig_pc / 0x10000) & 0xFFFF)
else
prog:write_u16(RETURN_STUB, 0xC0FF) -- jruc self
end
-- 6. enter the loader. A8=0 selects the fixed descriptor; SP points at
-- the return slot so mode=1 would return to the spin stub.
-- Leave SP alone for mode 0: the loader pushes onto the current
-- process stack, then restores it and resumes at ORIG_PC. (mode 1
-- would use the return stub.)
w32(0x01882A20, orig_pc) -- LD_ORIG_PC
print(string.format("LOADER: will resume at %s after create_process", hex(orig_pc)))
cpu.state['A8'].value = A8DESC and DESC or 0
if MODE == 1 then
-- Simulate a CALLA from the current process: push the return
-- address on the *firmware* stack (a real transport would do this).
local sp = cpu.state['SP'].value - 0x20
w32(sp, RETURN_STUB)
cpu.state['SP'].value = sp
end
cpu.state['PC'].value = LOADER_BASE
injected = true
print(string.format("LOADER: frame %d: entered loader at %s (caller SP=%s)",
frames, hex(LOADER_BASE), hex(cpu.state['SP'].value)))
end
local function inspect()
result_dump()
-- Verify the placement from RAM: .text/.data must equal the file bytes.
for _, s in ipairs(app.sections) do
if s.scnptr ~= 0 and s.size > 0 then
local nb = math.floor(s.size / 8)
local bytes = coff:sub(s.scnptr + 1, s.scnptr + nb)
local _, bad = verify_bytes(s.paddr, bytes)
print(string.format("LOADER: placed %s at %s: %d bytes, %d differing",
s.name, hex(s.paddr), nb, bad))
end
end
local base = COFF:match("([^/]+)$") or COFF
if base:find("marker") then
print(string.format("LOADER: marker app: start=%s data=%s data2=%s bss=%s count=%d",
hex(r32(0x018E0000)), hex(r32(0x018E0020)), hex(r32(0x018E0040)),
hex(r32(0x018E0060)), r32(0x018E0080)))
elseif base:find("xhello") then
print(string.format("LOADER: xhello: stage=%d strlen=%d display=%s root=%s window=%s sync=%s",
r32(0x01890140), r32(0x01890160), hex(r32(0x01890100)),
hex(r32(0x01890110)), hex(r32(0x01890120)), hex(r32(0x01890130))))
end
if DL ~= 0 then
print(string.format("DIAG: loops=%d events=%d last=%d focus=%08X rev=%d quit=%d window=%08X", r32(DL), r32(DE), r32(DT), DF ~= 0 and r32(DF) or 0, DR ~= 0 and r32(DR) or 0, DQ ~= 0 and r32(DQ) or 0, DW ~= 0 and r32(DW) or 0))
if DTICK ~= 0 then
print(string.format("DIAG: tick=%d", r32(DTICK)))
end
end
print(string.format("LOADER: PC at inspection = %s", hex(cpu.state['PC'].value)))
end
emu.register_frame_done(function()
frames = frames + 1
if frames == 2900 then tabx:set_value(700); taby:set_value(500); print("PLAYKEYS: move") end
if frames == 3000 then tabb:set_value(1); print("PLAYKEYS: click down") end
if frames == 3020 then tabb:set_value(0); print("PLAYKEYS: click up") end
if PLAY_MODE == "move" then
if frames == 3100 then key_a:set_value(1) end
if frames == 3110 then key_a:set_value(0) end
if frames == 3120 then key_d:set_value(1) end
if frames == 3130 then key_d:set_value(0) end
if frames == 3140 then key_s:set_value(1) end
if frames == 3150 then key_s:set_value(0) end
if frames == 3160 then key_w:set_value(1) end
if frames == 3170 then key_w:set_value(0) end
if frames == 3180 then key_0:set_value(1) end
if frames == 3190 then key_0:set_value(0) end
elseif PLAY_MODE == "panels" then
if frames == 3100 then key_h:set_value(1) end
if frames == 3110 then key_h:set_value(0) end
if frames == 3120 then key_r:set_value(1) end
if frames == 3130 then key_r:set_value(0) end
elseif PLAY_MODE == "quit" then
if frames == 3095 then print("PLAYKEYS: quit arm") end
if frames == 3100 then print("PLAYKEYS: Q down"); key_q:set_value(1) end
if frames == 3110 then print("PLAYKEYS: Q up"); key_q:set_value(0) end
end
if frames == AT and not injected then
inject()
if TRACE then dbg:command("trace /tmp/opencode/ld1.tr") end
end
if injected and TRACE and frames == AT + 1 then
dbg:command("trace off")
print("LOADER: trace written to /tmp/opencode/ld.tr")
end
if injected and not done then
if TRACE and frames <= AT + 40 then
print(string.format("LOADER: trace f=%d PC=%s SP=%s A14=%s status=%s entry=%s",
frames, hex(cpu.state['PC'].value), hex(cpu.state['SP'].value),
hex(cpu.state['A14'].value), hex(r32(RESULT + 0x000)),
hex(r32(RESULT + 0x020))))
end
local st = r32(RESULT + 0x000)
-- status 0 means OK; nonzero is final too once parsing has begun
if st ~= 0xFFFFFFFF and not done then
-- The loader writes 0 on success. A nonzero status is final
-- only after it has been written once (it is 0 from power-on,
-- so require the loader to have run: PC no longer at entry or
-- the app counter moved).
end
if VERBOSE and frames % 25 == 0 then
result_dump()
end
if base_contains_marker then
local c = r32(0x018E0080)
if c ~= last_count then
if last_count ~= -1 then
print(string.format("LOADER: marker counter %d -> %d (child running)",
last_count, c))
end
last_count = c
end
end
end
if frames >= ENDF then
if injected then
done = true
inspect()
if SNAP then
local screen = nil
for _, s in pairs(machine.screens) do screen = s break end
if screen then
local out = OUT .. "/loader_frame" .. frames .. ".png"
local ok, err = pcall(function() return screen:snapshot(out) end)
print(string.format("LOADER: snapshot %s ok=%s %s",
out, tostring(ok), tostring(err or "")))
end
end
end
machine:exit()
end
end, "vt1200-loader")
-- (Lua has no forward use of a name defined later; set a small flag here.)
base_contains_marker = (COFF:match("([^/]+)$") or ""):find("marker") ~= nil
dbg.visible_cpu = cpu
dbg.execution_state = 'run'
+104
View File
@@ -0,0 +1,104 @@
#!/bin/sh
# play_keys.sh -- run the M4-built termtris COFF in MAME through the proven
# play_keys paths (board, move, panels, quit), capture a snapshot per mode,
# and print the DIAG line. Threshold enforcement lives in
# scripts/tests/m4/termtris.sh.
#
# scripts/m4/termtris/play_keys.sh
#
# Env: MODES (default "board move panels quit"), TERMTRIS_NOBUILD=1,
# MAME, TERMTRIS_WORK, LOADER_AT/LOADER_END.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
DIR=$ROOT/scripts/m4/termtris
VENDOR=${VT1200_TERMTRIS:-$ROOT/vendor/vt1200-termtris}
WORK=${TERMTRIS_WORK:-$ROOT/build/m4-termtris}
M=${MAME:-$ROOT/build/mame-src/vt1200}
COFF=$WORK/termtris.coff
MAP=$WORK/termtris.map
EV=$WORK/evidence
LOADER=$ROOT/vendor/vt1200/dwt/loader/build/loader.bin
fail() {
echo "termtris play_keys: $*" >&2
exit 1
}
if [ "${TERMTRIS_NOBUILD:-}" != 1 ]; then
"$DIR/build.sh" > "$WORK/build.log" 2>&1 \
|| { tail -40 "$WORK/build.log" >&2; fail "build failed"; }
fi
[ -f "$COFF" ] || fail "missing COFF: $COFF"
[ -f "$MAP" ] || fail "missing link map: $MAP"
[ -x "$M" ] || fail "MAME binary not built: $M"
[ -f "$LOADER" ] || fail "COFF loader missing: $LOADER"
mkdir -p "$EV" "$WORK/run/nvram" "$WORK/run/cfg"
# The link map prints two address/name pairs per line, so the symbol address
# is the field before the name (the vendored play_keys.sh does the same).
addr() {
awk -v sym="$1" '{for (i = 2; i <= NF; i++) if ($i == sym) print $(i - 1)}' \
"$MAP" | head -1
}
DIAG_LOOPS=$(addr _dbg_loops)
DIAG_EVENTS=$(addr _dbg_events)
DIAG_LAST=$(addr _dbg_last)
DIAG_FOCUS=$(addr _dbg_focus)
DIAG_REV=$(addr _dbg_rev)
DIAG_QUIT=$(addr _quit)
DIAG_TICK=$(addr _tick_interval)
DIAG_WIN=$(addr _vt_win)
for sym in "$DIAG_LOOPS" "$DIAG_EVENTS" "$DIAG_LAST" "$DIAG_FOCUS" \
"$DIAG_REV" "$DIAG_QUIT" "$DIAG_TICK" "$DIAG_WIN"; do
[ -n "$sym" ] || fail "a termtris DIAG symbol is missing from $MAP"
done
echo "== termtris markers: loops=$DIAG_LOOPS events=$DIAG_EVENTS last=$DIAG_LAST"
echo " focus=$DIAG_FOCUS rev=$DIAG_REV quit=$DIAG_QUIT tick=$DIAG_TICK win=$DIAG_WIN"
for mode in ${MODES:-board move panels quit}; do
LOG=$WORK/play_$mode.log
rm -f "$EV/loader_frame3200.png" "$EV/loader_frame"*.png
echo "== play_keys: $mode"
VT1200_SKIP_LEVEL1=1 \
VT1200_MONITOR=VR319 \
LOADER_SNAP=1 \
PLAY_MODE=$mode \
DIAG_LOOPS="0x$DIAG_LOOPS" \
DIAG_EVENTS="0x$DIAG_EVENTS" \
DIAG_LAST="0x$DIAG_LAST" \
DIAG_FOCUS="0x$DIAG_FOCUS" \
DIAG_REV="0x$DIAG_REV" \
DIAG_QUIT="0x$DIAG_QUIT" \
DIAG_TICK="0x$DIAG_TICK" \
DIAG_WIN="0x$DIAG_WIN" \
LOADER_AT="${LOADER_AT:-2500}" \
LOADER_END="${LOADER_END:-3200}" \
LOADER_BIN="$LOADER" \
LOADER_COFF="$COFF" \
LOADER_OUT="$EV" \
timeout "${MAME_TIMEOUT:-900}" "$M" vt1200 \
-rompath "$ROOT/vendor/vt1200/harness/mame/roms" \
-video none -sound none -nothrottle -skip_gameinfo \
-nvram_directory "$WORK/run/nvram" \
-cfg_directory "$WORK/run/cfg" \
-debug -debugger none \
-autoboot_script "$DIR/play_keys.lua" -autoboot_delay 0 \
> "$LOG" 2>&1 \
|| { tail -40 "$LOG" >&2; fail "MAME run failed for $mode"; }
grep -E 'PLAYKEYS:|LOADER: result|LOADER: snapshot' "$LOG" | tail -5 \
|| true
DIAG=$(grep -a 'DIAG: loops' "$LOG" | tail -1 || true)
[ -n "$DIAG" ] || { tail -40 "$LOG" >&2; \
fail "no DIAG line for $mode (see $LOG)"; }
echo " $DIAG"
echo "$DIAG" > "$WORK/diag_$mode.txt"
[ -s "$EV/loader_frame${LOADER_END:-3200}.png" ] \
|| fail "no $mode snapshot in $EV"
cp "$EV/loader_frame${LOADER_END:-3200}.png" \
"$EV/termtris_${mode}_${LOADER_END:-3200}.png"
echo " $EV/termtris_${mode}_${LOADER_END:-3200}.png"
done
+24
View File
@@ -0,0 +1,24 @@
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n)*0x20u))
int le(int a, int b) { return a <= b; }
unsigned ule(unsigned a, unsigned b) { return a <= b; }
int sum_le(int n) { int s = 0, i; for (i = 0; i <= n; i++) s += i; return s; }
int ge(int a, int b) { return a >= b; }
unsigned uge(unsigned a, unsigned b) { return a >= b; }
int gt(int a, int b) { return a > b; }
unsigned ugt(unsigned a, unsigned b) { return a > b; }
int ne(int a, int b) { return a != b; }
int lt(int a, int b) { return a < b; }
unsigned ult(unsigned a, unsigned b) { return a < b; }
int unsigned_test(void) {
MARK(0) = (unsigned)le(5, 7);
MARK(1) = (unsigned)ule(7u, 5u);
MARK(2) = (unsigned)sum_le(10);
MARK(3) = (unsigned)ge(7, 5);
MARK(4) = (unsigned)uge(5u, 7u);
MARK(5) = (unsigned)gt(5, 7);
MARK(6) = (unsigned)ugt(7u, 5u);
MARK(7) = (unsigned)ne(7, 5);
MARK(8) = (unsigned)lt(7, 5);
MARK(9) = (unsigned)ult(5u, 7u);
return 0;
}
+278
View File
@@ -0,0 +1,278 @@
#!/bin/sh
# M4 ROM symbol validation.
#
# Every entry in the v2.2 table must:
# - sit in the ROM bit range 3F200000h..3FFFFFFFh,
# - be 16-bit aligned (TMS34010 instructions are 16-bit words), and
# - start with a plausible function prologue (MMTM/MOVE/SETF/CMP/ADDI).
#
# The correction-sensitive entries are also checked semantically with dis34:
# - XMapWindow must be 0x3FD64F60 and build MapWindow request opcode 8;
# - XRaiseWindow must be 0x3FD645B0 and build ConfigureWindow opcode 12 with
# CWStackMode=Above (value mask 0x40, value 0);
# - XPutImage builds request opcode 0x48 and XGetImage opcode 0x49;
# - XLoadFont builds OpenFont opcode 45 (0x2d) and XCopyArea CopyArea
# opcode 62 (0x3e) — both from the ROM request builders, not termtris;
# - XCreatePixmap builds CreatePixmap opcode 0x35 and XSetInputFocus builds
# SetInputFocus opcode 0x2a;
# - XCreateImage allocates 0x58 bytes (sizeof(XImage) == 88);
# - XSetErrorHandler / XSetIOErrorHandler install the ROM defaults into the
# per-process XG block at +0x60 / +0x40.
#
# This is a separate gate from scripts/m3/validate-syms.sh: M3 is a four-entry
# regression, M4 walks the whole table. Both use the same range/alignment/
# prologue rules and the same corrected XMapWindow address.
#
# scripts/m4/validate-syms.sh [symbols/vt1200-v22.syms]
#
# Overridable: M4_SYMS DIS34 ROM
set -eu
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
SYMS=${1:-${M4_SYMS:-$ROOT/symbols/vt1200-v22.syms}}
DIS34=${DIS34:-$ROOT/vendor/vt1200/tools/smart34010/dis34}
ROM=${ROM:-$ROOT/vendor/vt1200/vt1200_v22.bin}
ROM_LO=$((0x3F200000))
ROM_HI=$((0x3FFFFFFF))
MAPWINDOW=$((0x3FD64F60))
RAISEWINDOW=$((0x3FD645B0))
fail() {
echo "m4 validate-syms: FAIL: $*" >&2
exit 1
}
[ -f "$SYMS" ] || fail "symbol file not found: $SYMS"
[ -x "$DIS34" ] || fail "dis34 not found or not executable: $DIS34"
[ -f "$ROM" ] || fail "ROM image not found: $ROM"
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m4-syms.XXXXXX")
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
sym_addr() { # sym_addr NAME
awk -v n="$1" '$1 == n { print $2; exit }' "$SYMS"
}
dis_at() { # dis_at BIT_ADDR BYTE_COUNT OUT
_daddr=$1
_dcount=$2
_dout=$3
_doff=$(( (_daddr - ROM_LO) / 8 ))
dd if="$ROM" of="$_dout.bin" bs=1 skip="$_doff" count="$_dcount" 2>/dev/null
# dis34 reports "unexpected end of input" when the last instruction is
# truncated; the leading instructions are still valid, so ignore its exit
# status and parse the output.
"$DIS34" -b "$(printf '%08X' "$_daddr")" -o "$_dout.bin" \
> "$_dout" 2>/dev/null || true
}
first_insn() { # first_insn DIS_TEXT
awk 'NF && $1 !~ /^L[0-9A-Fa-f]+:$/ { print tolower($1); exit }' "$1"
}
require_match() { # require_match DIS_TEXT ERE WHAT
grep -Eiq "$2" "$1" || {
sed 's/^/ /' "$1" >&2
fail "$3"
}
}
check_prologue() { # check_prologue NAME ADDR
_name=$1
_addr=$2
case "$_addr" in
0x* | 0X*) ;;
*) fail "$_name has malformed address '$_addr' (want 0x...)" ;;
esac
_dec=$((_addr))
[ "$_dec" -ge "$ROM_LO" ] && [ "$_dec" -le "$ROM_HI" ] \
|| fail "$_name $_addr is outside the ROM bit range 3F200000h..3FFFFFFFh"
[ $((_dec % 16)) -eq 0 ] \
|| fail "$_name $_addr is not 16-bit aligned"
dis_at "$_dec" 64 "$TMP/insn"
_first=$(first_insn "$TMP/insn")
[ -n "$_first" ] || fail "$_name $_addr disassembled to nothing"
case " move mmtm setf cmp addi " in
*" $_first "*) ;;
*) fail "$_name $_addr starts with '$_first', not a plausible function prologue" ;;
esac
echo " ok $_name $_addr: $_first ..."
}
# XMapWindow must be the MapWindow entry, not the ConfigureWindow entry.
check_mapwindow() {
_addr=$(sym_addr XMapWindow)
[ -n "$_addr" ] || fail "table has no XMapWindow entry"
_dec=$((_addr))
if [ "$_dec" -eq "$RAISEWINDOW" ]; then
fail "XMapWindow is $_addr, which is XRaiseWindow (ConfigureWindow opcode 12), not the MapWindow entry (0x3FD64F60): wrong address"
fi
[ "$_dec" -eq "$MAPWINDOW" ] \
|| fail "XMapWindow is $_addr, expected 0x3FD64F60 (MapWindow request opcode 8); wrong address"
dis_at "$_dec" 512 "$TMP/mapwindow"
require_match "$TMP/mapwindow" 'movk[[:space:]]+8,[[:space:]]+a11' \
"XMapWindow does not build MapWindow request opcode 8"
require_match "$TMP/mapwindow" 'movb[[:space:]]+a11,[[:space:]]+\*a10' \
"XMapWindow does not write the request opcode to the display buffer"
echo " ok XMapWindow builds MapWindow opcode 8 (XRaiseWindow is 0x3FD645B0)"
}
# XRaiseWindow must be the ConfigureWindow/Above entry.
check_raisewindow() {
_addr=$(sym_addr XRaiseWindow)
[ -n "$_addr" ] || fail "table has no XRaiseWindow entry"
_dec=$((_addr))
if [ "$_dec" -eq "$MAPWINDOW" ]; then
fail "XRaiseWindow is $_addr, which is XMapWindow (MapWindow opcode 8), not ConfigureWindow: wrong address"
fi
[ "$_dec" -eq "$RAISEWINDOW" ] \
|| fail "XRaiseWindow is $_addr, expected 0x3FD645B0 (ConfigureWindow opcode 12)"
dis_at "$_dec" 512 "$TMP/raisewindow"
require_match "$TMP/raisewindow" 'movk[[:space:]]+12,[[:space:]]+a[0-9]+' \
"XRaiseWindow does not build ConfigureWindow request opcode 12"
require_match "$TMP/raisewindow" 'movi[[:space:]]+0x40,[[:space:]]+a[0-9]+' \
"XRaiseWindow does not set the CWStackMode value mask 0x40"
require_match "$TMP/raisewindow" '\*a10\(0x40\)' \
"XRaiseWindow does not write the CWStackMode value-mask field"
require_match "$TMP/raisewindow" '\*a10\(0x60\)' \
"XRaiseWindow does not write the CWStackMode=Above value"
echo " ok XRaiseWindow builds ConfigureWindow opcode 12, CWStackMode=Above"
}
# XPutImage/XGetImage request opcodes, per the ROM strings they reference.
check_image_requests() {
_addr=$(sym_addr XPutImage)
[ -n "$_addr" ] || fail "table has no XPutImage entry"
dis_at "$((_addr))" 544 "$TMP/putimage"
require_match "$TMP/putimage" 'movi[[:space:]]+0x48,[[:space:]]+a[0-9]+' \
"XPutImage does not build PutImage request opcode 0x48"
require_match "$TMP/putimage" 'movb[[:space:]]+a[0-9]+,[[:space:]]+\*a[0-9]+' \
"XPutImage does not write its request opcode to the buffer"
echo " ok XPutImage builds PutImage opcode 0x48"
_addr=$(sym_addr XGetImage)
[ -n "$_addr" ] || fail "table has no XGetImage entry"
dis_at "$((_addr))" 512 "$TMP/getimage"
require_match "$TMP/getimage" 'movi[[:space:]]+0x49,[[:space:]]+a[0-9]+' \
"XGetImage does not build GetImage request opcode 0x49"
require_match "$TMP/getimage" 'movb[[:space:]]+a[0-9]+,[[:space:]]+\*a[0-9]+' \
"XGetImage does not write its request opcode to the buffer"
echo " ok XGetImage builds GetImage opcode 0x49"
}
# XLoadFont/XCopyArea are inherited from the M4 plan, not the termtris xlib.h.
# Their ROM bodies build OpenFont opcode 45 (0x2d) and CopyArea opcode 62
# (0x3e); both write the opcode to the request buffer.
check_font_copy() {
_addr=$(sym_addr XLoadFont)
[ -n "$_addr" ] || fail "table has no XLoadFont entry"
dis_at "$((_addr))" 512 "$TMP/loadfont"
require_match "$TMP/loadfont" 'movi[[:space:]]+0x2d,[[:space:]]+a[0-9]+' \
"XLoadFont does not build OpenFont request opcode 45 (0x2d)"
require_match "$TMP/loadfont" 'movb[[:space:]]+a[0-9]+,[[:space:]]+\*a[0-9]+' \
"XLoadFont does not write its request opcode to the buffer"
echo " ok XLoadFont builds OpenFont opcode 45 (not termtris xlib.h)"
_addr=$(sym_addr XCopyArea)
[ -n "$_addr" ] || fail "table has no XCopyArea entry"
dis_at "$((_addr))" 512 "$TMP/copyarea"
require_match "$TMP/copyarea" 'movi[[:space:]]+0x3e,[[:space:]]+a[0-9]+' \
"XCopyArea does not build CopyArea request opcode 62 (0x3e)"
require_match "$TMP/copyarea" 'movb[[:space:]]+a[0-9]+,[[:space:]]+\*a[0-9]+' \
"XCopyArea does not write its request opcode to the buffer"
echo " ok XCopyArea builds CopyArea opcode 62 (not termtris xlib.h)"
}
# XCreatePixmap/XSetInputFocus are used by the image client. Their request
# builders are CreatePixmap opcode 0x35 and SetInputFocus opcode 0x2a.
check_pixmap_focus() {
_addr=$(sym_addr XCreatePixmap)
[ -n "$_addr" ] || fail "table has no XCreatePixmap entry"
dis_at "$((_addr))" 512 "$TMP/createpixmap"
require_match "$TMP/createpixmap" 'movi[[:space:]]+0x35,[[:space:]]+a[0-9]+' \
"XCreatePixmap does not build CreatePixmap request opcode 0x35"
require_match "$TMP/createpixmap" 'movb[[:space:]]+a[0-9]+,[[:space:]]+\*a[0-9]+' \
"XCreatePixmap does not write its request opcode to the buffer"
echo " ok XCreatePixmap builds CreatePixmap opcode 0x35"
_addr=$(sym_addr XSetInputFocus)
[ -n "$_addr" ] || fail "table has no XSetInputFocus entry"
dis_at "$((_addr))" 256 "$TMP/setinputfocus"
require_match "$TMP/setinputfocus" 'movi[[:space:]]+0x2a,[[:space:]]+a[0-9]+' \
"XSetInputFocus does not build SetInputFocus request opcode 0x2a"
require_match "$TMP/setinputfocus" 'calla[[:space:]]+0x3fd526e0' \
"XSetInputFocus does not call the SetInputFocus request builder"
require_match "$TMP/setinputfocus" '\*a13\(0xffa0\)' \
"XSetInputFocus does not read the revert_to argument"
require_match "$TMP/setinputfocus" '\*a13\(0xffc0\)' \
"XSetInputFocus does not read the focus argument"
require_match "$TMP/setinputfocus" '\*a13\(0xffe0\)' \
"XSetInputFocus does not read the display argument"
echo " ok XSetInputFocus builds SetInputFocus opcode 0x2a"
}
check_xcreateimage() {
_addr=$(sym_addr XCreateImage)
[ -n "$_addr" ] || fail "table has no XCreateImage entry"
dis_at "$((_addr))" 256 "$TMP/createimage"
require_match "$TMP/createimage" 'movi[[:space:]]+0x58,[[:space:]]+a[0-9]+' \
"XCreateImage does not allocate 0x58 bytes (sizeof(XImage) == 88)"
require_match "$TMP/createimage" 'calla[[:space:]]+0x3f703d90' \
"XCreateImage does not call the ROM zeroing allocator (0x3F703D90)"
echo " ok XCreateImage allocates an 88-byte XImage"
}
# The error-handler setters write the per-process XG block: IO at +0x40,
# protocol error at +0x60. The defaults are the ROM's _XDefaultIOError and
# _XDefaultError.
check_error_handlers() {
_addr=$(sym_addr XSetErrorHandler)
[ -n "$_addr" ] || fail "table has no XSetErrorHandler entry"
dis_at "$((_addr))" 448 "$TMP/seterror"
require_match "$TMP/seterror" 'movi[[:space:]]+0x3f9bd9f0,[[:space:]]+a[0-9]+' \
"XSetErrorHandler does not install _XDefaultError (0x3F9BD9F0) when the handler is null"
require_match "$TMP/seterror" '\*a11\(0x60\)' \
"XSetErrorHandler does not store into the XG block at +0x60"
echo " ok XSetErrorHandler stores XG+0x60, default _XDefaultError"
_addr=$(sym_addr XSetIOErrorHandler)
[ -n "$_addr" ] || fail "table has no XSetIOErrorHandler entry"
dis_at "$((_addr))" 448 "$TMP/setioerror"
require_match "$TMP/setioerror" 'movi[[:space:]]+0x3f9bcd40,[[:space:]]+a[0-9]+' \
"XSetIOErrorHandler does not install _XDefaultIOError (0x3F9BCD40) when the handler is null"
require_match "$TMP/setioerror" '\*a11\(0x40\)' \
"XSetIOErrorHandler does not store into the XG block at +0x40"
echo " ok XSetIOErrorHandler stores XG+0x40, default _XDefaultIOError"
}
# Walk every symbol line. Keep this generic so a newly added entry cannot
# silently bypass the range/alignment/prologue gate.
echo "== validate every v2.2 table entry"
count=0
while read -r name addr rest; do
case "$name" in
'' | '#'*) continue ;;
esac
[ -n "$addr" ] || fail "malformed symbol line for '$name' (no address)"
check_prologue "$name" "$addr"
count=$((count + 1))
done < "$SYMS"
[ "$count" -gt 0 ] || fail "no entries found in $SYMS"
echo "== validate the correction-sensitive entries"
check_mapwindow
check_raisewindow
check_image_requests
check_font_copy
check_pixmap_focus
check_xcreateimage
check_error_handlers
echo "m4 validate-syms: PASS ($count v2.2 entries in ROM, aligned, prologues valid; XMapWindow/XRaiseWindow pinned)"
+31
View File
@@ -0,0 +1,31 @@
-- varargs-check.lua -- load the varargs ABI probe with the M4 COFF loader and
-- check the three sums. The callee walks the contiguous program stack below
-- its last fixed argument; these values prove the variadic slots are real.
--
-- M4_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua
-- LOADER_END loader inspection/exit frame
dofile(os.getenv("M4_LOADER_LUA"))
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
local frames = 0
local checked = false
emu.register_frame_done(function()
frames = frames + 1
if frames == ENDF - 1 and not checked then
checked = true
local prog = manager.machine.devices[':maincpu'].spaces['program']
local m = {}
for i = 0, 2 do
m[i] = prog:read_u32(0x018A8000 + i * 0x20)
end
print(string.format("M4: varargs sum1=%d sum2=%d sum3=%d",
m[0], m[1], m[2]))
if m[0] == 15 and m[1] == 150 and m[2] == 1500 then
print("M4 MAME: PASS")
else
print("M4 MAME: FAIL (want 15, 150, 1500)")
end
end
end, "varargs-check")
+13
View File
@@ -0,0 +1,13 @@
; varargs-entry.asm -- M4 entry for the varargs ABI probe.
.text
.globl _start
.globl _varargs_test
_start:
setf 16, 0, 0
setf 32, 0, 1
movi _cstack, a14
move a14, a13
calla _varargs_test
rets
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
+35
View File
@@ -0,0 +1,35 @@
/*
* varargs.c -- the contiguous-stack varargs ABI probe.
*
* M4 supports only the TI ABI's stack layout: a variadic callee takes the
* address of its last fixed argument and walks down one 32-bit slot per extra
* argument. There is no va_start/va_arg/va_list. The termtris compat.c
* sprintf uses exactly this pattern; this probe proves the values arrive.
*
* Markers (0x20 bits apart, written by the test):
* 018A8000 sumvar(1,2,3,4,5) == 15
* 018A8020 sumvar(10,20,30,40,50) == 150
* 018A8040 sumvar(100,200,300,400,500) == 1500
*/
#define MARK(n) (*(volatile int *)(0x018A8000UL + (n) * 0x20UL))
static int sumvar(int a, int b, ...)
{
int *p = &b;
int s = a + b;
p--;
s += *p; /* first variadic argument */
p--;
s += *p; /* second */
p--;
s += *p; /* third */
return s;
}
void varargs_test(void)
{
MARK(0) = sumvar(1, 2, 3, 4, 5);
MARK(1) = sumvar(10, 20, 30, 40, 50);
MARK(2) = sumvar(100, 200, 300, 400, 500);
}
+314
View File
@@ -0,0 +1,314 @@
/*
* Minimal X11R4-compatible declarations for the M4 VT1200 clients.
*
* The VT1200 X server is X11R4 on a 32-bit TMS34010, so this header models
* that ABI: XID/Window/KeySym/Time are 32-bit unsigned long, GC is a 32-bit
* pointer, and XImage carries the inline six-entry struct funcs tail.
*
* Display/Screen deliberately do NOT include the host X11R4 header. The
* VT1200 ROM's Display is the M3-verified layout: Display.screens at byte
* 0x90 (bit 0x480) and Screen.root at byte 0x8 (bit 0x40). Do not include
* scripts/m3/vt1200_xlib.h here; its XOpenDisplay/XCreateSimpleWindow
* prototypes conflict with the wider M4 ones below.
*
* Offset assertions live in scripts/m4/layout.c; the TI golden in
* scripts/m4/layout-ti.s carries the same bit displacements.
*/
#ifndef X11M4_H
#define X11M4_H
typedef unsigned long XID;
typedef XID Window;
typedef XID Drawable;
typedef XID Pixmap;
typedef XID Atom;
typedef XID Colormap;
typedef XID Cursor;
typedef XID Font;
typedef XID GContext;
typedef XID KeySym;
typedef unsigned long Mask;
typedef unsigned long Time;
typedef int Bool;
typedef unsigned char KeyCode;
typedef char *XPointer;
typedef struct _XGC *GC;
typedef struct _Visual Visual; /* opaque: clients only pass a Visual * */
typedef struct _XImage XImage;
/*
* The VT1200 ROM layout, reused from scripts/m3/vt1200_xlib.h. TMS34010
* indirect displacements are bit offsets, so screens is at byte 0x90 and
* root at byte 0x8.
*/
typedef struct _Display {
unsigned char pad[0x90];
void *screens;
} Display;
typedef struct _Screen {
unsigned char pad[0x8];
unsigned int root;
} Screen;
/* X11R4 xlib.h: the image function table is inline in XImage, not a pointer. */
struct funcs {
XImage *(*create_image)(Display *, Visual *, unsigned int, int, int, char *,
unsigned int, unsigned int, int, int);
int (*destroy_image)(XImage *);
unsigned long (*get_pixel)(XImage *, int, int);
int (*put_pixel)(XImage *, int, int, unsigned long);
XImage *(*sub_image)(XImage *, int, int, unsigned int, unsigned int);
int (*add_pixel)(XImage *, long);
};
typedef 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 funcs f;
} XImage;
typedef struct {
int type;
unsigned long serial;
Bool send_event;
Display *display;
Window window;
} XAnyEvent;
typedef struct {
int type;
unsigned long serial;
Bool send_event;
Display *display;
Window window;
Window root;
Window subwindow;
Time time;
int x, y;
int x_root, y_root;
unsigned int state;
unsigned int keycode;
Bool same_screen;
} XKeyEvent;
typedef struct {
int type;
unsigned long serial;
Bool send_event;
Display *display;
Window window;
Window root;
Window subwindow;
Time time;
int x, y;
int x_root, y_root;
unsigned int state;
unsigned int button;
Bool same_screen;
} XButtonEvent;
typedef struct {
int type;
unsigned long serial;
Bool send_event;
Display *display;
Window window;
int mode;
int detail;
} XFocusChangeEvent;
typedef struct {
int type;
unsigned long serial;
Bool send_event;
Display *display;
Window window;
int x, y;
int width, height;
int count;
} XExposeEvent;
typedef struct {
int type;
unsigned long serial;
Bool send_event;
Display *display;
Window event;
Window window;
int x, y;
int width, height;
int border_width;
Window above;
Bool override_redirect;
} XConfigureEvent;
typedef struct {
int type;
unsigned long serial;
Bool send_event;
Display *display;
Window window;
Atom message_type;
int format;
union {
char b[20];
short s[10];
long l[5];
} data;
} XClientMessageEvent;
typedef struct {
int type;
Display *display;
XID resourceid;
unsigned long serial;
unsigned char error_code;
unsigned char request_code;
unsigned char minor_code;
/*
* X11R4's 32-bit XErrorEvent is 20 bytes. The TMS34010 C ABI packs
* structs (and TI C does too), so the three trailing chars would make the
* packed object 19 bytes. This byte is an X11R4 object-size compatibility
* pad; the client reads only the handler-passed fields.
*/
unsigned char abi_pad;
} XErrorEvent;
/*
* X11R4's XEvent is 96 bytes: the largest member is pad[24] (the protocol
* xEvent is 24 longs). The termtris event buffer is vt_u32 evbuf[24].
*/
typedef union _XEvent {
int type;
XAnyEvent xany;
XKeyEvent xkey;
XButtonEvent xbutton;
XFocusChangeEvent xfocus;
XExposeEvent xexpose;
XConfigureEvent xconfigure;
XClientMessageEvent xclient;
XErrorEvent xerror;
long pad[24];
} XEvent;
/* Event codes (X11R4). */
#define KeyPress 2
#define KeyRelease 3
#define ButtonPress 4
#define ButtonRelease 5
#define FocusIn 9
#define FocusOut 10
#define Expose 12
/* Event masks (X11R4). */
#define NoEventMask 0L
#define KeyPressMask (1L << 0)
#define KeyReleaseMask (1L << 1)
#define ButtonPressMask (1L << 2)
#define ButtonReleaseMask (1L << 3)
#define ExposureMask (1L << 15)
#define StructureNotifyMask (1L << 17)
#define FocusChangeMask (1L << 21)
/* Image byte/bit order and format. */
#define LSBFirst 0
#define MSBFirst 1
#define XYBitmap 0
#define XYPixmap 1
#define ZPixmap 2
#define True 1
#define False 0
#define None 0L
typedef int (*XErrorHandler)(Display *, XErrorEvent *);
typedef int (*XIOErrorHandler)(Display *);
/*
* Real Xlib entry points. The VT1200 ROM exports these names; Task 5
* validates their bit addresses in symbols/vt1200-v22.syms.
*
* XCreateGC's XGCValues* and XLookupString's XComposeStatus* are modelled as
* void*: the layout is not needed by the M4 clients and the 32-bit pointer
* ABI is unchanged.
*/
extern Display *XOpenDisplay(const char *name);
extern Window XCreateSimpleWindow(Display *display, Window parent, int x, int y,
unsigned int width, unsigned int height,
unsigned int border_width,
unsigned long border,
unsigned long background);
extern int XMapWindow(Display *display, Window window);
/* CreatePixmap opcode 0x35; depth 1 for the image client's 1-bit pixmap. */
extern Pixmap XCreatePixmap(Display *display, Drawable drawable,
unsigned int width, unsigned int height,
unsigned int depth);
/* ConfigureWindow(CWStackMode=Above): map-and-raise for a client that must be
* above the Terminal Manager (XMapWindow alone does not raise). */
extern int XRaiseWindow(Display *display, Window window);
extern int XSync(Display *display, Bool discard);
extern int XFlush(Display *display);
extern int XSelectInput(Display *display, Window window, long event_mask);
extern GC XCreateGC(Display *display, Drawable drawable,
unsigned long valuemask, void *values);
extern int XSetForeground(Display *display, GC gc, unsigned long foreground);
extern int XFillRectangle(Display *display, Drawable drawable, GC gc, int x,
int y, unsigned int width, unsigned int height);
extern int XDrawString(Display *display, Drawable drawable, GC gc, int x, int y,
const char *string, int length);
extern int XNextEvent(Display *display, XEvent *event);
extern int XLookupString(XKeyEvent *event, char *buffer, int bytes,
KeySym *keysym, void *compose_status);
extern int XPending(Display *display);
/* The ROM's ms sleep (symbols/vt1200-v22.syms: 0x3FB33A20); yields so the
* X server and the other firmware processes keep running. */
extern int sleep(int ms);
extern int XGetInputFocus(Display *display, Window *focus, int *revert_to);
/*
* The ROM's XSetInputFocus reads three arguments (display, focus, revert_to)
* and supplies CurrentTime inside the request builder; the disassembly at
* 0x3FD536B0 reads 0xffe0/0xffc0/0xffa0 and builds SetInputFocus opcode 0x2a.
*/
#define RevertToParent 2
extern int XSetInputFocus(Display *display, Window focus, int revert_to);
/* ROM blitter memcpy used by _XPutImage; the ROM order is (src, dst, bytes). */
extern int blit_copy(void *src, void *dst, int nbytes);
extern Font XLoadFont(Display *display, const char *name);
extern int XCopyArea(Display *display, Drawable src, Drawable dest, GC gc,
int src_x, int src_y, unsigned int width,
unsigned int height, int dest_x, int dest_y);
extern XImage *XCreateImage(Display *display, Visual *visual,
unsigned int depth, int format, int offset,
char *data, unsigned int width,
unsigned int height, int bitmap_pad,
int bytes_per_line);
extern int XPutImage(Display *display, Drawable drawable, GC gc, XImage *image,
int src_x, int src_y, int dest_x, int dest_y,
unsigned int width, unsigned int height);
extern int XDestroyImage(XImage *image);
extern XErrorHandler XSetErrorHandler(XErrorHandler handler);
extern XIOErrorHandler XSetIOErrorHandler(XIOErrorHandler handler);
/* Install the per-process XG block before XOpenDisplay (scripts/m4/xgfix.c). */
extern int vt_install_xg(void);
/* Follow Display.screens (bit 0x480) and read Screen.root (bit 0x40). */
static inline __attribute__((always_inline)) unsigned int
vt_root(Display *display) {
return ((Screen *)((Display *)display)->screens)->root;
}
#endif /* X11M4_H */
+21
View File
@@ -0,0 +1,21 @@
/*
* Per-process Xlib context (XG) installer for the VT1200 loader child.
*
* The ROM Xlib keeps its per-process state in the user word at
* *(current_process + 0x100). A process created by the loader inherits a
* zeroed cell there, so the ROM's _XEnq sees XG == NULL and XNextEvent reads
* garbage or never reports events. Points that word at a zeroed 1 KiB block
* before XOpenDisplay.
*
* This is a RAM write only; the ROM image is untouched. Must be called before
* XOpenDisplay, once per process.
*/
int vt_install_xg(void) {
unsigned int proc = *(volatile unsigned int *)0x001B3900u;
if (proc < 0x100u || proc > 0x01FFFFFFu) return 0;
unsigned int pp = *(volatile unsigned int *)(proc + 0x100u);
if (pp < 0x100u || pp > 0x01FFFFFFu) return 0;
static unsigned int xg[256]; /* 1 KiB zeroed .bss */
*(volatile unsigned int *)pp = (unsigned int)xg;
return 1;
}
+153
View File
@@ -0,0 +1,153 @@
-- xshow-check.lua -- load the M4 image/event COFF with dwt/loader/loader.lua,
-- inject a real LK401 `Q` key, and prove the mapped 1-bit XImage in VRAM.
--
-- M4_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua
-- LOADER_AT injection frame (default 2500)
-- LOADER_END loader inspection/exit frame (default 3400)
--
-- Markers (0x20 bits apart):
-- 018A8000 display nonzero
-- 018A8020 window nonzero
-- 018A8040 image nonzero
-- 018A8060 quit 1 after the `q` KeyPress
-- 018A8080 events count of XNextEvent calls (> 0)
-- 018A80A0 keycode ev.xkey.keycode (193 for the LK401 Q)
-- 018A80C0 keysym XLookupString keysym (XK_q = 0x71)
-- 018A80E0 xerrors 0
-- 018A8100 focus XGetInputFocus after XSetInputFocus
-- 018A8120 last last X event type (2 = KeyPress)
--
-- The client window is XCreateSimpleWindow(..., 20, 20, 640, 480, ...) with
-- no border. The VT1200 server copies a 1-bit bitmap correctly only when the
-- destination x is 32-bit aligned, so the client draws the aligned pixmap at
-- window x = 12 (screen x = 32) and fills the 12-pixel prefix with the
-- preceding cell's colour. The checker cells are 32 pixels wide with a
-- 12-pixel phase: image data x = sx - 20 - 12. The window-relative points
-- (30,30) and (50,30) are adjacent cells (0,0) and (1,0), as are (30,62) and
-- (50,62). The samples at data (368,280), (468,380) and (568,430) prove the
-- banded PutImage assembled the whole 640x480 image, not just its first band.
-- The prefix samples at screen (25,30)/(25,62) prove the preceding-cell fill.
-- The pixel is read from the exact VRAM bit address (VR319: 2048-bit rows,
-- LSB-first), not from a whole unaligned word that would cross a cell.
--
-- The focus path is the tablet click: XSetInputFocus alone leaves the DWT WM
-- at PointerRoot (verified: focus=0x1 and no key), so the gate moves the
-- absolute tablet (0..2199) to (700,500), the termtris-proven in-window point,
-- and clicks. That point maps inside termtris' (80..560,80..392) rectangle,
-- which is contained in this client's (20..660,20..500) window. The focus
-- marker must become the client's own window; the ButtonRelease handler
-- samples it after the click.
dofile(os.getenv("M4_LOADER_LUA"))
local ENDF = tonumber(os.getenv("LOADER_END") or "3400")
local ports = manager.machine.ioport.ports
local key_q = ports[":lk401:K13"].fields["Q"]
local tabx = ports[":tablet:TABLET_X"].fields["Tablet X"]
local taby = ports[":tablet:TABLET_Y"].fields["Tablet Y"]
local tabb = ports[":tablet:TABLET_BTN"].fields["P1 Button 1"]
local MOVE_AT = 2900
local CLICK_DOWN = 2920
local CLICK_UP = 2930
local Q_DOWN = 3000
local Q_UP = 3010
local VRAM_BASE = 0x04000000
local VRAM_PITCH = 2048 -- VR319: 2048 bits per row
local WIN_X, WIN_Y = 20, 20
local IMG_PHASE = 12 -- pixmap is copied to window x=12 (screen x=32)
local frames = 0
local checked = false
local function read_bit(prog, sx, sy)
local bit = VRAM_BASE + sy * VRAM_PITCH + sx
return prog:read_u16(bit) % 2, bit
end
emu.register_frame_done(function()
frames = frames + 1
if frames == MOVE_AT then
tabx:set_value(700)
taby:set_value(500)
print(string.format("M4: inject frame %d tablet (700,500)", frames))
elseif frames == CLICK_DOWN then
tabb:set_value(1)
print(string.format("M4: inject frame %d button down", frames))
elseif frames == CLICK_UP then
tabb:set_value(0)
print(string.format("M4: inject frame %d button up", frames))
elseif frames == Q_DOWN then
key_q:set_value(1)
print(string.format("M4: inject frame %d Q down", frames))
elseif frames == Q_UP then
key_q:set_value(0)
print(string.format("M4: inject frame %d Q up", frames))
end
if frames == ENDF - 1 and not checked then
checked = true
local prog = manager.machine.devices[':maincpu'].spaces['program']
local m = {}
for i = 0, 9 do
m[i] = prog:read_u32(0x018A8000 + i * 0x20)
end
print(string.format(
"M4: display=%u window=%u image=%u quit=%d events=%d keycode=%d keysym=0x%X xerrors=%d focus=0x%X last=%d",
m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9]))
-- screen -> image (window-relative) -> 32-pixel cell
local function sample(sx, sy)
local b, bit = read_bit(prog, sx, sy)
local rx, ry = sx - WIN_X - IMG_PHASE, sy - WIN_Y
local cx, cy = math.floor(rx / 32), math.floor(ry / 32)
print(string.format(
"M4: pixel screen(%d,%d) image(%d,%d) cell(%d,%d) bit=0x%08X value=%d %s",
sx, sy, rx, ry, cx, cy, bit, b,
b == 1 and "white" or "black"))
return b
end
-- The task's exact pair plus the orthogonal pair, at cell centres and
-- at points well inside the cells (away from a 32-pixel boundary).
local b00 = sample(50, 50) -- data (18,30) cell (0,0) -> black
local b10 = sample(70, 50) -- data (38,30) cell (1,0) -> white
local b01 = sample(50, 82) -- data (18,62) cell (0,1) -> white
local b11 = sample(70, 82) -- data (38,62) cell (1,1) -> black
local c00 = sample(36, 36) -- data (4,16) cell (0,0) -> black
local c10 = sample(68, 36) -- data (36,16) cell (1,0) -> white
local c01 = sample(36, 68) -- data (4,48) cell (0,1) -> white
local c11 = sample(68, 68) -- data (36,48) cell (1,1) -> black
-- later bands, so a repeated first-band bug cannot pass
local d1 = sample(400, 300) -- data (368,280) cell (11,8) -> white
local d2 = sample(500, 400) -- data (468,380) cell (14,11) -> white
local d3 = sample(600, 450) -- data (568,430) cell (17,13) -> black
-- the 12-pixel prefix is the preceding cell: white in band 0, black
-- in band 1, so it cannot be a constant left stripe.
local p0 = sample(25, 30) -- data (-7,10) cell (-1,0) -> white
local p1 = sample(25, 62) -- data (-7,42) cell (-1,1) -> black
local keysym_ok = (m[6] == 0x71)
local image_ok = b00 == 0 and b10 == 1 and b01 == 1 and b11 == 0
and c00 == 0 and c10 == 1 and c01 == 1 and c11 == 0
and d1 == 1 and d2 == 1 and d3 == 0
and p0 == 1 and p1 == 0
local ok = m[0] ~= 0 and m[1] ~= 0 and m[2] ~= 0
and m[3] == 1 and m[4] > 0 and m[7] == 0
and m[5] == 193 and keysym_ok
and m[8] == m[1] and m[8] ~= 0 and m[9] == 2
and image_ok
if ok then
print("M4 MAME: PASS")
else
print(string.format(
"M4 MAME: FAIL (markers display/window/image=%u/%u/%u quit=%d events=%d keycode=%d keysym=0x%X xerrors=%d focus=0x%X last=%d; image %s)",
m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9],
image_ok and "checker-ok" or "checker-mismatch"))
end
end
end, "xshow-check")
+22
View File
@@ -0,0 +1,22 @@
; xshow-entry.asm -- M4 image/event client entry, linked at bit 018A0000.
;
; Calls the compiled xshow() client. Markers (0x20 bits apart):
; 018A8000 display nonzero
; 018A8020 window nonzero
; 018A8040 image nonzero
; 018A8060 quit 1 after the real `q` KeyPress
; 018A8080 events count of XNextEvent calls
; 018A80A0 keycode ev.xkey.keycode
; 018A80C0 keysym XLookupString keysym
; 018A80E0 xerrors 0
.text
.globl _start
_start:
setf 16, 0, 0
setf 32, 0, 1
movi _cstack, a14
move a14, a13
calla _xshow
rets
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
+280
View File
@@ -0,0 +1,280 @@
/*
* xshow.c -- the M4 1-bit XImage + real-key-event client.
*
* The client installs the per-process XG block (Task 5), opens the VT1200 X
* display, creates a 640x480 window at (20,20), draws a 32-pixel checkerboard
* through a 1-bit LSBFirst XYBitmap XImage, then drains X events and quits on
* the real keyboard's `q`.
*
* Markers live at 0x018A8000 + n*0x20 (bit addresses):
* 0 Display * nonzero
* 1 Window nonzero
* 2 XImage * nonzero
* 3 quit 1 after the `q` KeyPress
* 4 events count of every XNextEvent
* 5 keycode ev.xkey.keycode of the last KeyPress
* 6 keysym XLookupString keysym of the last KeyPress
* 7 xerrors count of X protocol errors seen by on_error
* 8 focus XGetInputFocus window (0x400001 after the click)
* 9 last last X event type (2 = KeyPress)
*
* The static image is 480 * 80 bytes in .bss (640 pixels / 8 = 80 bytes per
* row). It is intentionally never passed to XDestroyImage: the ROM destructor
* frees the data pointer, which is .bss here, not heap memory.
*
* Two VT1200 server/Xlib constraints shape the drawing path. A single 38 KiB
* PutImage request does not complete (the ROM request buffer is 16 KiB), so the
* image is emitted in 32-row bands and each band selects its rows by moving
* XImage.data (the server ignores src_y). The server also copies a 1-bit
* bitmap correctly only when the destination x is 32-bit aligned, so the bands
* go into an aligned depth-1 pixmap and XCopyArea places the image at window
* x=12 (screen x=32); the 12-pixel prefix is filled with the first cell's
* colour. The checker cells are still 32 pixels, with a 12-pixel phase inside
* the (20,20) window.
*/
#include "x11m4.h"
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n) * 0x20u))
#define IMG_W 640
#define IMG_H 480
#define IMG_BPL 80 /* ((640 + 31) / 32) * 4 */
static int xerrors;
/*
* The image bytes: 480 rows * 80 bytes. It is built as 32-bit words because
* each word spans exactly one 32-pixel cell and LSBFirst makes word bit i
* pixel x = 32*cell + i. (The brief's per-bit byte OR loop compiles to an
* i8 load with `anyext`, which this target cannot select at -O2; the word
* form writes the identical bytes.) The 4-byte alignment lets the compiler
* use whole-word stores.
*/
static unsigned char data[IMG_H * IMG_BPL] __attribute__((aligned(4)));
/*
* The ROM calls this through the pointer installed by XSetErrorHandler. The
* Task 4 header's XErrorHandler is authoritative: int (Display*, XErrorEvent*).
*/
static int on_error(Display *d, XErrorEvent *e) {
(void)d;
(void)e;
xerrors++;
return 0;
}
/*
* Call the ROM sleep through a function pointer: the image then exercises the
* Task 3 indirect-call contract (CALL Rs) as well as the direct CALLA imports.
* It is volatile so the optimizer cannot devirtualise the load into a direct
* CALLA and erase that coverage. The pointer lives in .data as `.long _sleep`.
*/
static int (*volatile vt_sleep)(int) = sleep;
/*
* The ROM Xlib copies an XImage into its request buffer with the TMS34010
* blitter. `blit_copy` is the validated ROM entry _XPutImage calls
* (0x3F9CBFE0 -> 0x3FB44400): (source, destination, byte count). A process
* created by the loader inherits a zeroed blitter window/pitch context, so the
* first copy writes nothing; prime it once with a four-byte self-copy.
*/
static void prime_blitter(void) {
blit_copy(data, data, 4);
}
/*
* A single 640x480 1-bit PutImage is 0xC0 + 480*80 = 38592 bytes, but the ROM
* Xlib's request buffer is only 16384 bytes. XPutImage's direct-send path for
* an image larger than the buffer does not make progress in the harness, so
* emit the image in 32-row bands: each band's request fits the buffer, and the
* explicit flush/yield lets the X server drain it. The assembled drawable is
* still the whole 640x480 image.
*/
static void present(Display *d, Drawable target, Window w, GC gc) {
int y;
/*
* The server copies 1-bit bitmaps correctly only when the destination x is
* 32-bit aligned. Draw the aligned pixmap starting at window x = 12
* (screen x = 32) and fill the 12-pixel prefix with the preceding cell's
* colour, band by band, so the visible 32-pixel phase is preserved. The
* checker cells are still 32 pixels wide; the phase is offset by 12 pixels
* inside the (20,20) window. Only IMG_W - 12 = 628 columns are copied, so
* the rightmost 12 source columns (the final 12 px of the last cell) are
* not displayed and the last visible cell is 20 px, not 32.
*/
XCopyArea(d, target, w, gc, 0, 0, IMG_W - 12, IMG_H, 12, 0);
for (y = 0; y < IMG_H; y += 32) {
XSetForeground(d, gc, ((y / 32) & 1) == 0 ? 1 : 0);
XFillRectangle(d, w, gc, 0, y, 12, 32);
}
XSetForeground(d, gc, 1);
XFlush(d);
}
static void draw_image(Display *d, Drawable target, Window w, GC gc,
XImage *img) {
char *base = (char *)data;
int y;
prime_blitter();
for (y = 0; y < IMG_H; y += 32) {
/*
* src_y is ignored by the bitmap path, so select the band by moving
* the image data pointer.
*/
img->data = base + y * IMG_BPL;
XPutImage(d, target, gc, img, 0, 0, 0, y, IMG_W, 32);
XFlush(d);
vt_sleep(1);
}
img->data = base;
present(d, target, w, gc);
}
int xshow(void) {
Display *d;
Window w;
Pixmap pm;
GC gc;
XImage *img;
int tries;
int x, y;
if (!vt_install_xg())
return -1;
/*
* The display server may still be starting when the loader creates this
* process; retry as the termtris client does. XOpenDisplay allocates the
* connection, so one success ends the loop.
*/
d = 0;
for (tries = 0; tries < 40; tries++) {
d = XOpenDisplay("0::0");
if (d)
break;
vt_sleep(50);
}
MARK(0) = (unsigned)d;
if (!d)
return -1;
w = XCreateSimpleWindow(d, vt_root(d), 20, 20, IMG_W, IMG_H, 0, 0, 0);
MARK(1) = (unsigned)w;
/* These are counters/state, not loader-zeroed .bss: clear them first. */
MARK(3) = 0;
MARK(4) = 0;
MARK(5) = 0;
MARK(6) = 0;
MARK(7) = 0;
XSelectInput(d, w, ExposureMask | KeyPressMask | KeyReleaseMask |
ButtonPressMask | ButtonReleaseMask |
StructureNotifyMask | FocusChangeMask);
gc = XCreateGC(d, w, 0, 0);
XSetForeground(d, gc, 1);
pm = XCreatePixmap(d, w, IMG_W, IMG_H, 1);
/*
* 32-pixel checkerboard, LSB-first in each byte/word. Build whole 32-bit
* words (each word is exactly one 32-pixel cell) and toggle the row fill
* every 32 rows; this avoids the i8 anyext load and the i1
* sign_extend_inreg that the brief's per-bit loop creates at -O2.
*/
{
unsigned int fill = 0xFFFFFFFFu;
unsigned int band = 0;
for (y = 0; y < IMG_H; y++) {
unsigned int *row = (unsigned int *)(data + y * IMG_BPL);
unsigned int v = fill;
for (x = 0; x < IMG_BPL / 4; x++) {
row[x] = v;
v = ~v;
}
if (++band == 32) {
band = 0;
fill = ~fill;
}
}
}
/*
* visual 0 is safe for the ROM XCreateImage: it only dereferences the
* visual when the pointer is nonzero, and a depth-1 XYBitmap takes its
* byte/bit order from the Display. We set both orders explicitly below.
*/
img = XCreateImage(d, 0, 1, XYBitmap, 0, (char *)data, IMG_W, IMG_H, 32,
IMG_BPL);
MARK(2) = (unsigned)img;
if (!img)
return -1;
img->byte_order = LSBFirst;
img->bitmap_bit_order = LSBFirst;
XSetErrorHandler(on_error);
XMapWindow(d, w);
XRaiseWindow(d, w);
XSetInputFocus(d, w, RevertToParent);
draw_image(d, pm, w, gc, img);
XSync(d, 0);
{
Window fw = 0;
int rv = 0;
XGetInputFocus(d, &fw, &rv);
MARK(8) = (unsigned)fw;
}
for (;;) {
XEvent ev;
while (XPending(d)) {
XNextEvent(d, &ev);
MARK(4) = MARK(4) + 1;
MARK(9) = (unsigned)ev.type;
if (ev.type == KeyPress) {
char buf[8];
unsigned long ks = 0;
int n;
buf[0] = 0;
n = XLookupString(&ev.xkey, buf, 8, &ks, 0);
MARK(5) = (unsigned)ev.xkey.keycode;
MARK(6) = (unsigned)ks;
/* The server may hand back the shifted form. */
if (n > 0 && (buf[0] == 'q' || buf[0] == 'Q')) {
MARK(3) = 1;
break;
}
} else if (ev.type == ButtonRelease) {
Window fw = 0;
int rv = 0;
XGetInputFocus(d, &fw, &rv);
MARK(8) = (unsigned)fw;
} else if (ev.type == Expose) {
present(d, pm, w, gc);
}
}
MARK(7) = (unsigned)xerrors;
if (MARK(3))
break;
XFlush(d);
vt_sleep(5);
}
/*
* Quit: stop serving events, but keep the process alive so the harness can
* read the mapped image out of VRAM at LOADER_END. Returning here would
* let the firmware tear the process (and its window) down before the read.
*/
for (;;) {
MARK(7) = (unsigned)xerrors;
XFlush(d);
vt_sleep(5);
}
}
+568
View File
@@ -0,0 +1,568 @@
#!/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 direct CALLA _apply calls (at least three; TI has three,
# ours has four because the fixture calls apply again in the return).
# 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, on a copy of the image golden
# with the XPutImage argument pushes fully reversed, and on a copy with only
# the Display and drawable pushes swapped. All 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 LASTDISP 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.
# LASTDISP is 1 when the final push's source is the first-argument slot: the
# TI golden's `*FP(-32)`, or our register loaded from the ADDI -32 slot.
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"
if (a[1] == "*FP(-32)" ||
(isreg(a[1]) && hasdisp[a[1]]))
lastdisp = 1
else
lastdisp = 0
next
}
if (n >= 3 && a[2] ~ /^-\*SP$/ && (a[1] == "STK" || a[1] == "A14")) {
oldstk = 1
next
}
if (n >= 2 && isreg(a[2])) {
dsrc = 0
if (a[1] == "*FP(-32)")
dsrc = 1
else if (a[1] ~ /^\*A[0-9]+$/ && firstaddr[substr(a[1], 2)])
dsrc = 1
else if (isreg(a[1]) && hasdisp[a[1]])
dsrc = 1
if (a[1] ~ /^-?[0-9]+$/) {
val[a[2]] = a[1]; has[a[2]] = 1; hasdisp[a[2]] = 0
} else if (isreg(a[1]) && has[a[1]]) {
val[a[2]] = val[a[1]]; has[a[2]] = 1; hasdisp[a[2]] = dsrc
} else {
has[a[2]] = 0; hasdisp[a[2]] = dsrc
}
}
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; hasdisp[a[2]] = 0
} 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
hasdisp[trim(line)] = 0
}
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; hasdisp[a[2]] = 0
} 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])
hasdisp[a[2]] = 0
} 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])
if (a[1] == "-32")
firstaddr[a[2]] = 1
else
firstaddr[a[2]] = 0
}
next
}
/^[[:space:]]*CALLA[[:space:]]/ {
callee = $2
sub(/^_/, "", callee)
if (callee == want) {
print npush, oldstk, lastdisp, consts
exit
}
npush = 0; oldstk = 0; lastdisp = 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
_disp=$3
shift 3
_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"
[ "$_disp" = 1 ] \
|| fail "$_func/$_callee last push is not the Display argument (first-argument slot)"
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 at least 3"; }
echo " ok $_who: $_test makes $_calls direct CALLA _apply calls (want >= 3)"
}
# 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/^/ /'
# 3. Swap only the last two XPutImage pushes (Display and drawable). The
# leading constants and the non-constant last-token rule still hold, so
# only the first-argument (Display) check can reject this.
awk '
{ lines[NR] = $0 }
/^[[:space:]]*MOVE[[:space:]]+[^,]+,[[:space:]]*\*STK\+,[[:space:]]*1[[:space:]]*$/ {
npush++
pos[npush] = NR
next
}
/^[[:space:]]*CALLA[[:space:]]+_XPutImage/ {
if (npush >= 2) {
tmp = lines[pos[npush]]
lines[pos[npush]] = lines[pos[npush - 1]]
lines[pos[npush - 1]] = tmp
}
npush = 0
next
}
/^[[:space:]]*CALLA[[:space:]]/ { npush = 0; next }
END { for (i = 1; i <= NR; i++) print lines[i] }
' "$TI_IMAGE" > "$TMP/image-swapped.s"
if cmp -s "$TI_IMAGE" "$TMP/image-swapped.s"; then
fail "Display/drawable swap mutation did not change the image golden"
fi
if M4_DIFF_POSITIVE_ONLY=1 TI_IMAGE="$TMP/image-swapped.s" \
"$0" > "$TMP/neg-swap.out" 2>&1; then
sed 's/^/ /' "$TMP/neg-swap.out" >&2
fail "check accepted the Display/drawable push swap"
fi
echo " ok negative control: Display swapped with the drawable rejected"
grep 'M4 diff: FAIL' "$TMP/neg-swap.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
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
# M4 dis34 gate: rebuild the image client through the normal GSPA/GSPLNK path,
# then verify the ROM import stubs, the direct/indirect call encodings,
# XCreateSimpleWindow's nine-argument order, the XSetErrorHandler callback,
# SETF/RETS/old-STK, and the checker .bss.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
DIR=$ROOT/scripts/m4
WORK=${M4_XSHOW_WORK:-$ROOT/build/m4-xshow}
M4_SKIP_MAME=1 \
M4_SOURCES="xshow xgfix" \
M4_ENTRY="$DIR/xshow-entry.asm" \
M4_CLANG_OPT=-O2 \
M4_ROM_SYMBOLS=1 \
M4_WORK="$WORK" \
"$ROOT/scripts/m4/run.sh"
M4_WORK="$WORK" exec "$ROOT/scripts/m4/dis-roundtrip.sh"
+159
View File
@@ -0,0 +1,159 @@
#!/bin/sh
# M4 function-pointer/indirect-call gate: compile fnptr.c with the default
# tms-clang -O2 pipeline, link with GSPA/GSPLNK, run in MAME and check that all
# four markers are 42. The linked assembly must call through an A register in
# _apply (CALL, not CALLA) while _fnptr_test still makes the direct CALLA to
# apply and materialises inc's address with MOVI. dis34 must decode the
# indirect call as `call aN` at the linked address.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
DIR=$ROOT/scripts/m4
WORK=${M4_FNPTR_WORK:-$ROOT/build/m4-fnptr}
COFF=$WORK/m4.coff
DIS34=${DIS34:-$ROOT/vendor/vt1200/tools/smart34010/dis34}
fail() {
echo "m4 fnptr: FAIL: $*" >&2
exit 1
}
M4_SOURCES=fnptr \
M4_ENTRY="$DIR/fnptr-entry.asm" \
M4_CHECK_LUA="$DIR/fnptr-check.lua" \
M4_CLANG_OPT=-O2 \
M4_WORK="$WORK" \
M4_COFF="$COFF" \
"$ROOT/scripts/m4/run.sh" || fail "M4 function-pointer run failed"
ASM=$WORK/m4.asm
MAP=$WORK/m4.map
[ -f "$ASM" ] || fail "linked assembly not found: $ASM"
[ -f "$MAP" ] || fail "linker map not found: $MAP"
EXPECTED="M4: fnptr=42 fnptr_volatile=42 fnptr_global=42 fnptr_ret=42"
grep -Fqx "$EXPECTED" "$WORK/mame.log" \
|| { grep -E '^M4: ' "$WORK/mame.log" >&2 || true; fail "runtime markers did not match: $EXPECTED"; }
echo " ok runtime markers: $EXPECTED"
# Stop at the next function label (a column-0 label that is not an LBB block
# label), so a following local function cannot leak into this slice.
body() { # body FILE FUNC
awk -v want="_$2:" '
$1 == want { infn = 1; next }
infn {
if ($0 ~ /^[A-Za-z_][A-Za-z0-9_]*:/ && $0 !~ /^LBB/) exit
print
}
' "$1"
}
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m4-fnptr.XXXXXX")
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
for fn in inc dec apply fnptr_test; do
body "$ASM" "$fn" > "$TMP/$fn.s"
[ -s "$TMP/$fn.s" ] || fail "linked assembly has no _$fn body"
done
check_seq() { # check_seq FILE ERE... : each pattern in order
_file=$1
shift
_last=0
for _pat in "$@"; do
_line=$(awk -v start="$_last" -v pat="$_pat" '
NR > start && $0 ~ pat { print NR; exit }
' "$_file")
[ -n "$_line" ] || {
sed 's/^/ /' "$_file" >&2
fail "_${_file##*/} is missing sequence /$_pat/"
}
_last=$_line
done
}
# apply receives the callback in a stack slot: the target is loaded/copied into
# an allocatable A register after the argument push, then called with the
# one-word register call. It must not become a direct CALLA.
if grep -Eq '^[[:space:]]*CALLA[[:space:]]' "$TMP/apply.s"; then
sed 's/^/ /' "$TMP/apply.s" >&2
fail "_apply still contains a direct CALLA"
fi
check_seq "$TMP/apply.s" \
'MOVE A14, -\*SP, 1' \
'MOVE .*\*A14\+, 1' \
'^[[:space:]]*CALL A[0-9]+' \
'SETF 32, 0, 1' \
'MOVE \*SP\(.*\), A14, 1' \
'RETS 2'
echo " ok _apply: old-STK, argument push, CALL A?, SETF, old-STK reload, RETS 2"
# Passing inc as an argument is a MOVI of the global symbol; the calls to the
# helper itself remain the direct CALLA path. The global function pointer is
# a .data word holding dec's bit address and is loaded at runtime.
check_seq "$TMP/fnptr_test.s" 'MOVI _inc, A[0-9]+' 'CALLA _apply'
if [ "$(grep -Ec '^[[:space:]]*CALLA _apply' "$TMP/fnptr_test.s")" -lt 3 ]; then
sed 's/^/ /' "$TMP/fnptr_test.s" >&2
fail "_fnptr_test does not make all three direct CALLA _apply calls"
fi
grep -Eq '^[[:space:]]*MOVI _g_fn[A-Za-z0-9_]*, A[0-9]+' "$TMP/fnptr_test.s" \
|| { sed 's/^/ /' "$TMP/fnptr_test.s" >&2; fail "_fnptr_test does not load the global function pointer"; }
grep -Fq '.long _dec' "$ASM" \
|| { grep -n '\.long' "$ASM" >&2 || true; fail "the .data function pointer does not hold _dec"; }
echo " ok _fnptr_test: MOVI _inc + three direct CALLA _apply, loads _g_fn, .long _dec"
# Reassemble-time evidence: GSPA 2.00 accepted the uppercase CALL A? spelling
# (it rejects CALL $A?) and encoded it as the one-word 0x0920 form. The
# listing shows the opcode word next to the instruction.
grep -Eq '^[[:space:]]*[0-9]+[[:space:]]+[0-9A-F]+[[:space:]]+0920[[:space:]]+CALL[[:space:]]+A[0-9]+' "$WORK/m4.lst" \
|| { grep -i 'call' "$WORK/m4.lst" >&2 || true; fail "GSPA listing has no 0x0920 CALL A?"; }
echo " ok GSPA listing encoded CALL A? as 0x0920"
# dis34 evidence at the linked address: _apply's indirect call decodes as
# `call aN` (the disassembler's spelling for CALL Rs).
[ -x "$DIS34" ] || fail "dis34 not executable: $DIS34"
APPLY_ADDR=$(awk '$1 == "_apply" { print $2; exit } $2 == "_apply" { print $1; exit }' "$MAP")
[ -n "$APPLY_ADDR" ] || fail "linker map has no _apply symbol"
read_u8s() { od -An -v -tu1 -j "$1" -N "$2" "$COFF"; }
u16() { read_u8s "$1" 2 | awk '{ print $1 + 256 * $2 }'; }
u32() { read_u8s "$1" 4 | awk '{ print $1 + 256 * $2 + 65536 * $3 + 16777216 * $4 }'; }
nscns=$(u16 2)
opthdr=$(u16 16)
TEXT_ADDR=
TEXT_BITS=
TEXT_PTR=
sh_base=$((20 + opthdr))
i=0
while [ "$i" -lt "$nscns" ]; do
o=$((sh_base + i * 40))
name=$(dd if="$COFF" bs=1 skip="$o" count=8 2>/dev/null | tr -d '\000')
if [ "$name" = ".text" ]; then
TEXT_ADDR=$(u32 $((o + 8)))
TEXT_BITS=$(u32 $((o + 16)))
TEXT_PTR=$(u32 $((o + 20)))
fi
i=$((i + 1))
done
[ -n "$TEXT_ADDR" ] || fail "COFF has no .text section"
[ $((TEXT_BITS % 8)) -eq 0 ] || fail ".text size $TEXT_BITS bits is not whole bytes"
TEXT_HEX=$(printf '%08X' "$TEXT_ADDR")
TEXT_BYTES=$((TEXT_BITS / 8))
dd if="$COFF" of="$TMP/text.bin" bs=1 skip="$TEXT_PTR" count="$TEXT_BYTES" 2>/dev/null
[ "$(wc -c < "$TMP/text.bin")" -eq "$TEXT_BYTES" ] || fail "could not extract .text"
"$DIS34" -b "$TEXT_HEX" -l -o "$TMP/text.bin" > "$TMP/dis34.txt" 2> "$TMP/dis34.err" \
|| { cat "$TMP/dis34.err" >&2; fail "dis34 failed"; }
APPLY_L=$(printf '%s' "$APPLY_ADDR" | tr 'A-F' 'a-f')
awk -v a="$APPLY_L" '
{ low = tolower($0) }
low ~ ("^" a ":") { on = 1 }
on { print }
on && low ~ /^[0-9a-f]+:[[:space:]]+rets([[:space:]]|;|$)/ { exit }
' "$TMP/dis34.txt" > "$TMP/apply.dis"
grep -Eq '^[0-9a-f]+:[[:space:]]+call[[:space:]]+a[0-9]+' "$TMP/apply.dis" \
|| { sed 's/^/ /' "$TMP/apply.dis" >&2; fail "dis34 does not decode _apply's CALL Rs as 'call aN'"; }
echo " ok dis34 _apply @ 0x$APPLY_L: $(grep -E '^[0-9a-f]+:[[:space:]]+call[[:space:]]+a[0-9]+' "$TMP/apply.dis" | head -1 | sed 's/^[[:space:]]*//')"
echo "m4 fnptr: PASS"
+148
View File
@@ -0,0 +1,148 @@
#!/bin/sh
# M4 layout gate: the X11R4-compatible XImage/XEvent/XErrorEvent and the ROM's
# Display/Screen layouts must match the 32-bit VT1200 ABI.
#
# layout.c asserts the byte offsets with _Static_assert and returns the ROM's
# bit displacements (byte offset * 8). layout-ti.c is the same layout compiled
# by the 1987 TI compiler once; its &-of-member is already a bit address.
# Both asm files must carry the same constants.
#
# No host X11 headers are used: layout.c includes only scripts/m4/x11m4.h and
# tms-clang runs with -nostdinc.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
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_LLC=${TMS_LLC:-$LLVM_BIN/llc}
TMS_BITADDR=${TMS_BITADDR:-$ROOT/tools/tms-bitaddr/tms-bitaddr}
WORK=${M4_LAYOUT_WORK:-$ROOT/build/m4-layout}
GOLDEN=$ROOT/scripts/m4/layout-ti.s
fail() {
echo "layout.sh: FAIL: $*" >&2
exit 1
}
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_LLC" "$TMS_BITADDR"; do
[ -x "$tool" ] || fail "missing tool: $tool"
done
[ -f "$GOLDEN" ] || fail "missing TI golden: $GOLDEN"
mkdir -p "$WORK"
# Exit 0 here means every _Static_assert in layout.c held on the target.
TMS_CLANG="$TMS_CLANG" TMS_OPT="$TMS_OPT" TMS_BITADDR="$TMS_BITADDR" \
"$ROOT/scripts/tms-clang" -O0 -o "$WORK/layout.ll" \
"$ROOT/scripts/m4/layout.c" \
|| fail "target compile failed (XImage/XEvent/XErrorEvent/Display static asserts?)"
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm \
"$WORK/layout.ll" -o "$WORK/layout.s" \
|| fail "llc failed for layout.ll"
# Print one function body, stopping at the next function label. TI's
# EPI0_n labels are inside the function and come after the constant we check.
body() { # body FILE FUNC
awk -v want="_$2:" '
$1 == want { infn = 1; next }
infn {
if ($0 ~ /^[A-Za-z_][A-Za-z0-9_]*:/ && $0 !~ /^LBB/) exit
print
}
' "$1"
}
# Allowed spellings for a constant: C hex, assembler hex with `h`, decimal.
has_const() { # has_const FILE FUNC ERE
body "$1" "$2" | tr 'A-Z' 'a-z' | grep -Eq "$3"
}
check() { # check FILE FUNC ERE WHAT
if ! has_const "$1" "$2" "$3"; then
echo "layout.sh: $1: _$2 body:" >&2
body "$1" "$2" >&2
fail "_$2 in $1 does not contain $4"
fi
}
# First MOVI immediate in a function body, in decimal.
const_value() { # const_value FILE FUNC
body "$1" "$2" | awk '
/MOVI/ {
for (i = 1; i <= NF; i++)
if ($i == "MOVI") {
v = $(i + 1)
sub(/,.*/, "", v)
print v
exit
}
}
'
}
to_dec() { # to_dec TOKEN
case "$1" in
0x* | 0X*) printf '%d' "$1" ;;
*h | *H) printf '%d' "0x${1%?}" ;;
*) printf '%d' "$1" ;;
esac
}
HEX480='(0x480|480h|[^0-9a-f]1152[^0-9a-f])'
HEX40='(0x40|40h|[^0-9a-f]64[^0-9a-f])'
HEX80='(0x80|80h|[^0-9a-f]128[^0-9a-f])'
HEXA0='(0xa0|0a0h|[^0-9a-f]160[^0-9a-f])'
HEX140='(0x140|140h|[^0-9a-f]320[^0-9a-f])'
HEX160='(0x160|160h|[^0-9a-f]352[^0-9a-f])'
HEX200='(0x200|200h|[^0-9a-f]512[^0-9a-f])'
HEX1A0='(0x1a0|1a0h|[^0-9a-f]416[^0-9a-f])'
for asm in "$WORK/layout.s" "$GOLDEN"; do
check "$asm" layout_display_screens "$HEX480" "0x480 (Display.screens)"
check "$asm" layout_screen_root "$HEX40" "0x40 (Screen.root)"
check "$asm" layout_image_data "$HEX80" "0x80 (XImage.data at byte 16)"
check "$asm" layout_image_byte_order "$HEXA0" "0xA0 (XImage.byte_order at byte 20)"
check "$asm" layout_image_bytes_per_line "$HEX140" "0x140 (XImage.bytes_per_line at byte 40)"
check "$asm" layout_image_bits_per_pixel "$HEX160" "0x160 (XImage.bits_per_pixel at byte 44)"
check "$asm" layout_image_f "$HEX200" "0x200 (XImage.f at byte 64)"
check "$asm" layout_error_error_code "$HEX80" "0x80 (XErrorEvent.error_code at byte 16)"
check "$asm" layout_event_xkey_keycode "$HEX1A0" "0x1A0 (XEvent.xkey.keycode at byte 52)"
done
# sizeof(XEvent) is in bytes. It must cover the termtris vt_u32 evbuf[24].
TARGET_TOKEN=$(const_value "$WORK/layout.s" layout_event_size) \
|| fail "could not read layout_event_size from layout.s"
GOLDEN_TOKEN=$(const_value "$GOLDEN" layout_event_size) \
|| fail "could not read layout_event_size from the TI golden"
[ -n "$TARGET_TOKEN" ] || fail "layout.s layout_event_size has no MOVI constant"
[ -n "$GOLDEN_TOKEN" ] || fail "TI golden layout_event_size has no MOVI constant"
TARGET_SIZE=$(to_dec "$TARGET_TOKEN") \
|| fail "could not parse layout_event_size '$TARGET_TOKEN' from layout.s"
GOLDEN_SIZE=$(to_dec "$GOLDEN_TOKEN") \
|| fail "could not parse layout_event_size '$GOLDEN_TOKEN' from the TI golden"
[ "$TARGET_SIZE" -ge 96 ] \
|| fail "target sizeof(XEvent) = $TARGET_SIZE bytes, want >= 96"
[ "$GOLDEN_SIZE" -ge 96 ] \
|| fail "TI sizeof(XEvent) = $GOLDEN_SIZE bytes, want >= 96"
[ "$TARGET_SIZE" -eq "$GOLDEN_SIZE" ] \
|| fail "target sizeof(XEvent)=$TARGET_SIZE but TI golden=$GOLDEN_SIZE"
# root_probe exercises the real access path: vt_root() follows Display.screens
# (bit 0x480) and reads Screen.root (bit 0x40). The helper is always_inline.
check "$WORK/layout.s" root_probe "$HEX480" "0x480 (Display.screens)"
check "$WORK/layout.s" root_probe "$HEX40" "0x40 (Screen.root)"
if body "$WORK/layout.s" root_probe | grep -q 'MOVB'; then
fail "root_probe contains MOVB: align-1 field loads did not select MOVE"
fi
# Guard against the old byte/bit-confusion mistake: Display.screens must not
# be 0x480*8 (9216) and Screen.root must not be 0x40*8 (512).
if body "$WORK/layout.s" layout_display_screens | tr 'A-Z' 'a-z' |
grep -Eq '(^|[^0-9])(9216|2400h)([^0-9a-f]|$)' ||
body "$WORK/layout.s" layout_screen_root | tr 'A-Z' 'a-z' |
grep -Eq '(^|[^0-9])(512|200h)([^0-9a-f]|$)'; then
fail "target carries byte-pad offsets (9216/512); ROM displacements are bits 0x480/0x40"
fi
echo "layout.sh: PASS (XImage data/byte_order/bpl/bpp/f = 0x80/0xA0/0x140/0x160/0x200; XErrorEvent.error_code=0x80; XEvent.xkey.keycode=0x1A0; sizeof(XEvent)=$TARGET_SIZE; Display.screens=0x480, Screen.root=0x40; target + TI golden)"
+107
View File
@@ -0,0 +1,107 @@
#!/bin/sh
# M4 multiply/divide and -O2 loop gate: compile mul.c and loop.c with the
# default tms-clang -O2 (the driver wrapper, not a local opt wrapper), link with
# GSPA/GSPLNK, run in MAME and check every arithmetic marker. The linked
# assembly must use MPYS/MPYU/DIVS/DIVU for the operations mul.c names.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
DIR=$ROOT/scripts/m4
WORK=${M4_MUL_WORK:-$ROOT/build/m4-mul}
fail() {
echo "m4 mul: FAIL: $*" >&2
exit 1
}
M4_SOURCES="mul loop" \
M4_ENTRY="$DIR/mul-entry.asm" \
M4_CHECK_LUA="$DIR/mul-check.lua" \
M4_CLANG_OPT=-O2 \
M4_WORK="$WORK" \
"$ROOT/scripts/m4/run.sh" || fail "M4 multiply/loop run failed"
ASM=$WORK/m4.asm
[ -f "$ASM" ] || fail "linked assembly not found: $ASM"
# The loop source must compile with the ordinary driver -O2 pipeline: no local
# -replexitval flag and no i33/i64 multiply in the emitted IR.
grep -q -- '-replexitval=never' "$ROOT/scripts/tms-clang" \
|| fail "scripts/tms-clang does not disable IndVarSimplify exit-value replacement"
if grep -Eq '\bmul i(33|64)\b' "$WORK/asm/loop.ll"; then
sed 's/^/ /' "$WORK/asm/loop.ll" >&2
fail "loop.ll still contains an i33/i64 multiply"
fi
echo " ok driver -O2 loop IR has no i33/i64 multiply"
# The 16x16 unsigned product must not carry the old signed-overflow
# annotation. With the multiply done in unsigned arithmetic the IR is
# `mul nuw`, not `mul nuw nsw`; the latter contradicts the 0xfffe0001 result.
if awk '/^define .*@umul16\(/{infn=1} infn{print} infn && /^}/{exit}' \
"$WORK/asm/mul.ll" | grep -q 'mul nuw nsw'; then
fail "umul16 still has a signed no-wrap multiply (the product is unsigned)"
fi
echo " ok umul16 multiplies without signed no-wrap"
# The wide product must likewise be an unsigned multiply: signed `nsw` on
# values whose product exceeds 32 bits would make the marker UB/poison.
if awk '/^define .*@mulu\(/{infn=1} infn{print} infn && /^}/{exit}' \
"$WORK/asm/mul.ll" | grep -Eq 'mul (nuw )?nsw'; then
fail "mulu has a signed no-wrap multiply (the wide product is unsigned)"
fi
echo " ok mulu multiplies without signed no-wrap"
# Stop at the next function label (a column-0 label that is not an LBB block
# label), so a following local function cannot leak into this slice.
body() { # body FILE FUNC
awk -v want="_$2:" '
$1 == want { infn = 1; next }
infn {
if ($0 ~ /^[A-Za-z_][A-Za-z0-9_]*:/ && $0 !~ /^LBB/) exit
print
}
' "$1"
}
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m4-mul.XXXXXX")
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
EXPECTED="M4: mul=42 umul16=4294836225 sdiv=14 udiv=613566756 srem=2 urem=2 sdivneg=4294967282 sremneg=4294967294 sumto=55 squares=30 widemul=606937216 widerem=2"
grep -Fqx "$EXPECTED" "$WORK/mame.log" \
|| { grep -E '^M4: ' "$WORK/mame.log" >&2 || true; fail "runtime markers did not match: $EXPECTED"; }
echo " ok runtime markers: $EXPECTED"
for fn in mul mulu umul16 sdiv udiv srem urem sumto squares; do
body "$ASM" "$fn" > "$TMP/$fn.s"
[ -s "$TMP/$fn.s" ] || fail "linked assembly has no _$fn body"
done
check_insn() { # check_insn FUNC MNEMONIC WHAT
if grep -Eq "^[[:space:]]*$2([[:space:]]|$)" "$TMP/$1.s"; then
echo " ok _$1 uses $2 ($3)"
return 0
fi
sed 's/^/ /' "$TMP/$1.s" >&2
fail "_$1 does not use $2 ($3)"
}
check_insn mul MPYS "signed 32x32 low product"
check_insn mulu MPYS "wide unsigned 32x32 low product"
check_insn umul16 MPYU "16x16 unsigned product"
check_insn sdiv DIVS "signed 32/32 divide"
check_insn udiv DIVU "unsigned 32/32 divide"
check_insn srem DIVS "signed remainder divide"
check_insn srem MPYS "signed remainder multiply"
check_insn srem SUB "signed remainder subtract"
check_insn urem DIVU "unsigned remainder divide"
check_insn urem SUB "unsigned remainder subtract"
check_insn squares MPYS "loop multiply"
# The driven loop must remain a loop (a backward branch), not a closed form.
if ! grep -Eq '^[[:space:]]*JA(LT|LE|GT|GE|LO|LS|HI|HS|EQ|NE)[[:space:]]' "$TMP/sumto.s"; then
sed 's/^/ /' "$TMP/sumto.s" >&2
fail "_sumto has no conditional branch (loop was not kept)"
fi
echo " ok _sumto keeps a conditional loop branch"
echo "m4 mul: PASS"
+54
View File
@@ -0,0 +1,54 @@
#!/bin/sh
# M4 private-symbol gate: compile two translation units that both contain the
# string literal "same", assemble/link them with GSPA/GSPLNK, and require a
# clean link. Before M4 both TUs emitted the same ``__2EL_2Estr`` label and
# GSPA's flat symbol table reported a duplicate definition.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
DIR=$ROOT/scripts/m4
WORK=${M4_PRIVATE_WORK:-$ROOT/build/m4-private}
fail() {
echo "m4 private: FAIL: $*" >&2
exit 1
}
M4_SKIP_MAME=1 \
M4_SOURCES="private-a private-b" \
M4_ENTRY="$DIR/private-entry.asm" \
M4_WORK="$WORK" \
"$ROOT/scripts/m4/run.sh" || fail "two-TU GSPA link failed"
ASM_A=$WORK/asm/private-a.s
ASM_B=$WORK/asm/private-b.s
for f in "$ASM_A" "$ASM_B"; do
[ -f "$f" ] || fail "missing assembly: $f"
done
# Each TU's string label must be GSPA-safe and module-unique.
A_SYM=$(grep -oE '__2EL_2Estr_[0-9A-F]+:' "$ASM_A" | head -1 | tr -d ':')
B_SYM=$(grep -oE '__2EL_2Estr_[0-9A-F]+:' "$ASM_B" | head -1 | tr -d ':')
[ -n "$A_SYM" ] || { sed 's/^/ /' "$ASM_A" >&2; fail "private-a has no module-unique string label"; }
[ -n "$B_SYM" ] || { sed 's/^/ /' "$ASM_B" >&2; fail "private-b has no module-unique string label"; }
[ "$A_SYM" != "$B_SYM" ] \
|| fail "both TUs use the same private label: $A_SYM"
echo " ok private labels differ: $A_SYM vs $B_SYM"
if grep -qx '__2EL_2Estr:' "$ASM_A" || grep -qx '__2EL_2Estr:' "$ASM_B"; then
fail "a TU still emits the colliding bare __2EL_2Estr label"
fi
# Public names must not be rewritten.
grep -Eq '^[[:space:]]*\.globl[[:space:]]+_a([[:space:]]|$)' "$ASM_A" \
|| fail "private-a does not expose public _a"
grep -Eq '^[[:space:]]*\.globl[[:space:]]+_b([[:space:]]|$)' "$ASM_B" \
|| fail "private-b does not expose public _b"
echo " ok public names unchanged: _a and _b"
grep -q '^_a:' "$WORK/m4.asm" || fail "linked assembly has no _a"
grep -q '^_b:' "$WORK/m4.asm" || fail "linked assembly has no _b"
awk '$2 == "_a" || $2 == "_b" { found++ } END { exit found == 2 ? 0 : 1 }' \
"$WORK/m4.map" || fail "linker map does not define both _a and _b"
echo "m4 private: PASS"
+75
View File
@@ -0,0 +1,75 @@
#!/bin/sh
# M4 image/event gate: compile the 1-bit XImage client and xgfix, assemble and
# link with GSPA/GSPLNK, run in MAME, inject the real LK401 `Q`, and require the
# image pixels, the key, the focus (the client's own window), and zero X errors.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
DIR=$ROOT/scripts/m4
WORK=${M4_XSHOW_WORK:-$ROOT/build/m4-xshow}
fail() {
echo "m4 xshow: FAIL: $*" >&2
exit 1
}
# The client uses the Task 4 XImage/XEvent layout and the Task 5 ROM table.
"$ROOT/scripts/tests/m4/layout.sh" || fail "XImage/XEvent layout gate failed"
"$ROOT/scripts/m4/validate-syms.sh" || fail "ROM symbol validation failed"
M4_SOURCES="xshow xgfix" \
M4_ENTRY="$DIR/xshow-entry.asm" \
M4_CHECK_LUA="$DIR/xshow-check.lua" \
M4_CLANG_OPT=-O2 \
M4_ROM_SYMBOLS=1 \
LOADER_AT=2500 \
LOADER_END=3400 \
M4_WORK="$WORK" \
VT1200_SKIP_LEVEL1=1 \
VT1200_MONITOR=VR319 \
"$ROOT/scripts/m4/run.sh" || fail "M4 image/event run failed"
LOG=$WORK/mame.log
[ -f "$LOG" ] || fail "no MAME log: $LOG"
MARKER=$(grep -aE '^M4: display=' "$LOG" | tail -1 || true)
[ -n "$MARKER" ] || fail "no M4 marker line"
echo " $MARKER"
case "$MARKER" in
*" quit=1 events="*) ;;
*) fail "quit marker is not 1" ;;
esac
case "$MARKER" in
*" keycode=193 keysym=0x71 "*) ;;
*) fail "the injected Q did not produce keycode 193 / keysym 0x71" ;;
esac
case "$MARKER" in
*" xerrors=0 "*) ;;
*) fail "xerrors is not 0" ;;
esac
case "$MARKER" in
*" focus=0x400001 "*) ;;
*) fail "input focus is not the client window 0x400001" ;;
esac
case "$MARKER" in
*" last=2"*) ;;
*) fail "the last X event is not KeyPress (last=2)" ;;
esac
# The image evidence: the two required cells plus a later band.
grep -Fq 'M4: pixel screen(50,50) image(18,30) cell(0,0) bit=0x04019032 value=0 black' "$LOG" \
|| fail "window-relative (30,30) is not the black checker cell"
grep -Fq 'M4: pixel screen(70,50) image(38,30) cell(1,0) bit=0x04019046 value=1 white' "$LOG" \
|| fail "window-relative (50,30) is not the white checker cell"
grep -Fq 'M4: pixel screen(500,400) image(468,380) cell(14,11) bit=0x040C81F4 value=1 white' "$LOG" \
|| fail "later-band checker cell is wrong"
# The 12-pixel prefix is the preceding cell: white in band 0, black in band 1.
grep -Eq '^M4: pixel screen\(25,30\) image\(-7,10\) cell\(-1,0\) bit=0x[0-9A-F]+ value=1 white$' "$LOG" \
|| fail "left prefix in band 0 is not the preceding (white) cell"
grep -Eq '^M4: pixel screen\(25,62\) image\(-7,42\) cell\(-1,1\) bit=0x[0-9A-F]+ value=0 black$' "$LOG" \
|| fail "left prefix in band 1 is not the preceding (black) cell"
grep -q '^M4 MAME: PASS' "$LOG" || fail "image/key check did not pass"
grep -q 'LOADER: result status=0 (OK)' "$LOG" || fail "loader status is not OK"
echo "m4 xshow: PASS (1-bit image + real q key + window focus + 0 X errors)"
+54
View File
@@ -0,0 +1,54 @@
#!/bin/sh
# M4 symbol gate.
#
# Runs scripts/m4/validate-syms.sh on the real v2.2 table and pins the
# XMapWindow/XRaiseWindow correction: a table that maps XMapWindow to
# 0x3FD645B0 (ConfigureWindow/XRaiseWindow) must be rejected and named.
# Also proves the generic alignment gate is live.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
VALIDATE=$ROOT/scripts/m4/validate-syms.sh
SYMS=${M4_SYMS:-$ROOT/symbols/vt1200-v22.syms}
fail() {
echo "symbols.sh: FAIL: $*" >&2
exit 1
}
[ -x "$VALIDATE" ] || fail "validator not executable: $VALIDATE"
[ -f "$SYMS" ] || fail "symbol file not found: $SYMS"
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m4-symbols.XXXXXX")
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
echo "== the real v2.2 table"
"$VALIDATE" "$SYMS" || fail "the committed v2.2 table did not validate"
echo "== negative control: XMapWindow 0x3FD645B0"
sed 's/^XMapWindow[[:space:]].*/XMapWindow 0x3FD645B0/' \
"$SYMS" > "$TMP/wrong-mapwindow.syms"
grep -q '^XMapWindow[[:space:]]*0x3FD645B0' "$TMP/wrong-mapwindow.syms" \
|| fail "could not construct the wrong-XMapWindow table"
if "$VALIDATE" "$TMP/wrong-mapwindow.syms" > "$TMP/wrong.out" 2>&1; then
sed 's/^/ /' "$TMP/wrong.out" >&2
fail "validator accepted XMapWindow 0x3FD645B0"
fi
grep -q 'XRaiseWindow' "$TMP/wrong.out" \
|| { sed 's/^/ /' "$TMP/wrong.out" >&2; fail "rejection did not name XRaiseWindow"; }
grep -qi 'wrong' "$TMP/wrong.out" \
|| { sed 's/^/ /' "$TMP/wrong.out" >&2; fail "rejection did not report the wrong address"; }
echo " ok XMapWindow 0x3FD645B0 rejected as XRaiseWindow/wrong"
echo "== negative control: unaligned entry"
sed 's/^XPutImage[[:space:]].*/XPutImage 0x3F9CEC71/' \
"$SYMS" > "$TMP/unaligned.syms"
if "$VALIDATE" "$TMP/unaligned.syms" > "$TMP/unaligned.out" 2>&1; then
sed 's/^/ /' "$TMP/unaligned.out" >&2
fail "validator accepted an unaligned XPutImage address"
fi
grep -qi 'aligned' "$TMP/unaligned.out" \
|| { sed 's/^/ /' "$TMP/unaligned.out" >&2; fail "alignment failure not reported"; }
echo " ok unaligned XPutImage rejected"
echo "symbols.sh: PASS (v2.2 table validates; XMapWindow correction and alignment are pinned)"
+104
View File
@@ -0,0 +1,104 @@
#!/bin/sh
# M4 termtris gate: build the pinned termtris with this compiler, run the
# proven play_keys paths in MAME (board, move, panels, quit), and enforce the
# loop/event/quit/focus thresholds and the four snapshots.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
DIR=$ROOT/scripts/m4/termtris
VENDOR=${VT1200_TERMTRIS:-$ROOT/vendor/vt1200-termtris}
WORK=${TERMTRIS_WORK:-$ROOT/build/m4-termtris}
EV=$WORK/evidence
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}
PIN=be5b323f5d2cc4f7a7d25e7143c67bd9828ccbdf
fail() {
echo "m4 termtris: FAIL: $*" >&2
exit 1
}
[ -d "$VENDOR" ] || fail "missing pinned termtris tree: $VENDOR"
if git -C "$VENDOR" rev-parse --git-dir >/dev/null 2>&1; then
rev=$(git -C "$VENDOR" rev-parse HEAD)
[ "$rev" = "$PIN" ] || fail "vendor tree is $rev, want $PIN"
fi
# The optimizer's switch-to-lookup-table transform creates a late byte-offset
# GEP that --normalize cannot distinguish from an already-scaled bit offset.
# tms-clang disables it; a dense char switch is the regression probe. The
# termtris init_game/runesc switches are the real instances.
TMP=$(mktemp -d "${TMPDIR:-/tmp}/tt-switch.XXXXXX")
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
export TMS_CLANG TMS_OPT TMS_BITADDR
"$ROOT/scripts/tms-clang" -O2 -o "$TMP/switch.ll" "$ROOT/scripts/m4/switch.c" \
|| fail "tms-clang failed for the switch probe"
if grep -q 'switch\.table' "$TMP/switch.ll"; then
fail "switch-to-lookup table survived the driver pipeline (8x index bug)"
fi
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/switch.ll" \
-o "$TMP/switch.s" || fail "llc failed for the switch probe"
grep -q 'CMPI' "$TMP/switch.s" || fail "the switch probe has no compare/branch"
echo "== build the pinned termtris with this compiler"
"$DIR/build.sh" || fail "termtris build failed"
echo "== run board/move/panels/quit in MAME"
TERMTRIS_NOBUILD=1 \
MODES="board move panels quit" \
LOADER_AT=2500 LOADER_END=3200 \
"$DIR/play_keys.sh" || fail "play_keys run failed"
field() {
# field FILE NAME -> the decimal/hex value after NAME=
sed -n "s/.*[[:space:]]$2=\\([0-9A-Fa-f]*\\).*/\\1/p" "$1" | head -1
}
for mode in board move panels quit; do
DIAG=$WORK/diag_$mode.txt
LOG=$WORK/play_$mode.log
[ -f "$DIAG" ] || fail "no DIAG for $mode"
[ -f "$LOG" ] || fail "no MAME log for $mode"
LOOPS=$(field "$DIAG" loops)
EVENTS=$(field "$DIAG" events)
QUIT=$(field "$DIAG" quit)
FOCUS=$(field "$DIAG" focus)
WINDOW=$(field "$DIAG" window)
[ -n "$LOOPS" ] && [ -n "$EVENTS" ] && [ -n "$QUIT" ] \
&& [ -n "$FOCUS" ] && [ -n "$WINDOW" ] \
|| fail "$mode DIAG line is incomplete: $(cat "$DIAG")"
[ "$LOOPS" -ge 100 ] || fail "$mode main loop barely ran (loops=$LOOPS)"
[ "$FOCUS" != 00000000 ] && [ "$FOCUS" != 00000001 ] \
|| fail "$mode focus is not a created window (focus=$FOCUS)"
[ "$FOCUS" = "$WINDOW" ] \
|| fail "$mode focus $FOCUS is not the termtris window $WINDOW"
case $mode in
move|panels)
[ "$EVENTS" -ge 6 ] || fail "$mode got $EVENTS events, expected the click and keys"
[ "$QUIT" = 0 ] || fail "$mode quit unexpectedly"
;;
quit)
[ "$EVENTS" -ge 6 ] || fail "$mode got $EVENTS events, expected the click and q"
[ "$QUIT" = 1 ] || fail "q did not reach the game (quit=$QUIT)"
;;
board)
[ "$EVENTS" -ge 2 ] || fail "$mode got $EVENTS events, expected the click"
[ "$QUIT" = 0 ] || fail "$mode quit unexpectedly"
;;
esac
grep -q 'LOADER: result status=0 (OK)' "$LOG" \
|| fail "$mode loader status is not OK"
grep -q 'placed \.text .* 0 differing' "$LOG" \
|| fail "$mode loader did not verify the placed .text bytes"
[ -s "$EV/termtris_${mode}_3200.png" ] \
|| fail "$mode snapshot is missing"
echo " $mode loops=$LOOPS events=$EVENTS quit=$QUIT focus=$FOCUS"
done
echo "m4 termtris: PASS (board/move/panels/quit; ${EV}/termtris_*_3200.png)"
+79
View File
@@ -0,0 +1,79 @@
#!/bin/sh
# M4 unsigned runtime gate: compile unsigned.c at -O0, link with GSPA/GSPLNK,
# run in MAME, and check every signed/unsigned predicate marker. The linked
# assembly must use the real condition mnemonics for each function.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
WORK=${M4_UNSIGNED_WORK:-$ROOT/build/m4-unsigned}
fail() {
echo "m4 unsigned: FAIL: $*" >&2
exit 1
}
M4_WORK="$WORK" "$ROOT/scripts/m4/run.sh" || fail "M4 unsigned run failed"
ASM=$WORK/m4.asm
[ -f "$ASM" ] || fail "linked assembly not found: $ASM"
# Stop at the next function label (a column-0 label that is not an LBB block
# label), so a following local function cannot leak into this slice.
body() { # body FILE FUNC
awk -v want="_$2:" '
$1 == want { infn = 1; next }
infn {
if ($0 ~ /^[A-Za-z_][A-Za-z0-9_]*:/ && $0 !~ /^LBB/) exit
print
}
' "$1"
}
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m4-unsigned.XXXXXX")
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
EXPECTED="M4: le=1 ule=0 sum_le=55 ge=1 uge=0 gt=0 ugt=1 ne=1 lt=0 ult=1"
grep -Fqx "$EXPECTED" "$WORK/mame.log" \
|| { grep -E '^M4: ' "$WORK/mame.log" >&2 || true; fail "runtime markers did not match: $EXPECTED"; }
echo " ok runtime markers: $EXPECTED"
for fn in le ule sum_le ge uge gt ugt ne lt ult; do
body "$ASM" "$fn" > "$TMP/$fn.s"
[ -s "$TMP/$fn.s" ] || fail "linked assembly has no _$fn body"
done
check_branch() { # check_branch FUNC MNEMONIC WHAT
if grep -Eq "^[[:space:]]*$2[[:space:]]" "$TMP/$1.s"; then
echo " ok _$1 uses $2 ($3)"
return 0
fi
sed 's/^/ /' "$TMP/$1.s" >&2
fail "_$1 does not use $2 ($3)"
}
reject_branch() { # reject_branch FUNC ERE WHAT
if grep -Eq "^[[:space:]]*$2[[:space:]]" "$TMP/$1.s"; then
sed 's/^/ /' "$TMP/$1.s" >&2
fail "_$1 uses the wrong condition $2 ($3)"
fi
}
check_branch le JALE "signed <="
check_branch ule JALS "unsigned <="
check_branch sum_le JAGT "signed i > n loop exit"
check_branch ge JAGE "signed >="
check_branch uge JAHS "unsigned >="
check_branch gt JAGT "signed >"
check_branch ugt JAHI "unsigned >"
check_branch ne JANE "!="
check_branch lt JALT "signed <"
check_branch ult JALO "unsigned <"
# The unsigned predicates must not silently select a signed branch form.
reject_branch ule 'JA(LT|LE|GT|GE)' "unsigned <="
reject_branch uge 'JA(LT|LE|GT|GE)' "unsigned >="
reject_branch ugt 'JA(LT|LE|GT|GE)' "unsigned >"
reject_branch ult 'JA(LT|LE|GT|GE)' "unsigned <"
reject_branch ne 'JAEQ' "!="
echo "m4 unsigned: PASS"
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
# M4 varargs ABI gate: compile the contiguous-stack probe with this compiler,
# link it with the M4 runner, and require the three sums in MAME.
set -eu
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
DIR=$ROOT/scripts/m4
WORK=${M4_VARARGS_WORK:-$ROOT/build/m4-varargs}
fail() {
echo "m4 varargs: FAIL: $*" >&2
exit 1
}
M4_SOURCES=varargs \
M4_ENTRY="$DIR/varargs-entry.asm" \
M4_CHECK_LUA="$DIR/varargs-check.lua" \
M4_CLANG_OPT=-O0 \
M4_WORK="$WORK" \
LOADER_AT=2500 \
LOADER_END=2700 \
"$DIR/run.sh" || fail "M4 varargs run failed"
LOG=$WORK/mame.log
[ -f "$LOG" ] || fail "no MAME log: $LOG"
MARKER=$(grep -aE '^M4: varargs ' "$LOG" | tail -1 || true)
[ -n "$MARKER" ] || fail "no varargs marker line"
echo " $MARKER"
grep -q '^M4 MAME: PASS' "$LOG" || fail "varargs marker values are wrong"
grep -q 'LOADER: result status=0 (OK)' "$LOG" || fail "loader status is not OK"
echo "m4 varargs: PASS (three contiguous stack args per call)"
+15 -1
View File
@@ -49,7 +49,21 @@ trap 'rm -rf "$TMP"' EXIT HUP INT TERM
"$TMS_BITADDR" --scale "$TMP/step1.ll" "$TMP/scaled.ll" 2>/dev/null "$TMS_BITADDR" --scale "$TMP/step1.ll" "$TMP/scaled.ll" 2>/dev/null
if [ "$OPTIMIZE" -eq 1 ]; then if [ "$OPTIMIZE" -eq 1 ]; then
"$TMS_OPT" -passes='default<O2>' -S "$TMP/scaled.ll" -o "$TMP/opt.ll" # This target has no i64 and no multiply-high, so IndVarSimplify's
# exit-value replacement (which rewrites a loop's final value into a
# closed-form i33 multiply, e.g. n*(n+1)/2 for sumto) produces IR that
# llc cannot select. -replexitval is an option of IndVarSimplify; disabling
# only that rewrite keeps the loop, which is what ordinary -O2 code needs.
# This is a driver policy, not a target TTI policy: opt runs without a
# target machine here.
# `-switch-to-lookup=false` is a driver policy, not a target feature: the
# optimizer's switch-to-lookup-table transform creates a *new* byte-offset
# GEP after --scale, and --normalize can only treat late GEP offsets as
# bit offsets. Such a GEP would index the table one bit per slot (an 8x
# address bug). The backend also has no jump-table lowering, so keeping
# the compare/branch switch is correct.
"$TMS_OPT" -passes='default<O2>' -replexitval=never \
-switch-to-lookup=false -S "$TMP/scaled.ll" -o "$TMP/opt.ll"
NORMALIZE_IN="$TMP/opt.ll" NORMALIZE_IN="$TMP/opt.ll"
else else
NORMALIZE_IN="$TMP/scaled.ll" NORMALIZE_IN="$TMP/scaled.ll"
+124 -26
View File
@@ -3,38 +3,136 @@
One file per ROM revision. Addresses are **bit** addresses and are not assumed One file per ROM revision. Addresses are **bit** addresses and are not assumed
stable across firmware revisions; v2.2 is the first target. stable across firmware revisions; v2.2 is the first target.
`vt1200-v22.syms` contains only the entries already proven by the hand-written `vt1200-v22.syms` is the per-ROM entry table. Every entry is validated by
payloads in `vendor/vt1200/dwt/gui/xhello.asm` and the README. **M2 consumes `scripts/m4/validate-syms.sh` against the v2.2 image (md5
only `strlen`**: `scripts/m2/gen-rom-imports.sh` emits an ABI-preserving `b46a3e442ccb86134d2356bd70d22fd2`): it must be in the ROM bit range
`jauc` stub for every entry, and the M2 harness calls `_strlen` at `3F200000h..3FFFFFFFh`, 16-bit aligned, and disassemble to a plausible function
`0x3FB458D0`. **M3 consumes exactly the four Xlib entries below** through the prologue (`MMTM`/`MOVE`/`SETF`/`CMP`/`ADDI`). `scripts/tests/m4/symbols.sh`
same stub generator; the compiled `scripts/m3/xhello.c` is the end-to-end proof. runs the gate and also pins the `XMapWindow`/`XRaiseWindow` correction.
The table has not been widened beyond those five validated entries (the four
Xlib entries below plus `strlen`). M4 extends it for the
real client (`XImage`/`XEvent` and the rest of the X11R4 calls) from the Ghidra
project / `ptr_constants.py`, and validates each new entry on MAME, then on the
real unit. Do not assume an address here is validated just because it is
listed.
## v2.2 Xlib entries validated 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. 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.
The four Xlib entries used by the compiled client are **validated** by Do not assume an address here is validated just because it is listed. Each
`vendor/vt1200/dwt/gui/xhello.asm` (the hand-written payload that already runs entry below names its evidence. Nothing is guessed: `XCreateImage`,
against the live DWT X server): `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). They are inherited from the M4 plan
(`task-5-brief.md`) and are ROM-validated here by their request builders:
the `XLoadFont` body at `0x3FD4AE80` builds `movi 0x2d` (OpenFont opcode 45)
and the `XCopyArea` body at `0x3FD3E220` builds `movi 0x3e` (CopyArea
opcode 62); both write the opcode with `movb` to the request buffer. The same
addresses/opcodes are listed in the vendored DWT `GUI_APPS.md` table.
`scripts/m4/validate-syms.sh` now checks both builders. Do not cite the
termtris header for them.
## `XMapWindow` correction
The M3 table used `0x3FD645B0` under the name `XMapWindow`. That address is
**`XRaiseWindow`**: its body builds X11 request opcode **12**
(`ConfigureWindow`) with value-mask `0x40` (`CWStackMode`) and value `0`
(`Above`). The true `XMapWindow` is **`0x3FD64F60`**, whose body builds request
opcode **8** (`MapWindow`). The termtris VT1200 `xlib.h` already used
`0x3FD64F60`.
`scripts/m3/validate-syms.sh` and `scripts/m3/dis-roundtrip.sh` now expect
`0x3FD64F60`, and `scripts/tests/m4/symbols.sh` fails if a table maps
`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 | | entry | bit address | evidence |
|---|---|---| |---|---|---|
| `XOpenDisplay` | `0x3F9C2570` | `xhello.asm` calls it; `CALLA _XOpenDisplay` reaches the stub in MAME | | `XOpenDisplay` | `0x3F9C2570` | termtris `xlib.h`; `xhello.asm` calls it; MAME child connects |
| `XCreateSimpleWindow` | `0x3FD41B90` | nine-argument window create; window id marker is nonzero | | `XCreateSimpleWindow` | `0x3FD41B90` | termtris `xlib.h`; nine-argument create, window id nonzero |
| `XMapWindow` | `0x3FD645B0` | mapped window produces the white VRAM pixel | | `XCreatePixmap` | `0x3FD416E0` | CreatePixmap request opcode `0x35`; five arguments (display, drawable, width, height, depth); M4 image client's aligned 1-bit pixmap |
| `XSync` | `0x3FD9F6B0` | called after mapping; return value is diagnostic only | | `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 |
| `XFlush` | `0x3F9B1030` | termtris `xlib.h` |
| `XPending` | `0x3FD67620` | termtris `xlib.h` |
| `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` |
`scripts/m3/validate-syms.sh` is the regression check: each entry must be in ### Blitter copy helper
the ROM bit range `3F200000h..3FFFFFFFh`, 16-bit aligned, and disassemble to a
plausible function prologue (`MOVE`/`MMTM`/`SETF`/`CMP`/`ADDI`). | entry | bit address | evidence |
`scripts/tests/m3/run.sh` runs it before every MAME run, so the compiled client |---|---|---|
cannot pass with a stale stub. It is not a re-extraction; M4 extends the table | `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`. |
for `XImage`/`XEvent` and the rest of the real client.
### Drawing and GC
| entry | bit address | evidence |
|---|---|---|
| `XCreateGC` | `0x3F997000` | termtris `xlib.h`; request opcode 55 |
| `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` | ROM body builds `movi 0x2d` (OpenFont opcode 45) and `movb` to the request buffer; `dwt/notes/GUI_APPS.md` table; **not** in the termtris `xlib.h`; checked by `validate-syms.sh` |
| `XCopyArea` | `0x3FD3E220` | ROM body builds `movi 0x3e` (CopyArea opcode 62) and `movb` to the request buffer; `dwt/notes/GUI_APPS.md` table; **not** in the termtris `xlib.h`; checked by `validate-syms.sh`; the M4 image client copies its aligned pixmap with it |
| `XLookupString` | `0x3F9A9C30` | termtris `xlib.h`; key translation |
| `sleep` | `0x3FB33A20` | termtris `xlib.h`; called directly |
### `XImage` and error handlers (ROM-validated)
| entry | bit address | evidence |
|---|---|---|
| `XCreateImage` | `0x3F9A0490` | installed as `XImage.f.create_image` by the ROM initializer at `0x3F9A6AA0`; `calloc(1, 0x58)` = `sizeof(XImage) == 88` and field initialization; called by `XGetImage` with ten arguments at `0x3FD56190` |
| `XPutImage` | `0x3F9CEC70` | prologue `MMTM sp, a5,a7,a9,a10,a11,a13`; body builds request opcode `0x48` at `0x3F9CEE00`; references `XPutImage: bits_per_pixel conversion unimplemented.` at `0x3F9D0E80` |
| `XGetImage` | `0x3FD556B0` | prologue `MMTM sp, a7,a9,a10,a11,a13`; body builds request opcode `0x49` at `0x3FD55830`; error path references `XGetImage: Can't get requested memory` at `0x3FD567A0` |
| `XDestroyImage` | `0x3F9A0D80` | ROM private `_XDestroyImage`, installed as `XImage.f.destroy_image` by the same initializer; real Xlib defines `XDestroyImage` as a macro over this pointer. The stub name is `_XDestroyImage` |
| `XSetErrorHandler` | `0x3FD45700` | stores the handler in the per-process XG block at `+0x60`; default `0x3F9BD9F0` (`_XDefaultError`); session manager, WM, and VTE call it with an error-event handler |
| `XSetIOErrorHandler` | `0x3FD458F0` | stores the handler in the XG block at `+0x40`; default `0x3F9BCD40` (`_XDefaultIOError`); same callers pass an I/O-error handler |
The error-handler setters operate on the per-process XG block, the same block
that `scripts/m4/xgfix.c` installs before `XOpenDisplay`. `Ghidra` byte
addresses are reproduced as ROM bit addresses (`bit = byte << 3`); the
reconstructed ROM image starts at bit `0x3F200000`.
### C runtime
| entry | bit address | evidence |
|---|---|---|
| `strlen` | `0x3FB458D0` | `xhello.asm` ABI check (`strlen("0::0") == 4`) |
| `malloc` | `0x3F701230` | ROM allocator; called by `XCreateImage`/`XGetImage` |
| `free` | `0x3F702440` | ROM free; called by `XDestroyImage` |
## 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 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 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. bake them into the compiler: keep them in a per-ROM linker script.
+68 -12
View File
@@ -1,14 +1,70 @@
# VT1200 v2.2 ROM entry points, bit addresses. # VT1200 v2.2 ROM entry points, bit addresses.
# Source: dwt/gui/xhello.asm comments and README.md (v2.2 image md5 # Source: the termtris VT1200 xlib.h table, dwt/gui/xhello.asm comments, the
# b46a3e442ccb86134d2356bd70d22fd2). These are the known-good entries used # v2.2 ROM disassembly (dis34 / reference/dis34-a-v22.txt), and README.md
# by the existing hand-written payloads; the rest must be extracted from the # (v2.2 image md5 b46a3e442ccb86134d2356bd70d22fd2).
# Ghidra project / ROM before the Xlib client milestone.
# #
# C name bit address # Every entry is checked by scripts/m4/validate-syms.sh: ROM bit range
XOpenDisplay 0x3F9C2570 # 3F200000h..3FFFFFFFh, 16-bit aligned, and a real function prologue.
XCreateSimpleWindow 0x3FD41B90 #
XMapWindow 0x3FD645B0 # C name bit address evidence
XSync 0x3FD9F6B0 #
strlen 0x3FB458D0 # --- Xlib connection / window lifecycle ---
malloc 0x3F701230 XOpenDisplay 0x3F9C2570 termtris xlib.h; xhello.asm
free 0x3F702440 XCreateSimpleWindow 0x3FD41B90 termtris xlib.h; xhello.asm
# XCreatePixmap builds CreatePixmap opcode 0x35 and takes five arguments
# (display, drawable, width, height, depth); used by the M4 image client to
# render an aligned pixmap and CopyArea it to the (unaligned) window.
XCreatePixmap 0x3FD416E0 CreatePixmap opcode 0x35
XMapWindow 0x3FD64F60 MapWindow request opcode 8; termtris xlib.h
XRaiseWindow 0x3FD645B0 ConfigureWindow opcode 12, CWStackMode=Above
XSync 0x3FD9F6B0 termtris xlib.h; xhello.asm
XFlush 0x3F9B1030 termtris xlib.h
XPending 0x3FD67620 termtris xlib.h
XNextEvent 0x3F9C1CF0 termtris xlib.h
XSelectInput 0x3FD97D50 termtris xlib.h
XGetInputFocus 0x3FD55210 termtris xlib.h
# XSetInputFocus builds SetInputFocus opcode 0x2a (0x3FD536B0; the sibling of
# XGetInputFocus, which builds opcode 0x2b). Used by the M4 image client to
# give its own window the input focus (the WM leaves the focus at PointerRoot).
XSetInputFocus 0x3FD536B0 SetInputFocus request opcode 0x2a
#
# --- image copy helper ---
# _XPutImage copies the XImage into its request buffer through this ROM entry
# (0x3F9CBFE0 calls it). It is the TMS34010 blitter memcpy with the ROM's
# argument order (source, destination, byte count) and a normal prologue
# (MMTM B15,...). The image client primes the blitter with a 4-byte self-copy.
blit_copy 0x3FB44400 _XPutImage copy entry; (src,dst,n)
#
# --- drawing / GC ---
XCreateGC 0x3F997000 termtris xlib.h
XSetForeground 0x3FD99A60 termtris xlib.h
XFillRectangle 0x3F99E8F0 termtris xlib.h
XDrawString 0x3FDA0000 termtris xlib.h
XLoadFont 0x3FD4AE80 ROM movi 0x2d (OpenFont); not termtris xlib.h
XCopyArea 0x3FD3E220 ROM movi 0x3e (CopyArea); not termtris xlib.h
XLookupString 0x3F9A9C30 termtris xlib.h
sleep 0x3FB33A20 termtris xlib.h
#
# --- XImage and error handlers (validated against the ROM, not xlib.h) ---
# XCreateImage is installed as XImage.f.create_image by the ROM initializer at
# 0x3F9A6AA0; it calloc(1, 88) exactly sizeof(XImage) and initializes the fields.
XCreateImage 0x3F9A0490 f.create_image; 88-byte XImage alloc
# XPutImage builds PutImage request opcode 0x48 at 0x3F9CEE00 and references the
# "XPutImage: bits_per_pixel conversion unimplemented." string at 0x3F9D0E80.
XPutImage 0x3F9CEC70 request opcode 0x48
# XGetImage builds GetImage request opcode 0x49 at 0x3FD55830 and references the
# "XGetImage: Can't get requested memory" string at 0x3FD567A0.
XGetImage 0x3FD556B0 request opcode 0x49
# XDestroyImage is an Xlib macro; this is the ROM's private _XDestroyImage,
# installed as XImage.f.destroy_image. The stub is _XDestroyImage.
XDestroyImage 0x3F9A0D80 f.destroy_image (_XDestroyImage)
# The per-process XG block holds the error handlers at +0x40 (IO) and +0x60
# (protocol). The ROM defaults are 0x3F9BCD40 (_XDefaultIOError) and
# 0x3F9BD9F0 (_XDefaultError); the session manager/WM/VTE call these setters.
XSetErrorHandler 0x3FD45700 XG+0x60, default _XDefaultError
XSetIOErrorHandler 0x3FD458F0 XG+0x40, default _XDefaultIOError
#
# --- C runtime ---
strlen 0x3FB458D0 xhello.asm ABI check
malloc 0x3F701230 ROM allocator
free 0x3F702440 ROM free