docs: M4 Xlib images, events and termtris integration plan
This commit is contained in:
1 parent
24f877362b
commit
dbf71bdc5f
1 file changed
+547
@@ -0,0 +1,547 @@
|
||||
# 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`.
|
||||
- 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.
|
||||
Reference in new issue
Block a user