Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d980dc6e3 | ||
|
|
1511919437 | ||
|
|
2d5ed90fb2 | ||
|
|
1a91329864 | ||
|
|
fb0159620e | ||
|
|
26697b9585 | ||
|
|
7946519828 | ||
|
|
8cb060cdb4 | ||
|
|
7a89a1fe4c | ||
|
|
d8efad2479 | ||
|
|
00522b56fc | ||
|
|
7cd9a48ee3 | ||
|
|
cfe751ccab | ||
|
|
57ea80a5c0 | ||
|
|
28c9ee7e3c | ||
|
|
dbf71bdc5f | ||
|
|
24f877362b | ||
|
|
2681be26c0 | ||
|
|
ed58040fbd | ||
|
|
09db9e22e9 | ||
|
|
b34016a9f3 | ||
|
|
2187f59b20 | ||
|
|
d1ba1e4d86 | ||
|
|
2d4919db36 | ||
|
|
f1a604d70e | ||
|
|
3a188b35fb | ||
|
|
fa00b8740b | ||
|
|
b614e70851 | ||
|
|
0008d7be20 | ||
|
|
d4f165fa7d | ||
|
|
3f30806a6d | ||
|
|
e7ba50d7a3 | ||
|
|
469b1d870e | ||
|
|
e60a1fdee9 | ||
|
|
5e8dbffb01 | ||
|
|
c0746338b0 |
No files matched your search
@@ -3,83 +3,579 @@
|
|||||||
A modern C toolchain for the DEC VT1200 / TI TMS34010, built as a small fork of
|
A modern C toolchain for the DEC VT1200 / TI TMS34010, built as a small fork of
|
||||||
upstream LLVM.
|
upstream LLVM.
|
||||||
|
|
||||||
**Status: M1.** `llc -march=tms34010` emits GSPA-compatible assembly for a
|
**Status: M4.** `llc -march=tms34010` emits GSPA-compatible assembly using the
|
||||||
small set of leaf functions, and that assembly assembles, links and runs in
|
TI ABI: integer/pointer arguments are pushed on the program stack, the result is
|
||||||
MAME. M1 is an **assembly-only, test-only** snapshot: it uses an A0–A3/A0
|
returned in `A8`, frames use `A13`/`A14`/`A15`, and functions end in `RETS 2`
|
||||||
register convention, not the TI ABI, and has no object writer, calls or runtime.
|
(`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)
|
- Design: [`docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md`](docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md)
|
||||||
- M0 plan: [`docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md`](docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md)
|
- M0 plan: [`docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md`](docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md)
|
||||||
- M1 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md`](docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md)
|
- M1 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md`](docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md)
|
||||||
|
- M2 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md`](docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md)
|
||||||
|
- M3 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m3-control-flow-and-xlib-window.md`](docs/superpowers/plans/2026-11-11-tms34010-m3-control-flow-and-xlib-window.md)
|
||||||
|
- M4 plan: [`docs/superpowers/plans/2026-11-12-tms34010-m4-xlib-image-events-and-termtris.md`](docs/superpowers/plans/2026-11-12-tms34010-m4-xlib-image-events-and-termtris.md)
|
||||||
- Spike: [`spike/REPORT-23.md`](spike/REPORT-23.md) (current, LLVM 23.1.5);
|
- Spike: [`spike/REPORT-23.md`](spike/REPORT-23.md) (current, LLVM 23.1.5);
|
||||||
[`spike/REPORT.md`](spike/REPORT.md) (historical, 19.1.7)
|
[`spike/REPORT.md`](spike/REPORT.md) (historical, 19.1.7)
|
||||||
|
|
||||||
## Repositories
|
## Repositories
|
||||||
|
|
||||||
- **Compiler fork:** <https://git.wut.ee/opencode.siil.arti.ee/llvm-tms34010>
|
- **Compiler fork:** <https://git.wut.ee/opencode.siil.arti.ee/llvm-tms34010>
|
||||||
(branch `tms34010`) — the full buildable LLVM 23.1.5 tree with the TMS34010
|
(branch `m4`) — the full buildable LLVM 23.1.5 tree with the TMS34010
|
||||||
target, clang bit-address changes and the M1 backend.
|
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>
|
- **This project:** <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>
|
||||||
— plans/specs, VT1200 integration tooling, `tms-bitaddr`, `tms-clang`,
|
(branch `m4`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
|
||||||
symbols, the M1 harness and the spike.
|
`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`
|
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; M1
|
here is a **frozen M0 mirror** used by the default upstream+patches build;
|
||||||
does not change it.
|
M2/M3/M4 do not change it.
|
||||||
|
|
||||||
## What M1 provides
|
## What M4 provides
|
||||||
|
|
||||||
M1 fills in the M0 target skeleton and stops at assembly:
|
M4 closes the remaining Xlib path to a real image/event client and a full-game
|
||||||
|
integration, still assembly-only:
|
||||||
|
|
||||||
- `llc -march=tms34010 -filetype=asm` for leaf 32-bit functions:
|
- **Function pointers and `CALL Rs`.** A function pointer is a 32-bit bit
|
||||||
integer/pointer arguments in `A0`–`A3`, result in `A0`, return via `RETS 0`.
|
address; an indirect call lowers to the one-word register call `CALL Rs`
|
||||||
- `ADD` for `i32`, `MOVI` for constants, `MOVE ..., 1` (FS1) for 32-bit
|
(GSPA `0x0920`, `dis34` `call aN`), with the same old-STK save, argument
|
||||||
load/store, `MOVE ..., 0` (FS0) for `i16`, and sign-extending `MOVB` for
|
pushes, `SETF` fixup and `RETS 2` as a direct call. Direct calls remain
|
||||||
`i8` load/store.
|
`CALLA _sym`. `m4-fnptr.ll` + `scripts/tests/m4/fnptr.sh`.
|
||||||
- Constant global addresses as `MOVI _sym+bits`; a surviving GEP offset is a
|
- **32-bit multiply/divide.** `MPYS`/`MPYU`/`DIVS`/`DIVU` with an odd
|
||||||
bit offset (`ADDI 24`, not `ADDI 192`).
|
destination (the even form is the `Rd:Rd+1` pair and would clobber the next
|
||||||
- Hard errors, not silent fallbacks, for calls (`M1 has no calls; M2 adds the
|
register); remainder is quotient + `MPYS` + `SUB`. There is no compiler
|
||||||
TI ABI`), stack frames (`M1 has no stack frame`), more than four register
|
multiply/divide helper in `RTS.LIB`; `_div` is only the C `div()` wrapper.
|
||||||
arguments, unsupported signatures (varargs, `i64`/float/aggregate
|
`m4-mul.ll` + `scripts/tests/m4/mul.sh`.
|
||||||
parameters, `sret`/`byval`/`inreg`/`nest`/`returned`), unsupported return
|
- **Zero-extending and extending narrow loads.** `ZEXTLOAD`/`EXTLOAD` i8/i16
|
||||||
types, and initialized globals or string literals.
|
lower to the sign-extending load plus `ANDI 255`/`ANDI 65535`; an i1 load is
|
||||||
- Seventeen `llvm-lit`/FileCheck tests under
|
a byte load masked to one bit. `m4-zext.ll`, `m4-anyext.ll`,
|
||||||
`llvm/test/CodeGen/TMS34010/m1-*.ll` in the compiler fork, including
|
`m4-i1load.ll`.
|
||||||
negative tests that pin the loud refusals.
|
- **Module-unique private labels.** Every local symbol gets a custom 64-bit
|
||||||
- An end-to-end path: `scripts/tms-clang` IR → `llc -O0 -filetype=asm` →
|
string-hash token (FNV-style, not canonical FNV-1a), so two TUs with literals
|
||||||
**1987 GSPA/GSPLNK 2.00 under DOSBox** → COFF → the VT1200 loader in MAME.
|
no longer collide in GSPA's flat table; public names are unchanged.
|
||||||
The run checks `add(20, 22) == 42`, `load(&word) == 0x12345678`,
|
`m4-private.ll` and the two-TU `scripts/tests/m4/private.sh`.
|
||||||
`second() == &arr[1]` (a `symbol+32` bit relocation), `loadb` of a byte
|
- **`i8`/`i16` fixed arguments.** Promoted to a 4-byte stack slot; the caller
|
||||||
`0x81` returns the sign-extended `0xFFFFFF81`, and `storeb` leaves `0x81`
|
sign/zero-extends per the parameter attribute and the callee truncates.
|
||||||
in memory.
|
`i8`/`i16`/`i64`/float/aggregate returns remain rejected. `m4-arg8.ll`.
|
||||||
- A `dis34` round-trip that reads the linked `.text` from the COFF and checks
|
- **Contiguous-stack varargs.** Only the `&last_fixed` walk used by termtris'
|
||||||
the instructions GSPA encoded at their linked bit addresses.
|
`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.
|
||||||
|
|
||||||
## M1 is not the TI ABI
|
## What M3 provides (branch `m3`)
|
||||||
|
|
||||||
| | M1 test convention | TI ABI (M2) |
|
M3 adds integer control flow and the first compiled C Xlib client, still
|
||||||
|
assembly-only:
|
||||||
|
|
||||||
|
- Conditional and unconditional branches for `i32` and pointer values:
|
||||||
|
`CMP`/`CMPI` plus `JAEQ`/`JANE`, the signed `JALT`/`JALE`/`JAGT`/`JAGE`, the
|
||||||
|
unsigned `JALO`/`JALS`/`JAHI`/`JAHS`, and `JAUC` for an unconditional jump.
|
||||||
|
Ordinary `while`/`for` loops (subject to the M3 limitations below), if/else
|
||||||
|
and `?:` work; `?:` expands to a compare plus a two-block diamond
|
||||||
|
(`SELECTCC`), and `setcc` becomes that diamond over 1/0.
|
||||||
|
- Integer ALU and shift operations: `SUB`, `AND`, `OR`, `XOR`, `NEG`, `NOT`,
|
||||||
|
`SLL`, `SRL`, `SRA`, plus `ADDI`/`ANDI`/`SLLK`/`SRAK`/`SRLK` immediates.
|
||||||
|
- The first compiled C Xlib window: `XOpenDisplay`, `XCreateSimpleWindow`
|
||||||
|
(nine arguments in the ROM's order: display, root, x, y, width, height,
|
||||||
|
border width, border, background), `XMapWindow` and `XSync`. All four go
|
||||||
|
through validated ROM import stubs.
|
||||||
|
- The ROM's `Display`/`Screen` field displacements are **bit** offsets
|
||||||
|
`0x480` (`Display.screens`) and `0x40` (`Screen.root`), i.e. byte offsets
|
||||||
|
`0x90` and `0x8` in `scripts/m3/vt1200_xlib.h`.
|
||||||
|
- 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 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`,
|
||||||
|
floats, aggregates, varargs and indirect calls are not supported. The backend
|
||||||
|
still emits assembly only.
|
||||||
|
|
||||||
|
### M3 limitations
|
||||||
|
|
||||||
|
- **Narrow loads are sign-extending only.** `MOVB` handles signed `char` and
|
||||||
|
the FS0 `MOVE` handles signed `short`; `unsigned char`, `unsigned short` and
|
||||||
|
`_Bool` loads need a zero-extending pattern that M3 does not have. They stop
|
||||||
|
`llc` with a raw `Cannot select`, which `m3-reject-zext.ll` pins. M4 adds the
|
||||||
|
zero-extending loads; do not assume general narrow-load support in M3.
|
||||||
|
- **`-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 adds
|
||||||
|
multiply/divide and moves the `-replexitval=never` policy into
|
||||||
|
`scripts/tms-clang`; see "What M4 provides".
|
||||||
|
|
||||||
|
## What M2 provides (branch `m2`)
|
||||||
|
|
||||||
|
M2 replaces the M1 test convention with the TI ABI and stops at assembly:
|
||||||
|
|
||||||
|
- `llc -march=tms34010 -filetype=asm` for straight-line `i32`/pointer
|
||||||
|
functions with calls, frames and locals.
|
||||||
|
- A caller saves `A14` (STK) on the system stack, pushes arguments on the
|
||||||
|
program stack **in reverse order** (each a 32-bit slot), emits `CALLA`,
|
||||||
|
then re-establishes both field sizes (`SETF 16,0,0`, `SETF 32,0,1`).
|
||||||
|
- A callee starts with `SETF 16,0,0`/`SETF 32,0,1`, saves the used
|
||||||
|
A0–A7/A9–A12 registers and the old FP on the system stack, sets `FP = STK`,
|
||||||
|
allocates the frame with `ADDI`, and restores both stacks in the epilogue.
|
||||||
|
The first explicit argument is at `FP(-32)`, the second at `FP(-64)`, and
|
||||||
|
so on; locals live at positive FP offsets.
|
||||||
|
- `void`, `i32` and pointer returns, in `A8`; `RETS 2` when the function has
|
||||||
|
explicit arguments, `RETS 0` otherwise.
|
||||||
|
- Initialized globals, string/byte-array literals and pointer initializers in
|
||||||
|
`.data`; zero-initialized mutable globals stay in `.bss`. The loader copies
|
||||||
|
`.data` along with `.text`.
|
||||||
|
- ROM import stubs generated from `symbols/vt1200-v22.syms`. M2 consumes
|
||||||
|
`strlen` only: `_strlen: jauc 3FB458D0h`. `CALLA _strlen` pushes the return
|
||||||
|
PC, the stub jumps to the ROM, and the ROM's `RETS 2` returns directly to
|
||||||
|
our caller.
|
||||||
|
- Thirty-seven `llvm-lit`/FileCheck tests under
|
||||||
|
`llvm/test/CodeGen/TMS34010/m2-*.ll`, including negative tests that pin the
|
||||||
|
loud refusals. The ABI/data coverage includes `m2-returned`,
|
||||||
|
`m2-returned-call`, `m2-call-noret`, `m2-data-align`, `m2-data-avail`,
|
||||||
|
`m2-data-z64` and `m2-data-i1`.
|
||||||
|
- End-to-end MAME and dis34 gates, plus a differential check against assembly
|
||||||
|
generated once by the 1987 TI compiler (see below).
|
||||||
|
|
||||||
|
## The TI ABI
|
||||||
|
|
||||||
|
| | M1 test convention (branch `m1`) | TI ABI (M2/M3/M4) |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Integer/pointer arguments | `A0`–`A3`, four maximum | program stack (via `A14` STK) |
|
| 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)`, … |
|
||||||
| Return value | `A0` | `A8` |
|
| Return value | `A0` | `A8` |
|
||||||
| Frame | none; leaf functions only | `A13` FP, `A14` STK, `A15` system stack |
|
| Frame | none; leaf functions only | `A13` FP, `A14` STK, `A15` system stack |
|
||||||
| Return instruction | `RETS 0` | `RETS 2` |
|
| Locals | none | positive FP offsets |
|
||||||
|
| Return instruction | `RETS 0` | `RETS 2` with arguments, `RETS 0` without |
|
||||||
| Calls, callee-saves, stack args | rejected | required |
|
| Calls, callee-saves, stack args | rejected | required |
|
||||||
| Field select | harness sets FS1/FS0 before the leaf | `FS1 = 32` preserved across calls |
|
| Field size | harness sets FS1/FS0 before entry | `FS0 = 16` and `FS1 = 32`, set in the prologue and after every call |
|
||||||
|
|
||||||
M1 assembly is not ABI-compatible with TI's compiler or the ROM Xlib. M2
|
The callee-saved set is `A0`–`A7` and `A9`–`A12`; `A8` is the return register
|
||||||
replaces the convention; do not build on the M1 register assignment.
|
and `A13`–`A15` are reserved. After `n` system-stack pushes the saved old STK
|
||||||
|
is at `SP + (n+1)*32`, and `RETS N` pops the return PC and advances `SP` by
|
||||||
|
`32 + 16N`. `FS0` must be 16 and `FS1` must be 32 at function entry and
|
||||||
|
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 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.
|
||||||
|
|
||||||
|
```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: 46 passed (37 m2-* + 9 m3-*)
|
||||||
|
build/llvm-clean-build/bin/llvm-lit -v \
|
||||||
|
build/llvm-clean-build/test/CodeGen/TMS34010 --filter='(m2|m3)-'
|
||||||
|
|
||||||
|
# 3. Layout/symbols, then compile, assemble, link and run in MAME
|
||||||
|
scripts/tests/m3/run.sh
|
||||||
|
# -> layout.sh: PASS (Display.screens=0x480, Screen.root=0x40; ...)
|
||||||
|
# -> M3 MAME: PASS
|
||||||
|
# (display/root/window nonzero, sum=55 max=22 lt=1, screens=0x480 root=0x40)
|
||||||
|
|
||||||
|
# 4. Rebuild the COFF and check the GSPA encodings with dis34
|
||||||
|
scripts/tests/m3/dis-roundtrip.sh
|
||||||
|
# -> M3 dis34 round-trip: PASS
|
||||||
|
|
||||||
|
# 5. Differential TI-compiler control-flow/Xlib check (no DOSBox)
|
||||||
|
scripts/tests/m3/diff/check.sh
|
||||||
|
# -> M3 diff: PASS
|
||||||
|
|
||||||
|
# 6. Negative controls: the same check must reject a wrong TI displacement
|
||||||
|
scripts/tests/m3/diff/negative-control.sh
|
||||||
|
# -> M3 diff negative controls: PASS
|
||||||
|
|
||||||
|
# 7. GEP scaling/normalization tool tests
|
||||||
|
tools/tms-bitaddr/tests/run.sh
|
||||||
|
# -> tms-bitaddr: PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/tests/m3/run.sh` first runs `scripts/tests/m3/layout.sh` (the ROM's
|
||||||
|
`0x480`/`0x40` bit displacements in both our asm and the 1987 golden) and
|
||||||
|
`scripts/m3/validate-syms.sh` (the four Xlib imports), then compiles
|
||||||
|
`scripts/m3/branch.c`, `layout.c` and `xhello.c` with `scripts/tms-clang -O2`,
|
||||||
|
runs `llc -march=tms34010 -O0 -filetype=asm`, generates the per-ROM import
|
||||||
|
stubs, concatenates the result with `scripts/m3/entry.asm`, assembles/links
|
||||||
|
with GSPA/GSPLNK 2.00 under DOSBox, and loads the COFF in MAME. `branch.c` is
|
||||||
|
built with `opt -replexitval=never`, because the default exit-value rewrite
|
||||||
|
turns `sumto`'s loop into an `i33` multiply that this target cannot select;
|
||||||
|
the loop itself is the point of the test.
|
||||||
|
|
||||||
|
The MAME run pins `VT1200_MONITOR=VR319` (the real 2048-bit row pitch) and
|
||||||
|
checks the ABI markers — nonzero display/root/window ids, `sumto(10) == 55`,
|
||||||
|
`max2(20, 22) == 22`, `lt(5, 7) == 1`, `layout_screens() == 0x480`,
|
||||||
|
`layout_root() == 0x40` — and, decisively, two all-white 32-bit client words in
|
||||||
|
VRAM at (150,150) and (250,200) inside the mapped window. A markers-only run or
|
||||||
|
a root-window bit is a failure. Paths are overridable with `TMS_CLANG`,
|
||||||
|
`TMS_OPT`, `TMS_BITADDR`, `TMS_LLC`, `MAME`, `DOSBOX`, `SDK_IMG`, `M3_WORK`,
|
||||||
|
`M3_COFF` and `M3_SDK`.
|
||||||
|
|
||||||
|
`scripts/tests/m3/dis-roundtrip.sh` rebuilds through the normal GSPA/GSPLNK
|
||||||
|
path and uses `dis34` to check the linked Xlib stubs (`JAUC` to the ROM bit
|
||||||
|
addresses), the nine-argument `XCreateSimpleWindow` push order, the branch code
|
||||||
|
(`CMP`, `JALT`, `JAGT`, `JAEQ`, `JAUC`, `LBB…` labels), the old-STK restore
|
||||||
|
offsets, and the `"0::0"` `.data` bytes.
|
||||||
|
|
||||||
|
`scripts/tests/m3/diff/check.sh` is the CI golden check. It compares the ABI
|
||||||
|
shape of our assembly with stored output from the 1987 TI compiler for the same
|
||||||
|
K&R sources: a conditional branch and an unconditional loop back-edge in
|
||||||
|
`sumto`, a conditional branch in `max2`, the function's last `RETS` being
|
||||||
|
`RETS 2`, nine `XCreateSimpleWindow` argument pushes, and the `0x480`/`0x40`
|
||||||
|
bit displacements. The displacements must appear in the load that uses them
|
||||||
|
(TI `*A7(1152)` and `*A7(64),*FP(32),1`; ours an `ADDI 1152`/`ADDI 64` feeding
|
||||||
|
a load through the same register), with exact token boundaries, so an
|
||||||
|
unrelated local at `*FP(64)`, the constant `0x400`, or a prefix such as
|
||||||
|
`0x4800` cannot satisfy them. `scripts/tests/m3/diff/negative-control.sh`
|
||||||
|
mutates the stored TI displacement to 512 (and to `0x4800`) and requires
|
||||||
|
`check.sh` to fail. The check does not verify the result register or argument
|
||||||
|
order: the MAME gate checks the returned values and `dis-roundtrip.sh` checks
|
||||||
|
the push order. It does not compare instruction selection byte-for-byte. The
|
||||||
|
stored `ti-branch.s` and `ti-xhello.s` were generated once with
|
||||||
|
`gspcpp`/`gspcc`/`gspcg` (GSP C Compiler 2.01) under DOSBox; CI never runs
|
||||||
|
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 (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
|
||||||
|
M1 harness and suite are on branch `m1` and are **not** M2 gates: they encode
|
||||||
|
the retired M1 register convention.
|
||||||
|
|
||||||
|
```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: 37 passed
|
||||||
|
build/llvm-clean-build/bin/llvm-lit -v \
|
||||||
|
build/llvm-clean-build/test/CodeGen/TMS34010 --filter='m2-'
|
||||||
|
|
||||||
|
# 3. Compile, assemble, link and run in MAME
|
||||||
|
scripts/tests/m2/run.sh
|
||||||
|
# -> M2 MAME: PASS
|
||||||
|
# (check=4 first=20 second=22 caller=42 locals=7 keep=16)
|
||||||
|
|
||||||
|
# 4. Rebuild the COFF and check the GSPA encodings with dis34
|
||||||
|
scripts/tests/m2/dis-roundtrip.sh
|
||||||
|
# -> M2 dis34 round-trip: PASS
|
||||||
|
|
||||||
|
# 5. Differential TI-compiler ABI check (no DOSBox)
|
||||||
|
scripts/tests/m2/diff/check.sh
|
||||||
|
# -> M2 diff: PASS
|
||||||
|
|
||||||
|
# 6. GEP scaling/normalization tool tests
|
||||||
|
tools/tms-bitaddr/tests/run.sh
|
||||||
|
# -> tms-bitaddr: PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/tests/m2/run.sh` compiles five C inputs with `scripts/tms-clang -O2`,
|
||||||
|
runs `llc -march=tms34010 -O0 -filetype=asm` on the normalized IR, generates
|
||||||
|
the per-ROM import stubs, concatenates the result with `scripts/m2/entry.asm`,
|
||||||
|
assembles/links with GSPA/GSPLNK 2.00 under DOSBox, then loads the COFF in
|
||||||
|
MAME. It checks `strlen("0::0") == 4`, two-argument order, a direct call, a
|
||||||
|
local frame, and callee-saved preservation. Paths are overridable with
|
||||||
|
`TMS_CLANG`, `TMS_OPT`, `TMS_BITADDR`, `TMS_LLC`, `MAME`, `DOSBOX`, `SDK_IMG`,
|
||||||
|
`M2_WORK`, `M2_COFF` and `M2_SDK`.
|
||||||
|
|
||||||
|
`dis-roundtrip.sh` runs the same build and disassembles the linked `.text` with
|
||||||
|
the VT1200 tree's `dis34`, checking the TI-ABI instructions at their linked bit
|
||||||
|
addresses, the old-STK restore offset against the push count, and the copied
|
||||||
|
`.data` bytes.
|
||||||
|
|
||||||
|
`scripts/tests/m2/diff/check.sh` is the CI golden check. It compares the ABI
|
||||||
|
shape of our assembly with stored output from the 1987 TI compiler for the same
|
||||||
|
K&R sources: first argument at `FP(-32)`, second at `FP(-64)`, result in `A8`,
|
||||||
|
`RETS 2` for `add2`, a nonzero frame and `RETS 0` for `locals`. It does not
|
||||||
|
compare instruction selection byte-for-byte. The stored `ti-add2.s` and
|
||||||
|
`ti-locals.s` were generated once with `gspcpp`/`gspcc`/`gspcg` (GSP C Compiler
|
||||||
|
2.01) under DOSBox; CI never runs DOSBox, it greps the stored files.
|
||||||
|
|
||||||
|
## `.data`, strings and the loader
|
||||||
|
|
||||||
|
GSPA 2.00 has no ELF sections. M2 emits raw `.data` text, never `.rodata`,
|
||||||
|
`.asciz`, `.p2align` or `.section`:
|
||||||
|
|
||||||
|
- `i8` scalar or byte/string array: `.byte` with GSPA hex (`068h`)
|
||||||
|
- `i16` scalar: `.word`
|
||||||
|
- `i32` scalar: `.long`
|
||||||
|
- pointer: `.long _symbol+<bit addend>` (the addend is already in bits)
|
||||||
|
- `.even` before a `.data` global whose alignment is at least 16 bits, so the
|
||||||
|
label and the following `.word`/`.long` agree.
|
||||||
|
|
||||||
|
A scalar's directive must match its width, otherwise the next global's offset
|
||||||
|
is wrong. GSPA's `.even` aligns to a 16-bit boundary only, so an explicit
|
||||||
|
`align 4`/`align 8` on a `.data` object is **not** honored in M2: the target's
|
||||||
|
standalone object alignment is 2 bytes. Private/local symbol names are
|
||||||
|
sanitized to GSPA-safe names (`_HH` for non-alphanumerics). Unsupported
|
||||||
|
initializer shapes — including arrays whose element width has no GSPA
|
||||||
|
directive (`i1`, `i64`) — fail with
|
||||||
|
`M2 .data supports only integer, pointer and byte-array initializers`.
|
||||||
|
Zero-initialized mutable globals remain `.bss` and are zeroed by the loader.
|
||||||
|
|
||||||
|
## ROM imports
|
||||||
|
|
||||||
|
ROM entry points are per-ROM-revision bit addresses, kept out of the compiler
|
||||||
|
in `symbols/vt1200-v22.syms`. `scripts/m2/gen-rom-imports.sh` turns each entry
|
||||||
|
into an absolute-jump stub:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
.globl _strlen
|
||||||
|
_strlen:
|
||||||
|
jauc 3FB458D0h
|
||||||
|
```
|
||||||
|
|
||||||
|
`JAUC` is the TMS34010 unconditional absolute jump; GSPA 2.00 rejects bare
|
||||||
|
`ja`. M2 uses only `strlen`. M3 adds the four Xlib entries
|
||||||
|
(`XOpenDisplay`, `XCreateSimpleWindow`, `XMapWindow`, `XSync`) and validates
|
||||||
|
their stubs in `scripts/m3/validate-syms.sh` and in MAME; `symbols/README.md`
|
||||||
|
records the evidence. Do not add other Xlib dependencies until they are
|
||||||
|
validated.
|
||||||
|
|
||||||
|
## M2 non-goals
|
||||||
|
|
||||||
|
M2 deliberately rejects or omits:
|
||||||
|
|
||||||
|
- **Branches/control flow:** conditional and unconditional branches, loops,
|
||||||
|
and spill/fixup paths that need them. A branch that reaches ISel fails
|
||||||
|
loudly; M3 owns control flow.
|
||||||
|
- **Floating point:** float/double arguments, returns and arithmetic (M5).
|
||||||
|
- **Aggregates:** structs/unions by value, `sret`, `byval`, bitfields (M4/M5).
|
||||||
|
- **Varargs:** `...` functions and calls (M5).
|
||||||
|
- **Function pointers and indirect calls** (M4).
|
||||||
|
- **Tail calls:** ordinary `tail` markers are demoted to ordinary calls;
|
||||||
|
`musttail` is not implemented (M2 C does not emit it). Real tail calls remain
|
||||||
|
a later milestone.
|
||||||
|
- **Narrow ABI types:** `i8`/`i16` parameters, `i64`, and narrow returns remain
|
||||||
|
rejected. Narrow accesses inside a function are sign-extending only: `MOVB`
|
||||||
|
for signed `char` and FS0 `MOVE` for signed `short`; zero-extending
|
||||||
|
`unsigned char`/`unsigned short`/`_Bool` loads are M4 work.
|
||||||
|
- **Object output:** no `MCCodeEmitter`, object writer, `llc -filetype=obj`,
|
||||||
|
`clang -c` or native COFF. GSPA assembles and GSPLNK links the asm.
|
||||||
|
- **Xlib and clients:** no X11 headers, Xlib calls or real VT1200 clients in
|
||||||
|
M2; only the `strlen` ROM call.
|
||||||
|
|
||||||
|
The optimizer's `returned` argument attribute is accepted because it does not
|
||||||
|
affect the stack-slot ABI; `sret`/`byval`/`inreg`/`nest` remain rejected.
|
||||||
|
|
||||||
|
## M3 non-goals
|
||||||
|
|
||||||
|
M3 deliberately stops at the first window and ordinary integer control flow:
|
||||||
|
|
||||||
|
- **XImage/XEvent and the real client:** no image transfer, event handling,
|
||||||
|
full X11R4 headers or real VT1200 client (M4). M3 uses exactly the four Xlib
|
||||||
|
calls above and no other Xlib entry.
|
||||||
|
- **Aggregates by value:** structs/unions passed or returned by value, `sret`,
|
||||||
|
`byval`, bitfields (M4/M5). Struct *fields* through pointers are supported.
|
||||||
|
- **Function pointers and indirect calls:** the callee must be a direct global
|
||||||
|
symbol (M4).
|
||||||
|
- **Floating point and `i64`:** no float/double or 64-bit integer arguments,
|
||||||
|
returns, arithmetic or branches (M5).
|
||||||
|
- **Object output:** still no `MCCodeEmitter`, object writer,
|
||||||
|
`llc -filetype=obj`, `clang -c` or native COFF; GSPA/GSPLNK produce the COFF.
|
||||||
|
The `JR`/branch-relaxation code-size pass is M5 as well.
|
||||||
|
- **Varargs, `musttail`, narrow ABI parameters/returns:** unchanged from M2.
|
||||||
|
|
||||||
|
### M3 carry-forwards in 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.
|
||||||
|
|
||||||
|
## 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
|
## Repository layout
|
||||||
|
|
||||||
```text
|
```text
|
||||||
build/llvm-clean/ compiler fork checkout used for M1 (ignored)
|
build/llvm-clean/ compiler fork checkout used for M2/M3/M4 (ignored)
|
||||||
build/llvm-clean-build/ its Ninja build tree (ignored)
|
build/llvm-clean-build/ its Ninja build tree (ignored)
|
||||||
build/llvm-src/ M0 working fork (ignored)
|
build/llvm-src/ M0 working fork (ignored)
|
||||||
patches/llvm/ frozen M0 patch series against llvmorg-23.1.5
|
patches/llvm/ frozen M0 patch series against llvmorg-23.1.5
|
||||||
tools/tms-bitaddr/ GEP scale/normalize tool
|
tools/tms-bitaddr/ GEP scale/normalize tool
|
||||||
scripts/tms-clang IR pipeline wrapper (clang + tms-bitaddr + opt)
|
scripts/tms-clang IR pipeline wrapper (clang + tms-bitaddr + opt)
|
||||||
scripts/m1/ M1 harness: C inputs, entry.asm, link.cmd, checks
|
scripts/m2/ M2 harness: C inputs, entry.asm, link.cmd, stubs, checks
|
||||||
scripts/tests/m1/ M1 gates (run.sh, dis-roundtrip.sh)
|
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
|
scripts/tests/m0/ M0 integration tests
|
||||||
scripts/build-llvm.sh fork or upstream+patches build
|
scripts/build-llvm.sh fork or upstream+patches build
|
||||||
scripts/fetch-vt1200.sh fetch the VT1200 RE tree at its pinned commit
|
scripts/fetch-vt1200.sh fetch the VT1200 RE tree at its pinned commit
|
||||||
@@ -92,10 +588,13 @@ docs/ design and plans
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
`cmake`, `ninja-build`, `ccache`, a C++17 host compiler (GCC 14 is used here),
|
`cmake`, `ninja-build`, `ccache`, a C++17 host compiler (GCC 14 is used here),
|
||||||
Python 3, and about 20 GB of disk for the LLVM source and build. The M1 gates
|
Python 3, and about 20 GB of disk for the LLVM source and build. The M2/M3/M4
|
||||||
additionally need `dosbox`, `mtools` (`mcopy`), the VT1200 SDK image, the
|
MAME and dis34 gates additionally need `dosbox`, `mtools` (`mcopy`), the
|
||||||
`dwt` COFF loader, and a built MAME `vt1200` binary
|
VT1200 SDK image, the `dwt` COFF loader, `dis34`, and a built MAME `vt1200`
|
||||||
(`build/mame-src/vt1200`). On Debian:
|
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
|
```sh
|
||||||
sudo apt-get install -y cmake ninja-build ccache dosbox mtools
|
sudo apt-get install -y cmake ninja-build ccache dosbox mtools
|
||||||
@@ -112,13 +611,13 @@ Xlib call sequence, `as34`, `dis34`, SDKs, manuals). It is not vendored:
|
|||||||
|
|
||||||
## Build the compiler
|
## Build the compiler
|
||||||
|
|
||||||
### Compiler fork (M1, the normal path)
|
### Compiler fork (M4, the normal path)
|
||||||
|
|
||||||
The fork checkout already exists at `build/llvm-clean` on `tms34010`. Build it
|
The fork checkout already exists at `build/llvm-clean` on `m4`. Build it in
|
||||||
in place:
|
place:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=tms34010 ./scripts/build-llvm.sh
|
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m4 ./scripts/build-llvm.sh
|
||||||
# binaries: build/llvm-clean-build/bin/{llc,clang,opt,llvm-config,FileCheck}
|
# binaries: build/llvm-clean-build/bin/{llc,clang,opt,llvm-config,FileCheck}
|
||||||
# lit driver: build/llvm-clean-build/bin/llvm-lit
|
# lit driver: build/llvm-clean-build/bin/llvm-lit
|
||||||
```
|
```
|
||||||
@@ -127,7 +626,7 @@ To clone the fork first instead of using an existing checkout:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
LLVM_REPO=gitea@git.wut.ee:opencode.siil.arti.ee/llvm-tms34010.git \
|
LLVM_REPO=gitea@git.wut.ee:opencode.siil.arti.ee/llvm-tms34010.git \
|
||||||
LLVM_BRANCH=tms34010 ./scripts/build-llvm.sh
|
LLVM_BRANCH=m4 ./scripts/build-llvm.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Fork mode **never** checks out, resets, cleans or patches the source tree; it
|
Fork mode **never** checks out, resets, cleans or patches the source tree; it
|
||||||
@@ -144,22 +643,23 @@ TMS34010; a first build takes roughly 20–40 minutes.
|
|||||||
|
|
||||||
This mode clones `llvmorg-23.1.5`, applies `patches/llvm/*.patch`, and builds
|
This mode clones `llvmorg-23.1.5`, applies `patches/llvm/*.patch`, and builds
|
||||||
`build/llvm-clean-build`. It owns and resets only a plain `llvm-project` clone:
|
`build/llvm-clean-build`. It owns and resets only a plain `llvm-project` clone:
|
||||||
if `build/llvm-clean` has some other origin (for example the M1 fork), it
|
if `build/llvm-clean` has some other origin (for example the M2/M3/M4 fork),
|
||||||
refuses rather than destroying it. Use fork mode for that checkout.
|
it refuses rather than destroying it. Use fork mode for that checkout.
|
||||||
|
|
||||||
`scripts/export-patches.sh` regenerates the committed M0 patch series with
|
`scripts/export-patches.sh` regenerates the committed M0 patch series with
|
||||||
`git diff` (including new files via `git add -N`); it is not used for M1.
|
`git diff` (including new files via `git add -N`); it is not used for
|
||||||
|
M1/M2/M3/M4.
|
||||||
|
|
||||||
## Build the M0 support tools
|
## Build the M0 support tools
|
||||||
|
|
||||||
The M1 pipeline still uses `tms-bitaddr` and `tms-clang`:
|
The M2/M3/M4 pipeline uses `tms-bitaddr` and `tms-clang`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
LLVM_CONFIG=$PWD/build/llvm-clean-build/bin/llvm-config \
|
LLVM_CONFIG=$PWD/build/llvm-clean-build/bin/llvm-config \
|
||||||
tools/tms-bitaddr/build.sh
|
tools/tms-bitaddr/build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
M0 regression test (optional in M1):
|
M0 regression test (optional in M2/M3/M4):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
|
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
|
||||||
@@ -169,71 +669,34 @@ TMS_BITADDR=$PWD/tools/tms-bitaddr/tms-bitaddr \
|
|||||||
# -> M0 pipeline: PASS
|
# -> M0 pipeline: PASS
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run the M1 gates
|
|
||||||
|
|
||||||
These are the documented commands; all three must pass:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# 1. LLVM codegen tests: 17 passed
|
|
||||||
build/llvm-clean-build/bin/llvm-lit -v \
|
|
||||||
build/llvm-clean-build/test/CodeGen/TMS34010 --filter='m1-'
|
|
||||||
|
|
||||||
# 2. Compile, assemble, link and run in MAME
|
|
||||||
./scripts/tests/m1/run.sh
|
|
||||||
# -> M1 MAME: PASS
|
|
||||||
|
|
||||||
# 3. Rebuild the COFF and check the GSPA encodings with dis34
|
|
||||||
./scripts/tests/m1/dis-roundtrip.sh
|
|
||||||
# -> M1 dis34 round-trip: PASS
|
|
||||||
```
|
|
||||||
|
|
||||||
`run.sh` compiles five C inputs with `scripts/tms-clang -O2`, runs
|
|
||||||
`llc -march=tms34010 -O0 -filetype=asm` on the normalized IR, concatenates
|
|
||||||
the result with `scripts/m1/entry.asm` and `scripts/m1/char-test.asm`,
|
|
||||||
assembles/links with GSPA/GSPLNK 2.00 under DOSBox, then loads the COFF in
|
|
||||||
MAME. `dis-roundtrip.sh` runs the same build and then disassembles the linked
|
|
||||||
`.text` at the symbol addresses from the COFF symbol table. Paths are
|
|
||||||
overridable with `TMS_CLANG`, `TMS_OPT`, `TMS_BITADDR`, `TMS_LLC`, `MAME`,
|
|
||||||
`DOSBOX`, `SDK_IMG`, `M1_WORK`, `M1_COFF` and `M1_SDK`.
|
|
||||||
|
|
||||||
## Emit assembly by hand
|
## Emit assembly by hand
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
|
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
|
||||||
TMS_OPT=$PWD/build/llvm-clean-build/bin/opt \
|
TMS_OPT=$PWD/build/llvm-clean-build/bin/opt \
|
||||||
TMS_BITADDR=$PWD/tools/tms-bitaddr/tms-bitaddr \
|
TMS_BITADDR=$PWD/tools/tms-bitaddr/tms-bitaddr \
|
||||||
scripts/tms-clang -O2 -o /tmp/add.ll scripts/m1/add.c
|
scripts/tms-clang -O2 -o /tmp/add2.ll scripts/tests/m2/diff/ti-add2.c
|
||||||
|
|
||||||
build/llvm-clean-build/bin/llc -march=tms34010 -O0 -filetype=asm \
|
build/llvm-clean-build/bin/llc -march=tms34010 -O0 -filetype=asm \
|
||||||
/tmp/add.ll -o /tmp/add.s
|
/tmp/add2.ll -o /tmp/add2.s
|
||||||
```
|
```
|
||||||
|
|
||||||
Always produce the IR with `scripts/tms-clang` (or run `tms-bitaddr --scale`)
|
The resulting function loads its arguments at `FP(-32)` and `FP(-64)`, returns
|
||||||
before `llc`: raw clang IR carries byte GEP offsets, but `llc` treats every
|
in `A8`, and ends in `RETS 2`. Always produce the IR with `scripts/tms-clang`
|
||||||
surviving GEP offset as a *bit* offset, so addresses would be 8× too small.
|
(or run `tms-bitaddr --scale`) before `llc`: raw clang IR carries byte GEP
|
||||||
Run `llc -O0` on the normalized IR that `tms-clang` already optimized and
|
offsets, but `llc` treats every surviving GEP offset as a *bit* offset, so
|
||||||
scaled. `tms-bitaddr --normalize` consumes that scaled IR and now rejects a
|
addresses would be 8× too small. Run `llc -O0` on the normalized IR that
|
||||||
typed (non-`i8`) or vector GEP instead of emitting its byte offset as bits.
|
`tms-clang` already optimized and scaled. `tms-bitaddr --normalize` consumes
|
||||||
The lit tests use `llc -march=tms34010 -O2 -filetype=asm` on hand-written IR
|
that scaled IR and rejects a typed (non-`i8`) or vector GEP instead of emitting
|
||||||
to exercise the selector pipeline directly. Output is assembly only: `llc`
|
its byte offset as bits. The lit tests use
|
||||||
cannot emit an object file or a usable COFF for this target.
|
`llc -march=tms34010 -O2 -filetype=asm` on hand-written IR to exercise the
|
||||||
|
selector pipeline directly. Output is assembly only: `llc` cannot emit an
|
||||||
|
object file or a usable COFF for this target.
|
||||||
|
|
||||||
## Not yet supported (M2 and later)
|
## Later milestones
|
||||||
|
|
||||||
- **Data:** only zero-initialized mutable globals (`.bss`) are supported.
|
M5 adds the native COFF path (`MCCodeEmitter`/object writer), floats,
|
||||||
Initialized globals, string literals and constant/private data (`.data`,
|
`long long`, general varargs (`va_list`), bitfields, structs by value and the
|
||||||
`.rodata`) make `llc` fail loudly; the `.data` initializer probe moves to
|
`JR`/branch-relaxation code-size pass, plus MMTM/MMFM register-list saves and
|
||||||
M2.
|
deeper libc/runtime coverage. 2ine r5.01 is a later speed-up for the
|
||||||
- **Object emission:** no `MCCodeEmitter`, object writer, `llc -filetype=obj`,
|
GSPA/GSPLNK step.
|
||||||
`clang -c`, or native COFF. GSPA assembles and GSPLNK links; the existing
|
|
||||||
COFF loader loads.
|
|
||||||
- **TI ABI:** `A8` return, `A13`/`A14`/`A15` dual stack, `RETS 2`, `FS1 = 32`
|
|
||||||
across calls, callee-saves, stack arguments, more than four arguments.
|
|
||||||
- **C features:** calls, locals/`alloca`, structures/`byval`, floats, varargs,
|
|
||||||
bitfields, 64-bit arithmetic, `memcpy`/`alloca` lowering.
|
|
||||||
- **Runtime and clients:** ROM/Xlib imports, `strlen`/`malloc`, X11 headers,
|
|
||||||
real VT1200 clients, debug info.
|
|
||||||
- **2ine:** M1 uses the proven 1987 GSPA/GSPLNK under DOSBox, not the external
|
|
||||||
2ine r5.01 tools; 2ine is an M2 speed-up.
|
|
||||||
|
|
||||||
These are M2–M5 in the design spec, each with its own plan.
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,685 @@
|
|||||||
|
# TMS34010 M3 — Control Flow and the First Xlib Window 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 calls the ROM Xlib (`XOpenDisplay`, `XCreateSimpleWindow`, `XMapWindow`, `XSync`), creates and maps a window, and that window appears in MAME; the backend also gains integer control flow (branches, comparisons, loops) for real C.
|
||||||
|
|
||||||
|
**Architecture:** M3 keeps the M2 asm-only path and TI ABI. It adds `JAcc`-based branches with GSPA-safe basic-block labels, `CMP`/condition-code lowering for `BRCOND`/`BR_CC`/`SETCC`/`SELECT_CC`, and the integer ALU/shift operations C control flow needs. It then adds a ROM-verified minimal `Display`/`Screen` layout and the four proven Xlib entry points, and an M3 harness that compiles C, links the `JAUC` stubs, loads the COFF in MAME, and checks both ABI markers and a window pixel.
|
||||||
|
|
||||||
|
**Tech Stack:** LLVM/Clang 23.1.5 fork (`llvm-tms34010`, branch `m3`), C++17, CMake/Ninja, `llc`, GSPA/GSPLNK 2.00 under DOSBox, COFF loader, MAME, `dis34`, `llvm-lit`/FileCheck, 1987 TI compiler as a differential oracle.
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md` (§4.2, §7.3.1, §7.4–§7.7, §7.9, M3, M4)
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Compiler work happens in **`llvm-tms34010`**, branch `m3`, created from `m2` at `39512c272d0f`. Use the existing full-fork checkout `build/llvm-clean` and build tree `build/llvm-clean-build`; do not clone another LLVM tree.
|
||||||
|
- Project plans/tests live in **`tms-c-compiler`**, branch `m3`, created from `m2` at `2d4919d` plus the M3 plan commit. The M2 branches stay the default snapshot until M3 is reviewed; do not merge M2 into `m0`.
|
||||||
|
- The M2 TI ABI is unchanged: A8 return, A13 FP, A14 STK, A15 SP; reverse argument pushes; first argument at `FP(-32)`; old-STK save/restore; `RETS 2/0`; callee-saved A0–A12 except A8; `SETF 16,0,0` + `SETF 32,0,1` at entry and after every call; `.data` width/alignment rules.
|
||||||
|
- M3 output is still **assembly only**: `llc -march=tms34010 -filetype=asm`; GSPA/GSPLNK 2.00 under DOSBox; the existing COFF loader. No LLVM object writer, `MCCodeEmitter`, asm parser or linker.
|
||||||
|
- **Branches use `JAcc` absolute 3-word forms in M3** (`JAUC`/`JAEQ`/`JALT`/…). Do not add `JR` relative branches or branch relaxation in M3; `JR`/relaxation is a later code-size task. GSPA resolves the absolute target.
|
||||||
|
- **Basic-block labels must be GSPA-valid.** GSPA 2.00 rejects dotted ELF names (`.L`, `_.str`). Set the MC private/linker-private prefixes so block labels are `LBB…`/`L…` with no dot; test a two-block function through GSPA.
|
||||||
|
- **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 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`.
|
||||||
|
- M3 must observe a window, not only markers: the harness creates the window with **background pixel 1 (white)**, maps it, calls `XSync`, and checks a pixel inside the client area in emulated VRAM (base bit `0x04000000`, VRAM pitch 2048 bits on the default VR319 monitor, LSB-first). No fake pass.
|
||||||
|
- The M2 `m2-*` suite must stay green; M3 adds an `m3-*` suite. Do not regress the single-basic-block ABI tests.
|
||||||
|
- Tests: LLVM lit/FileCheck for branch/compare/op shapes; a GSPA assemble/link probe for block labels; a MAME run with branch results and a window pixel; a `dis34` round-trip; a TI-compiler golden check for branch/struct/call shape.
|
||||||
|
|
||||||
|
## Review Focus
|
||||||
|
|
||||||
|
1. **Branch target and label correctness:** GSPA block labels must have no dot; `JAUC`/`JCC` must target the right block; a wrong label jumps into data. Tests in Task 1 (`m3-branch`, GSPA probe) and Task 2 (`m3-loop`).
|
||||||
|
2. **Compare polarity and signedness:** the `CMP` operand order and `JALT`/`JALO`/`JAHI`/`JAEQ` mapping must match the C predicate; a reversed condition silently returns the wrong value. Tests in Task 2 (`m3-setcc`, `m3-if`, `m3-unsigned`) and Task 4 (`sumto(10)==55`, `max(20,22)==22`, `lt(5,7)==1` in MAME).
|
||||||
|
3. **Stack/FS invariants on every path:** prologue, multiple return blocks, loops and calls must keep A14/A15 balanced and FS0/FS1 correct; the old-STK/`RETS` rules still apply. Tests in Task 2 (loop returns) and Task 4 (MAME).
|
||||||
|
4. **Display/Screen offsets and root retrieval:** `Display.screens` at byte 0x90 (bit displacement 0x480), then `Screen.root` at byte 0x8 (bit 0x40); a wrong screen/root pointer yields no window or a crash. Tests in Task 3 (static asserts + TI golden) and Task 4 (window).
|
||||||
|
5. **Nine-argument `XCreateSimpleWindow` order:** all nine slots, first at `FP(-32)`, last at `FP(-288)`; a wrong order maps the wrong rectangle or parent. Tests in Task 4 (`dis34` push count and MAME window).
|
||||||
|
6. **The window really appears:** the loader child, the X server, `XSync`, a white client-area pixel; marker-only success is not enough. Test in Task 4.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Branch infrastructure, unconditional branches, GSPA-safe block labels
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `build/llvm-clean/llvm/lib/Target/TMS34010/TMS34010InstrInfo.td`, `TMS34010InstrInfo.cpp`, `TMS34010InstrInfo.h`
|
||||||
|
- Modify: `build/llvm-clean/llvm/lib/Target/TMS34010/MCTargetDesc/TMS34010MCAsmInfo.cpp`
|
||||||
|
- Test: `build/llvm-clean/llvm/test/CodeGen/TMS34010/m3-branch.ll`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: M2 target, MCInstLower's `MO_MachineBasicBlock` lowering, asm printer.
|
||||||
|
- Produces: `JUncond` (`JAUC <target>`), a `brtarget` operand, `AnalyzeBranch`/`InsertBranch`/`RemoveBranch`, GSPA-safe block labels.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Check out the M3 branches**
|
||||||
|
|
||||||
|
The controller creates compiler `m3` from `m2` at `39512c272d0f` and project `m3` from `m2` at `2d4919d` plus the M3 plan commit.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /home/arti/code/tms-c-compiler
|
||||||
|
git checkout m3
|
||||||
|
git -C build/llvm-clean checkout m3
|
||||||
|
git -C build/llvm-clean rev-parse --short HEAD # based on 39512c272d0f
|
||||||
|
git rev-parse --short HEAD # plan commit on top of 2d4919d
|
||||||
|
```
|
||||||
|
|
||||||
|
If a branch is missing, create it with `git checkout -b m3 m2` (project) or `git -C build/llvm-clean checkout -b m3 m2` (compiler).
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write the failing branch test**
|
||||||
|
|
||||||
|
`m3-branch.ll`:
|
||||||
|
|
||||||
|
```llvm
|
||||||
|
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||||
|
define void @spin() {
|
||||||
|
entry:
|
||||||
|
br label %loop
|
||||||
|
loop:
|
||||||
|
br label %loop
|
||||||
|
}
|
||||||
|
; CHECK-LABEL: _spin:
|
||||||
|
; CHECK: [[L:LBB[0-9_]+]]:
|
||||||
|
; CHECK: JAUC [[L]]
|
||||||
|
```
|
||||||
|
|
||||||
|
The self-loop keeps the unconditional branch through code layout; a `br` to the immediate fallthrough can be removed by block placement even at `-O0`. This is a compile-only shape test; the executable branch tests are in Task 2 and Task 4.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run it red**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
build/llvm-clean-build/bin/llvm-lit -v \
|
||||||
|
build/llvm-clean-build/test/CodeGen/TMS34010 --filter='m3-branch'
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL — `Cannot select: br` (M2 has no branch).
|
||||||
|
|
||||||
|
- [ ] **Step 4: Add `JUncond`, the branch target operand, and block-label prefixes**
|
||||||
|
|
||||||
|
In `TMS34010InstrInfo.td`:
|
||||||
|
|
||||||
|
```tablegen
|
||||||
|
def JUncond : TMS34010Inst<(outs), (ins brtarget:$target), "JAUC $target",
|
||||||
|
[(br bb:$target)]> {
|
||||||
|
let isBranch = 1;
|
||||||
|
let isTerminator = 1;
|
||||||
|
let isBarrier = 1;
|
||||||
|
let Size = 6;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`brtarget` is the standard `Target.td` branch-target operand; the `br` pattern selects the unconditional branch. `JAUC` is the GSPA unconditional absolute jump (the M2 ROM stub already uses it; bare `ja` is rejected by GSPA 2.00).
|
||||||
|
|
||||||
|
In `TMS34010MCAsmInfo.cpp`, set GSPA-safe label prefixes (the default `.L`/`l` contains a dot):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
PrivateGlobalPrefix = "L";
|
||||||
|
LinkerPrivateGlobalPrefix = "L";
|
||||||
|
```
|
||||||
|
|
||||||
|
The base asm printer then emits block labels such as `LBB0_1`; verify the actual spelling with the test and adjust the prefix if needed (no dot, GSPA-valid).
|
||||||
|
|
||||||
|
- [ ] **Step 5: Implement the branch hooks**
|
||||||
|
|
||||||
|
In `TMS34010InstrInfo`:
|
||||||
|
|
||||||
|
- `analyzeBranch(MBB, TBB, FBB, Cond)`: if the last terminator is `JUncond`, set `TBB` to its target and `FBB` to null; leave `Cond` empty. Return false. (M3 branches are self-contained `JAcc` instructions; do not invent a separate condition instruction.)
|
||||||
|
- `insertBranch(MBB, TBB, FBB, Cond, DL)`: insert `JUncond` for `TBB`; M3 has no conditional branch yet, so `FBB` must be null.
|
||||||
|
- `removeBranch(MBB)`: remove `JUncond` terminators.
|
||||||
|
- Keep `getInstSizeInBytes` at the `Size` field (bytes); `JUncond` is 6 bytes.
|
||||||
|
|
||||||
|
The MCInstLower already lowers `MO_MachineBasicBlock` to the block symbol; the InstPrinter prints the expression; the `PrivateGlobalPrefix` handles the label text.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run the lit test and the GSPA label probe**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
build/llvm-clean-build/bin/llvm-lit -v \
|
||||||
|
build/llvm-clean-build/test/CodeGen/TMS34010 --filter='m3-branch'
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS. Then:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
build/llvm-clean-build/bin/llc -march=tms34010 -O0 -filetype=asm \
|
||||||
|
build/llvm-clean/llvm/test/CodeGen/TMS34010/m3-branch.ll -o /tmp/m3-branch.s
|
||||||
|
printf '\t.end\n' >> /tmp/m3-branch.s
|
||||||
|
# assemble under DOSBox with the M1 recipe; require No Errors, No Warnings
|
||||||
|
```
|
||||||
|
|
||||||
|
The GSPA listing must show the two `JAUC LBB…` instructions and the matching `LBB…:` label, with no `.L`/`_` dotted label.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git -C build/llvm-clean add llvm/lib/Target/TMS34010 llvm/test/CodeGen/TMS34010/m3-branch.ll
|
||||||
|
git -C build/llvm-clean commit -m "tms34010: add JAcc branches and GSPA-safe block labels"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 2: Comparisons, conditional branches, SETCC/SELECT, loops, and integer ALU
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `build/llvm-clean/llvm/lib/Target/TMS34010/TMS34010RegisterInfo.td` (reserved `ST`)
|
||||||
|
- Modify: `TMS34010InstrInfo.td` (`CMP`, `CMPI`, `JAEQ`…`JANN`, `AND`/`OR`/`XOR`, `SLL`/`SRL`/`SRA`, `SUB`, `NEG`, `NOT`)
|
||||||
|
- Modify: `TMS34010ISelLowering.cpp/.h` (`LowerBR_CC`, `LowerBRCOND`, `LowerSETCC`, `LowerSELECT_CC`, operation actions)
|
||||||
|
- Modify: `TMS34010InstrInfo.cpp` (conditional `AnalyzeBranch`/`InsertBranch`/`RemoveBranch`)
|
||||||
|
- Test: `m3-if.ll`, `m3-setcc.ll`, `m3-loop.ll`, `m3-unsigned.ll`, `m3-ops.ll`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Task 1's `JUncond`, block labels, asm printer.
|
||||||
|
- Produces: `CMP`/`CMPI` with a reserved `ST` flag register; the 16 `JA<cc>` branch instructions; `SETCC`/`SELECT_CC`/`BRCOND` lowering; ALU/shift patterns.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing tests**
|
||||||
|
|
||||||
|
`m3-setcc.ll`:
|
||||||
|
|
||||||
|
```llvm
|
||||||
|
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||||
|
define i32 @lt(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp slt i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
; CHECK-LABEL: _lt:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK: JALT
|
||||||
|
; CHECK: MOVI 0
|
||||||
|
; CHECK: MOVI 1
|
||||||
|
; CHECK: RETS 2
|
||||||
|
```
|
||||||
|
|
||||||
|
`m3-if.ll`:
|
||||||
|
|
||||||
|
```llvm
|
||||||
|
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||||
|
define i32 @max(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp sgt i32 %a, %b
|
||||||
|
%r = select i1 %c, i32 %a, i32 %b
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
; CHECK-LABEL: _max:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK: JAGT
|
||||||
|
; CHECK: RETS 2
|
||||||
|
```
|
||||||
|
|
||||||
|
`m3-loop.ll` (a real phi/loop, run at `-O0` to keep the loop shape):
|
||||||
|
|
||||||
|
```llvm
|
||||||
|
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||||
|
define i32 @sumto(i32 %n) {
|
||||||
|
entry:
|
||||||
|
br label %loop
|
||||||
|
loop:
|
||||||
|
%i = phi i32 [ 1, %entry ], [ %i.next, %loop ]
|
||||||
|
%s = phi i32 [ 0, %entry ], [ %s.next, %loop ]
|
||||||
|
%s.next = add i32 %s, %i
|
||||||
|
%i.next = add i32 %i, 1
|
||||||
|
%c = icmp sle i32 %i, %n
|
||||||
|
br i1 %c, label %loop, label %exit
|
||||||
|
exit:
|
||||||
|
ret i32 %s
|
||||||
|
}
|
||||||
|
; CHECK-LABEL: _sumto:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK: JALE
|
||||||
|
; CHECK: JAUC
|
||||||
|
; CHECK: RETS 2
|
||||||
|
```
|
||||||
|
|
||||||
|
`m3-unsigned.ll`:
|
||||||
|
|
||||||
|
```llvm
|
||||||
|
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||||
|
define i32 @ult(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp ult i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
define i32 @ugt(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp ugt i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
; CHECK-LABEL: _ult:
|
||||||
|
; CHECK: JALO
|
||||||
|
; CHECK-LABEL: _ugt:
|
||||||
|
; CHECK: JAHI
|
||||||
|
```
|
||||||
|
|
||||||
|
`m3-ops.ll`:
|
||||||
|
|
||||||
|
```llvm
|
||||||
|
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||||
|
define i32 @ops(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%s = sub i32 %a, %b
|
||||||
|
%an = and i32 %s, %a
|
||||||
|
%o = or i32 %an, %b
|
||||||
|
%x = xor i32 %o, %s
|
||||||
|
%l = shl i32 %x, 3
|
||||||
|
%r = lshr i32 %l, 2
|
||||||
|
%q = ashr i32 %r, 1
|
||||||
|
ret i32 %q
|
||||||
|
}
|
||||||
|
; CHECK: SUB
|
||||||
|
; CHECK: AND
|
||||||
|
; CHECK: OR
|
||||||
|
; CHECK: XOR
|
||||||
|
; CHECK: SLL
|
||||||
|
; CHECK: SRL
|
||||||
|
; CHECK: SRA
|
||||||
|
; CHECK: RETS 2
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run them red**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
build/llvm-clean-build/bin/llvm-lit -v \
|
||||||
|
build/llvm-clean-build/test/CodeGen/TMS34010 --filter='m3-(if|setcc|loop|unsigned|ops)'
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL — `Cannot select: brcond`, `setcc`, ALU/shift opcodes.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add the reserved status register and instructions**
|
||||||
|
|
||||||
|
In `TMS34010RegisterInfo.td`, add a reserved non-allocatable `ST` pseudo-register (`Register<32>` with `Namespace = "TMS34010"`; not in `ARegs`/`ARegsAll`/`BRegs`). Reserve it in `getReservedRegs`.
|
||||||
|
|
||||||
|
In `TMS34010InstrInfo.td`, add:
|
||||||
|
|
||||||
|
- `CMP` (`CMP $src, $dst`, 1 word, `isCompare = 1`, `Defs = [ST]`; encoding base `0x4800`).
|
||||||
|
- `CMPI` (`CMPI $imm, $dst`, 3 words, `Defs = [ST]`; encoding base `0x0B60`, word form `0x0B40`).
|
||||||
|
- `AND`/`OR`/`XOR` (`$src, $dst`, base `0x5000`/`0x5400`/`0x5600`; `XOR` self is `CLR`).
|
||||||
|
- `SUB` (`SUB $src, $dst`, base `0x4400`), `NEG` (`NEG $dst`, base `0x03A0`), `NOT` (`NOT $dst`, base `0x03E0`).
|
||||||
|
- `SLL`/`SRL`/`SRA` register forms (`0x6200`/`0x6600`/`0x6400`) and immediate-constant forms if needed for the patterns.
|
||||||
|
- 16 absolute conditional branches: `JAEQ`, `JANE`, `JALT`, `JALE`, `JAGT`, `JAGE`, `JALO`/`JAC`, `JAHS`/`JANC`, `JALS`, `JAHI`, `JAP`, `JAN`, `JAV`, `JANV`, `JANN`, and `JAUC`; each `(ins brtarget:$target)`, `isBranch=1`, `isTerminator=1`, `Uses=[ST]` (except `JAUC`), `Size=6`; conditional branches are **not** `isBarrier` (they fall through), while `JAUC` is `isBarrier=1`; asm `"JA<cc> $target"` (GSPA mnemonics; `UC` unconditional).
|
||||||
|
|
||||||
|
Add patterns for the register/immediate ALU forms where the SelectionDAG can select them directly; otherwise lower them in `TMS34010ISelLowering`/`ISelDAGToDAG`.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Lower comparisons and branches**
|
||||||
|
|
||||||
|
In `TMS34010TargetLowering`:
|
||||||
|
|
||||||
|
- `setOperationAction(ISD::SETCC, MVT::i32, Expand)`; `SELECT`/`SELECT_CC` `Expand`; `BR_CC`/`BRCOND` `Custom`.
|
||||||
|
- Override `LowerBR_CC` and `LowerBRCOND`:
|
||||||
|
- For `icmp <pred> a, b`, emit `CMP b, a` (so the flags represent `a - b`), then the `JA<cc>` selected from the predicate and signedness; the target is the `BR_CC`/`BRCOND` destination. Use `CMPI` when `b` is a constant.
|
||||||
|
- For a general `BRCOND` value (not an `ISD::SETCC`), emit `CMPI 0, value` + `JANE`/`JAEQ` as appropriate.
|
||||||
|
- Keep the `ST` dependency: `CMP`/`CMPI` define `ST`; the branch uses it.
|
||||||
|
- Let `SETCC`/`SELECT_CC` expand to the generic `BRCOND`/`SELECT`/PHI form; the `SETCC` result is 0/1 (`ZeroOrOneBooleanContent`). The `m3-setcc` sequence should be `MOVI 0`; a conditional `JA<cc>` over the `MOVI 1`; the join label.
|
||||||
|
- The condition mapping (TMS34010 `cc` from `dis34`): signed `LT/GE/LE/GT` = `JALT/JAGE/JALE/JAGT`; unsigned `LT/GE/LE/GT` = `JALO` (`JAC`)/`JAHS` (`JANC`)/`JALS`/`JAHI`; equality `JAEQ`/`JANE`.
|
||||||
|
|
||||||
|
Extend `analyzeBranch`/`insertBranch`/`removeBranch` for `JA<cc>`: a conditional branch has `TBB` = target, `FBB` = fallthrough, `Cond` empty (the compare is fused in the branch instruction); `insertBranch` emits the `JA<cc>`; `removeBranch` removes it. Keep the `MachineBlockPlacement` conservative.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Build and run the tests**
|
||||||
|
|
||||||
|
```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='m3-(if|setcc|loop|unsigned|ops)'
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 5/5 PASS. Also run the M2 suite (`--filter='m2-'`) and confirm it remains 37/37.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git -C build/llvm-clean add llvm/lib/Target/TMS34010 llvm/test/CodeGen/TMS34010
|
||||||
|
git -C build/llvm-clean commit -m "tms34010: add integer comparisons, branches and ALU ops"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 3: Xlib `Display`/`Screen` layout and header
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `scripts/m3/vt1200_xlib.h`, `scripts/m3/layout.c`, `scripts/m3/layout-ti.c`
|
||||||
|
- Test: `scripts/tests/m3/layout.sh` (or the M3 harness markers; see Task 4)
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `xhello.asm`'s proven offsets (display+0x480, screen+0x40).
|
||||||
|
- Produces: `Display`/`Screen`/`Window`/`XID` types, the four Xlib prototypes, `vt1200_root()`, and static offset assertions.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing offset test**
|
||||||
|
|
||||||
|
`scripts/m3/vt1200_xlib.h`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#ifndef VT1200_XLIB_H
|
||||||
|
#define VT1200_XLIB_H
|
||||||
|
|
||||||
|
typedef unsigned int XID;
|
||||||
|
typedef unsigned int Window;
|
||||||
|
|
||||||
|
/* ROM v2.2 layout (xhello.asm): Display.screens is the pointer at byte 0x90
|
||||||
|
(bit 0x480); Screen.root is the 32-bit root id at byte 0x8 (bit 0x40). */
|
||||||
|
typedef struct {
|
||||||
|
unsigned char pad[0x90]; /* bit 0x480 */
|
||||||
|
void *screens;
|
||||||
|
} Display;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char pad[0x8]; /* bit 0x40 */
|
||||||
|
unsigned int root;
|
||||||
|
} Screen;
|
||||||
|
|
||||||
|
extern Display *XOpenDisplay(const char *name);
|
||||||
|
extern Window XCreateSimpleWindow(Display *display, Window parent,
|
||||||
|
int x, int y, unsigned width,
|
||||||
|
unsigned height, unsigned border_width,
|
||||||
|
unsigned long border,
|
||||||
|
unsigned long background);
|
||||||
|
extern int XMapWindow(Display *display, Window window);
|
||||||
|
extern int XSync(Display *display, int discard);
|
||||||
|
|
||||||
|
static unsigned int vt1200_root(Display *d) {
|
||||||
|
return ((Screen *)((Display *)d)->screens)->root;
|
||||||
|
}
|
||||||
|
|
||||||
|
int layout_screens(void);
|
||||||
|
int layout_root(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/m3/layout.c`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "vt1200_xlib.h"
|
||||||
|
|
||||||
|
_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)");
|
||||||
|
|
||||||
|
int layout_screens(void) { return (int)__builtin_offsetof(Display, screens) * 8; }
|
||||||
|
int layout_root(void) { return (int)__builtin_offsetof(Screen, root) * 8; }
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/m3/layout-ti.c` (K&R, for the 1987 compiler):
|
||||||
|
|
||||||
|
```c
|
||||||
|
struct display { char pad[0x90]; void *screens; };
|
||||||
|
struct screen { char pad[0x8]; unsigned int root; };
|
||||||
|
|
||||||
|
int layout_screens() { return (int)&((struct display *)0)->screens; }
|
||||||
|
int layout_root() { return (int)&((struct screen *)0)->root; }
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run it red/verify the target layout**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scripts/tms-clang -O0 -o /tmp/opencode/m3-layout.ll scripts/m3/layout.c
|
||||||
|
build/llvm-clean-build/bin/llc -march=tms34010 -O0 -filetype=asm \
|
||||||
|
/tmp/opencode/m3-layout.ll -o /tmp/opencode/m3-layout.s
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected initially: the `_Static_assert`s fail if the target's struct layout pads `Display.screens` away from byte 0x90 (bit 0x480) or `Screen.root` away from byte 0x8 (bit 0x40). Fix the header (the padded structs are the intended ROM layout) or the target's struct layout if the padded struct itself is laid out wrongly. Do not use the host X11R7 `_XDisplay` layout.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build the TI golden**
|
||||||
|
|
||||||
|
Compile `scripts/m3/layout-ti.c` with the 1987 TI compiler (`gspcpp`/`gspcc`/`gspcg` under DOSBox, the `c-hello/build.sh` pattern). Store the LF-normalized asm as `scripts/m3/layout-ti.s`. The two functions must return `0x480` and `0x40`; check the asm's `MOVI` constants.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Add the layout gate**
|
||||||
|
|
||||||
|
`scripts/tests/m3/layout.sh`:
|
||||||
|
|
||||||
|
- compile `layout.c` with `tms-clang` + `llc`; require exit 0 (the static asserts);
|
||||||
|
- check `layout.s` and `layout-ti.s` contain the constants `0x480`/`0x40` (allowed spellings: `480h`, `1152`, etc.);
|
||||||
|
- fail loudly on a mismatch.
|
||||||
|
|
||||||
|
Run it; expected PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git add scripts/m3/vt1200_xlib.h scripts/m3/layout.c scripts/m3/layout-ti.c \
|
||||||
|
scripts/m3/layout-ti.s scripts/tests/m3/layout.sh
|
||||||
|
git commit -m "tests: M3 Xlib Display/Screen layout"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 4: Xlib client, ROM imports, branch/Xlib MAME harness, window
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `scripts/m3/xhello.c`, `branch.c`, `entry.asm`, `link.cmd`, `run.sh`, `m3-check.lua`, `dis-roundtrip.sh`, `validate-syms.sh`
|
||||||
|
- Modify: `build/llvm-clean/llvm/lib/Target/TMS34010/TMS34010ISelLowering.cpp/.h` (misaligned accesses)
|
||||||
|
- Test: `build/llvm-clean/llvm/test/CodeGen/TMS34010/m3-unaligned.ll`
|
||||||
|
- Create: `scripts/tests/m3/run.sh`, `scripts/tests/m3/dis-roundtrip.sh`
|
||||||
|
- Test: the MAME window and the branch markers
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Tasks 1–3's branches, layout, and the M2 `.data`/ABI/loader.
|
||||||
|
- Produces: `M3 MAME: PASS` with the Xlib markers, branch results, and a white window pixel; `M3 dis34 round-trip: PASS`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Fix the packed-struct unaligned-load gap (compiler)**
|
||||||
|
|
||||||
|
`vt1200_root()` loads the `Display.screens` pointer and `Screen.root` from packed structs (`clang` `PackStruct = 1` → every field load is `align 1`). The DAG legalizer currently splits those into four `zext i8` loads, and M2/M3 only has a sign-extending `MOVB`, so `llc` aborts. Add `TMS34010TargetLowering::allowsMisalignedMemoryAccesses` returning true for i16/i32/pointers in address space 0 (the ISA allows misaligned accesses; spec §7.5), so the existing `MOVE`/FS0 forms select directly. Add a focused lit test (`m3-unaligned.ll`: `load i32, ptr %p, align 1` and `store i32 ..., align 1` → `MOVE`/`MOVE ...,1`; a pointer load; an i16 align-1 load). Also add a `vt1200_root` probe to `layout.c` (or a small TU) that calls the helper and require the selected asm to contain the `0480h`/`040h` bit displacements. Do not paper over this with `__attribute__((aligned(2)))`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write the C client and branch test**
|
||||||
|
|
||||||
|
`scripts/m3/xhello.c`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "vt1200_xlib.h"
|
||||||
|
|
||||||
|
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n) * 0x20u))
|
||||||
|
|
||||||
|
int xhello(void) {
|
||||||
|
Display *d = XOpenDisplay("0::0");
|
||||||
|
MARK(0) = (unsigned int)d;
|
||||||
|
if (!d)
|
||||||
|
return -1;
|
||||||
|
unsigned int root = vt1200_root(d);
|
||||||
|
MARK(1) = root;
|
||||||
|
Window w = XCreateSimpleWindow(d, root, 100, 100, 400, 200, 2, 0, 1);
|
||||||
|
MARK(2) = (unsigned int)w;
|
||||||
|
XMapWindow(d, w);
|
||||||
|
int s = XSync(d, 0);
|
||||||
|
MARK(3) = (unsigned int)s;
|
||||||
|
return (int)w;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/m3/branch.c`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n) * 0x20u))
|
||||||
|
|
||||||
|
int sumto(int n) {
|
||||||
|
int s = 0;
|
||||||
|
int i;
|
||||||
|
for (i = 1; i <= n; i++)
|
||||||
|
s += i;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int max2(int a, int b) { return a > b ? a : b; }
|
||||||
|
int lt(int a, int b) { return a < b; }
|
||||||
|
|
||||||
|
int branch_test(void) {
|
||||||
|
MARK(4) = (unsigned int)sumto(10);
|
||||||
|
MARK(5) = (unsigned int)max2(20, 22);
|
||||||
|
MARK(6) = (unsigned int)lt(5, 7);
|
||||||
|
MARK(7) = (unsigned int)layout_screens();
|
||||||
|
MARK(8) = (unsigned int)layout_root();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `layout_screens`/`layout_root` functions come from `layout.c` (Task 3); compile it into the M3 image.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Write the entry and linker command**
|
||||||
|
|
||||||
|
`scripts/m3/entry.asm`:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
.text
|
||||||
|
.globl _start
|
||||||
|
_start:
|
||||||
|
setf 16, 0, 0
|
||||||
|
setf 32, 0, 1
|
||||||
|
movi _cstack, a14
|
||||||
|
move a14, a13
|
||||||
|
calla _branch_test
|
||||||
|
calla _xhello
|
||||||
|
rets
|
||||||
|
|
||||||
|
.bss _cstack, 32768, 16 ; 4096 bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/m3/link.cmd` (CRLF; same shape as M2):
|
||||||
|
|
||||||
|
```text
|
||||||
|
-a
|
||||||
|
-o M3.OUT
|
||||||
|
-m M3.MAP
|
||||||
|
-e _start
|
||||||
|
M3.obj
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
prog: origin = 018A0000h, length = 8000h
|
||||||
|
ram: origin = 018A9000h, length = 40000h
|
||||||
|
}
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
.text: {} > prog
|
||||||
|
.data: {} > prog
|
||||||
|
.bss: {} > ram
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Validate the ROM symbols**
|
||||||
|
|
||||||
|
`scripts/m3/validate-syms.sh`:
|
||||||
|
|
||||||
|
- require the four entries in `symbols/vt1200-v22.syms`;
|
||||||
|
- require each address in the ROM bit range `3F200000h..3FFFFFFFh` and 16-bit aligned;
|
||||||
|
- run `dis34 -b <addr> -s <byte-offset>` for each and require a plausible function prologue (first instruction is a `MOVE`/`MMTM`/`SETF`/`CMP`/`ADDI`, not data);
|
||||||
|
- record the known-good `xhello.asm` evidence in `symbols/README.md`.
|
||||||
|
|
||||||
|
This is a regression check, not a re-extraction; the four entries are proven by `xhello.asm`. M4 extends the table for `XImage`/`XEvent`.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Write the M3 runner and check**
|
||||||
|
|
||||||
|
`scripts/m3/run.sh` adapts `scripts/m2/run.sh` with:
|
||||||
|
|
||||||
|
- `M3_WORK=${M3_WORK:-$ROOT/build/m3}`, `M3_COFF`, `M3_SDK`;
|
||||||
|
- compile `xhello.c`, `branch.c`, `layout.c` with `scripts/tms-clang -O2` + `llc -march=tms34010 -O0 -filetype=asm` (keep `layout.c` compiled with the client; the static asserts must pass);
|
||||||
|
- generate `rom-imports.asm` from `symbols/vt1200-v22.syms`;
|
||||||
|
- concatenate `entry.asm`, the three `.s` files, and `rom-imports.asm`; assemble/link with GSPA/GSPLNK under DOSBox; require `No Errors`, `No Warnings`; `_start=018A0000`, `.bss` origin `018A9000`, non-empty `.data`, loader `.text`/`.data` 0 differing;
|
||||||
|
- run MAME with `VT1200_SKIP_LEVEL1=1`, the loader, `LOADER_AT=2500`, `LOADER_END=3400` (adjust if the X server/window needs more frames), `M3_LOADER_LUA=$ROOT/vendor/vt1200/dwt/loader/loader.lua`, `-autoboot_script $ROOT/scripts/m3/m3-check.lua`.
|
||||||
|
|
||||||
|
`scripts/m3/m3-check.lua` reads at frame `ENDF-1`:
|
||||||
|
|
||||||
|
| marker | expected |
|
||||||
|
|---|---|
|
||||||
|
| `0x018A8000` | nonzero Display pointer |
|
||||||
|
| `0x018A8020` | nonzero root window id |
|
||||||
|
| `0x018A8040` | nonzero window id |
|
||||||
|
| `0x018A8060` | `XSync` result (diagnostic only; do not gate the pass on it) |
|
||||||
|
| `0x018A8080` | `55` (`sumto(10)`) |
|
||||||
|
| `0x018A80A0` | `22` (`max2(20,22)`) |
|
||||||
|
| `0x018A80C0` | `1` (`lt(5,7)`) |
|
||||||
|
| `0x018A80E0` | `0x480` (`layout_screens`) |
|
||||||
|
| `0x018A8100` | `0x40` (`layout_root`) |
|
||||||
|
|
||||||
|
Then read the VRAM pixel inside the window: the window is at `(100,100)`, 400×200; background pixel 1 (white). On the default VR319 monitor the VRAM base is bit `0x04000000` and the row pitch is 2048 bits; read a 32-bit word containing `(150,150)` and require the bit `1 << (150 & 31)` to be set. Print `M3: ...` and exactly `M3 MAME: PASS` on success. The window must be observed; a markers-only pass is not enough.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Write the `dis34` round-trip**
|
||||||
|
|
||||||
|
`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`/`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);
|
||||||
|
- the `.data` bytes `30 3A 3A 30 00` (`"0::0"`);
|
||||||
|
- the old-STK push-count `(pushes+1)*32` for the branch/Xlib functions; the `rets 1` negative control.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Run the M3 gates**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scripts/tests/m3/run.sh
|
||||||
|
scripts/tests/m3/dis-roundtrip.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `M3 MAME: PASS` with `display/root/window/sync`, `sum=55 max=22 lt=1`, `screens=0x480 root=0x40`, and the window pixel white; `M3 dis34 round-trip: PASS`.
|
||||||
|
|
||||||
|
- [ ] **Step 8: Commit**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git add scripts/m3 scripts/tests/m3
|
||||||
|
git commit -m "tests: M3 branches and first Xlib window in MAME"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 5: Encoding/differential checks, docs, and push
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `scripts/tests/m3/diff/ti-branch.c`, `ti-branch.s`, `ti-xhello.c`, `ti-xhello.s`, `check.sh`
|
||||||
|
- Modify: `README.md` (project), `build/llvm-clean/TMS34010.md` (compiler), `symbols/README.md`
|
||||||
|
- Push: compiler `m3`, project `m3`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: all previous tasks.
|
||||||
|
- Produces: stored TI goldens for branch/struct/call shape, documented M3, pushed `m3` branches.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Produce the TI goldens**
|
||||||
|
|
||||||
|
Using the 1987 compiler under DOSBox, compile K&R versions:
|
||||||
|
|
||||||
|
`scripts/tests/m3/diff/ti-branch.c`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int sumto(n) int n; { int s; int i; s = 0; for (i = 1; i <= n; i++) s = s + i; return s; }
|
||||||
|
int max2(a, b) int a; int b; { if (a > b) return a; return b; }
|
||||||
|
```
|
||||||
|
|
||||||
|
`ti-xhello.c`: the K&R Xlib client with the same `Display`/`Screen` padded structs and the four calls.
|
||||||
|
|
||||||
|
Store the LF-normalized asm as `ti-branch.s`/`ti-xhello.s`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write the differential check**
|
||||||
|
|
||||||
|
`scripts/tests/m3/diff/check.sh`:
|
||||||
|
|
||||||
|
- generate our `branch.s`/`xhello.s` with `scripts/tms-clang -O2` + `llc -O0`;
|
||||||
|
- in both the TI golden and our asm require: a loop back-edge (`JALE`/`JLE` and `JAUC`/`JRU`), `max2`'s conditional branch, the `sumto` `RETS 2`/`max2` `RETS 2`, the nine `XCreateSimpleWindow` argument pushes, and the `Display`/`Screen` offsets `0x480`/`0x40`;
|
||||||
|
- accept TI spelling variants (`0FFE0h`, `ADDK`, `JRU`/`JAUC`); do not require byte-for-byte equality. No DOSBox in CI.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update the docs**
|
||||||
|
|
||||||
|
- Project `README.md`: M3 status; branch/loop support and its limits (i32/pointer, signed/unsigned compares, `and/or/xor/shift/sub`); the Xlib four-call window; the `Display`/`Screen` offsets; the commands for `scripts/tests/m3/run.sh`, `dis-roundtrip.sh`, `diff/check.sh`; M3 non-goals (XImage/XEvent/real client/structs by value/function pointers/floats/i64/object writer); the `m3` branch and M2 default; the M2/M1 snapshots.
|
||||||
|
- Compiler `TMS34010.md`: `JAcc` branches, the `ST` flag register, the condition mapping, GSPA-safe `LBB` labels, the `CMP` order, the `SETCC`/`SELECT_CC` approach, the new ALU/shift instructions, and the Xlib client evidence.
|
||||||
|
- `symbols/README.md`: the four Xlib entries are validated by `xhello.asm` and `validate-syms.sh`; M4 extends the table.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run every M3 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)-'
|
||||||
|
scripts/tests/m3/run.sh
|
||||||
|
scripts/tests/m3/dis-roundtrip.sh
|
||||||
|
scripts/tests/m3/diff/check.sh
|
||||||
|
tools/tms-bitaddr/tests/run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all green; the M2 suite remains 37/37; M3 adds 5 branch tests plus the layout/harness gates.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit and push**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git -C build/llvm-clean add TMS34010.md
|
||||||
|
git -C build/llvm-clean commit -m "docs: M3 branches and Xlib window"
|
||||||
|
git -C build/llvm-clean push origin m3
|
||||||
|
git add README.md symbols scripts/tests/m3 scripts/m3
|
||||||
|
git commit -m "docs: M3 control flow and first Xlib window"
|
||||||
|
git push origin m3
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not force-push, merge into `m0`, or change the default branch; the controller changes the remote default after the final M3 review if the user asks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Execution Handoff
|
||||||
|
|
||||||
|
M3 is the first two halves of the remaining Xlib path: integer control flow (needed by real C) and the four-call window. M4 owns the real client, `XImage`/`XEvent`, the full X11R4 headers, structs by value and function pointers. M5 owns the native COFF path, floats, varargs, bitfields, `long long` and the `JR`/branch-relaxation code-size pass. The compiler repo is canonical for target code; this project repo holds the plans, wrappers, symbols and integration tests.
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
__attribute__((noinline)) int add2(int a, int b) { return a + b; }
|
||||||
|
__attribute__((noinline)) int first(int a, int b) { return a; }
|
||||||
|
__attribute__((noinline)) int second(int a, int b) { return b; }
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
extern int add2(int, int);
|
||||||
|
int caller(void) { return add2(20, 22); }
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
/* First ROM call: strlen("0::0") must be 4. */
|
||||||
|
extern int strlen(const char *);
|
||||||
|
int check(void) { return strlen("0::0"); }
|
||||||
Executable
+327
@@ -0,0 +1,327 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M2 dis34 round-trip.
|
||||||
|
#
|
||||||
|
# scripts/m2/dis-roundtrip.sh
|
||||||
|
#
|
||||||
|
# Extracts the linked .text/.data from build/m2/m2.coff (using the COFF
|
||||||
|
# section table, not hard-coded file offsets), disassembles the text with the
|
||||||
|
# VT1200 tree's dis34 at the linked bit address, and checks that GSPA encoded
|
||||||
|
# the TI-ABI code llc printed:
|
||||||
|
#
|
||||||
|
# _strlen JAUC 3FB458D0 (the ROM stub, ABI-preserving)
|
||||||
|
# _first MOVE A13,A? ; ADDI -32,A? ; RETS 2
|
||||||
|
# _second ADDI -64,A? ; RETS 2
|
||||||
|
# _keep old-STK push, argument pushes, CALLA, SETF, LOADSTK, RETS 2
|
||||||
|
# _locals nonzero frame ADDI, a frame access, RETS 0
|
||||||
|
#
|
||||||
|
# It also checks the old-STK operand against the number of pushes before
|
||||||
|
# SETFP, and the .data bytes contain the "0::0" string literal.
|
||||||
|
#
|
||||||
|
# Overridable: M2_COFF DIS34
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||||
|
COFF=${M2_COFF:-$ROOT/build/m2/m2.coff}
|
||||||
|
DIS34=${DIS34:-$ROOT/vendor/vt1200/tools/smart34010/dis34}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "M2 dis34 round-trip: FAIL: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -f "$COFF" ] || fail "linked COFF not found: $COFF (run scripts/m2/run.sh)"
|
||||||
|
[ -x "$DIS34" ] || fail "dis34 not found or not executable: $DIS34"
|
||||||
|
|
||||||
|
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m2-dis34.XXXXXX")
|
||||||
|
FAIL_LOG=${M2_DIS_FAIL_LOG:-${TMPDIR:-/tmp}/m2-dis34-failure.txt}
|
||||||
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||||
|
|
||||||
|
# Read little-endian fields out of the COFF. od keeps this dependency-free.
|
||||||
|
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 (nscns='$nscns')"
|
||||||
|
[ "$opthdr" -gt 0 ] 2>/dev/null || fail "COFF has no optional header (opthdr='$opthdr')"
|
||||||
|
|
||||||
|
TEXT_ADDR=
|
||||||
|
TEXT_BITS=
|
||||||
|
TEXT_PTR=
|
||||||
|
DATA_ADDR=
|
||||||
|
DATA_BITS=
|
||||||
|
DATA_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)))
|
||||||
|
elif [ "$name" = ".data" ]; then
|
||||||
|
DATA_ADDR=$(u32 $((o + 8)))
|
||||||
|
DATA_BITS=$(u32 $((o + 16)))
|
||||||
|
DATA_PTR=$(u32 $((o + 20)))
|
||||||
|
fi
|
||||||
|
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"
|
||||||
|
[ "$DATA_BITS" -gt 0 ] 2>/dev/null || fail ".data is empty"
|
||||||
|
[ $((DATA_BITS % 8)) -eq 0 ] || fail ".data size $DATA_BITS bits is not whole bytes"
|
||||||
|
|
||||||
|
TEXT_HEX=$(printf '%08X' "$TEXT_ADDR")
|
||||||
|
ENTRY=$(u32 $((20 + 16)))
|
||||||
|
ENTRY_HEX=$(printf '%08X' "$ENTRY")
|
||||||
|
TEXT_BYTES=$((TEXT_BITS / 8))
|
||||||
|
DATA_BYTES=$((DATA_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"
|
||||||
|
[ "$TEXT_PTR" -gt 0 ] || fail ".text has no raw data (scnptr=0)"
|
||||||
|
[ "$DATA_PTR" -gt 0 ] || fail ".data has no raw data (scnptr=0)"
|
||||||
|
|
||||||
|
echo " .text at $TEXT_HEX: $TEXT_BITS bits ($TEXT_BYTES bytes), scnptr=$TEXT_PTR"
|
||||||
|
echo " .data at $(printf '%08X' "$DATA_ADDR"): $DATA_BITS bits ($DATA_BYTES bytes), scnptr=$DATA_PTR"
|
||||||
|
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 "extracted $(wc -c < "$TMP/text.bin") .text bytes, want $TEXT_BYTES"
|
||||||
|
dd if="$COFF" of="$TMP/data.bin" bs=1 skip="$DATA_PTR" count="$DATA_BYTES" 2>/dev/null
|
||||||
|
[ "$(wc -c < "$TMP/data.bin")" -eq "$DATA_BYTES" ] \
|
||||||
|
|| fail "extracted $(wc -c < "$TMP/data.bin") .data bytes, want $DATA_BYTES"
|
||||||
|
|
||||||
|
echo "== resolve the linked symbols from the COFF symbol table"
|
||||||
|
symptr=$(u32 8)
|
||||||
|
nsyms=$(u32 12)
|
||||||
|
[ "$symptr" -gt 0 ] 2>/dev/null || fail "COFF has no symbol table"
|
||||||
|
[ "$nsyms" -gt 0 ] 2>/dev/null || fail "COFF symbol table is empty"
|
||||||
|
|
||||||
|
sym_addr() {
|
||||||
|
_want=$1
|
||||||
|
_i=0
|
||||||
|
while [ "$_i" -lt "$nsyms" ]; do
|
||||||
|
_o=$((symptr + _i * 18))
|
||||||
|
_n=$(dd if="$COFF" bs=1 skip="$_o" count=8 2>/dev/null | tr -d '\000')
|
||||||
|
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() {
|
||||||
|
_name=$1
|
||||||
|
_addr=$(sym_addr "$_name") || fail "COFF symbol table has no $_name"
|
||||||
|
[ -n "$_addr" ] || fail "COFF symbol table has no $_name"
|
||||||
|
printf '%s' "$_addr"
|
||||||
|
}
|
||||||
|
|
||||||
|
STRLEN=$(get_sym _strlen)
|
||||||
|
FIRST=$(get_sym _first)
|
||||||
|
SECOND=$(get_sym _second)
|
||||||
|
KEEP=$(get_sym _keep)
|
||||||
|
LOCALS=$(get_sym _locals)
|
||||||
|
ADD2=$(get_sym _add2)
|
||||||
|
STRLEN_HEX=$(printf '%08X' "$STRLEN")
|
||||||
|
FIRST_HEX=$(printf '%08X' "$FIRST")
|
||||||
|
SECOND_HEX=$(printf '%08X' "$SECOND")
|
||||||
|
KEEP_HEX=$(printf '%08X' "$KEEP")
|
||||||
|
LOCALS_HEX=$(printf '%08X' "$LOCALS")
|
||||||
|
ADD2_HEX=$(printf '%08X' "$ADD2")
|
||||||
|
echo " _strlen=$STRLEN_HEX _first=$FIRST_HEX _second=$SECOND_HEX"
|
||||||
|
echo " _add2=$ADD2_HEX _keep=$KEEP_HEX _locals=$LOCALS_HEX"
|
||||||
|
|
||||||
|
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 DECIMAL_ADDR ERE
|
||||||
|
# dis34 -l prints "018a05c0:\tmove a13, a0\t; 4da0". The operand must be
|
||||||
|
# followed by optional whitespace and then the comment/end-of-line marker, so
|
||||||
|
# "rets 1" cannot satisfy a "rets" pattern.
|
||||||
|
match_at() {
|
||||||
|
_file=$1
|
||||||
|
_addr=$2
|
||||||
|
_pat=$3
|
||||||
|
_laddr=$(printf '%08x' "$_addr")
|
||||||
|
grep -Eiq "^${_laddr}:[[:space:]]+${_pat}([[:space:]]*(;|$))" "$_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# expect_at LABEL DECIMAL_ADDR EXPECTED_TEXT ERE
|
||||||
|
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 "M2 dis34 round-trip: FAIL"
|
||||||
|
echo " $_label at bit 0x$_laddr: expected $_want"
|
||||||
|
echo " dis34 output near 0x$_laddr:"
|
||||||
|
awk -v a="$_laddr" '
|
||||||
|
{ low = tolower($0) }
|
||||||
|
low ~ ("^" a ":") { seen = 1 }
|
||||||
|
seen { print " " $0; n++ }
|
||||||
|
seen && n >= 4 { exit }
|
||||||
|
' "$TMP/dis34.txt"
|
||||||
|
if cp "$TMP/dis34.txt" "$FAIL_LOG" 2>/dev/null; then
|
||||||
|
echo " full disassembly preserved at: $FAIL_LOG"
|
||||||
|
else
|
||||||
|
echo " full disassembly: $TMP/dis34.txt (temporary; removed on exit)"
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# func_body DECIMAL_ADDR > FILE: the function's instructions, through RETS.
|
||||||
|
func_body() {
|
||||||
|
_laddr=$(printf '%08x' "$1")
|
||||||
|
awk -v a="$_laddr" '
|
||||||
|
{ low = tolower($0) }
|
||||||
|
low ~ ("^" a ":") { on = 1 }
|
||||||
|
on { print }
|
||||||
|
on && low ~ /^[0-9a-f]+:[[:space:]]+rets([[:space:]]|;|$)/ { exit }
|
||||||
|
' "$TMP/dis34.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
# expect_in BODY EXPECTED_TEXT ERE
|
||||||
|
expect_in() {
|
||||||
|
_file=$1
|
||||||
|
_want=$2
|
||||||
|
_pat=$3
|
||||||
|
if grep -Eiq "$_pat" "$_file"; then
|
||||||
|
echo " ok $_want"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "M2 dis34 round-trip: FAIL: missing $_want" >&2
|
||||||
|
sed 's/^/ /' "$_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# expect_seq BODY REGEX...: each pattern must occur, in order, after the last.
|
||||||
|
expect_seq() {
|
||||||
|
_file=$1
|
||||||
|
shift
|
||||||
|
_last=0
|
||||||
|
for _pat in "$@"; do
|
||||||
|
_line=$(awk -v start="$_last" -v pat="$_pat" '
|
||||||
|
NR > start && tolower($0) ~ pat { print NR; exit }
|
||||||
|
' "$_file")
|
||||||
|
[ -n "$_line" ] || {
|
||||||
|
echo "M2 dis34 round-trip: FAIL: call sequence missing /$_pat/" >&2
|
||||||
|
sed 's/^/ /' "$_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
_last=$_line
|
||||||
|
done
|
||||||
|
echo " ok call sequence ($# instructions/operands, in order)"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "== check the linked instructions"
|
||||||
|
# ROM import stub: absolute unconditional jump, ABI-preserving.
|
||||||
|
expect_at "_strlen" "$STRLEN" "JAUC 3FB458D0" \
|
||||||
|
'jauc[[:space:]]+0x3fb458d0'
|
||||||
|
|
||||||
|
# _first must materialise its frame pointer and load argument 0 at FP(-32).
|
||||||
|
func_body "$FIRST" > "$TMP/first.txt"
|
||||||
|
expect_in "$TMP/first.txt" "_first MOVEFP (MOVE A13,A?)" \
|
||||||
|
'move[[:space:]]+a13,[[:space:]]*a[0-9]+'
|
||||||
|
expect_in "$TMP/first.txt" "_first ADDI -32" \
|
||||||
|
'addi[[:space:]]+(0xffe0|-0x20|-32),'
|
||||||
|
expect_in "$TMP/first.txt" "_first RETS 2" \
|
||||||
|
'rets[[:space:]]+2([[:space:]]*(;|$))'
|
||||||
|
|
||||||
|
# _second loads argument 1 at FP(-64).
|
||||||
|
func_body "$SECOND" > "$TMP/second.txt"
|
||||||
|
expect_in "$TMP/second.txt" "_second ADDI -64" \
|
||||||
|
'addi[[:space:]]+(0xffc0|-0x40|-64),'
|
||||||
|
expect_in "$TMP/second.txt" "_second RETS 2" \
|
||||||
|
'rets[[:space:]]+2([[:space:]]*(;|$))'
|
||||||
|
|
||||||
|
# _keep exercises the whole call/return path against _add2.
|
||||||
|
func_body "$KEEP" > "$TMP/keep.txt"
|
||||||
|
expect_seq "$TMP/keep.txt" \
|
||||||
|
'move[[:space:]]+a14,[[:space:]]*-\*sp,[[:space:]]*1' \
|
||||||
|
'move[[:space:]]+[^,]+,[[:space:]]*\*a14\+,[[:space:]]*1' \
|
||||||
|
'calla' \
|
||||||
|
'setf[[:space:]]+16,[[:space:]]*0,[[:space:]]*0' \
|
||||||
|
'setf[[:space:]]+32,[[:space:]]*0,[[:space:]]*1' \
|
||||||
|
'move[[:space:]]+\*sp\(0x[0-9a-f]+\),[[:space:]]*a14,[[:space:]]*1' \
|
||||||
|
'rets[[:space:]]+2([[:space:]]*(;|$))'
|
||||||
|
|
||||||
|
# _locals has a nonzero frame allocation, a frame access and RETS 0.
|
||||||
|
func_body "$LOCALS" > "$TMP/locals.txt"
|
||||||
|
expect_in "$TMP/locals.txt" "_locals frame ADDI" \
|
||||||
|
'addi[[:space:]]+0x0*[1-9a-f][0-9a-f]*,[[:space:]]*a14'
|
||||||
|
expect_in "$TMP/locals.txt" "_locals frame access" \
|
||||||
|
'move[[:space:]]+(a[0-9]+,[[:space:]]*\*a[0-9]+|\*a[0-9]+,[[:space:]]*a[0-9]+)'
|
||||||
|
expect_in "$TMP/locals.txt" "_locals RETS 0" \
|
||||||
|
'rets([[:space:]]+0)?([[:space:]]*(;|$))'
|
||||||
|
|
||||||
|
# Old-STK offset: the LOADSTK operand must count every callee-saved push
|
||||||
|
# before SETFP, plus the caller's pushed old STK. This is the check the M2
|
||||||
|
# lit tests deliberately leave to dis34.
|
||||||
|
echo "== check the old-STK restore offsets"
|
||||||
|
check_stk() {
|
||||||
|
_label=$1
|
||||||
|
_addr=$2
|
||||||
|
func_body "$_addr" > "$TMP/func.txt"
|
||||||
|
_pushes=$(awk '
|
||||||
|
tolower($0) ~ /move[[:space:]]+[a-z0-9]+,[[:space:]]*-\*sp,[[:space:]]*1/ { n++ }
|
||||||
|
tolower($0) ~ /move[[:space:]]+a14,[[:space:]]*a13/ { print n; exit }
|
||||||
|
' "$TMP/func.txt")
|
||||||
|
[ -n "$_pushes" ] || fail "$_label has no SETFP (MOVE A14,A13)"
|
||||||
|
_operand=$(grep -Eio '\*sp\(0x[0-9a-f]+\)' "$TMP/func.txt" | head -1 \
|
||||||
|
| sed 's/.*0x//; s/).*//')
|
||||||
|
[ -n "$_operand" ] || fail "$_label has no LOADSTK (*SP(n),A14)"
|
||||||
|
_want=$(( (_pushes + 1) * 32 ))
|
||||||
|
_got=$((0x$_operand))
|
||||||
|
[ "$_got" -eq "$_want" ] \
|
||||||
|
|| fail "$_label restores A14 from *SP(0x$_operand) ($_got bits) but $_pushes pushes need $_want bits"
|
||||||
|
echo " ok $_label: $_pushes pushes -> *SP($(printf '0x%x' "$_want")), A14, 1"
|
||||||
|
}
|
||||||
|
check_stk "_add2" "$ADD2"
|
||||||
|
check_stk "_first" "$FIRST"
|
||||||
|
check_stk "_second" "$SECOND"
|
||||||
|
check_stk "_keep" "$KEEP"
|
||||||
|
|
||||||
|
# Negative control for the RETS operand boundary: "rets 1" must not match.
|
||||||
|
printf '%08x:\trets\t1\t; 0961\n' "$ADD2" > "$TMP/rets-neg.txt"
|
||||||
|
if match_at "$TMP/rets-neg.txt" "$ADD2" 'rets([[:space:]]+0)?'; then
|
||||||
|
fail "negative control: the RETS pattern accepted 'rets 1'"
|
||||||
|
fi
|
||||||
|
echo " ok negative control @ 0x$(printf '%08x' "$ADD2"): 'rets 1' rejected"
|
||||||
|
|
||||||
|
echo "== check the copied .data bytes"
|
||||||
|
# The check.c string literal "0::0\0" must survive llc -> GSPA -> COFF.
|
||||||
|
DATA_HEX=$(od -An -v -tx1 "$TMP/data.bin" | tr -d ' \n')
|
||||||
|
case "$DATA_HEX" in
|
||||||
|
*303a3a3000*) echo " ok .data contains 30 3A 3A 30 00 (\"0::0\")" ;;
|
||||||
|
*) fail ".data does not contain 30 3A 3A 30 00 (got $DATA_HEX)" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "M2 dis34 round-trip: PASS"
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
; entry.asm -- M2 TI ABI smoke-test entry, linked at bit 018A0000.
|
||||||
|
;
|
||||||
|
; Markers (0x20 bits apart):
|
||||||
|
; 018A8000 check() strlen("0::0") == 4
|
||||||
|
; 018A8020 first(20,22) == 20
|
||||||
|
; 018A8040 second(20,22) == 22
|
||||||
|
; 018A8060 caller() add2(20,22) == 42
|
||||||
|
; 018A8080 locals() == 7
|
||||||
|
; 018A80A0 keep(10) == 16 (g == 5 before the call, + 11)
|
||||||
|
.text
|
||||||
|
.globl _start
|
||||||
|
_start:
|
||||||
|
setf 32, 0, 1
|
||||||
|
setf 16, 0, 0
|
||||||
|
movi _cstack, a14
|
||||||
|
move a14, a13
|
||||||
|
|
||||||
|
calla _check
|
||||||
|
movi 018A8000h, a2
|
||||||
|
move a8, *a2, 1
|
||||||
|
|
||||||
|
; first(20, 22): last argument first, on the program stack
|
||||||
|
move a14, -*sp, 1
|
||||||
|
movi 22, a0
|
||||||
|
move a0, *a14+, 1
|
||||||
|
movi 20, a0
|
||||||
|
move a0, *a14+, 1
|
||||||
|
calla _first
|
||||||
|
movi 018A8020h, a2
|
||||||
|
move a8, *a2, 1
|
||||||
|
|
||||||
|
; second(20, 22)
|
||||||
|
move a14, -*sp, 1
|
||||||
|
movi 22, a0
|
||||||
|
move a0, *a14+, 1
|
||||||
|
movi 20, a0
|
||||||
|
move a0, *a14+, 1
|
||||||
|
calla _second
|
||||||
|
movi 018A8040h, a2
|
||||||
|
move a8, *a2, 1
|
||||||
|
|
||||||
|
calla _caller
|
||||||
|
movi 018A8060h, a2
|
||||||
|
move a8, *a2, 1
|
||||||
|
|
||||||
|
calla _locals
|
||||||
|
movi 018A8080h, a2
|
||||||
|
move a8, *a2, 1
|
||||||
|
|
||||||
|
move a14, -*sp, 1
|
||||||
|
movi 10, a0
|
||||||
|
move a0, *a14+, 1
|
||||||
|
calla _keep
|
||||||
|
movi 018A80A0h, a2
|
||||||
|
move a8, *a2, 1
|
||||||
|
|
||||||
|
rets
|
||||||
|
|
||||||
|
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Generate absolute-jump stubs for the ROM entry points in a per-ROM symbol file.
|
||||||
|
# Usage: gen-rom-imports.sh symbols/vt1200-v22.syms OUT.asm
|
||||||
|
set -eu
|
||||||
|
SYMS=${1:?symbol file}
|
||||||
|
OUT=${2:?output file}
|
||||||
|
printf '\t.text\n' > "$OUT"
|
||||||
|
awk '
|
||||||
|
/^[[:space:]]*#/ || NF < 2 { next }
|
||||||
|
{
|
||||||
|
name = $1
|
||||||
|
addr = $2
|
||||||
|
sub(/^0x/, "", addr)
|
||||||
|
printf "\t.globl _%s\n_%s:\n\tjauc %sh\n", name, name, addr
|
||||||
|
}
|
||||||
|
' "$SYMS" >> "$OUT"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
extern int add2(int, int);
|
||||||
|
int g = 5; /* initialized .data: also tests the loader copy */
|
||||||
|
int keep(int a) { int x = g; int y = add2(a, 1); return x + y; }
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-a
|
||||||
|
-o M2.OUT
|
||||||
|
-m M2.MAP
|
||||||
|
-e _start
|
||||||
|
M2.obj
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
prog: origin = 018A0000h, length = 8000h
|
||||||
|
ram: origin = 018A9000h, length = 40000h
|
||||||
|
}
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
.text: {} > prog
|
||||||
|
.data: {} > prog
|
||||||
|
.bss: {} > ram
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
int locals(void) { volatile int x = 7; return x; }
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
-- m2-check.lua -- load the M2 COFF with dwt/loader/loader.lua and read the
|
||||||
|
-- marker words the compiled TI-ABI code wrote.
|
||||||
|
--
|
||||||
|
-- M2_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua
|
||||||
|
-- M2_EXPECT_CHECK 4 (strlen("0::0"))
|
||||||
|
-- M2_EXPECT_FIRST 20 (first(20,22))
|
||||||
|
-- M2_EXPECT_SECOND 22 (second(20,22))
|
||||||
|
-- M2_EXPECT_CALLER 42 (add2(20,22))
|
||||||
|
-- M2_EXPECT_LOCALS 7
|
||||||
|
-- M2_EXPECT_KEEP 16 (g == 5, + keep(10))
|
||||||
|
--
|
||||||
|
-- The marker read runs one frame before loader.lua's inspection/exit frame.
|
||||||
|
|
||||||
|
dofile(os.getenv("M2_LOADER_LUA"))
|
||||||
|
|
||||||
|
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
|
||||||
|
local EXPECT_CHECK = tonumber(os.getenv("M2_EXPECT_CHECK") or "4")
|
||||||
|
local EXPECT_FIRST = tonumber(os.getenv("M2_EXPECT_FIRST") or "20")
|
||||||
|
local EXPECT_SECOND = tonumber(os.getenv("M2_EXPECT_SECOND") or "22")
|
||||||
|
local EXPECT_CALLER = tonumber(os.getenv("M2_EXPECT_CALLER") or "42")
|
||||||
|
local EXPECT_LOCALS = tonumber(os.getenv("M2_EXPECT_LOCALS") or "7")
|
||||||
|
local EXPECT_KEEP = tonumber(os.getenv("M2_EXPECT_KEEP") or "16")
|
||||||
|
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 check = prog:read_u32(0x018A8000)
|
||||||
|
local first = prog:read_u32(0x018A8020)
|
||||||
|
local second = prog:read_u32(0x018A8040)
|
||||||
|
local caller = prog:read_u32(0x018A8060)
|
||||||
|
local locals = prog:read_u32(0x018A8080)
|
||||||
|
local keep = prog:read_u32(0x018A80A0)
|
||||||
|
print(string.format(
|
||||||
|
"M2: check=%d first=%d second=%d caller=%d locals=%d keep=%d",
|
||||||
|
check, first, second, caller, locals, keep))
|
||||||
|
if check == EXPECT_CHECK and first == EXPECT_FIRST
|
||||||
|
and second == EXPECT_SECOND and caller == EXPECT_CALLER
|
||||||
|
and locals == EXPECT_LOCALS and keep == EXPECT_KEEP then
|
||||||
|
print("M2 MAME: PASS")
|
||||||
|
else
|
||||||
|
print(string.format(
|
||||||
|
"M2 MAME: FAIL (want check=%d first=%d second=%d caller=%d locals=%d keep=%d)",
|
||||||
|
EXPECT_CHECK, EXPECT_FIRST, EXPECT_SECOND, EXPECT_CALLER,
|
||||||
|
EXPECT_LOCALS, EXPECT_KEEP))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, "m2-check")
|
||||||
Executable
+171
@@ -0,0 +1,171 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M2 end-to-end smoke test: TI ABI + a real ROM strlen call.
|
||||||
|
#
|
||||||
|
# scripts/m2/run.sh
|
||||||
|
#
|
||||||
|
# Compiles five C translation units with scripts/tms-clang + llc -O0, assembles
|
||||||
|
# and links them with the 1987 GSPA/GSPLNK 2.00 under DOSBox, then loads the
|
||||||
|
# COFF into MAME through dwt/loader/loader.lua and checks the marker words:
|
||||||
|
#
|
||||||
|
# check() strlen("0::0") == 4
|
||||||
|
# first(20,22) == 20
|
||||||
|
# second(20,22) == 22
|
||||||
|
# caller() add2(20,22) == 42
|
||||||
|
# locals() == 7
|
||||||
|
# keep(10) == 16
|
||||||
|
#
|
||||||
|
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC MAME DOSBOX DOSBOX_CONF
|
||||||
|
# SDK_IMG M2_WORK M2_COFF M2_SDK LOADER_AT LOADER_END
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||||
|
DIR=$ROOT/scripts/m2
|
||||||
|
|
||||||
|
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=${M2_WORK:-$ROOT/build/m2}
|
||||||
|
COFF=${M2_COFF:-$WORK/m2.coff}
|
||||||
|
SDK=${M2_SDK:-$ROOT/build/m2-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=${M2_SYMS:-$ROOT/symbols/vt1200-v22.syms}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "m2: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do
|
||||||
|
[ -x "$tool" ] || fail "missing tool: $tool"
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$ASM" "$WORK/evidence" "$WORK/run/nvram" "$WORK/run/cfg"
|
||||||
|
mkdir -p "$(dirname "$COFF")"
|
||||||
|
|
||||||
|
echo "== compile and select"
|
||||||
|
export TMS_CLANG TMS_OPT TMS_BITADDR
|
||||||
|
for name in check args caller locals keep; do
|
||||||
|
"$ROOT/scripts/tms-clang" -O2 -o "$ASM/$name.ll" "$DIR/$name.c"
|
||||||
|
"$TMS_LLC" -march=tms34010 -O0 -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
|
||||||
|
|
||||||
|
# strlen("0::0") must be a real ROM call, not a folded 4.
|
||||||
|
grep -q 'CALLA _strlen' "$ASM/check.s" \
|
||||||
|
|| fail "check.s has no 'CALLA _strlen' (strlen was folded or not called)"
|
||||||
|
echo " check.s: $(grep -E 'CALLA _strlen' "$ASM/check.s" | head -1 | sed 's/^[[:space:]]*//')"
|
||||||
|
|
||||||
|
echo "== generate the per-ROM import stubs"
|
||||||
|
"$DIR/gen-rom-imports.sh" "$SYMS" "$WORK/rom-imports.asm"
|
||||||
|
grep -q '^_strlen:' "$WORK/rom-imports.asm" \
|
||||||
|
|| fail "rom-imports.asm has no _strlen stub"
|
||||||
|
grep -A1 '^_strlen:' "$WORK/rom-imports.asm" | grep -qi 'jauc 3FB458D0h' \
|
||||||
|
|| fail "rom-imports.asm _strlen stub does not jump to 3FB458D0h"
|
||||||
|
echo " _strlen: $(grep -A1 '^_strlen:' "$WORK/rom-imports.asm" | tail -1 | sed 's/^[[:space:]]*//')"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
echo "== assemble and link with GSPA/GSPLNK under DOSBox"
|
||||||
|
cat "$DIR/entry.asm" "$ASM/check.s" "$ASM/args.s" "$ASM/caller.s" \
|
||||||
|
"$ASM/locals.s" "$ASM/keep.s" "$WORK/rom-imports.asm" > "$SDK/M2.ASM"
|
||||||
|
# The linker command file must keep CRLF line endings.
|
||||||
|
sed 's/\r$//' "$DIR/link.cmd" | sed 's/$/\r/' > "$SDK/M2.CMD"
|
||||||
|
rm -f "$SDK/M2.OBJ" "$SDK/M2.OUT" "$SDK/M2.MAP" "$SDK/M2.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 M2 M2.obj M2.lst -l" \
|
||||||
|
-c "gsplnk M2.cmd" \
|
||||||
|
-c "exit" -exit > "$WORK/dosbox.log" 2>&1 \
|
||||||
|
|| { tail -20 "$WORK/dosbox.log" >&2; fail "DOSBox failed"; }
|
||||||
|
|
||||||
|
[ -f "$SDK/M2.LST" ] || fail "GSPA produced no listing (see $WORK/dosbox.log)"
|
||||||
|
sed 's/\r$//' "$SDK/M2.LST" > "$WORK/m2.lst"
|
||||||
|
grep -q 'No Errors' "$WORK/m2.lst" \
|
||||||
|
|| { grep -i -B2 -A2 'error' "$WORK/m2.lst" >&2 || true; fail "GSPA reported errors"; }
|
||||||
|
grep -q 'No Warnings' "$WORK/m2.lst" \
|
||||||
|
|| { grep -i -B2 -A2 'warning' "$WORK/m2.lst" >&2 || true; fail "GSPA reported warnings"; }
|
||||||
|
[ -f "$SDK/M2.OUT" ] || fail "GSPLNK produced no M2.OUT"
|
||||||
|
sed 's/\r$//' "$SDK/M2.MAP" > "$WORK/m2.map"
|
||||||
|
cp "$SDK/M2.OUT" "$COFF"
|
||||||
|
cp "$SDK/M2.ASM" "$WORK/m2.asm"
|
||||||
|
|
||||||
|
ENTRY=$(awk '$2 == "_start" { print $1; exit }' "$WORK/m2.map")
|
||||||
|
[ "$ENTRY" = "018A0000" ] || fail "linker entry _start is '$ENTRY', want 018A0000"
|
||||||
|
grep -q 'ENTRY POINT SYMBOL: "_start"' "$WORK/m2.map" \
|
||||||
|
|| fail "linker map has no _start entry point"
|
||||||
|
|
||||||
|
BSS_ORIGIN=$(awk '$1 == ".bss" && $2 == "0" { print $3; exit }' "$WORK/m2.map")
|
||||||
|
BSS_LENGTH=$(awk '$1 == ".bss" && $2 == "0" { print $4; exit }' "$WORK/m2.map")
|
||||||
|
[ -n "$BSS_ORIGIN" ] || fail "linker map has no .bss section"
|
||||||
|
[ "$BSS_ORIGIN" = "018A9000" ] \
|
||||||
|
|| fail "linker .bss origin is $BSS_ORIGIN, want 018A9000 (link.cmd ram origin)"
|
||||||
|
[ "$BSS_LENGTH" = "00008000" ] \
|
||||||
|
|| fail ".bss is $BSS_LENGTH bits, expected 00008000 (_cstack)"
|
||||||
|
DATA_ORIGIN=$(awk '$1 == ".data" && $2 == "0" { print $3; exit }' "$WORK/m2.map")
|
||||||
|
DATA_LENGTH=$(awk '$1 == ".data" && $2 == "0" { print $4; exit }' "$WORK/m2.map")
|
||||||
|
[ -n "$DATA_LENGTH" ] || fail "linker map has no .data section"
|
||||||
|
[ "$DATA_LENGTH" != "00000000" ] || fail ".data is empty (no initialized globals)"
|
||||||
|
echo " linked .text at 018A0000, .data at $DATA_ORIGIN ($DATA_LENGTH bits), .bss at $BSS_ORIGIN ($BSS_LENGTH bits)"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
VT1200_SKIP_LEVEL1=1 \
|
||||||
|
LOADER_AT="$LOADER_AT" LOADER_END="$LOADER_END" \
|
||||||
|
LOADER_BIN="$LOADER_BIN" LOADER_COFF="$COFF" \
|
||||||
|
LOADER_OUT="$WORK/evidence" \
|
||||||
|
M2_LOADER_LUA="$LOADER_LUA" \
|
||||||
|
M2_EXPECT_CHECK=4 M2_EXPECT_FIRST=20 M2_EXPECT_SECOND=22 \
|
||||||
|
M2_EXPECT_CALLER=42 M2_EXPECT_LOCALS=7 M2_EXPECT_KEEP=16 \
|
||||||
|
timeout "${MAME_TIMEOUT:-600}" "$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 "$DIR/m2-check.lua" -autoboot_delay 0 \
|
||||||
|
> "$WORK/mame.log" 2>&1 \
|
||||||
|
|| { tail -30 "$WORK/mame.log" >&2; fail "MAME run failed"; }
|
||||||
|
|
||||||
|
MARKERS=$(grep -E '^M2: check=' "$WORK/mame.log" | tail -1 || true)
|
||||||
|
[ -n "$MARKERS" ] || { tail -30 "$WORK/mame.log" >&2; fail "MAME produced no M2 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 'placed \.data .* 0 differing' "$WORK/mame.log" \
|
||||||
|
|| fail "COFF loader could not verify the placed .data bytes"
|
||||||
|
grep -q '^M2 MAME: PASS' "$WORK/mame.log" \
|
||||||
|
|| { tail -30 "$WORK/mame.log" >&2; fail "marker check failed"; }
|
||||||
|
|
||||||
|
echo "M2 MAME: PASS"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include "vt1200_xlib.h"
|
||||||
|
|
||||||
|
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n) * 0x20u))
|
||||||
|
|
||||||
|
int sumto(int n) {
|
||||||
|
int s = 0;
|
||||||
|
int i;
|
||||||
|
for (i = 1; i <= n; i++)
|
||||||
|
s += i;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int max2(int a, int b) { return a > b ? a : b; }
|
||||||
|
int lt(int a, int b) { return a < b; }
|
||||||
|
|
||||||
|
int branch_test(void) {
|
||||||
|
MARK(4) = (unsigned int)sumto(10);
|
||||||
|
MARK(5) = (unsigned int)max2(20, 22);
|
||||||
|
MARK(6) = (unsigned int)lt(5, 7);
|
||||||
|
MARK(7) = (unsigned int)layout_screens();
|
||||||
|
MARK(8) = (unsigned int)layout_root();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Executable
+464
@@ -0,0 +1,464 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 dis34 round-trip.
|
||||||
|
#
|
||||||
|
# scripts/m3/dis-roundtrip.sh
|
||||||
|
#
|
||||||
|
# Parses build/m3/m3.coff, disassembles its linked .text at 018A0000 with the
|
||||||
|
# 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,
|
||||||
|
# 2 -> _XSync, each followed by the ABI's SETF 16/32 pair
|
||||||
|
# branch code: CMP, JALT, JAGT, JAEQ, JAUC and LBB… labels (never .LBB)
|
||||||
|
# .data: 30 3A 3A 30 00 ("0::0")
|
||||||
|
# old-STK restore offsets: *SP((pushes+1)*32),A14
|
||||||
|
#
|
||||||
|
# Overridable: M3_COFF M3_ASM DIS34
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||||
|
WORK=${M3_WORK:-$ROOT/build/m3}
|
||||||
|
COFF=${M3_COFF:-$WORK/m3.coff}
|
||||||
|
ASM=${M3_ASM:-$WORK/m3.asm}
|
||||||
|
DIS34=${DIS34:-$ROOT/vendor/vt1200/tools/smart34010/dis34}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "M3 dis34 round-trip: FAIL: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -f "$COFF" ] || fail "linked COFF not found: $COFF (run scripts/m3/run.sh)"
|
||||||
|
[ -f "$ASM" ] || fail "linked assembly not found: $ASM (run scripts/m3/run.sh)"
|
||||||
|
[ -x "$DIS34" ] || fail "dis34 not found or not executable: $DIS34"
|
||||||
|
|
||||||
|
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m3-dis34.XXXXXX")
|
||||||
|
FAIL_LOG=${M3_DIS_FAIL_LOG:-${TMPDIR:-/tmp}/m3-dis34-failure.txt}
|
||||||
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||||
|
|
||||||
|
# Read little-endian fields out of the COFF. od keeps this dependency-free.
|
||||||
|
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 (nscns='$nscns')"
|
||||||
|
[ "$opthdr" -gt 0 ] 2>/dev/null || fail "COFF has no optional header (opthdr='$opthdr')"
|
||||||
|
|
||||||
|
TEXT_ADDR=
|
||||||
|
TEXT_BITS=
|
||||||
|
TEXT_PTR=
|
||||||
|
DATA_ADDR=
|
||||||
|
DATA_BITS=
|
||||||
|
DATA_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)))
|
||||||
|
elif [ "$name" = ".data" ]; then
|
||||||
|
DATA_ADDR=$(u32 $((o + 8)))
|
||||||
|
DATA_BITS=$(u32 $((o + 16)))
|
||||||
|
DATA_PTR=$(u32 $((o + 20)))
|
||||||
|
fi
|
||||||
|
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"
|
||||||
|
[ "$DATA_BITS" -gt 0 ] 2>/dev/null || fail ".data is empty"
|
||||||
|
[ $((DATA_BITS % 8)) -eq 0 ] || fail ".data size $DATA_BITS bits is not whole bytes"
|
||||||
|
|
||||||
|
TEXT_HEX=$(printf '%08X' "$TEXT_ADDR")
|
||||||
|
ENTRY=$(u32 $((20 + 16)))
|
||||||
|
ENTRY_HEX=$(printf '%08X' "$ENTRY")
|
||||||
|
TEXT_BYTES=$((TEXT_BITS / 8))
|
||||||
|
DATA_BYTES=$((DATA_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"
|
||||||
|
[ "$TEXT_PTR" -gt 0 ] || fail ".text has no raw data (scnptr=0)"
|
||||||
|
[ "$DATA_PTR" -gt 0 ] || fail ".data has no raw data (scnptr=0)"
|
||||||
|
|
||||||
|
echo " .text at $TEXT_HEX: $TEXT_BITS bits ($TEXT_BYTES bytes), scnptr=$TEXT_PTR"
|
||||||
|
echo " .data at $(printf '%08X' "$DATA_ADDR"): $DATA_BITS bits ($DATA_BYTES bytes), scnptr=$DATA_PTR"
|
||||||
|
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 "extracted $(wc -c < "$TMP/text.bin") .text bytes, want $TEXT_BYTES"
|
||||||
|
dd if="$COFF" of="$TMP/data.bin" bs=1 skip="$DATA_PTR" count="$DATA_BYTES" 2>/dev/null
|
||||||
|
[ "$(wc -c < "$TMP/data.bin")" -eq "$DATA_BYTES" ] \
|
||||||
|
|| fail "extracted $(wc -c < "$TMP/data.bin") .data bytes, want $DATA_BYTES"
|
||||||
|
|
||||||
|
echo "== resolve the linked symbols from the COFF symbol table"
|
||||||
|
symptr=$(u32 8)
|
||||||
|
nsyms=$(u32 12)
|
||||||
|
[ "$symptr" -gt 0 ] 2>/dev/null || fail "COFF has no symbol table"
|
||||||
|
[ "$nsyms" -gt 0 ] 2>/dev/null || fail "COFF symbol table is empty"
|
||||||
|
|
||||||
|
sym_addr() {
|
||||||
|
_want=$1
|
||||||
|
_strtab=$((symptr + nsyms * 18))
|
||||||
|
_i=0
|
||||||
|
while [ "$_i" -lt "$nsyms" ]; do
|
||||||
|
_o=$((symptr + _i * 18))
|
||||||
|
# COFF inline names are 8 bytes; longer names are 0,0,0,0 + a
|
||||||
|
# string-table offset (the table starts with its own 4-byte size).
|
||||||
|
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() {
|
||||||
|
_name=$1
|
||||||
|
_addr=$(sym_addr "$_name") || fail "COFF symbol table has no $_name"
|
||||||
|
[ -n "$_addr" ] || fail "COFF symbol table has no $_name"
|
||||||
|
printf '%s' "$_addr"
|
||||||
|
}
|
||||||
|
|
||||||
|
XOPEN=$(get_sym _XOpenDisplay)
|
||||||
|
XCREATE=$(get_sym _XCreateSimpleWindow)
|
||||||
|
XMAP=$(get_sym _XMapWindow)
|
||||||
|
XSYNC=$(get_sym _XSync)
|
||||||
|
XHELLO=$(get_sym _xhello)
|
||||||
|
SUMTO=$(get_sym _sumto)
|
||||||
|
MAX2=$(get_sym _max2)
|
||||||
|
LT=$(get_sym _lt)
|
||||||
|
BRANCH=$(get_sym _branch_test)
|
||||||
|
ROOTPROBE=$(get_sym _root_probe)
|
||||||
|
XOPEN_HEX=$(printf '%08X' "$XOPEN")
|
||||||
|
XCREATE_HEX=$(printf '%08X' "$XCREATE")
|
||||||
|
XMAP_HEX=$(printf '%08X' "$XMAP")
|
||||||
|
XSYNC_HEX=$(printf '%08X' "$XSYNC")
|
||||||
|
XHELLO_HEX=$(printf '%08X' "$XHELLO")
|
||||||
|
XOPEN_L=$(printf '%x' "$XOPEN")
|
||||||
|
XCREATE_L=$(printf '%x' "$XCREATE")
|
||||||
|
XMAP_L=$(printf '%x' "$XMAP")
|
||||||
|
XSYNC_L=$(printf '%x' "$XSYNC")
|
||||||
|
echo " _XOpenDisplay=$XOPEN_HEX _XCreateSimpleWindow=$XCREATE_HEX"
|
||||||
|
echo " _XMapWindow=$XMAP_HEX _XSync=$XSYNC_HEX"
|
||||||
|
echo " _xhello=$XHELLO_HEX _sumto=$(printf '%08X' "$SUMTO") _max2=$(printf '%08X' "$MAX2") _lt=$(printf '%08X' "$LT")"
|
||||||
|
echo " _branch_test=$(printf '%08X' "$BRANCH") _root_probe=$(printf '%08X' "$ROOTPROBE")"
|
||||||
|
|
||||||
|
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 DECIMAL_ADDR ERE
|
||||||
|
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 "M3 dis34 round-trip: FAIL"
|
||||||
|
echo " $_label at bit 0x$_laddr: expected $_want"
|
||||||
|
echo " dis34 output near 0x$_laddr:"
|
||||||
|
awk -v a="$_laddr" '
|
||||||
|
{ low = tolower($0) }
|
||||||
|
low ~ ("^" a ":") { seen = 1 }
|
||||||
|
seen { print " " $0; n++ }
|
||||||
|
seen && n >= 4 { exit }
|
||||||
|
' "$TMP/dis34.txt"
|
||||||
|
if cp "$TMP/dis34.txt" "$FAIL_LOG" 2>/dev/null; then
|
||||||
|
echo " full disassembly preserved at: $FAIL_LOG"
|
||||||
|
else
|
||||||
|
echo " full disassembly: $TMP/dis34.txt (temporary; removed on exit)"
|
||||||
|
fi
|
||||||
|
} >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func_body() {
|
||||||
|
_laddr=$(printf '%08x' "$1")
|
||||||
|
awk -v a="$_laddr" '
|
||||||
|
{ low = tolower($0) }
|
||||||
|
low ~ ("^" a ":") { on = 1 }
|
||||||
|
on { print }
|
||||||
|
on && low ~ /^[0-9a-f]+:[[:space:]]+rets([[:space:]]|;|$)/ { exit }
|
||||||
|
' "$TMP/dis34.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_in() {
|
||||||
|
_file=$1
|
||||||
|
_want=$2
|
||||||
|
_pat=$3
|
||||||
|
if grep -Eiq "$_pat" "$_file"; then
|
||||||
|
echo " ok $_want"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "M3 dis34 round-trip: FAIL: missing $_want" >&2
|
||||||
|
sed 's/^/ /' "$_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# count_between FILE START_RE END_RE PUSH_RE: count push encodings after the
|
||||||
|
# START_RE line up to (not including) the END_RE line.
|
||||||
|
count_between() {
|
||||||
|
_file=$1
|
||||||
|
_start=$2
|
||||||
|
_end=$3
|
||||||
|
_push=$4
|
||||||
|
awk -v s="$_start" -v e="$_end" -v p="$_push" '
|
||||||
|
{ low = tolower($0) }
|
||||||
|
on && low ~ e { exit }
|
||||||
|
on && low ~ p { n++ }
|
||||||
|
low ~ s { on = 1 }
|
||||||
|
END { print n + 0 }
|
||||||
|
' "$_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "_XMapWindow" "$XMAP" "JAUC 3FD64F60" \
|
||||||
|
'jauc[[:space:]]+0x3fd64f60'
|
||||||
|
expect_at "_XSync" "$XSYNC" "JAUC 3FD9F6B0" \
|
||||||
|
'jauc[[:space:]]+0x3fd9f6b0'
|
||||||
|
|
||||||
|
echo "== check the compiled Xlib call sequence"
|
||||||
|
func_body "$XHELLO" > "$TMP/xhello.txt"
|
||||||
|
expect_in "$TMP/xhello.txt" "CALLA _XOpenDisplay ($XOPEN_HEX)" \
|
||||||
|
"calla[[:space:]]+l${XOPEN_L}"
|
||||||
|
expect_in "$TMP/xhello.txt" "CALLA _XCreateSimpleWindow ($XCREATE_HEX)" \
|
||||||
|
"calla[[:space:]]+l${XCREATE_L}"
|
||||||
|
expect_in "$TMP/xhello.txt" "CALLA _XMapWindow ($XMAP_HEX)" \
|
||||||
|
"calla[[:space:]]+l${XMAP_L}"
|
||||||
|
expect_in "$TMP/xhello.txt" "CALLA _XSync ($XSYNC_HEX)" \
|
||||||
|
"calla[[:space:]]+l${XSYNC_L}"
|
||||||
|
|
||||||
|
# The arguments are pushed last-first onto the program stack; the first
|
||||||
|
# argument (background=1 for XCreateSimpleWindow) is pushed last.
|
||||||
|
P1=$(count_between "$TMP/xhello.txt" '^[0-9a-f]+:[[:space:]]+setf[[:space:]]+16' \
|
||||||
|
"calla[[:space:]]+l${XOPEN_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
||||||
|
P2=$(count_between "$TMP/xhello.txt" "calla[[:space:]]+l${XOPEN_L}" \
|
||||||
|
"calla[[:space:]]+l${XCREATE_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
||||||
|
P3=$(count_between "$TMP/xhello.txt" "calla[[:space:]]+l${XCREATE_L}" \
|
||||||
|
"calla[[:space:]]+l${XMAP_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
||||||
|
P4=$(count_between "$TMP/xhello.txt" "calla[[:space:]]+l${XMAP_L}" \
|
||||||
|
"calla[[:space:]]+l${XSYNC_L}" 'move[[:space:]]+[^,]+, *\*a14\+, *1')
|
||||||
|
[ "$P1" -eq 1 ] || fail "XOpenDisplay has $P1 argument pushes, want 1"
|
||||||
|
[ "$P2" -eq 9 ] || fail "XCreateSimpleWindow has $P2 argument pushes, want 9"
|
||||||
|
[ "$P3" -eq 2 ] || fail "XMapWindow has $P3 argument pushes, want 2"
|
||||||
|
[ "$P4" -eq 2 ] || fail "XSync has $P4 argument pushes, want 2"
|
||||||
|
echo " ok argument pushes: XOpenDisplay=1 XCreateSimpleWindow=9 XMapWindow=2 XSync=2"
|
||||||
|
|
||||||
|
# Counting pushes is not enough: the nine words must reach the callee in the
|
||||||
|
# ABI order. build/m3/m3.lst records background 1, border 0, border width 2,
|
||||||
|
# height 200, width 400, y 100, then x 100 (x reuses y's register, so there are
|
||||||
|
# six MOVIs for the seven constants), followed by the root (A2) and display
|
||||||
|
# (A0) pushes. Assert the immediates and push registers as one sequence, so a
|
||||||
|
# swapped pair fails even though the count stays nine, and require the final
|
||||||
|
# push before CALLA to be the display register.
|
||||||
|
awk '
|
||||||
|
tolower($0) ~ /calla[[:space:]]+l'"$XOPEN_L"'/ { on = 1; next }
|
||||||
|
tolower($0) ~ /calla[[:space:]]+l'"$XCREATE_L"'/ { exit }
|
||||||
|
!on { next }
|
||||||
|
tolower($0) ~ /move[[:space:]]+a14,[[:space:]]*-\*sp,[[:space:]]*1/ { started = 1; next }
|
||||||
|
!started { next }
|
||||||
|
tolower($0) ~ /movi[[:space:]]+/ {
|
||||||
|
line = $0
|
||||||
|
sub(/^[^:]*:[[:space:]]+movi[[:space:]]+/, "", line)
|
||||||
|
sub(/[[:space:]]*;.*/, "", line)
|
||||||
|
sub(/[[:space:]]+/, " ", line)
|
||||||
|
split(line, p, /,[[:space:]]*/)
|
||||||
|
printf "IMM %s %s\n", tolower(p[1]), tolower(p[2])
|
||||||
|
next
|
||||||
|
}
|
||||||
|
tolower($0) ~ /move[[:space:]]+a[0-9]+,[[:space:]]*\*a14\+,[[:space:]]*1/ {
|
||||||
|
line = $0
|
||||||
|
sub(/^[^:]*:[[:space:]]+move[[:space:]]+/, "", line)
|
||||||
|
sub(/,.*/, "", line)
|
||||||
|
printf "PUSH %s\n", tolower(line)
|
||||||
|
next
|
||||||
|
}
|
||||||
|
' "$TMP/xhello.txt" > "$TMP/xcreate.seq"
|
||||||
|
cat > "$TMP/xcreate.want" <<'EOF'
|
||||||
|
IMM 0x1 a1
|
||||||
|
PUSH a1
|
||||||
|
IMM 0x0 a1
|
||||||
|
PUSH a1
|
||||||
|
IMM 0x2 a3
|
||||||
|
PUSH a3
|
||||||
|
IMM 0xc8 a3
|
||||||
|
PUSH a3
|
||||||
|
IMM 0x190 a3
|
||||||
|
PUSH a3
|
||||||
|
IMM 0x64 a3
|
||||||
|
PUSH a3
|
||||||
|
PUSH a3
|
||||||
|
PUSH a2
|
||||||
|
PUSH a0
|
||||||
|
EOF
|
||||||
|
if ! cmp -s "$TMP/xcreate.want" "$TMP/xcreate.seq"; then
|
||||||
|
{
|
||||||
|
echo "M3 dis34 round-trip: FAIL: XCreateSimpleWindow argument order"
|
||||||
|
echo " expected (background,border,border_width,height,width,y,x,root,display):"
|
||||||
|
sed 's/^/ /' "$TMP/xcreate.want"
|
||||||
|
echo " got:"
|
||||||
|
sed 's/^/ /' "$TMP/xcreate.seq"
|
||||||
|
} >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ok XCreateSimpleWindow argument order: 1,0,2,200,400,100,100,root,display (display pushed last)"
|
||||||
|
|
||||||
|
# Xlib clobbers SETF; the compiler must re-establish 16/FS0 and 32/FS1 after
|
||||||
|
# every call. Check in the linked assembly, where CALLA symbols survive.
|
||||||
|
awk '
|
||||||
|
/^_xhello:/ { on = 1; next }
|
||||||
|
on && /-- End function/ { exit }
|
||||||
|
on && /^[[:space:]]*CALLA/ { pending = 1; next }
|
||||||
|
on && pending && /SETF[[:space:]]+16,[[:space:]]*0,[[:space:]]*0/ { saw16 = 1; next }
|
||||||
|
on && pending && saw16 && /SETF[[:space:]]+32,[[:space:]]*0,[[:space:]]*1/ {
|
||||||
|
n++; pending = 0; saw16 = 0; next
|
||||||
|
}
|
||||||
|
END { print n + 0 }
|
||||||
|
' "$ASM" > "$TMP/setf_count"
|
||||||
|
SETF_COUNT=$(cat "$TMP/setf_count")
|
||||||
|
[ "$SETF_COUNT" -eq 4 ] \
|
||||||
|
|| fail "found $SETF_COUNT post-call SETF 16/32 pairs in _xhello, want 4"
|
||||||
|
echo " ok four post-call SETF 16,0,0 + SETF 32,0,1 pairs"
|
||||||
|
|
||||||
|
# _xhello takes no arguments of its own (the nine-argument call is inside it),
|
||||||
|
# so it returns with RETS 0; the argument-taking branch functions use RETS 2.
|
||||||
|
func_body "$XHELLO" > "$TMP/xhello.txt"
|
||||||
|
expect_in "$TMP/xhello.txt" "_xhello RETS 0" 'rets([[:space:]]+0)?([[:space:]]*(;|$))'
|
||||||
|
for pair in "$SUMTO:_sumto" "$MAX2:_max2" "$LT:_lt"; do
|
||||||
|
addr=${pair%%:*}
|
||||||
|
label=${pair##*:}
|
||||||
|
func_body "$addr" > "$TMP/fn.txt"
|
||||||
|
expect_in "$TMP/fn.txt" "$label RETS 2" 'rets[[:space:]]+2([[:space:]]*(;|$))'
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "== check the branch code"
|
||||||
|
for pair in "$SUMTO:_sumto" "$MAX2:_max2" "$LT:_lt"; do
|
||||||
|
addr=${pair%%:*}
|
||||||
|
label=${pair##*:}
|
||||||
|
func_body "$addr" > "$TMP/fn.txt"
|
||||||
|
if ! grep -Eiq 'cmp' "$TMP/fn.txt"; then
|
||||||
|
sed 's/^/ /' "$TMP/fn.txt" >&2
|
||||||
|
fail "$label has no CMP"
|
||||||
|
fi
|
||||||
|
if ! grep -Eiq '\bjauc\b' "$TMP/fn.txt"; then
|
||||||
|
sed 's/^/ /' "$TMP/fn.txt" >&2
|
||||||
|
fail "$label has no JAUC"
|
||||||
|
fi
|
||||||
|
echo " ok $label: CMP + JAUC"
|
||||||
|
done
|
||||||
|
for pair in "$XHELLO:_xhello"; do
|
||||||
|
addr=${pair%%:*}
|
||||||
|
label=${pair##*:}
|
||||||
|
func_body "$addr" > "$TMP/fn.txt"
|
||||||
|
if ! grep -Eiq '\bjauc\b' "$TMP/fn.txt"; then
|
||||||
|
sed 's/^/ /' "$TMP/fn.txt" >&2
|
||||||
|
fail "$label has no JAUC"
|
||||||
|
fi
|
||||||
|
echo " ok $label: JAUC"
|
||||||
|
done
|
||||||
|
func_body "$BRANCH" > "$TMP/branch.txt"
|
||||||
|
for callee in _sumto _max2 _lt _layout_screens _layout_root; do
|
||||||
|
grep -Eiq "calla[[:space:]]+L$(get_sym "$callee" | xargs printf '%X')" "$TMP/branch.txt" \
|
||||||
|
|| fail "_branch_test does not call $callee"
|
||||||
|
done
|
||||||
|
echo " ok _branch_test calls sumto, max2, lt, layout_screens, layout_root"
|
||||||
|
# Conditional branch opcodes emitted by this compiled image. JALE/JALO/JAHI
|
||||||
|
# coverage lives in the m3-loop/m3-unsigned/m3-cmpi lit tests; this image has
|
||||||
|
# the signed compare, loop-exit and smax branches.
|
||||||
|
for op in jalt jagt jaeq; do
|
||||||
|
grep -Eiq "^[0-9a-f]+:[[:space:]]+$op([[:space:]]|;|$)" "$TMP/dis34.txt" \
|
||||||
|
|| fail "no $op instruction in the linked .text"
|
||||||
|
echo " ok $op present"
|
||||||
|
done
|
||||||
|
|
||||||
|
# GSPA rejects dotted .L labels; the harness gives each TU an LBB prefix.
|
||||||
|
grep -q 'LBB' "$ASM" || fail "linked assembly has no LBB block labels"
|
||||||
|
if grep -q '\.LBB' "$ASM"; then
|
||||||
|
fail "linked assembly contains dotted .LBB labels (GSPA rejects them)"
|
||||||
|
fi
|
||||||
|
echo " ok LBB… block labels (no dot)"
|
||||||
|
|
||||||
|
echo "== check the old-STK restore offsets"
|
||||||
|
check_stk() {
|
||||||
|
_label=$1
|
||||||
|
_addr=$2
|
||||||
|
func_body "$_addr" > "$TMP/func.txt"
|
||||||
|
_pushes=$(awk '
|
||||||
|
tolower($0) ~ /move[[:space:]]+[a-z0-9]+,[[:space:]]*-\*sp,[[:space:]]*1/ { n++ }
|
||||||
|
tolower($0) ~ /move[[:space:]]+a14,[[:space:]]*a13/ { print n; exit }
|
||||||
|
' "$TMP/func.txt")
|
||||||
|
[ -n "$_pushes" ] || fail "$_label has no SETFP (MOVE A14,A13)"
|
||||||
|
_operand=$(grep -Eio '\*sp\(0x[0-9a-f]+\)' "$TMP/func.txt" | head -1 \
|
||||||
|
| sed 's/.*0x//; s/).*//')
|
||||||
|
[ -n "$_operand" ] || fail "$_label has no LOADSTK (*SP(n),A14)"
|
||||||
|
_want=$(( (_pushes + 1) * 32 ))
|
||||||
|
_got=$((0x$_operand))
|
||||||
|
[ "$_got" -eq "$_want" ] \
|
||||||
|
|| fail "$_label restores A14 from *SP(0x$_operand) ($_got bits) but $_pushes pushes need $_want bits"
|
||||||
|
echo " ok $_label: $_pushes pushes -> *SP($(printf '0x%x' "$_want")), A14, 1"
|
||||||
|
}
|
||||||
|
check_stk "_sumto" "$SUMTO"
|
||||||
|
check_stk "_max2" "$MAX2"
|
||||||
|
check_stk "_lt" "$LT"
|
||||||
|
check_stk "_root_probe" "$ROOTPROBE"
|
||||||
|
|
||||||
|
# Negative control for the RETS operand boundary: "rets 1" must not match.
|
||||||
|
printf '%08x:\trets\t1\t; 0961\n' "$SUMTO" > "$TMP/rets-neg.txt"
|
||||||
|
if match_at "$TMP/rets-neg.txt" "$SUMTO" 'rets([[:space:]]+0)?'; then
|
||||||
|
fail "negative control: the RETS pattern accepted 'rets 1'"
|
||||||
|
fi
|
||||||
|
echo " ok negative control @ 0x$(printf '%08x' "$SUMTO"): 'rets 1' rejected"
|
||||||
|
|
||||||
|
echo "== check the copied .data bytes"
|
||||||
|
DATA_HEX=$(od -An -v -tx1 "$TMP/data.bin" | tr -d ' \n')
|
||||||
|
case "$DATA_HEX" in
|
||||||
|
*303a3a3000*) echo " ok .data contains 30 3A 3A 30 00 (\"0::0\")" ;;
|
||||||
|
*) fail ".data does not contain 30 3A 3A 30 00 (got $DATA_HEX)" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "M3 dis34 round-trip: PASS"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
; entry.asm -- M3 entry, linked at bit 018A0000.
|
||||||
|
;
|
||||||
|
; Calls the compiled branch/loop tests and then the first compiled C Xlib
|
||||||
|
; client. Markers (0x20 bits apart):
|
||||||
|
; 018A8000 Display pointer (xhello)
|
||||||
|
; 018A8020 root window (xhello)
|
||||||
|
; 018A8040 window id (xhello)
|
||||||
|
; 018A8060 XSync diagnostic (xhello)
|
||||||
|
; 018A8080 sumto(10) == 55 (branch_test)
|
||||||
|
; 018A80A0 max2(20,22) == 22 (branch_test)
|
||||||
|
; 018A80C0 lt(5,7) == 1 (branch_test)
|
||||||
|
; 018A80E0 layout_screens() == 0x480
|
||||||
|
; 018A8100 layout_root() == 0x40
|
||||||
|
.text
|
||||||
|
.globl _start
|
||||||
|
_start:
|
||||||
|
setf 16, 0, 0
|
||||||
|
setf 32, 0, 1
|
||||||
|
movi _cstack, a14
|
||||||
|
move a14, a13
|
||||||
|
calla _branch_test
|
||||||
|
calla _xhello
|
||||||
|
rets
|
||||||
|
|
||||||
|
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
struct display { char pad[0x90]; void *screens; };
|
||||||
|
struct screen { char pad[0x8]; unsigned int root; };
|
||||||
|
|
||||||
|
/* The 1987 compiler's &-of-member is a bit address, so these return the
|
||||||
|
ROM's bit offsets 0x480/0x40 directly. */
|
||||||
|
int layout_screens() { return (int)&((struct display *)0)->screens; }
|
||||||
|
int layout_root() { return (int)&((struct screen *)0)->root; }
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
*******************************************************
|
||||||
|
* GSP C COMPILER, Version 2.01, 87.300 *
|
||||||
|
*******************************************************
|
||||||
|
FP .set A13
|
||||||
|
STK .set A14
|
||||||
|
.globl _layout_screens
|
||||||
|
.file "LAYOUT.c"
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _layout_screens
|
||||||
|
******************************************************
|
||||||
|
_layout_screens:
|
||||||
|
MOVE A7,-*SP,1
|
||||||
|
MOVI 1152,A7
|
||||||
|
MOVE A7,A8
|
||||||
|
EPI0_1:
|
||||||
|
MOVE *SP+,A7,1
|
||||||
|
RETS 0
|
||||||
|
.globl _layout_root
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _layout_root
|
||||||
|
******************************************************
|
||||||
|
_layout_root:
|
||||||
|
MOVE A7,-*SP,1
|
||||||
|
MOVI 64,A7
|
||||||
|
MOVE A7,A8
|
||||||
|
EPI0_2:
|
||||||
|
MOVE *SP+,A7,1
|
||||||
|
RETS 0
|
||||||
|
.end
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include "vt1200_xlib.h"
|
||||||
|
|
||||||
|
/* The ROM's indirect displacements are bit offsets (xhello.asm):
|
||||||
|
Display.screens at bit 0x480, Screen.root at bit 0x40. C struct offsets
|
||||||
|
are bytes, so assert the byte offsets and return the ROM's bit offsets. */
|
||||||
|
_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)");
|
||||||
|
|
||||||
|
int layout_screens(void) {
|
||||||
|
return (int)__builtin_offsetof(Display, screens) * 8;
|
||||||
|
}
|
||||||
|
int layout_root(void) {
|
||||||
|
return (int)__builtin_offsetof(Screen, root) * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exercise the real access path: vt1200_root() follows Display.screens (bit
|
||||||
|
0x480) and reads Screen.root (bit 0x40). The align-1 loads this generates
|
||||||
|
must select MOVE directly (see m3-unaligned.ll). */
|
||||||
|
unsigned int root_probe(Display *d) { return vt1200_root(d); }
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-a
|
||||||
|
-o M3.OUT
|
||||||
|
-m M3.MAP
|
||||||
|
-e _start
|
||||||
|
M3.obj
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
prog: origin = 018A0000h, length = 8000h
|
||||||
|
ram: origin = 018A9000h, length = 40000h
|
||||||
|
}
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
.text: {} > prog
|
||||||
|
.data: {} > prog
|
||||||
|
.bss: {} > ram
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
-- m3-check.lua -- load the M3 COFF with dwt/loader/loader.lua, read the
|
||||||
|
-- compiled branch/Xlib marker words, and prove a window was mapped by reading
|
||||||
|
-- the white background pixel in VRAM.
|
||||||
|
--
|
||||||
|
-- M3_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.
|
||||||
|
-- XSync is diagnostic only; the pass requires the visible white pixel.
|
||||||
|
--
|
||||||
|
-- The DWT root window is a fine monochrome weave, so one set bit at (150,150)
|
||||||
|
-- cannot distinguish the mapped client window from the root. Require the
|
||||||
|
-- whole 32-bit VRAM word containing (150,150) to be 0xFFFFFFFF, and check a
|
||||||
|
-- second interior word at (250,200) so the client area is proven solid, not
|
||||||
|
-- just a lucky white stripe.
|
||||||
|
|
||||||
|
dofile(os.getenv("M3_LOADER_LUA"))
|
||||||
|
|
||||||
|
local ENDF = tonumber(os.getenv("LOADER_END") or "3400")
|
||||||
|
|
||||||
|
-- Window: XCreateSimpleWindow(display, root, 100, 100, 400, 200, 2, 0, 1).
|
||||||
|
-- The client area starts at (102,102); sample well inside it.
|
||||||
|
local VRAM_BASE = 0x04000000
|
||||||
|
local VRAM_PITCH = 2048 -- VR319: 2048 bits per row
|
||||||
|
local WHITE_WORD = 0xFFFFFFFF
|
||||||
|
local PX, PY = 150, 150
|
||||||
|
local PX2, PY2 = 250, 200
|
||||||
|
|
||||||
|
local frames = 0
|
||||||
|
local checked = false
|
||||||
|
|
||||||
|
local function vram_word(prog, x, y)
|
||||||
|
local bit = VRAM_BASE + y * VRAM_PITCH + x
|
||||||
|
local word = bit - (bit % 32)
|
||||||
|
return prog:read_u32(word), word, bit
|
||||||
|
end
|
||||||
|
|
||||||
|
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 display = prog:read_u32(0x018A8000)
|
||||||
|
local root = prog:read_u32(0x018A8020)
|
||||||
|
local window = prog:read_u32(0x018A8040)
|
||||||
|
local sync = prog:read_u32(0x018A8060)
|
||||||
|
local sum = prog:read_u32(0x018A8080)
|
||||||
|
local maxv = prog:read_u32(0x018A80A0)
|
||||||
|
local ltv = prog:read_u32(0x018A80C0)
|
||||||
|
local screens = prog:read_u32(0x018A80E0)
|
||||||
|
local rootoff = prog:read_u32(0x018A8100)
|
||||||
|
|
||||||
|
-- The full words containing each sample pixel.
|
||||||
|
local pix, word, bit = vram_word(prog, PX, PY)
|
||||||
|
local pix2, word2, bit2 = vram_word(prog, PX2, PY2)
|
||||||
|
local solid = pix == WHITE_WORD
|
||||||
|
local solid2 = pix2 == WHITE_WORD
|
||||||
|
|
||||||
|
print(string.format(
|
||||||
|
"M3: display=%u root=%u window=%u sync=%d sum=%d max=%d lt=%d screens=0x%x root_off=0x%x",
|
||||||
|
display, root, window, sync, sum, maxv, ltv, screens, rootoff))
|
||||||
|
print(string.format(
|
||||||
|
"M3: pixel(%d,%d) bit=0x%08X word=0x%08X value=0x%08X %s",
|
||||||
|
PX, PY, bit, word, pix, solid and "SOLID-WHITE" or "not-solid-white"))
|
||||||
|
print(string.format(
|
||||||
|
"M3: pixel(%d,%d) bit=0x%08X word=0x%08X value=0x%08X %s",
|
||||||
|
PX2, PY2, bit2, word2, pix2, solid2 and "SOLID-WHITE" or "not-solid-white"))
|
||||||
|
|
||||||
|
local ok = display ~= 0 and root ~= 0 and window ~= 0
|
||||||
|
and sum == 55 and maxv == 22 and ltv == 1
|
||||||
|
and screens == 0x480 and rootoff == 0x40
|
||||||
|
and solid and solid2
|
||||||
|
if ok then
|
||||||
|
print("M3 MAME: PASS")
|
||||||
|
else
|
||||||
|
print("M3 MAME: FAIL")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, "m3-check")
|
||||||
Executable
+226
@@ -0,0 +1,226 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 end-to-end test: compiled branches/loops plus the first compiled C Xlib
|
||||||
|
# client, linked with the 1987 GSPA/GSPLNK toolchain and run in MAME.
|
||||||
|
#
|
||||||
|
# scripts/m3/run.sh
|
||||||
|
#
|
||||||
|
# Compiles xhello.c, branch.c and layout.c with scripts/tms-clang -O2 and
|
||||||
|
# llc -O0, assembles/links them with the SDK under DOSBox, loads the COFF
|
||||||
|
# through dwt/loader/loader.lua, and checks:
|
||||||
|
#
|
||||||
|
# 018A8000 Display pointer (nonzero)
|
||||||
|
# 018A8020 root window id (nonzero)
|
||||||
|
# 018A8040 window id (nonzero)
|
||||||
|
# 018A8060 XSync result (diagnostic only)
|
||||||
|
# 018A8080 sumto(10) == 55
|
||||||
|
# 018A80A0 max2(20,22)== 22
|
||||||
|
# 018A80C0 lt(5,7) == 1
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC MAME DOSBOX DOSBOX_CONF
|
||||||
|
# SDK_IMG M3_WORK M3_COFF M3_SDK LOADER_AT LOADER_END
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||||
|
DIR=$ROOT/scripts/m3
|
||||||
|
|
||||||
|
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=${M3_WORK:-$ROOT/build/m3}
|
||||||
|
COFF=${M3_COFF:-$WORK/m3.coff}
|
||||||
|
SDK=${M3_SDK:-$ROOT/build/m3-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:-3400}
|
||||||
|
|
||||||
|
SYMS=${M3_SYMS:-$ROOT/symbols/vt1200-v22.syms}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "m3: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do
|
||||||
|
[ -x "$tool" ] || fail "missing tool: $tool"
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$ASM" "$WORK/evidence" "$WORK/run/nvram" "$WORK/run/cfg"
|
||||||
|
mkdir -p "$(dirname "$COFF")"
|
||||||
|
|
||||||
|
# IndVarSimplify's exit-value replacement turns sumto's loop into an i33
|
||||||
|
# multiply (n*(n+1)/2), and this target has no multiply or i64 support: llc
|
||||||
|
# then dies selecting umul_lohi. Keep the loop (which is the point of the M3
|
||||||
|
# branch test) by disabling only that rewrite, and only for branch.c; xhello
|
||||||
|
# and layout use the normal optimizer pipeline. branch.c itself is unchanged.
|
||||||
|
OPT_NOREPLEXIT=$WORK/opt-keep-loops
|
||||||
|
printf '#!/bin/sh\nexec "%s" -replexitval=never "$@"\n' "$TMS_OPT" > "$OPT_NOREPLEXIT"
|
||||||
|
chmod +x "$OPT_NOREPLEXIT"
|
||||||
|
|
||||||
|
echo "== compile and select"
|
||||||
|
export TMS_CLANG TMS_BITADDR
|
||||||
|
for name in xhello branch layout; do
|
||||||
|
opt=$TMS_OPT
|
||||||
|
[ "$name" = branch ] && opt=$OPT_NOREPLEXIT
|
||||||
|
TMS_OPT=$opt "$ROOT/scripts/tms-clang" -O2 -I "$DIR" -o "$ASM/$name.ll" "$DIR/$name.c"
|
||||||
|
"$TMS_LLC" -march=tms34010 -O0 -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
|
||||||
|
|
||||||
|
# The Xlib calls must be real ROM imports, and the window must use background
|
||||||
|
# pixel 1 (white).
|
||||||
|
grep -q 'CALLA _XOpenDisplay' "$ASM/xhello.s" \
|
||||||
|
|| fail "xhello.s has no 'CALLA _XOpenDisplay'"
|
||||||
|
grep -q 'CALLA _XCreateSimpleWindow' "$ASM/xhello.s" \
|
||||||
|
|| fail "xhello.s has no 'CALLA _XCreateSimpleWindow'"
|
||||||
|
grep -q 'CALLA _XMapWindow' "$ASM/xhello.s" \
|
||||||
|
|| fail "xhello.s has no 'CALLA _XMapWindow'"
|
||||||
|
grep -q 'CALLA _XSync' "$ASM/xhello.s" \
|
||||||
|
|| fail "xhello.s has no 'CALLA _XSync'"
|
||||||
|
# 9 arguments: background=1, border=0, width=2, height=200, width=400, y=100,
|
||||||
|
# x=100, root, display. The first argument (background) is the last pushed,
|
||||||
|
# i.e. the first MOVI before the nine '*A14+' stores.
|
||||||
|
PUSHES=$(awk '
|
||||||
|
/MOVE .*, *\*A14\+, *1/ { n++ }
|
||||||
|
/CALLA/ { if ($0 ~ /_XCreateSimpleWindow/) print n; n = 0 }
|
||||||
|
' "$ASM/xhello.s")
|
||||||
|
[ "$PUSHES" = "9" ] || fail "XCreateSimpleWindow has $PUSHES pushes, want 9"
|
||||||
|
grep -q 'MOVI 1, ' "$ASM/xhello.s" \
|
||||||
|
|| fail "xhello.s has no background pixel 1 (white)"
|
||||||
|
echo " XCreateSimpleWindow: 9 argument pushes, background pixel 1"
|
||||||
|
|
||||||
|
echo "== generate the per-ROM import stubs"
|
||||||
|
"$ROOT/scripts/m2/gen-rom-imports.sh" "$SYMS" "$WORK/rom-imports.asm"
|
||||||
|
for sym in XOpenDisplay XCreateSimpleWindow XMapWindow XSync; do
|
||||||
|
grep -q "^_$sym:" "$WORK/rom-imports.asm" \
|
||||||
|
|| fail "rom-imports.asm has no _$sym stub"
|
||||||
|
done
|
||||||
|
grep -A1 '^_XOpenDisplay:' "$WORK/rom-imports.asm" | grep -qi 'jauc 3F9C2570h' \
|
||||||
|
|| fail "rom-imports.asm _XOpenDisplay stub does not jump to 3F9C2570h"
|
||||||
|
echo " _XOpenDisplay: $(grep -A1 '^_XOpenDisplay:' "$WORK/rom-imports.asm" | tail -1 | sed 's/^[[:space:]]*//')"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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. Concatenating the
|
||||||
|
# three TUs would therefore collide (DUPLICATE DEFINITION). Give each TU its
|
||||||
|
# own LBB prefix; the labels stay LBB… (GSPA rejects dotted .L labels).
|
||||||
|
for pair in branch:B layout:L xhello:X; do
|
||||||
|
src=${pair%%:*}
|
||||||
|
tag=${pair##*:}
|
||||||
|
sed "s/LBB\([0-9][0-9]*_[0-9][0-9]*\)/LBB$tag\1/g" \
|
||||||
|
"$ASM/$src.s" > "$ASM/$src.unique.s"
|
||||||
|
done
|
||||||
|
cat "$DIR/entry.asm" "$ASM/branch.unique.s" "$ASM/layout.unique.s" \
|
||||||
|
"$ASM/xhello.unique.s" "$WORK/rom-imports.asm" > "$SDK/M3.ASM"
|
||||||
|
# The linker command file must keep CRLF line endings.
|
||||||
|
sed 's/\r$//' "$DIR/link.cmd" | sed 's/$/\r/' > "$SDK/M3.CMD"
|
||||||
|
rm -f "$SDK/M3.OBJ" "$SDK/M3.OUT" "$SDK/M3.MAP" "$SDK/M3.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 M3 M3.obj M3.lst -l" \
|
||||||
|
-c "gsplnk M3.cmd" \
|
||||||
|
-c "exit" -exit > "$WORK/dosbox.log" 2>&1 \
|
||||||
|
|| { tail -20 "$WORK/dosbox.log" >&2; fail "DOSBox failed"; }
|
||||||
|
|
||||||
|
[ -f "$SDK/M3.LST" ] || fail "GSPA produced no listing (see $WORK/dosbox.log)"
|
||||||
|
sed 's/\r$//' "$SDK/M3.LST" > "$WORK/m3.lst"
|
||||||
|
grep -q 'No Errors' "$WORK/m3.lst" \
|
||||||
|
|| { grep -i -B2 -A2 'error' "$WORK/m3.lst" >&2 || true; fail "GSPA reported errors"; }
|
||||||
|
grep -q 'No Warnings' "$WORK/m3.lst" \
|
||||||
|
|| { grep -i -B2 -A2 'warning' "$WORK/m3.lst" >&2 || true; fail "GSPA reported warnings"; }
|
||||||
|
[ -f "$SDK/M3.OUT" ] || fail "GSPLNK produced no M3.OUT"
|
||||||
|
sed 's/\r$//' "$SDK/M3.MAP" > "$WORK/m3.map"
|
||||||
|
cp "$SDK/M3.OUT" "$COFF"
|
||||||
|
cp "$SDK/M3.ASM" "$WORK/m3.asm"
|
||||||
|
|
||||||
|
ENTRY=$(awk '$2 == "_start" { print $1; exit }' "$WORK/m3.map")
|
||||||
|
[ "$ENTRY" = "018A0000" ] || fail "linker entry _start is '$ENTRY', want 018A0000"
|
||||||
|
grep -q 'ENTRY POINT SYMBOL: "_start"' "$WORK/m3.map" \
|
||||||
|
|| fail "linker map has no _start entry point"
|
||||||
|
|
||||||
|
BSS_ORIGIN=$(awk '$1 == ".bss" && $2 == "0" { print $3; exit }' "$WORK/m3.map")
|
||||||
|
BSS_LENGTH=$(awk '$1 == ".bss" && $2 == "0" { print $4; exit }' "$WORK/m3.map")
|
||||||
|
[ -n "$BSS_ORIGIN" ] || fail "linker map has no .bss section"
|
||||||
|
[ "$BSS_ORIGIN" = "018A9000" ] \
|
||||||
|
|| fail "linker .bss origin is $BSS_ORIGIN, want 018A9000 (link.cmd ram origin)"
|
||||||
|
[ "$BSS_LENGTH" = "00008000" ] \
|
||||||
|
|| fail ".bss is $BSS_LENGTH bits, expected 00008000 (_cstack)"
|
||||||
|
DATA_ORIGIN=$(awk '$1 == ".data" && $2 == "0" { print $3; exit }' "$WORK/m3.map")
|
||||||
|
DATA_LENGTH=$(awk '$1 == ".data" && $2 == "0" { print $4; exit }' "$WORK/m3.map")
|
||||||
|
[ -n "$DATA_LENGTH" ] || fail "linker map has no .data section"
|
||||||
|
[ "$DATA_LENGTH" != "00000000" ] || fail ".data is empty (no \"0::0\" literal)"
|
||||||
|
echo " linked .text at 018A0000, .data at $DATA_ORIGIN ($DATA_LENGTH bits), .bss at $BSS_ORIGIN ($BSS_LENGTH bits)"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
# Start from empty NVRAM and pin the monitor: the pixel check assumes the real
|
||||||
|
# VR319 2048-bit row pitch, and stale NVRAM must not change the boot state.
|
||||||
|
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" \
|
||||||
|
M3_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 "$DIR/m3-check.lua" -autoboot_delay 0 \
|
||||||
|
> "$WORK/mame.log" 2>&1 \
|
||||||
|
|| { tail -30 "$WORK/mame.log" >&2; fail "MAME run failed"; }
|
||||||
|
|
||||||
|
MARKERS=$(grep -E '^M3: display=' "$WORK/mame.log" | tail -1 || true)
|
||||||
|
[ -n "$MARKERS" ] || { tail -30 "$WORK/mame.log" >&2; fail "MAME produced no M3 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 'placed \.data .* 0 differing' "$WORK/mame.log" \
|
||||||
|
|| fail "COFF loader could not verify the placed .data bytes"
|
||||||
|
grep -q '^M3 MAME: PASS' "$WORK/mame.log" \
|
||||||
|
|| { tail -30 "$WORK/mame.log" >&2; fail "M3 marker/window check failed"; }
|
||||||
|
|
||||||
|
echo "M3 MAME: PASS"
|
||||||
Executable
+65
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 ROM symbol validation: the four Xlib entry points used by the compiled
|
||||||
|
# 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 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]
|
||||||
|
#
|
||||||
|
# Overridable: M3_SYMS DIS34 ROM
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||||
|
SYMS=${1:-${M3_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))
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "m3 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"
|
||||||
|
|
||||||
|
check_sym() { # check_sym NAME EXPECTED_HEX
|
||||||
|
name=$1
|
||||||
|
expected=$2
|
||||||
|
got=$(awk -v n="$name" '$1 == n { print $2; exit }' "$SYMS")
|
||||||
|
[ -n "$got" ] || fail "$SYMS has no $name entry"
|
||||||
|
[ "$got" = "$expected" ] \
|
||||||
|
|| fail "$name is $got, expected $expected (the xhello.asm-proven entry)"
|
||||||
|
|
||||||
|
addr=$((got))
|
||||||
|
[ "$addr" -ge "$ROM_LO" ] && [ "$addr" -le "$ROM_HI" ] \
|
||||||
|
|| fail "$name $got is outside the ROM bit range 3F200000h..3FFFFFFFh"
|
||||||
|
[ $((addr % 16)) -eq 0 ] \
|
||||||
|
|| fail "$name $got is not 16-bit aligned"
|
||||||
|
|
||||||
|
off=$(( (addr - ROM_LO) / 8 ))
|
||||||
|
first=$("$DIS34" -b "$got" -s "$off" "$ROM" 2>/dev/null |
|
||||||
|
awk 'NF && $1 !~ /^L[0-9A-Fa-f]+:$/ { print tolower($1); exit }')
|
||||||
|
[ -n "$first" ] || fail "$name $got disassembled to nothing"
|
||||||
|
case " move mmtm setf cmp addi " in
|
||||||
|
*" $first "*) ;;
|
||||||
|
*) fail "$name $got starts with '$first', not a plausible function prologue" ;;
|
||||||
|
esac
|
||||||
|
echo " ok $name $got: $first ..."
|
||||||
|
}
|
||||||
|
|
||||||
|
check_sym XOpenDisplay 0x3F9C2570
|
||||||
|
check_sym XCreateSimpleWindow 0x3FD41B90
|
||||||
|
check_sym XMapWindow 0x3FD64F60
|
||||||
|
check_sym XSync 0x3FD9F6B0
|
||||||
|
|
||||||
|
echo "m3 validate-syms: PASS (four v2.2 Xlib entries in ROM, aligned, prologues valid)"
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#ifndef VT1200_XLIB_H
|
||||||
|
#define VT1200_XLIB_H
|
||||||
|
|
||||||
|
typedef unsigned int XID;
|
||||||
|
typedef unsigned int Window;
|
||||||
|
|
||||||
|
/* ROM v2.2 layout (xhello.asm). TMS34010 indirect displacements are *bit*
|
||||||
|
offsets: xhello's `*a11(0480h)` / `*a10(040h)` are bit offsets 0x480/0x40,
|
||||||
|
which are byte offsets 0x90/0x8. Cross-checks: the classic 32-bit
|
||||||
|
Screen.root is at byte 8, and the Terminal Manager scales its screen index
|
||||||
|
by 0x280 = sizeof(Screen) (0x50 bytes) * 8. */
|
||||||
|
typedef struct {
|
||||||
|
unsigned char pad[0x90];
|
||||||
|
void *screens;
|
||||||
|
} Display;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char pad[0x8];
|
||||||
|
unsigned int root;
|
||||||
|
} Screen;
|
||||||
|
|
||||||
|
extern Display *XOpenDisplay(const char *name);
|
||||||
|
extern Window XCreateSimpleWindow(Display *display, Window parent,
|
||||||
|
int x, int y, unsigned width,
|
||||||
|
unsigned height, unsigned border_width,
|
||||||
|
unsigned long border,
|
||||||
|
unsigned long background);
|
||||||
|
extern int XMapWindow(Display *display, Window window);
|
||||||
|
extern int XSync(Display *display, int discard);
|
||||||
|
|
||||||
|
/* clang is invoked at -O0 (the optimizer runs separately), which marks every
|
||||||
|
definition noinline. Keep this helper always-inline so layout.c and the
|
||||||
|
Xlib client do not each emit a local _vt1200_root for GSPA/GSPLNK to
|
||||||
|
collide on. */
|
||||||
|
static inline __attribute__((always_inline)) unsigned int
|
||||||
|
vt1200_root(Display *d) {
|
||||||
|
return ((Screen *)((Display *)d)->screens)->root;
|
||||||
|
}
|
||||||
|
|
||||||
|
int layout_screens(void);
|
||||||
|
int layout_root(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#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) {
|
||||||
|
Display *d = XOpenDisplay("0::0");
|
||||||
|
MARK(0) = (unsigned int)d;
|
||||||
|
if (!d)
|
||||||
|
return -1;
|
||||||
|
unsigned int root = vt1200_root(d);
|
||||||
|
MARK(1) = root;
|
||||||
|
Window w = XCreateSimpleWindow(d, root, 100, 100, 400, 200, 2, 0, 1);
|
||||||
|
MARK(2) = (unsigned int)w;
|
||||||
|
XMapWindow(d, w);
|
||||||
|
int s = XSync(d, 0);
|
||||||
|
MARK(3) = (unsigned int)s;
|
||||||
|
return (int)w;
|
||||||
|
}
|
||||||
Executable
+312
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -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")
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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); }
|
||||||
@@ -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
|
||||||
@@ -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); }
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
char *a(void) { return "same"; }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
char *b(void) { return "same"; }
|
||||||
@@ -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
|
||||||
Executable
+190
@@ -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"
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Executable
+226
@@ -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'
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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'
|
||||||
Executable
+104
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Executable
+278
@@ -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)"
|
||||||
@@ -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")
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+133
@@ -0,0 +1,133 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M2 differential TI-compiler ABI check.
|
||||||
|
#
|
||||||
|
# scripts/tests/m2/diff/check.sh
|
||||||
|
#
|
||||||
|
# The stored ti-add2.s/ti-locals.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 same K&R sources.
|
||||||
|
#
|
||||||
|
# The oracle is the TI ABI layout, not instruction selection:
|
||||||
|
#
|
||||||
|
# add2 first argument at FP(-32), second at FP(-64), result in A8, RETS 2
|
||||||
|
# locals a nonzero frame, result in A8, RETS 0
|
||||||
|
#
|
||||||
|
# TI prints the negative offsets as -32/-64; 0FFE0h/0FFC0h are also accepted
|
||||||
|
# because the 1987 assembler may format the same displacement in hex.
|
||||||
|
#
|
||||||
|
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC TI_ADD2 TI_LOCALS
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../../../.." && pwd)
|
||||||
|
DIR=$ROOT/scripts/tests/m2/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_ADD2=${TI_ADD2:-$DIR/ti-add2.s}
|
||||||
|
TI_LOCALS=${TI_LOCALS:-$DIR/ti-locals.s}
|
||||||
|
export TMS_CLANG TMS_OPT TMS_BITADDR
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "M2 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_ADD2" ] || fail "stored TI golden missing: $TI_ADD2"
|
||||||
|
[ -f "$TI_LOCALS" ] || fail "stored TI golden missing: $TI_LOCALS"
|
||||||
|
|
||||||
|
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m2-diff.XXXXXX")
|
||||||
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||||
|
|
||||||
|
# Our add2 is built exactly like the M2 gate: tms-clang -O2, then llc -O0.
|
||||||
|
# locals uses -O0 because at -O2 the optimizer folds `x = 7; return x` to a
|
||||||
|
# constant and no frame is emitted; the TI compiler keeps the frame, so -O0 is
|
||||||
|
# the level at which the ABI frame shape is comparable.
|
||||||
|
echo "== compile our add2.s and locals.s"
|
||||||
|
"$ROOT/scripts/tms-clang" -O2 -o "$TMP/add2.ll" "$DIR/ti-add2.c"
|
||||||
|
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/add2.ll" -o "$TMP/add2.s"
|
||||||
|
"$ROOT/scripts/tms-clang" -O0 -o "$TMP/locals.ll" "$DIR/ti-locals.c"
|
||||||
|
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/locals.ll" -o "$TMP/locals.s"
|
||||||
|
echo " add2.s: $(grep -c . "$TMP/add2.s") lines"
|
||||||
|
echo " locals.s: $(grep -c . "$TMP/locals.s") lines"
|
||||||
|
|
||||||
|
# Ignore our `;` end-of-line comments so a comment cannot satisfy a check.
|
||||||
|
sed 's/;.*//' "$TMP/add2.s" > "$TMP/add2.code"
|
||||||
|
sed 's/;.*//' "$TMP/locals.s" > "$TMP/locals.code"
|
||||||
|
|
||||||
|
require() {
|
||||||
|
_file=$1
|
||||||
|
_pat=$2
|
||||||
|
_what=$3
|
||||||
|
if grep -Eiq "$_pat" "$_file"; then
|
||||||
|
echo " ok $_what"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "M2 diff: FAIL: missing $_what" >&2
|
||||||
|
echo " pattern: $_pat" >&2
|
||||||
|
sed 's/^/ /' "$_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_rets() {
|
||||||
|
require "$1" "^[[:space:]]*RETS[[:space:]]+$2([[:space:]]|;|$)" "$3"
|
||||||
|
}
|
||||||
|
|
||||||
|
# TI golden: the argument reads are direct FP-relative loads.
|
||||||
|
require "$TI_ADD2" 'MOVE[[:space:]]+\*FP\((-32|0FFE0h)\)' \
|
||||||
|
"TI add2 reads the first argument at FP(-32)"
|
||||||
|
require "$TI_ADD2" 'MOVE[[:space:]]+\*FP\((-64|0FFC0h)\)' \
|
||||||
|
"TI add2 reads the second argument at FP(-64)"
|
||||||
|
require "$TI_ADD2" 'MOVE[[:space:]]+[^,]+[[:space:]]*,[[:space:]]*A8([[:space:]]|,|$)' \
|
||||||
|
"TI add2 returns in A8"
|
||||||
|
require_rets "$TI_ADD2" 2 "TI add2 ends with RETS 2"
|
||||||
|
|
||||||
|
require "$TI_LOCALS" '(ADDK|ADDI)[[:space:]]+\+?[1-9][0-9]*[[:space:]]*,[[:space:]]*(STK|A14)' \
|
||||||
|
"TI locals allocates a nonzero frame"
|
||||||
|
require "$TI_LOCALS" 'MOVE[[:space:]]+(STK[[:space:]]*,[[:space:]]*(FP|A13)|A14[[:space:]]*,[[:space:]]*(FP|A13))' \
|
||||||
|
"TI locals establishes FP from STK"
|
||||||
|
require "$TI_LOCALS" 'MOVE[[:space:]]+[^,]+[[:space:]]*,[[:space:]]*A8([[:space:]]|,|$)' \
|
||||||
|
"TI locals returns in A8"
|
||||||
|
require_rets "$TI_LOCALS" 0 "TI locals ends with RETS 0"
|
||||||
|
|
||||||
|
# Our asm: FP is materialised with MOVE A13,A<n>; the argument read is at an
|
||||||
|
# ADDI offset from that register. Check the offset and that the register is
|
||||||
|
# then dereferenced.
|
||||||
|
check_our_arg() {
|
||||||
|
_file=$1
|
||||||
|
_off=$2
|
||||||
|
_desc=$3
|
||||||
|
_reg=$(sed -nE "s/.*ADDI[[:space:]]+($_off)[[:space:]]*,[[:space:]]*A([0-9]+).*/\2/p" \
|
||||||
|
"$_file" | sed -n '1p')
|
||||||
|
if [ -z "$_reg" ]; then
|
||||||
|
echo "M2 diff: FAIL: our add2 has no ADDI $_off frame offset for $_desc" >&2
|
||||||
|
sed 's/^/ /' "$_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
require "$_file" "MOVE[[:space:]]+A13[[:space:]]*,[[:space:]]*A$_reg" \
|
||||||
|
"our add2 materialises FP for $_desc"
|
||||||
|
require "$_file" "MOVE[[:space:]]+\*A$_reg([[:space:]]|,|$)" \
|
||||||
|
"our add2 reads $_desc through the FP register"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_our_arg "$TMP/add2.code" '-32|0FFE0h' "the first argument at FP(-32)"
|
||||||
|
check_our_arg "$TMP/add2.code" '-64|0FFC0h' "the second argument at FP(-64)"
|
||||||
|
require "$TMP/add2.code" 'MOVE[[:space:]]+[^,]+[[:space:]]*,[[:space:]]*A8([[:space:]]|,|$)' \
|
||||||
|
"our add2 returns in A8"
|
||||||
|
require_rets "$TMP/add2.code" 2 "our add2 ends with RETS 2"
|
||||||
|
|
||||||
|
require "$TMP/locals.code" 'ADDI[[:space:]]+\+?[1-9][0-9]*[[:space:]]*,[[:space:]]*A14' \
|
||||||
|
"our locals allocates a nonzero frame"
|
||||||
|
require "$TMP/locals.code" 'MOVE[[:space:]]+A14[[:space:]]*,[[:space:]]*A13' \
|
||||||
|
"our locals establishes FP from STK"
|
||||||
|
require "$TMP/locals.code" 'MOVE[[:space:]]+[^,]+[[:space:]]*,[[:space:]]*A8([[:space:]]|,|$)' \
|
||||||
|
"our locals returns in A8"
|
||||||
|
require_rets "$TMP/locals.code" 0 "our locals ends with RETS 0"
|
||||||
|
|
||||||
|
echo "M2 diff: PASS"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
int add2(a, b) int a; int b; { return a + b; }
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
*******************************************************
|
||||||
|
* GSP C COMPILER, Version 2.01, 87.300 *
|
||||||
|
*******************************************************
|
||||||
|
FP .set A13
|
||||||
|
STK .set A14
|
||||||
|
.globl _add2
|
||||||
|
.file "ADD2.c"
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _add2
|
||||||
|
******************************************************
|
||||||
|
_add2:
|
||||||
|
MMTM SP,A5,A7,FP
|
||||||
|
MOVE STK,FP
|
||||||
|
MOVE *FP(-32),A7,1
|
||||||
|
MOVE *FP(-64),A5,1
|
||||||
|
ADD A5,A7
|
||||||
|
MOVE A7,A8
|
||||||
|
EPI0_1:
|
||||||
|
MOVE *SP(128),STK,1
|
||||||
|
MMFM SP,A5,A7,FP
|
||||||
|
RETS 2
|
||||||
|
.end
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
int locals() { int x; x = 7; return x; }
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
*******************************************************
|
||||||
|
* GSP C COMPILER, Version 2.01, 87.300 *
|
||||||
|
*******************************************************
|
||||||
|
FP .set A13
|
||||||
|
STK .set A14
|
||||||
|
.globl _locals
|
||||||
|
.file "LOCALS.c"
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _locals
|
||||||
|
******************************************************
|
||||||
|
_locals:
|
||||||
|
MMTM SP,A12,FP
|
||||||
|
MOVE STK,FP
|
||||||
|
ADDK 32,STK
|
||||||
|
MOVK 7,A12
|
||||||
|
MOVE A12,*FP,1
|
||||||
|
MOVE *FP,A8,1
|
||||||
|
EPI0_1:
|
||||||
|
SUBK 32,STK
|
||||||
|
MMFM SP,A12,FP
|
||||||
|
RETS 0
|
||||||
|
.end
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M2 test gate: rebuild the linked COFF through the normal GSPA/GSPLNK path,
|
||||||
|
# then verify dis34 decodes the TI-ABI instructions at the linked addresses.
|
||||||
|
set -eu
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||||
|
|
||||||
|
# Always rebuild: a stale build/m2/m2.coff would let an asm-encoding
|
||||||
|
# regression pass without ever touching the compiler or assembler. Keep
|
||||||
|
# dis-roundtrip.sh pointed at the file this run produces, including when
|
||||||
|
# M2_WORK/M2_COFF are overridden.
|
||||||
|
WORK=${M2_WORK:-$ROOT/build/m2}
|
||||||
|
M2_COFF=${M2_COFF:-$WORK/m2.coff}
|
||||||
|
export M2_COFF
|
||||||
|
|
||||||
|
"$ROOT/scripts/m2/run.sh" "$@"
|
||||||
|
exec "$ROOT/scripts/m2/dis-roundtrip.sh" "$@"
|
||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M2 test gate: compile five C functions, assemble/link with GSPA/GSPLNK,
|
||||||
|
# load the COFF in MAME and check the TI-ABI marker words and ROM strlen call.
|
||||||
|
set -eu
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||||
|
exec "$ROOT/scripts/m2/run.sh" "$@"
|
||||||
Executable
+268
@@ -0,0 +1,268 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 differential TI-compiler control-flow/Xlib check.
|
||||||
|
#
|
||||||
|
# scripts/tests/m3/diff/check.sh
|
||||||
|
#
|
||||||
|
# The stored ti-branch.s/ti-xhello.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. Each requirement
|
||||||
|
# accepts the TI spellings and ours:
|
||||||
|
#
|
||||||
|
# sumto a conditional branch, an unconditional loop back-edge, and a last
|
||||||
|
# RETS of RETS 2
|
||||||
|
# max2 a conditional branch, and a last RETS of RETS 2
|
||||||
|
# xhello nine argument pushes before _XCreateSimpleWindow, and the ROM bit
|
||||||
|
# displacements 0x480 (Display.screens) / 0x40 (Screen.root)
|
||||||
|
#
|
||||||
|
# This check does not verify the result register, the argument order, or the
|
||||||
|
# "background first, display last" sequence: the MAME gate checks the returned
|
||||||
|
# values, and dis-roundtrip.sh checks the push order.
|
||||||
|
#
|
||||||
|
# TI emits JRLE/JRUC where we emit JAxx/JAUC; both are accepted. TI prints the
|
||||||
|
# displacements as decimal 1152/64, so 480h/40h are accepted too. A
|
||||||
|
# displacement only counts inside the load that uses it: a bare 64/1152
|
||||||
|
# elsewhere in the body (an unrelated local at *FP(64), the constant 0x400,
|
||||||
|
# an 0x4800 that merely shares a prefix) is not evidence.
|
||||||
|
#
|
||||||
|
# Our branch.c is built exactly like the M3 gate: tms-clang -O2 with the
|
||||||
|
# -replexitval=never optimizer (that rewrite would turn sumto's loop into an
|
||||||
|
# i33 multiply, which the target cannot select), then llc -O0. Dropping
|
||||||
|
# -replexitval=never makes this check fail to compile the loop, not pass.
|
||||||
|
#
|
||||||
|
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC TI_BRANCH TI_XHELLO
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../../../.." && pwd)
|
||||||
|
DIR=$ROOT/scripts/tests/m3/diff
|
||||||
|
SRC=$ROOT/scripts/m3
|
||||||
|
|
||||||
|
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_BRANCH=${TI_BRANCH:-$DIR/ti-branch.s}
|
||||||
|
TI_XHELLO=${TI_XHELLO:-$DIR/ti-xhello.s}
|
||||||
|
export TMS_CLANG TMS_OPT TMS_BITADDR
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "M3 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_BRANCH" ] || fail "stored TI golden missing: $TI_BRANCH"
|
||||||
|
[ -f "$TI_XHELLO" ] || fail "stored TI golden missing: $TI_XHELLO"
|
||||||
|
|
||||||
|
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m3-diff.XXXXXX")
|
||||||
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||||
|
|
||||||
|
# branch.c is generated through a wrapper so only this file keeps its loop.
|
||||||
|
OPT_NOREPLEXIT=$TMP/opt-noreplexit
|
||||||
|
printf '#!/bin/sh\nexec "%s" -replexitval=never "$@"\n' "$TMS_OPT" > "$OPT_NOREPLEXIT"
|
||||||
|
chmod +x "$OPT_NOREPLEXIT"
|
||||||
|
|
||||||
|
echo "== compile our branch.s and xhello.s"
|
||||||
|
TMS_OPT=$OPT_NOREPLEXIT "$ROOT/scripts/tms-clang" -O2 -I "$SRC" \
|
||||||
|
-o "$TMP/branch.ll" "$SRC/branch.c"
|
||||||
|
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/branch.ll" -o "$TMP/branch.s"
|
||||||
|
"$ROOT/scripts/tms-clang" -O2 -I "$SRC" \
|
||||||
|
-o "$TMP/xhello.ll" "$SRC/xhello.c"
|
||||||
|
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/xhello.ll" -o "$TMP/xhello.s"
|
||||||
|
echo " branch.s: $(grep -c . "$TMP/branch.s") lines"
|
||||||
|
echo " xhello.s: $(grep -c . "$TMP/xhello.s") lines"
|
||||||
|
|
||||||
|
# Our llc output has `;` end-of-line comments (`; %bb.0`). Strip them so a
|
||||||
|
# comment cannot satisfy a check; TI asm has none.
|
||||||
|
for f in "$TMP/branch.s" "$TMP/xhello.s"; do
|
||||||
|
sed 's/;.*//' "$f" > "$f.code"
|
||||||
|
done
|
||||||
|
|
||||||
|
# body FILE FUNC: from `_func:` to the next .globl/.end.
|
||||||
|
body() {
|
||||||
|
awk -v want="_$2:" '
|
||||||
|
$1 == want { infn = 1; next }
|
||||||
|
infn && ($1 == ".globl" || $1 == ".end") { exit }
|
||||||
|
infn { print }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
require() {
|
||||||
|
_file=$1
|
||||||
|
_pat=$2
|
||||||
|
_what=$3
|
||||||
|
if grep -Eiq "$_pat" "$_file"; then
|
||||||
|
echo " ok $_what"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "M3 diff: FAIL: missing $_what" >&2
|
||||||
|
echo " pattern: $_pat" >&2
|
||||||
|
sed 's/^/ /' "$_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# require_last_rets2 FILE WHAT: the last RETS instruction in the body (the
|
||||||
|
# epilogue) is RETS 2. Matching any RETS 2 elsewhere would let a wrong
|
||||||
|
# epilogue through.
|
||||||
|
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 "M3 diff: FAIL: $2 (last RETS is not RETS 2)" >&2
|
||||||
|
sed 's/^/ /' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# A conditional branch: JA/JR plus a condition suffix. JAUC/JRUC/JRU do not
|
||||||
|
# match because their suffix is U/UC.
|
||||||
|
COND='^[[:space:]]*J[A-Z]*(EQ|NE|LT|LE|GT|GE|V|NV|P|N|Z|NZ|PL|MI)[[:space:]]'
|
||||||
|
# An unconditional branch: our JAUC, TI's JRUC (and bare JRU).
|
||||||
|
UNCOND='^[[:space:]]*J(AUC|RUC|RU|UMP)[[:space:]]'
|
||||||
|
|
||||||
|
# has_backedge FILE: true when a branch targets a label defined earlier in the
|
||||||
|
# same function body. TI closes the loop with the conditional JRLE L4; we
|
||||||
|
# close it with the unconditional JAUC LBB0_1, so either form counts.
|
||||||
|
has_backedge() {
|
||||||
|
awk '
|
||||||
|
/^[A-Za-z_][A-Za-z0-9_]*:/ {
|
||||||
|
lbl = $1
|
||||||
|
sub(/:$/, "", lbl)
|
||||||
|
pos[lbl] = NR
|
||||||
|
next
|
||||||
|
}
|
||||||
|
/^[[:space:]]*J/ {
|
||||||
|
if ($2 in pos && pos[$2] < NR)
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
END { exit found ? 0 : 1 }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_branch() { # check_branch FILE LABEL
|
||||||
|
_f=$1
|
||||||
|
body "$_f" sumto > "$TMP/sumto.body"
|
||||||
|
body "$_f" max2 > "$TMP/max2.body"
|
||||||
|
require "$TMP/sumto.body" "$COND" "$2: sumto has a conditional branch"
|
||||||
|
require "$TMP/sumto.body" "$UNCOND" "$2: sumto has an unconditional branch"
|
||||||
|
if ! has_backedge "$TMP/sumto.body"; then
|
||||||
|
echo "M3 diff: FAIL: $2: sumto has no loop back-edge" >&2
|
||||||
|
sed 's/^/ /' "$TMP/sumto.body" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ok $2: sumto branches back to the loop"
|
||||||
|
require_last_rets2 "$TMP/sumto.body" "$2: sumto ends with RETS 2"
|
||||||
|
require "$TMP/max2.body" "$COND" "$2: max2 has a conditional branch"
|
||||||
|
require_last_rets2 "$TMP/max2.body" "$2: max2 ends with RETS 2"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "== ABI shape: TI golden"
|
||||||
|
check_branch "$TI_BRANCH" "TI"
|
||||||
|
echo "== ABI shape: our compiler"
|
||||||
|
check_branch "$TMP/branch.s.code" "ours"
|
||||||
|
|
||||||
|
# Count the argument pushes since the previous CALLA, then require nine for
|
||||||
|
# XCreateSimpleWindow. Both compilers push the last argument first: display,
|
||||||
|
# root, x, y, width, height, border_width, border, background.
|
||||||
|
count_xcreate_pushes() {
|
||||||
|
awk '
|
||||||
|
/^_xhello:/ { infn = 1; n = 0; next }
|
||||||
|
!infn { next }
|
||||||
|
/CALLA[[:space:]]+_XCreateSimpleWindow/ { print n; exit }
|
||||||
|
/CALLA/ { n = 0; next }
|
||||||
|
/MOVE[[:space:]]+[^,]+,[[:space:]]*\*(STK|A14)\+/ { n++ }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
for pair in "$TI_XHELLO:TI" "$TMP/xhello.s.code:ours"; do
|
||||||
|
f=${pair%%:*}
|
||||||
|
who=${pair##*:}
|
||||||
|
pushes=$(count_xcreate_pushes "$f")
|
||||||
|
[ -n "$pushes" ] || fail "$who xhello has no CALLA _XCreateSimpleWindow"
|
||||||
|
[ "$pushes" = "9" ] \
|
||||||
|
|| fail "$who XCreateSimpleWindow has $pushes argument pushes, want 9"
|
||||||
|
echo " ok $who: XCreateSimpleWindow takes nine argument pushes"
|
||||||
|
done
|
||||||
|
|
||||||
|
# TI: Display.screens is the indexed load `MOVE *A7(1152),A7,1`; Screen.root
|
||||||
|
# is loaded by `MOVE *A7(64),*FP(32),1`. The closing paren and the comma keep
|
||||||
|
# the boundary exact, so 0x4800/0x400 and decimal 11520/640 do not match; the
|
||||||
|
# register names may vary.
|
||||||
|
TI_SCREENS='^[[:space:]]*MOVE[[:space:]]+\*(A[0-9]+|FP|SP)\((1152|0x480|480h)\),'
|
||||||
|
TI_ROOT='^[[:space:]]*MOVE[[:space:]]+\*(A[0-9]+|FP|SP)\((64|0x40|40h)\),[[:space:]]*\*(A[0-9]+|FP|SP)\([0-9]+\)'
|
||||||
|
|
||||||
|
# has_addi_load FILE screens|root: our displacement is materialised by
|
||||||
|
# `ADDI <disp>, A<n>` and then used by a load through that same register
|
||||||
|
# (`MOVE *A<n>,...`). Only that pairing counts, not any ADDI in the body.
|
||||||
|
has_addi_load() {
|
||||||
|
awk -v kind="$2" '
|
||||||
|
function disp_ok(d) {
|
||||||
|
if (kind == "screens")
|
||||||
|
return d == "1152" || d == "0x480" || d == "480h"
|
||||||
|
return d == "64" || d == "0x40" || d == "40h"
|
||||||
|
}
|
||||||
|
/^[[:space:]]*ADDI[[:space:]]/ {
|
||||||
|
line = $0
|
||||||
|
sub(/^[[:space:]]*ADDI[[:space:]]+/, "", line)
|
||||||
|
split(line, f, /[[:space:]]*,[[:space:]]*/)
|
||||||
|
disp = f[1]
|
||||||
|
reg = f[2]
|
||||||
|
if (getline nxt <= 0)
|
||||||
|
next
|
||||||
|
while (nxt ~ /^[[:space:]]*$/) {
|
||||||
|
if (getline nxt <= 0)
|
||||||
|
next
|
||||||
|
}
|
||||||
|
if (disp_ok(disp) &&
|
||||||
|
nxt ~ ("^[[:space:]]*MOVE[[:space:]]+\\*" reg "([[:space:],]|$)"))
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
END { exit found ? 0 : 1 }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_offsets() { # check_offsets FILE WHO ti|ours
|
||||||
|
_f=$1
|
||||||
|
_who=$2
|
||||||
|
_side=$3
|
||||||
|
body "$_f" xhello > "$TMP/xhello.body"
|
||||||
|
if [ "$_side" = ti ]; then
|
||||||
|
require "$TMP/xhello.body" "$TI_SCREENS" \
|
||||||
|
"$_who: Display.screens is loaded at bit 0x480 (*A?(1152))"
|
||||||
|
require "$TMP/xhello.body" "$TI_ROOT" \
|
||||||
|
"$_who: Screen.root is loaded at bit 0x40 (*A?(64),*A?(...))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if has_addi_load "$TMP/xhello.body" screens; then
|
||||||
|
echo " ok $_who: Display.screens is loaded at bit 0x480 (ADDI 1152 + load)"
|
||||||
|
else
|
||||||
|
echo "M3 diff: FAIL: missing $_who: Display.screens load at bit 0x480" >&2
|
||||||
|
sed 's/^/ /' "$TMP/xhello.body" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if has_addi_load "$TMP/xhello.body" root; then
|
||||||
|
echo " ok $_who: Screen.root is loaded at bit 0x40 (ADDI 64 + load)"
|
||||||
|
else
|
||||||
|
echo "M3 diff: FAIL: missing $_who: Screen.root load at bit 0x40" >&2
|
||||||
|
sed 's/^/ /' "$TMP/xhello.body" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "== ROM bit displacements"
|
||||||
|
check_offsets "$TI_XHELLO" "TI" ti
|
||||||
|
check_offsets "$TMP/xhello.s.code" "ours" ours
|
||||||
|
|
||||||
|
echo "M3 diff: PASS"
|
||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Negative controls for scripts/tests/m3/diff/check.sh.
|
||||||
|
#
|
||||||
|
# scripts/tests/m3/diff/negative-control.sh
|
||||||
|
#
|
||||||
|
# The differential check must reject a TI golden whose real Screen.root or
|
||||||
|
# Display.screens displacement is wrong. This is a guard on the oracle
|
||||||
|
# itself: an earlier check matched `64`/`1152` anywhere in the function body,
|
||||||
|
# so the unrelated local `*FP(64)` (and the prefix `0x400`) satisfied it even
|
||||||
|
# after the real load was changed to 512. Each mutation below must make
|
||||||
|
# check.sh fail; the stored goldens are never modified.
|
||||||
|
#
|
||||||
|
# Overridable: the same variables as check.sh.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m3-diff-neg.XXXXXX")
|
||||||
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||||
|
|
||||||
|
expect_fail() { # expect_fail WHAT MUTATED_GOLDEN
|
||||||
|
if TI_XHELLO=$2 "$DIR/check.sh" > "$TMP/out" 2>&1; then
|
||||||
|
echo "M3 diff negative control: FAIL: check.sh accepted $1" >&2
|
||||||
|
sed 's/^/ /' "$TMP/out" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ok negative control: $1 rejected"
|
||||||
|
grep 'M3 diff: FAIL' "$TMP/out" | sed 's/^/ /'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Screen.root: *A7(64),*FP(32),1 -> *A7(512),*FP(32),1
|
||||||
|
sed 's/\*A7(64),\*FP(32)/*A7(512),*FP(32)/' \
|
||||||
|
"$DIR/ti-xhello.s" > "$TMP/root-512.s"
|
||||||
|
grep -q '\*A7(512),\*FP(32)' "$TMP/root-512.s" \
|
||||||
|
|| { echo "M3 diff negative control: mutation did not apply" >&2; exit 1; }
|
||||||
|
expect_fail "Screen.root displacement 512" "$TMP/root-512.s"
|
||||||
|
|
||||||
|
# Display.screens: *A7(1152) -> *A7(512)
|
||||||
|
sed 's/\*A7(1152)/*A7(512)/' \
|
||||||
|
"$DIR/ti-xhello.s" > "$TMP/screens-512.s"
|
||||||
|
grep -q '\*A7(512)' "$TMP/screens-512.s" \
|
||||||
|
|| { echo "M3 diff negative control: mutation did not apply" >&2; exit 1; }
|
||||||
|
expect_fail "Display.screens displacement 512" "$TMP/screens-512.s"
|
||||||
|
|
||||||
|
# Boundary: *A7(1152) -> *A7(0x4800), which shares the 0x480 prefix.
|
||||||
|
sed 's/\*A7(1152)/*A7(0x4800)/' \
|
||||||
|
"$DIR/ti-xhello.s" > "$TMP/screens-4800.s"
|
||||||
|
grep -q '\*A7(0x4800)' "$TMP/screens-4800.s" \
|
||||||
|
|| { echo "M3 diff negative control: mutation did not apply" >&2; exit 1; }
|
||||||
|
expect_fail "Display.screens displacement 0x4800 (hex boundary)" "$TMP/screens-4800.s"
|
||||||
|
|
||||||
|
echo "M3 diff negative controls: PASS"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/* K&R source for the M3 differential golden: the same two functions as
|
||||||
|
scripts/m3/branch.c, compiled by the 1987 TI compiler. */
|
||||||
|
int sumto(n) int n; { int s; int i; s = 0; for (i = 1; i <= n; i++) s = s + i; return s; }
|
||||||
|
int max2(a, b) int a; int b; { if (a > b) return a; return b; }
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
*******************************************************
|
||||||
|
* GSP C COMPILER, Version 2.01, 87.300 *
|
||||||
|
*******************************************************
|
||||||
|
FP .set A13
|
||||||
|
STK .set A14
|
||||||
|
.globl _sumto
|
||||||
|
.file "BRANCH.c"
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _sumto
|
||||||
|
******************************************************
|
||||||
|
_sumto:
|
||||||
|
MMTM SP,A5,A7,A12,FP
|
||||||
|
MOVE STK,FP
|
||||||
|
ADDI 64,STK
|
||||||
|
CLR A12
|
||||||
|
MOVE A12,*FP,1
|
||||||
|
ADDK 1,A12
|
||||||
|
MOVE A12,*FP(32),1
|
||||||
|
JRUC L1
|
||||||
|
L4:
|
||||||
|
MOVE *FP(32),A7,1
|
||||||
|
MOVE *FP,A5,1
|
||||||
|
ADD A7,A5
|
||||||
|
MOVE A5,*FP,1
|
||||||
|
ADDK 1,A7
|
||||||
|
MOVE A7,*FP(32),1
|
||||||
|
L1:
|
||||||
|
MOVE *FP(32),A7,1
|
||||||
|
MOVE *FP(-32),A5,1
|
||||||
|
CMP A5,A7
|
||||||
|
JRLE L4
|
||||||
|
MOVE *FP,A8,1
|
||||||
|
EPI0_1:
|
||||||
|
MOVE *SP(160),STK,1
|
||||||
|
MMFM SP,A5,A7,A12,FP
|
||||||
|
RETS 2
|
||||||
|
.globl _max2
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _max2
|
||||||
|
******************************************************
|
||||||
|
_max2:
|
||||||
|
MMTM SP,A5,A7,FP
|
||||||
|
MOVE STK,FP
|
||||||
|
MOVE *FP(-32),A7,1
|
||||||
|
MOVE *FP(-64),A5,1
|
||||||
|
CMP A5,A7
|
||||||
|
JRLE L5
|
||||||
|
MOVE A7,A8
|
||||||
|
JRUC EPI0_2
|
||||||
|
L5:
|
||||||
|
MOVE A5,A8
|
||||||
|
EPI0_2:
|
||||||
|
MOVE *SP(128),STK,1
|
||||||
|
MMFM SP,A5,A7,FP
|
||||||
|
RETS 2
|
||||||
|
.end
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/* K&R source for the M3 differential golden: the same four-call Xlib client
|
||||||
|
as scripts/m3/xhello.c, compiled by the 1987 TI compiler.
|
||||||
|
|
||||||
|
The ROM's Display/Screen fields are at bit displacements 0x480/0x40, i.e.
|
||||||
|
byte offsets 0x90/0x8; the 1987 compiler's &-of-member arithmetic is in
|
||||||
|
bits, so it emits 1152 and 64 for these pads. */
|
||||||
|
struct display {
|
||||||
|
char pad[0x90];
|
||||||
|
void *screens;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct screen {
|
||||||
|
char pad[0x8];
|
||||||
|
unsigned int root;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct display *XOpenDisplay();
|
||||||
|
extern int XCreateSimpleWindow();
|
||||||
|
extern int XMapWindow();
|
||||||
|
extern int XSync();
|
||||||
|
|
||||||
|
int xhello()
|
||||||
|
{
|
||||||
|
struct display *d;
|
||||||
|
unsigned int root;
|
||||||
|
int w;
|
||||||
|
int s;
|
||||||
|
|
||||||
|
d = XOpenDisplay("0::0");
|
||||||
|
if (!d)
|
||||||
|
return -1;
|
||||||
|
root = ((struct screen *)d->screens)->root;
|
||||||
|
w = XCreateSimpleWindow(d, root, 100, 100, 400, 200, 2, 0, 1);
|
||||||
|
XMapWindow(d, w);
|
||||||
|
s = XSync(d, 0);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
*******************************************************
|
||||||
|
* GSP C COMPILER, Version 2.01, 87.300 *
|
||||||
|
*******************************************************
|
||||||
|
FP .set A13
|
||||||
|
STK .set A14
|
||||||
|
.globl _XOpenDisplay
|
||||||
|
.globl _XCreateSimpleWindow
|
||||||
|
.globl _XMapWindow
|
||||||
|
.globl _XSync
|
||||||
|
.globl _xhello
|
||||||
|
.file "XHELLO.c"
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _xhello
|
||||||
|
******************************************************
|
||||||
|
_xhello:
|
||||||
|
MMTM SP,A7,FP
|
||||||
|
MOVE STK,FP
|
||||||
|
ADDI 128,STK
|
||||||
|
MOVE STK,-*SP,1
|
||||||
|
MOVI SL1,A8
|
||||||
|
MOVE A8,*STK+,1
|
||||||
|
CALLA _XOpenDisplay
|
||||||
|
MOVE A8,*FP,1
|
||||||
|
MOVE *FP,A7,1
|
||||||
|
JRNZ L1
|
||||||
|
MOVI -1,A8
|
||||||
|
JRUC EPI0_1
|
||||||
|
L1:
|
||||||
|
MOVE *A7(1152),A7,1
|
||||||
|
MOVE *A7(64),*FP(32),1
|
||||||
|
MOVE STK,-*SP,1
|
||||||
|
MOVK 1,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
SUB A7,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
ADDK 2,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVI 200,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVI 400,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVI 100,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVE *FP(32),*STK+,1
|
||||||
|
MOVE *FP(0),*STK+,1
|
||||||
|
CALLA _XCreateSimpleWindow
|
||||||
|
MOVE A8,*FP(64),1
|
||||||
|
MOVE STK,-*SP,1
|
||||||
|
MOVE *FP(64),*STK+,1
|
||||||
|
MOVE *FP(0),*STK+,1
|
||||||
|
CALLA _XMapWindow
|
||||||
|
MOVE STK,-*SP,1
|
||||||
|
CLR A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVE *FP(0),*STK+,1
|
||||||
|
CALLA _XSync
|
||||||
|
MOVE A8,*FP(96),1
|
||||||
|
MOVE *FP(64),A8,1
|
||||||
|
EPI0_1:
|
||||||
|
ADDI -128,STK
|
||||||
|
MMFM SP,A7,FP
|
||||||
|
RETS 0
|
||||||
|
******************************************************
|
||||||
|
* DEFINE STRING CONSTANTS *
|
||||||
|
******************************************************
|
||||||
|
.text
|
||||||
|
SL1: .string "0::0",0
|
||||||
|
.end
|
||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 test gate: rebuild the linked COFF through the normal GSPA/GSPLNK path,
|
||||||
|
# then verify dis34 decodes the Xlib call sequence, branch code and old-STK
|
||||||
|
# restore offsets at the linked addresses.
|
||||||
|
set -eu
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||||
|
|
||||||
|
# Always rebuild: a stale build/m3/m3.coff would let an assembler/compiler
|
||||||
|
# regression pass without ever being reassembled. Keep dis-roundtrip.sh
|
||||||
|
# pointed at the file this run produces, including when M3_WORK/M3_COFF are
|
||||||
|
# overridden.
|
||||||
|
WORK=${M3_WORK:-$ROOT/build/m3}
|
||||||
|
M3_COFF=${M3_COFF:-$WORK/m3.coff}
|
||||||
|
export M3_COFF
|
||||||
|
|
||||||
|
"$ROOT/scripts/m3/run.sh" "$@"
|
||||||
|
exec "$ROOT/scripts/m3/dis-roundtrip.sh" "$@"
|
||||||
Executable
+92
@@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 layout gate: Display.screens and Screen.root must be at the ROM's bit
|
||||||
|
# displacements 0x480 and 0x40.
|
||||||
|
#
|
||||||
|
# The ROM's indirect displacements are *bit* offsets (TMS34010), so those
|
||||||
|
# displacements are byte offsets 0x90 and 0x8. layout.c asserts the byte
|
||||||
|
# layout and returns the bit displacements; layout-ti.c does the same through
|
||||||
|
# the 1987 compiler's bit-addressed &-of-member. Both asm files must carry
|
||||||
|
# 0x480 / 0x40 (480h, 1152, ...).
|
||||||
|
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=${M3_LAYOUT_WORK:-$ROOT/build/m3-layout}
|
||||||
|
GOLDEN=$ROOT/scripts/m3/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 the _Static_asserts 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/m3/layout.c" \
|
||||||
|
|| fail "target compile failed (Display/Screen static asserts?)"
|
||||||
|
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm \
|
||||||
|
"$WORK/layout.ll" -o "$WORK/layout.s" \
|
||||||
|
|| fail "llc failed for layout.ll"
|
||||||
|
|
||||||
|
# Print the body of one function, from its label to the next .globl/.end.
|
||||||
|
body() { # body FILE FUNC
|
||||||
|
awk -v want="_$2:" '
|
||||||
|
$1 == want { infn = 1; next }
|
||||||
|
infn && ($1 == ".globl" || $1 == ".end") { exit }
|
||||||
|
infn { 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
|
||||||
|
}
|
||||||
|
|
||||||
|
HEX480='(0x480|480h|[^0-9a-f]1152[^0-9a-f])'
|
||||||
|
HEX40='(0x40|40h|[^0-9a-f]64[^0-9a-f])'
|
||||||
|
|
||||||
|
check "$WORK/layout.s" layout_screens "$HEX480" "0x480 (Display.screens)"
|
||||||
|
check "$WORK/layout.s" layout_root "$HEX40" "0x40 (Screen.root)"
|
||||||
|
check "$GOLDEN" layout_screens "$HEX480" "0x480 (Display.screens)"
|
||||||
|
check "$GOLDEN" layout_root "$HEX40" "0x40 (Screen.root)"
|
||||||
|
|
||||||
|
# root_probe exercises the real access path: vt1200_root() follows
|
||||||
|
# Display.screens (bit 0x480) and reads Screen.root (bit 0x40). The helper is
|
||||||
|
# always_inline, so the incoming align-1 loads select MOVE directly in
|
||||||
|
# root_probe (see m3-unaligned.ll). Require both displacements and reject a
|
||||||
|
# MOVB byte split.
|
||||||
|
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-pad mistake: 0x480*8 / 0x40*8 (9216 / 512)
|
||||||
|
# means the struct was padded in bytes past the ROM's bit displacements.
|
||||||
|
if body "$GOLDEN" layout_screens | tr 'A-Z' 'a-z' |
|
||||||
|
grep -Eq '(^|[^0-9])(9216|2400h)([^0-9a-f]|$)' ||
|
||||||
|
body "$GOLDEN" layout_root | tr 'A-Z' 'a-z' |
|
||||||
|
grep -Eq '(^|[^0-9])(512|200h)([^0-9a-f]|$)'; then
|
||||||
|
fail "TI golden carries byte-pad offsets (9216/512); ROM displacements are bits 0x480/0x40"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "layout.sh: PASS (Display.screens=0x480, Screen.root=0x40; target + TI golden)"
|
||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 test gate: validate the Display/Screen layout and ROM entries, compile the
|
||||||
|
# branch/loop tests and the first C Xlib client, assemble/link with GSPA/GSPLNK,
|
||||||
|
# load the COFF in MAME, and require the branch markers plus a solid white
|
||||||
|
# window area.
|
||||||
|
set -eu
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||||
|
|
||||||
|
# These two checks are part of the gate, not manual steps: layout.sh proves the
|
||||||
|
# 0x480/0x40 root probe still selects the ROM displacements, and
|
||||||
|
# validate-syms.sh proves the four Xlib imports are still the v2.2 entries.
|
||||||
|
"$ROOT/scripts/tests/m3/layout.sh"
|
||||||
|
"$ROOT/scripts/m3/validate-syms.sh"
|
||||||
|
|
||||||
|
exec "$ROOT/scripts/m3/run.sh" "$@"
|
||||||
Executable
+568
@@ -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"
|
||||||
@@ -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
|
||||||
Executable
+20
@@ -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"
|
||||||
Executable
+159
@@ -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"
|
||||||
Executable
+148
@@ -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)"
|
||||||
Executable
+107
@@ -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"
|
||||||
Executable
+54
@@ -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"
|
||||||
Executable
+75
@@ -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)"
|
||||||
Executable
+54
@@ -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)"
|
||||||
Executable
+104
@@ -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)"
|
||||||
Executable
+79
@@ -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"
|
||||||
Executable
+32
@@ -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)"
|
||||||
+17
-2
@@ -43,12 +43,27 @@ trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
|||||||
|
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
"$TMS_CLANG" --target=tms34010-unknown-none -O0 -Xclang -disable-O0-optnone \
|
"$TMS_CLANG" --target=tms34010-unknown-none -O0 -Xclang -disable-O0-optnone \
|
||||||
-S -emit-llvm -ffreestanding -nostdinc $INCLUDES -o "$TMP/step1.ll" "$IN"
|
-S -emit-llvm -ffreestanding -fno-builtin -nostdinc $INCLUDES \
|
||||||
|
-o "$TMP/step1.ll" "$IN"
|
||||||
|
|
||||||
"$TMS_BITADDR" --scale "$TMP/step1.ll" "$TMP/scaled.ll" 2>/dev/null
|
"$TMS_BITADDR" --scale "$TMP/step1.ll" "$TMP/scaled.ll" 2>/dev/null
|
||||||
|
|
||||||
if [ "$OPTIMIZE" -eq 1 ]; then
|
if [ "$OPTIMIZE" -eq 1 ]; then
|
||||||
"$TMS_OPT" -passes='default<O2>' -S "$TMP/scaled.ll" -o "$TMP/opt.ll"
|
# This target has no i64 and no multiply-high, so IndVarSimplify's
|
||||||
|
# exit-value replacement (which rewrites a loop's final value into a
|
||||||
|
# closed-form i33 multiply, e.g. n*(n+1)/2 for sumto) produces IR that
|
||||||
|
# llc cannot select. -replexitval is an option of IndVarSimplify; disabling
|
||||||
|
# only that rewrite keeps the loop, which is what ordinary -O2 code needs.
|
||||||
|
# This is a driver policy, not a target TTI policy: opt runs without a
|
||||||
|
# target machine here.
|
||||||
|
# `-switch-to-lookup=false` is a driver policy, not a target feature: the
|
||||||
|
# optimizer's switch-to-lookup-table transform creates a *new* byte-offset
|
||||||
|
# GEP after --scale, and --normalize can only treat late GEP offsets as
|
||||||
|
# bit offsets. Such a GEP would index the table one bit per slot (an 8x
|
||||||
|
# address bug). The backend also has no jump-table lowering, so keeping
|
||||||
|
# the compare/branch switch is correct.
|
||||||
|
"$TMS_OPT" -passes='default<O2>' -replexitval=never \
|
||||||
|
-switch-to-lookup=false -S "$TMP/scaled.ll" -o "$TMP/opt.ll"
|
||||||
NORMALIZE_IN="$TMP/opt.ll"
|
NORMALIZE_IN="$TMP/opt.ll"
|
||||||
else
|
else
|
||||||
NORMALIZE_IN="$TMP/scaled.ll"
|
NORMALIZE_IN="$TMP/scaled.ll"
|
||||||
|
|||||||
+130
-4
@@ -3,10 +3,136 @@
|
|||||||
One file per ROM revision. Addresses are **bit** addresses and are not assumed
|
One file per ROM revision. Addresses are **bit** addresses and are not assumed
|
||||||
stable across firmware revisions; v2.2 is the first target.
|
stable across firmware revisions; v2.2 is the first target.
|
||||||
|
|
||||||
`vt1200-v22.syms` contains only the entries already proven by the hand-written
|
`vt1200-v22.syms` is the per-ROM entry table. Every entry is validated by
|
||||||
payloads in `vendor/vt1200/dwt/gui/xhello.asm` and the README. The compiler
|
`scripts/m4/validate-syms.sh` against the v2.2 image (md5
|
||||||
milestone that first calls Xlib must extend it from the Ghidra project /
|
`b46a3e442ccb86134d2356bd70d22fd2`): it must be in the ROM bit range
|
||||||
`ptr_constants.py` and validate each entry on MAME, then on the real unit.
|
`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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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` | 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` |
|
||||||
|
|
||||||
|
### 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
|
The build resolves these as absolute symbols (or ABI-preserving stubs). Do not
|
||||||
bake them into the compiler: keep them in a per-ROM linker script.
|
bake them into the compiler: keep them in a per-ROM linker script.
|
||||||
+68
-12
@@ -1,14 +1,70 @@
|
|||||||
# VT1200 v2.2 ROM entry points, bit addresses.
|
# VT1200 v2.2 ROM entry points, bit addresses.
|
||||||
# Source: dwt/gui/xhello.asm comments and README.md (v2.2 image md5
|
# Source: the termtris VT1200 xlib.h table, dwt/gui/xhello.asm comments, the
|
||||||
# b46a3e442ccb86134d2356bd70d22fd2). These are the known-good entries used
|
# v2.2 ROM disassembly (dis34 / reference/dis34-a-v22.txt), and README.md
|
||||||
# by the existing hand-written payloads; the rest must be extracted from the
|
# (v2.2 image md5 b46a3e442ccb86134d2356bd70d22fd2).
|
||||||
# Ghidra project / ROM before the Xlib client milestone.
|
|
||||||
#
|
#
|
||||||
# C name bit address
|
# Every entry is checked by scripts/m4/validate-syms.sh: ROM bit range
|
||||||
XOpenDisplay 0x3F9C2570
|
# 3F200000h..3FFFFFFFh, 16-bit aligned, and a real function prologue.
|
||||||
XCreateSimpleWindow 0x3FD41B90
|
#
|
||||||
XMapWindow 0x3FD645B0
|
# C name bit address evidence
|
||||||
XSync 0x3FD9F6B0
|
#
|
||||||
strlen 0x3FB458D0
|
# --- Xlib connection / window lifecycle ---
|
||||||
malloc 0x3F701230
|
XOpenDisplay 0x3F9C2570 termtris xlib.h; xhello.asm
|
||||||
free 0x3F702440
|
XCreateSimpleWindow 0x3FD41B90 termtris xlib.h; xhello.asm
|
||||||
|
# XCreatePixmap builds CreatePixmap opcode 0x35 and takes five arguments
|
||||||
|
# (display, drawable, width, height, depth); used by the M4 image client to
|
||||||
|
# render an aligned pixmap and CopyArea it to the (unaligned) window.
|
||||||
|
XCreatePixmap 0x3FD416E0 CreatePixmap opcode 0x35
|
||||||
|
XMapWindow 0x3FD64F60 MapWindow request opcode 8; termtris xlib.h
|
||||||
|
XRaiseWindow 0x3FD645B0 ConfigureWindow opcode 12, CWStackMode=Above
|
||||||
|
XSync 0x3FD9F6B0 termtris xlib.h; xhello.asm
|
||||||
|
XFlush 0x3F9B1030 termtris xlib.h
|
||||||
|
XPending 0x3FD67620 termtris xlib.h
|
||||||
|
XNextEvent 0x3F9C1CF0 termtris xlib.h
|
||||||
|
XSelectInput 0x3FD97D50 termtris xlib.h
|
||||||
|
XGetInputFocus 0x3FD55210 termtris xlib.h
|
||||||
|
# XSetInputFocus builds SetInputFocus opcode 0x2a (0x3FD536B0; the sibling of
|
||||||
|
# XGetInputFocus, which builds opcode 0x2b). Used by the M4 image client to
|
||||||
|
# give its own window the input focus (the WM leaves the focus at PointerRoot).
|
||||||
|
XSetInputFocus 0x3FD536B0 SetInputFocus request opcode 0x2a
|
||||||
|
#
|
||||||
|
# --- image copy helper ---
|
||||||
|
# _XPutImage copies the XImage into its request buffer through this ROM entry
|
||||||
|
# (0x3F9CBFE0 calls it). It is the TMS34010 blitter memcpy with the ROM's
|
||||||
|
# argument order (source, destination, byte count) and a normal prologue
|
||||||
|
# (MMTM B15,...). The image client primes the blitter with a 4-byte self-copy.
|
||||||
|
blit_copy 0x3FB44400 _XPutImage copy entry; (src,dst,n)
|
||||||
|
#
|
||||||
|
# --- drawing / GC ---
|
||||||
|
XCreateGC 0x3F997000 termtris xlib.h
|
||||||
|
XSetForeground 0x3FD99A60 termtris xlib.h
|
||||||
|
XFillRectangle 0x3F99E8F0 termtris xlib.h
|
||||||
|
XDrawString 0x3FDA0000 termtris xlib.h
|
||||||
|
XLoadFont 0x3FD4AE80 ROM movi 0x2d (OpenFont); not termtris xlib.h
|
||||||
|
XCopyArea 0x3FD3E220 ROM movi 0x3e (CopyArea); not termtris xlib.h
|
||||||
|
XLookupString 0x3F9A9C30 termtris xlib.h
|
||||||
|
sleep 0x3FB33A20 termtris xlib.h
|
||||||
|
#
|
||||||
|
# --- XImage and error handlers (validated against the ROM, not xlib.h) ---
|
||||||
|
# XCreateImage is installed as XImage.f.create_image by the ROM initializer at
|
||||||
|
# 0x3F9A6AA0; it calloc(1, 88) exactly sizeof(XImage) and initializes the fields.
|
||||||
|
XCreateImage 0x3F9A0490 f.create_image; 88-byte XImage alloc
|
||||||
|
# XPutImage builds PutImage request opcode 0x48 at 0x3F9CEE00 and references the
|
||||||
|
# "XPutImage: bits_per_pixel conversion unimplemented." string at 0x3F9D0E80.
|
||||||
|
XPutImage 0x3F9CEC70 request opcode 0x48
|
||||||
|
# XGetImage builds GetImage request opcode 0x49 at 0x3FD55830 and references the
|
||||||
|
# "XGetImage: Can't get requested memory" string at 0x3FD567A0.
|
||||||
|
XGetImage 0x3FD556B0 request opcode 0x49
|
||||||
|
# XDestroyImage is an Xlib macro; this is the ROM's private _XDestroyImage,
|
||||||
|
# installed as XImage.f.destroy_image. The stub is _XDestroyImage.
|
||||||
|
XDestroyImage 0x3F9A0D80 f.destroy_image (_XDestroyImage)
|
||||||
|
# The per-process XG block holds the error handlers at +0x40 (IO) and +0x60
|
||||||
|
# (protocol). The ROM defaults are 0x3F9BCD40 (_XDefaultIOError) and
|
||||||
|
# 0x3F9BD9F0 (_XDefaultError); the session manager/WM/VTE call these setters.
|
||||||
|
XSetErrorHandler 0x3FD45700 XG+0x60, default _XDefaultError
|
||||||
|
XSetIOErrorHandler 0x3FD458F0 XG+0x40, default _XDefaultIOError
|
||||||
|
#
|
||||||
|
# --- C runtime ---
|
||||||
|
strlen 0x3FB458D0 xhello.asm ABI check
|
||||||
|
malloc 0x3F701230 ROM allocator
|
||||||
|
free 0x3F702440 ROM free
|
||||||
Reference in new issue
Block a user