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