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
upstream LLVM.
**Status: M3.** `llc -march=tms34010` emits GSPA-compatible assembly using the
**Status: M4.** `llc -march=tms34010` emits GSPA-compatible assembly using the
TI ABI: integer/pointer arguments are pushed on the program stack, the result is
returned in `A8`, frames use `A13`/`A14`/`A15`, and functions end in `RETS 2`
(`RETS 0` without arguments). M3 adds conditional/unconditional branches,
ordinary `while`/`for` loops, integer ALU/shift operations, and the first
compiled C Xlib client: a four-call window opened with `XOpenDisplay`, created with
`XCreateSimpleWindow`, mapped with `XMapWindow` and synchronized with `XSync`.
It is still **assembly-only**: no object writer, no `clang -c`, and no native
COFF; GSPA/GSPLNK under DOSBox produce the COFF. XImage/XEvent, the real client,
structs by value, function pointers, floats and `i64` remain M4/M5.
(`RETS 0` without arguments). M4 adds function pointers and `CALL Rs`, 32-bit
multiply/divide, zero-extending narrow loads, module-unique private labels,
`i8`/`i16` fixed arguments and the contiguous-stack varargs form, the X11R4
`XImage`/`XEvent` layouts, the full image/event client, the corrected 30-entry
ROM symbol table, and the pinned termtris full-game integration. It is still
**assembly-only**: no object writer, no `clang -c`, and no native COFF;
GSPA/GSPLNK under DOSBox produce the COFF. Floats, `long long`, general
varargs, bitfields, structs by value, narrow returns and native COFF are M5.
Branch `m4` is the current milestone snapshot in both repositories; the
default branch is `m3` until the controller/user updates it after the final
review. Branch `m3` remains the previous snapshot, `m2` is the M2 tip and
`m1` the reproducible M1 snapshot.
- Design: [`docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md`](docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md)
- M0 plan: [`docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md`](docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md)
- M1 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md`](docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md)
- M2 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md`](docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md)
- M3 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m3-control-flow-and-xlib-window.md`](docs/superpowers/plans/2026-11-11-tms34010-m3-control-flow-and-xlib-window.md)
- M4 plan: [`docs/superpowers/plans/2026-11-12-tms34010-m4-xlib-image-events-and-termtris.md`](docs/superpowers/plans/2026-11-12-tms34010-m4-xlib-image-events-and-termtris.md)
- Spike: [`spike/REPORT-23.md`](spike/REPORT-23.md) (current, LLVM 23.1.5);
[`spike/REPORT.md`](spike/REPORT.md) (historical, 19.1.7)
## Repositories
- **Compiler fork:** <https://git.wut.ee/opencode.siil.arti.ee/llvm-tms34010>
(branch `m3`) — the full buildable LLVM 23.1.5 tree with the TMS34010
target, clang bit-address changes and the M2/M3 backend. Branch `m2` (the
repository default) is the M2 snapshot; branch `tms34010` is the M1 tip and
keeps the original M1 backend and suite.
(branch `m4`) — the full buildable LLVM 23.1.5 tree with the TMS34010
target, clang bit-address changes and the M2/M3/M4 backend. Branch `m3`
(the repository default) is the previous snapshot; branch `m2` is the M2
tip and branch `tms34010` is the M1 tip and keeps the original M1 backend
and suite.
- **This project:** <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>
(branch `m3`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
`tms-clang`, symbols, the M2/M3 harnesses and the spike. Branch `m2` (the
repository default) is the M2 snapshot and branch `m1` is the reproducible M1
snapshot, including the retired M1 harness.
(branch `m4`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
`tms-clang`, symbols, the M2/M3/M4 harnesses and the spike. Branch `m3`
(the repository default) is the previous snapshot; branch `m2` is the M2
snapshot and branch `m1` is the reproducible M1 snapshot, including the
retired M1 harness.
The compiler fork is the canonical source for all target code. `patches/llvm`
here is a **frozen M0 mirror** used by the default upstream+patches build; M2/M3
do not change it.
here is a **frozen M0 mirror** used by the default upstream+patches build;
M2/M3/M4 do not change it.
## What M3 provides
## What M4 provides
M4 closes the remaining Xlib path to a real image/event client and a full-game
integration, still assembly-only:
- **Function pointers and `CALL Rs`.** A function pointer is a 32-bit bit
address; an indirect call lowers to the one-word register call `CALL Rs`
(GSPA `0x0920`, `dis34` `call aN`), with the same old-STK save, argument
pushes, `SETF` fixup and `RETS 2` as a direct call. Direct calls remain
`CALLA _sym`. `m4-fnptr.ll` + `scripts/tests/m4/fnptr.sh`.
- **32-bit multiply/divide.** `MPYS`/`MPYU`/`DIVS`/`DIVU` with an odd
destination (the even form is the `Rd:Rd+1` pair and would clobber the next
register); remainder is quotient + `MPYS` + `SUB`. 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
assembly-only:
@@ -62,8 +133,11 @@ assembly-only:
- Misaligned accesses: `MOVE`/`MOVB` and the FS0/FS1 field-size forms have no
alignment requirement on the TMS34010, so packed and bit-addressed struct
fields select directly instead of being split into byte-wide accesses.
- Nine `m3-*.ll` lit tests, for a total of 46 with the M2 suite; the M3
end-to-end, dis34, layout and differential gates (below).
- Nine `m3-*.ll` lit tests on the `m3` branch (46 with the M2 suite); M4
removes 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
(`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
`sumto`'s loop into a closed-form `i33` multiply, which M3 cannot select. The
M3 harness therefore builds `branch.c` with `-replexitval=never`. Ordinary
`while`/`for` loops that keep a real back-edge and phi work. M4/M5 must add
multiply/`i64` lowering, a target-level pass policy, or a target diagnostic
instead of a raw abort.
`while`/`for` loops that keep a real back-edge and phi work. M4 adds
multiply/divide and moves the `-replexitval=never` policy into
`scripts/tms-clang`; see "What M4 provides".
## What M2 provides (branch `m2`)
@@ -117,7 +191,7 @@ M2 replaces the M1 test convention with the TI ABI and stops at assembly:
## The TI ABI
| | M1 test convention (branch `m1`) | TI ABI (M2/M3) |
| | M1 test convention (branch `m1`) | TI ABI (M2/M3/M4) |
|---|---|---|
| Integer/pointer arguments | `A0`–`A3`, four maximum | program stack via `A14` (STK), pushed in reverse |
| Argument slots | registers | first at `FP(-32)`, second at `FP(-64)`, … |
@@ -136,7 +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
drives 32-bit words.
## Run the M3 gates
## Run the M4 gates
These are the documented commands on branch `m4`; all must pass. The MAME
gates are slow and are run once.
```sh
# 1. Build llc, opt, clang and the test tools
ninja -C build/llvm-clean-build llc opt clang llvm-config FileCheck count not
# 2. LLVM codegen tests: 58 passed (32 m2-* + 8 m3-* + 18 m4-*)
build/llvm-clean-build/bin/llvm-lit -v \
build/llvm-clean/llvm/test/CodeGen/TMS34010 --filter='(m2|m3|m4)-'
# 3. M4 image/event client in MAME (also runs layout.sh + validate-syms.sh)
scripts/tests/m4/run.sh
# -> m4 xshow: PASS (1-bit image + real q key + window focus + 0 X errors)
# 4. Linked .text decoded with dis34 (ROM stubs, CALL A?, SETF, RETS, checker)
scripts/tests/m4/dis-roundtrip.sh
# -> M4 dis34 round-trip: PASS
# 5. Layouts, the 30-entry symbol table, and the correction negative control
scripts/tests/m4/layout.sh
scripts/tests/m4/symbols.sh
# 6. The pinned termtris full-game integration
scripts/tests/m4/termtris.sh
# -> m4 termtris: PASS (board/move/panels/quit; .../termtris_*_3200.png)
# 7. Differential TI-compiler function-pointer/image ABI check (no DOSBox)
scripts/tests/m4/diff/check.sh
# -> M4 diff: PASS (goldens + negative controls)
# 8. The smaller M4 runtime gates
scripts/tests/m4/unsigned.sh
scripts/tests/m4/private.sh
scripts/tests/m4/mul.sh
scripts/tests/m4/fnptr.sh
scripts/tests/m4/varargs.sh
# 9. M2/M3 regressions and the GEP tool tests
scripts/tests/m3/run.sh
scripts/tests/m3/dis-roundtrip.sh
scripts/tests/m3/layout.sh
scripts/tests/m2/run.sh
scripts/tests/m2/dis-roundtrip.sh
scripts/tests/m2/diff/check.sh
tools/tms-bitaddr/tests/run.sh
# -> tms-bitaddr: PASS
```
`scripts/tests/m4/run.sh` first runs `layout.sh` and
`scripts/m4/validate-syms.sh`, then compiles `scripts/m4/xshow.c` and
`scripts/m4/xgfix.c` with `scripts/tms-clang -O2`, runs
`llc -march=tms34010 -O0 -filetype=asm`, generates the ROM import stubs,
concatenates `scripts/m4/xshow-entry.asm`, assembles/links with GSPA/GSPLNK
2.00 under DOSBox, loads the COFF in MAME, injects the real LK401 `Q`, and
requires the checker pixels, `quit=1`, keycode 193 / keysym `0x71`,
`xerrors=0`, focus `0x400001` and the last event KeyPress. The 12-pixel
prefix is checked as the preceding checker cell in bands 0 and 1.
`scripts/tests/m4/termtris.sh` runs the pinned tree through
`scripts/m4/termtris/build.sh` and `play_keys.sh`; it enforces `loops >= 100`
for all four modes, `events >= 6` for move/panels/quit (`>= 2` for board),
`quit = 1` only for quit, focus = the created termtris window,
`status=0 (OK)` and a non-empty screenshot each time. The vendored tree is
test-only (GPLv3).
`scripts/tests/m4/diff/check.sh` is the new CI golden check. It compiles our
function-pointer and image equivalents with `scripts/tms-clang` + `llc` and
compares ABI shape with `scripts/tests/m4/diff/ti-fnptr.s` and
`ti-image.s`: an indirect `CALL A?`, the old-STK save, argument/return/`RETS
2`, ten `XCreateImage` and ten `XPutImage` pushes (the plan's 10/9 is a typo;
`XPutImage` takes display, drawable, gc, image, src_x, src_y, dest_x, dest_y,
width, height), nine `XCreateSimpleWindow` pushes, the leading
constant/Display-last order, and the Display/Screen/XImage bit displacements.
It then mutates copies of the goldens (removing the indirect call and
reversing the `XPutImage` argument pushes) and requires the check to fail;
the stored goldens are not modified. The goldens were generated once with
GSP C Compiler 2.01 under DOSBox; CI never runs DOSBox.
Paths are overridable with `TMS_CLANG`, `TMS_OPT`, `TMS_BITADDR`, `TMS_LLC`,
`MAME`, `DOSBOX`, `SDK_IMG`, the per-gate work/COFF variables and `M4_SYMS`.
## Run the M3 gates (branch `m3`)
These are the documented commands on branch `m3`; all must pass.
@@ -217,7 +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
`sumto`'s loop into an unselectable `i33` multiply.
## Run the M2 gates (default branch)
## Run the M2 gates (branch `m2`)
These are the M2 commands; they also pass on `m3` (verified on branch `m3` on
2026-11-11: `M2 MAME: PASS`, `M2 dis34 round-trip: PASS`, `M2 diff: PASS`). The
@@ -357,32 +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.
- **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`,
`unsigned short` and `_Bool` loads (pinned by `m3-reject-zext.ll`); M4 must
add the `ZEXTLOAD i8`/`i16` patterns and replace the reject test.
- **Namespace every private label.** `scripts/tests/m3/run.sh` prefixes
`LBB<n>_<m>` per translation unit, but deterministic private string labels
are not renamed. Two TUs with literals therefore collide in GSPA's flat
symbol table. M4 must namespace all private labels or make compiler private
names module-unique.
- **Add unsigned/`<=` runtime coverage.** `JALE`, `JALO`, `JAHI` and `JALS`,
`JAHS`, `JANE`, `JAGE` are covered by lit tests only and do not appear in the
compiled M3 image. M4 should add an unsigned/`<=` runtime marker.
- **Replace fragile test plumbing.** The `always_inline` root helper and the
register-hardcoded nine-argument dis34 check are test-strength aids that will
not survive real M4 code; make them operand- and register-independent.
- **Tighten test strength.** `m3-cmpi`'s `CHECK-DAG` for 0/1 does not pin their
order, and `layout.sh` pairs the root probe with fixed constants; neither
weakens the current pass, but both should be tightened.
## M4 non-goals and the M5 boundary
M4 deliberately stops before:
- **Native object output:** no `MCCodeEmitter`, object writer,
`llc -filetype=obj`, `clang -c` or native COFF; GSPA/GSPLNK still produce
the COFF. The `JR`/branch-relaxation code-size pass and MMTM/MMFM
register-list saves are M5 (the current per-register system-stack pushes
have the identical ABI layout).
- **Floating point and `long long`:** no float/double or `i64` arguments,
returns, arithmetic or branches.
- **General varargs:** no `va_list`, `va_start` or `va_arg`; only the
contiguous `&last_fixed` pattern used by termtris `sprintf`.
- **Bitfields and aggregates by value:** no structs/unions by value, `sret`,
`byval`, bitfields or aggregate returns. Struct fields through pointers are
supported.
- **Narrow returns:** `i8`/`i16` returns remain rejected; fixed i8/i16
arguments are supported.
- **Xlib beyond the tested calls:** the client uses the validated image,
event, GC, window and error-handler entries; it does not implement the whole
Xlib surface, `XGetImage`, or `XDestroyImage` on a non-heap image.
- **Deeper libc/runtime coverage:** M5 owns the broader `RTS.LIB` integration.
The pinned termtris tree is test-only (GPLv3) and is not part of the compiler
or project distribution.
## Repository layout
```text
build/llvm-clean/ compiler fork checkout used for M2/M3 (ignored)
build/llvm-clean/ compiler fork checkout used for M2/M3/M4 (ignored)
build/llvm-clean-build/ its Ninja build tree (ignored)
build/llvm-src/ M0 working fork (ignored)
patches/llvm/ frozen M0 patch series against llvmorg-23.1.5
@@ -390,10 +565,15 @@ tools/tms-bitaddr/ GEP scale/normalize tool
scripts/tms-clang IR pipeline wrapper (clang + tms-bitaddr + opt)
scripts/m2/ M2 harness: C inputs, entry.asm, link.cmd, stubs, checks
scripts/m3/ M3 harness: branch/xhello/layout inputs, Xlib header, entry/link
scripts/m4/ M4 harness: unsigned/private/mul/fnptr/varargs, XImage client,
XG fix, termtris build, symbols validator
scripts/tests/m2/ M2 gates (run.sh, dis-roundtrip.sh)
scripts/tests/m2/diff/ TI golden asm and the differential ABI check
scripts/tests/m3/ M3 gates (run.sh, dis-roundtrip.sh, layout.sh)
scripts/tests/m3/diff/ TI branch/Xlib goldens, differential check + negative controls
scripts/tests/m4/ M4 gates (run/dis-roundtrip/layout/symbols/termtris and the
unsigned/private/mul/fnptr/varargs gates)
scripts/tests/m4/diff/ TI function-pointer/image goldens + the differential check
scripts/m1/ M1 harness (historical; also on branch `m1`, not an M2 gate)
scripts/tests/m1/ M1 gates (historical; also on branch `m1`, not an M2 gate)
scripts/tests/m0/ M0 integration tests
@@ -408,11 +588,12 @@ docs/ design and plans
## Prerequisites
`cmake`, `ninja-build`, `ccache`, a C++17 host compiler (GCC 14 is used here),
Python 3, and about 20 GB of disk for the LLVM source and build. The M2/M3 MAME
and dis34 gates additionally need `dosbox`, `mtools` (`mcopy`), the VT1200 SDK
image, the `dwt` COFF loader, `dis34`, and a built MAME `vt1200` binary
(`build/mame-src/vt1200`). The differential golden check needs no
DOSBox/MAME/SDK; it needs only the built `clang`, `opt`, `llc` and
Python 3, and about 20 GB of disk for the LLVM source and build. The M2/M3/M4
MAME and dis34 gates additionally need `dosbox`, `mtools` (`mcopy`), the
VT1200 SDK image, the `dwt` COFF loader, `dis34`, and a built MAME `vt1200`
binary (`build/mame-src/vt1200`). The termtris gate also needs the pinned
`vendor/vt1200-termtris` tree (test-only). The differential golden check needs
no DOSBox/MAME/SDK; it needs only the built `clang`, `opt`, `llc` and
`tools/tms-bitaddr/tms-bitaddr`. On Debian:
```sh
@@ -430,13 +611,13 @@ Xlib call sequence, `as34`, `dis34`, SDKs, manuals). It is not vendored:
## Build the compiler
### Compiler fork (M3, the normal path)
### Compiler fork (M4, the normal path)
The fork checkout already exists at `build/llvm-clean` on `m3`. Build it in
The fork checkout already exists at `build/llvm-clean` on `m4`. Build it in
place:
```sh
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m3 ./scripts/build-llvm.sh
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m4 ./scripts/build-llvm.sh
# binaries: build/llvm-clean-build/bin/{llc,clang,opt,llvm-config,FileCheck}
# lit driver: build/llvm-clean-build/bin/llvm-lit
```
@@ -445,7 +626,7 @@ To clone the fork first instead of using an existing checkout:
```sh
LLVM_REPO=gitea@git.wut.ee:opencode.siil.arti.ee/llvm-tms34010.git \
LLVM_BRANCH=m3 ./scripts/build-llvm.sh
LLVM_BRANCH=m4 ./scripts/build-llvm.sh
```
Fork mode **never** checks out, resets, cleans or patches the source tree; it
@@ -462,22 +643,23 @@ TMS34010; a first build takes roughly 20–40 minutes.
This mode clones `llvmorg-23.1.5`, applies `patches/llvm/*.patch`, and builds
`build/llvm-clean-build`. It owns and resets only a plain `llvm-project` clone:
if `build/llvm-clean` has some other origin (for example the M2/M3 fork), it
refuses rather than destroying it. Use fork mode for that checkout.
if `build/llvm-clean` has some other origin (for example the M2/M3/M4 fork),
it refuses rather than destroying it. Use fork mode for that checkout.
`scripts/export-patches.sh` regenerates the committed M0 patch series with
`git diff` (including new files via `git add -N`); it is not used for M1/M2/M3.
`git diff` (including new files via `git add -N`); it is not used for
M1/M2/M3/M4.
## Build the M0 support tools
The M2/M3 pipeline uses `tms-bitaddr` and `tms-clang`:
The M2/M3/M4 pipeline uses `tms-bitaddr` and `tms-clang`:
```sh
LLVM_CONFIG=$PWD/build/llvm-clean-build/bin/llvm-config \
tools/tms-bitaddr/build.sh
```
M0 regression test (optional in M2/M3):
M0 regression test (optional in M2/M3/M4):
```sh
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
@@ -513,7 +695,8 @@ object file or a usable COFF for this target.
## Later milestones
M4 adds the real Xlib client (`XImage`/`XEvent`, the full X11R4 headers),
structs by value and function pointers. M5 adds the native COFF path, floats,
varargs, bitfields, `long long` and the `JR`/branch-relaxation code-size pass;
2ine r5.01 is a later speed-up for the GSPA/GSPLNK step.
M5 adds the native COFF path (`MCCodeEmitter`/object writer), floats,
`long long`, general varargs (`va_list`), bitfields, structs by value and the
`JR`/branch-relaxation code-size pass, plus MMTM/MMFM register-list saves and
deeper libc/runtime coverage. 2ine r5.01 is a later speed-up for the
GSPA/GSPLNK step.
@@ -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.
- **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.
- **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.
- `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`.
@@ -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:
- 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;
- 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);
@@ -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:
#
# _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
# _XCreateSimpleWindow (arguments 1,0,2,200,400,100,100,root,
# display in that order, display pushed last), 2 -> _XMapWindow,
@@ -256,8 +257,8 @@ expect_at "_XOpenDisplay" "$XOPEN" "JAUC 3F9C2570" \
'jauc[[:space:]]+0x3f9c2570'
expect_at "_XCreateSimpleWindow" "$XCREATE" "JAUC 3FD41B90" \
'jauc[[:space:]]+0x3fd41b90'
expect_at "_XMapWindow" "$XMAP" "JAUC 3FD645B0" \
'jauc[[:space:]]+0x3fd645b0'
expect_at "_XMapWindow" "$XMAP" "JAUC 3FD64F60" \
'jauc[[:space:]]+0x3fd64f60'
expect_at "_XSync" "$XSYNC" "JAUC 3FD9F6B0" \
'jauc[[:space:]]+0x3fd9f6b0'
+3
View File
@@ -18,6 +18,9 @@
# 018A80E0 layout_screens() == 0x480
# 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
# (250,200) inside the mapped window. A markers-only or root-window bit is a
# 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
# aligned, and disassemble to a plausible function prologue.
#
# This is a regression check, not a re-extraction: dwt/gui/xhello.asm is the
# evidence that these entries work. M4 extends the table for XImage/XEvent.
# This is a regression check, not a re-extraction: dwt/gui/xhello.asm and the
# 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]
#
@@ -55,7 +59,7 @@ check_sym() { # check_sym NAME EXPECTED_HEX
check_sym XOpenDisplay 0x3F9C2570
check_sym XCreateSimpleWindow 0x3FD41B90
check_sym XMapWindow 0x3FD645B0
check_sym XMapWindow 0x3FD64F60
check_sym XSync 0x3FD9F6B0
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"
/*
* 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))
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
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"
else
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
stable across firmware revisions; v2.2 is the first target.
`vt1200-v22.syms` contains only the entries already proven by the hand-written
payloads in `vendor/vt1200/dwt/gui/xhello.asm` and the README. **M2 consumes
only `strlen`**: `scripts/m2/gen-rom-imports.sh` emits an ABI-preserving
`jauc` stub for every entry, and the M2 harness calls `_strlen` at
`0x3FB458D0`. **M3 consumes exactly the four Xlib entries below** through the
same stub generator; the compiled `scripts/m3/xhello.c` is the end-to-end proof.
The table has not been widened beyond those five 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.
`vt1200-v22.syms` is the per-ROM entry table. Every entry is validated by
`scripts/m4/validate-syms.sh` against the v2.2 image (md5
`b46a3e442ccb86134d2356bd70d22fd2`): it must be in the ROM bit range
`3F200000h..3FFFFFFFh`, 16-bit aligned, and disassemble to a plausible function
prologue (`MMTM`/`MOVE`/`SETF`/`CMP`/`ADDI`). `scripts/tests/m4/symbols.sh`
runs the gate and also pins the `XMapWindow`/`XRaiseWindow` correction.
## 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
`vendor/vt1200/dwt/gui/xhello.asm` (the hand-written payload that already runs
against the live DWT X server):
Do not assume an address here is validated just because it is listed. Each
entry below names its evidence. Nothing is guessed: `XCreateImage`,
`XPutImage`, `XGetImage`, `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 |
|---|---|---|
| `XOpenDisplay` | `0x3F9C2570` | `xhello.asm` calls it; `CALLA _XOpenDisplay` reaches the stub in MAME |
| `XCreateSimpleWindow` | `0x3FD41B90` | nine-argument window create; window id marker is nonzero |
| `XMapWindow` | `0x3FD645B0` | mapped window produces the white VRAM pixel |
| `XSync` | `0x3FD9F6B0` | called after mapping; return value is diagnostic only |
| `XOpenDisplay` | `0x3F9C2570` | termtris `xlib.h`; `xhello.asm` calls it; MAME child connects |
| `XCreateSimpleWindow` | `0x3FD41B90` | termtris `xlib.h`; nine-argument create, window id nonzero |
| `XCreatePixmap` | `0x3FD416E0` | CreatePixmap request opcode `0x35`; five arguments (display, drawable, width, height, depth); M4 image client's aligned 1-bit pixmap |
| `XMapWindow` | `0x3FD64F60` | request opcode 8 (`MapWindow`); termtris `xlib.h` |
| `XRaiseWindow` | `0x3FD645B0` | request opcode 12 (`ConfigureWindow`), `CWStackMode=Above` |
| `XSync` | `0x3FD9F6B0` | termtris `xlib.h`; called after mapping |
| `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
the ROM bit range `3F200000h..3FFFFFFFh`, 16-bit aligned, and disassemble to a
plausible function prologue (`MOVE`/`MMTM`/`SETF`/`CMP`/`ADDI`).
`scripts/tests/m3/run.sh` runs it before every MAME run, so the compiled client
cannot pass with a stale stub. It is not a re-extraction; M4 extends the table
for `XImage`/`XEvent` and the rest of the real client.
### Blitter copy helper
| entry | bit address | evidence |
|---|---|---|
| `blit_copy` | `0x3FB44400` | `_XPutImage`'s copy entry (`0x3F9CBFE0` calls `0x3FB44400`); TMS34010 blitter memcpy with the ROM order `(source, destination, byte count)` and a normal `MMTM` prologue. The M4 image client primes it once per draw with a 4-byte self-copy because a loader-created process starts with a zeroed blitter window/pitch context. Validated by `scripts/m4/validate-syms.sh` and the `_blit_copy` `JAUC`/`CALLA` checks in `dis-roundtrip.sh`. |
### Drawing and GC
| 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
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.
# Source: dwt/gui/xhello.asm comments and README.md (v2.2 image md5
# b46a3e442ccb86134d2356bd70d22fd2). These are the known-good entries used
# by the existing hand-written payloads; the rest must be extracted from the
# Ghidra project / ROM before the Xlib client milestone.
# Source: the termtris VT1200 xlib.h table, dwt/gui/xhello.asm comments, the
# v2.2 ROM disassembly (dis34 / reference/dis34-a-v22.txt), and README.md
# (v2.2 image md5 b46a3e442ccb86134d2356bd70d22fd2).
#
# C name bit address
XOpenDisplay 0x3F9C2570
XCreateSimpleWindow 0x3FD41B90
XMapWindow 0x3FD645B0
XSync 0x3FD9F6B0
strlen 0x3FB458D0
malloc 0x3F701230
free 0x3F702440
# Every entry is checked by scripts/m4/validate-syms.sh: ROM bit range
# 3F200000h..3FFFFFFFh, 16-bit aligned, and a real function prologue.
#
# C name bit address evidence
#
# --- Xlib connection / window lifecycle ---
XOpenDisplay 0x3F9C2570 termtris xlib.h; xhello.asm
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