20 Commits
m1 ... m3
Author SHA1 Message Date
arti 24f877362b docs: finalize M3 limitations and M4 prerequisites 2026-11-11 20:30:37 +00:00
arti 2681be26c0 tests: anchor the M3 Xlib displacement checks 2026-11-11 20:06:58 +00:00
arti ed58040fbd docs: M3 control flow and first Xlib window 2026-11-11 19:56:19 +00:00
arti 09db9e22e9 tests: M3 branches and first Xlib window in MAME 2026-11-11 19:46:00 +00:00
arti b34016a9f3 docs: correct M3 ROM bit displacements and unaligned-load carry-forward 2026-11-11 19:14:05 +00:00
arti 2187f59b20 tests: M3 Xlib Display/Screen layout 2026-11-11 19:07:44 +00:00
arti d1ba1e4d86 docs: M3 control flow and first Xlib window plan 2026-11-11 17:54:01 +00:00
arti 2d4919db36 docs: finalize M2 ABI and tail-call notes 2026-11-11 17:36:17 +00:00
arti f1a604d70e docs: correct M2 branch and epilogue guidance 2026-11-11 17:15:43 +00:00
arti 3a188b35fb docs: M2 TI ABI, ROM strlen and golden ABI check 2026-11-11 17:05:33 +00:00
arti fa00b8740b docs: finish the M2 loader and returned reconciliation 2026-11-11 16:59:30 +00:00
arti b614e70851 docs: reconcile M2 plan with JAUC, returned, loader and .data alignment 2026-11-11 16:59:23 +00:00
arti 0008d7be20 tests: M2 TI ABI and ROM strlen in MAME 2026-11-11 16:50:06 +00:00
arti d4f165fa7d docs: record the M2 .data width and pointer-init rules 2026-11-11 16:35:43 +00:00
arti 3f30806a6d docs: require width-based .data emission for i8/i16 scalars 2026-11-11 16:24:29 +00:00
arti e7ba50d7a3 docs: add caller-side rejection tests to M2 2026-11-11 16:10:38 +00:00
arti 469b1d870e docs: require a spill-slot frame assertion in m2-spill 2026-11-11 15:55:18 +00:00
arti e60a1fdee9 docs: pin the M2 callee stack arithmetic in m2-args 2026-11-11 15:54:59 +00:00
arti 5e8dbffb01 docs: move the M2 spill test to Task 3 2026-11-11 15:21:09 +00:00
arti c0746338b0 docs: M2 TI ABI and ROM call implementation plan 2026-11-11 14:57:27 +00:00
44 changed files with 4598 additions and 118 deletions

No files matched your search

+394 -114
View File
@@ -3,83 +3,399 @@
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: M3.** `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). M3 adds conditional/unconditional branches,
ordinary `while`/`for` loops, integer ALU/shift operations, and the first
compiled C Xlib client: a four-call window opened with `XOpenDisplay`, created with
`XCreateSimpleWindow`, mapped with `XMapWindow` and synchronized with `XSync`.
It is still **assembly-only**: no object writer, no `clang -c`, and no native
COFF; GSPA/GSPLNK under DOSBox produce the COFF. XImage/XEvent, the real client,
structs by value, function pointers, floats and `i64` remain M4/M5.
- 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)
- 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 `m3`) — 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 backend. Branch `m2` (the
repository default) is the M2 snapshot; 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 `m3`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
symbols, the M1 harness and the spike. `tms-clang`, symbols, the M2/M3 harnesses and the spike. Branch `m2` (the
repository default) is the M2 snapshot and branch `m1` is the reproducible M1
snapshot, including the retired M1 harness.
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; M2/M3
does not change it. do not change it.
## What M1 provides ## What M3 provides
M1 fills in the M0 target skeleton and stops at assembly: M3 adds integer control flow and the first compiled C Xlib client, still
assembly-only:
- `llc -march=tms34010 -filetype=asm` for leaf 32-bit functions: - Conditional and unconditional branches for `i32` and pointer values:
integer/pointer arguments in `A0`–`A3`, result in `A0`, return via `RETS 0`. `CMP`/`CMPI` plus `JAEQ`/`JANE`, the signed `JALT`/`JALE`/`JAGT`/`JAGE`, the
- `ADD` for `i32`, `MOVI` for constants, `MOVE ..., 1` (FS1) for 32-bit unsigned `JALO`/`JALS`/`JAHI`/`JAHS`, and `JAUC` for an unconditional jump.
load/store, `MOVE ..., 0` (FS0) for `i16`, and sign-extending `MOVB` for Ordinary `while`/`for` loops (subject to the M3 limitations below), if/else
`i8` load/store. and `?:` work; `?:` expands to a compare plus a two-block diamond
- Constant global addresses as `MOVI _sym+bits`; a surviving GEP offset is a (`SELECTCC`), and `setcc` becomes that diamond over 1/0.
bit offset (`ADDI 24`, not `ADDI 192`). - Integer ALU and shift operations: `SUB`, `AND`, `OR`, `XOR`, `NEG`, `NOT`,
- Hard errors, not silent fallbacks, for calls (`M1 has no calls; M2 adds the `SLL`, `SRL`, `SRA`, plus `ADDI`/`ANDI`/`SLLK`/`SRAK`/`SRLK` immediates.
TI ABI`), stack frames (`M1 has no stack frame`), more than four register - The first compiled C Xlib window: `XOpenDisplay`, `XCreateSimpleWindow`
arguments, unsupported signatures (varargs, `i64`/float/aggregate (nine arguments in the ROM's order: display, root, x, y, width, height,
parameters, `sret`/`byval`/`inreg`/`nest`/`returned`), unsupported return border width, border, background), `XMapWindow` and `XSync`. All four go
types, and initialized globals or string literals. through validated ROM import stubs.
- Seventeen `llvm-lit`/FileCheck tests under - The ROM's `Display`/`Screen` field displacements are **bit** offsets
`llvm/test/CodeGen/TMS34010/m1-*.ll` in the compiler fork, including `0x480` (`Display.screens`) and `0x40` (`Screen.root`), i.e. byte offsets
negative tests that pin the loud refusals. `0x90` and `0x8` in `scripts/m3/vt1200_xlib.h`.
- An end-to-end path: `scripts/tms-clang` IR → `llc -O0 -filetype=asm` → - Misaligned accesses: `MOVE`/`MOVB` and the FS0/FS1 field-size forms have no
**1987 GSPA/GSPLNK 2.00 under DOSBox** → COFF → the VT1200 loader in MAME. alignment requirement on the TMS34010, so packed and bit-addressed struct
The run checks `add(20, 22) == 42`, `load(&word) == 0x12345678`, fields select directly instead of being split into byte-wide accesses.
`second() == &arr[1]` (a `symbol+32` bit relocation), `loadb` of a byte - Nine `m3-*.ll` lit tests, for a total of 46 with the M2 suite; the M3
`0x81` returns the sign-extended `0xFFFFFF81`, and `storeb` leaves `0x81` end-to-end, dis34, layout and differential gates (below).
in memory.
- A `dis34` round-trip that reads the linked `.text` from the COFF and checks
the instructions GSPA encoded at their linked bit addresses.
## M1 is not the TI ABI 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.
| | M1 test convention | TI ABI (M2) | ### 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/M5 must add
multiply/`i64` lowering, a target-level pass policy, or a target diagnostic
instead of a raw abort.
## 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) |
|---|---|---| |---|---|---|
| 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 M3 gates
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 (default branch)
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.
## M4 prerequisites (carry-forwards)
The final M3 review left these concrete prerequisites for M4:
- **Add zero-extending narrow loads.** M3 rejects `unsigned char`,
`unsigned short` and `_Bool` loads (pinned by `m3-reject-zext.ll`); M4 must
add the `ZEXTLOAD i8`/`i16` patterns and replace the reject test.
- **Namespace every private label.** `scripts/tests/m3/run.sh` prefixes
`LBB<n>_<m>` per translation unit, but deterministic private string labels
are not renamed. Two TUs with literals therefore collide in GSPA's flat
symbol table. M4 must namespace all private labels or make compiler private
names module-unique.
- **Add unsigned/`<=` runtime coverage.** `JALE`, `JALO`, `JAHI` and `JALS`,
`JAHS`, `JANE`, `JAGE` are covered by lit tests only and do not appear in the
compiled M3 image. M4 should add an unsigned/`<=` runtime marker.
- **Replace fragile test plumbing.** The `always_inline` root helper and the
register-hardcoded nine-argument dis34 check are test-strength aids that will
not survive real M4 code; make them operand- and register-independent.
- **Tighten test strength.** `m3-cmpi`'s `CHECK-DAG` for 0/1 does not pin their
order, and `layout.sh` pairs the root probe with fixed constants; neither
weakens the current pass, but both should be tightened.
## 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 (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/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/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 +408,12 @@ docs/ design and plans
## Prerequisites ## Prerequisites
`cmake`, `ninja-build`, `ccache`, a C++17 host compiler (GCC 14 is used here), `cmake`, `ninja-build`, `ccache`, a C++17 host compiler (GCC 14 is used here),
Python 3, and about 20 GB of disk for the LLVM source and build. The M1 gates Python 3, and about 20 GB of disk for the LLVM source and build. The M2/M3 MAME
additionally need `dosbox`, `mtools` (`mcopy`), the VT1200 SDK image, the and dis34 gates additionally need `dosbox`, `mtools` (`mcopy`), the VT1200 SDK
`dwt` COFF loader, and a built MAME `vt1200` binary image, the `dwt` COFF loader, `dis34`, and a built MAME `vt1200` binary
(`build/mame-src/vt1200`). On Debian: (`build/mame-src/vt1200`). 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 +430,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 (M3, 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 `m3`. 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=m3 ./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 +445,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=m3 ./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 +462,22 @@ 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 fork), it
refuses rather than destroying it. Use fork mode for that checkout. 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.
## Build the M0 support tools ## Build the M0 support tools
The M1 pipeline still uses `tms-bitaddr` and `tms-clang`: The M2/M3 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):
```sh ```sh
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \ TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
@@ -169,71 +487,33 @@ 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. M4 adds the real Xlib client (`XImage`/`XEvent`, the full X11R4 headers),
Initialized globals, string literals and constant/private data (`.data`, structs by value and function pointers. M5 adds the native COFF path, floats,
`.rodata`) make `llc` fail loudly; the `.data` initializer probe moves to varargs, bitfields, `long long` and the `JR`/branch-relaxation code-size pass;
M2. 2ine r5.01 is a later speed-up for the GSPA/GSPLNK step.
- **Object emission:** no `MCCodeEmitter`, object writer, `llc -filetype=obj`,
`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 0x3FD645B0`, `XSync 0x3FD9F6B0`. 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`/`3FD645B0`/`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.
+3
View File
@@ -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; }
+2
View File
@@ -0,0 +1,2 @@
extern int add2(int, int);
int caller(void) { return add2(20, 22); }
+3
View File
@@ -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"); }
+327
View File
@@ -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"
+59
View File
@@ -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
+16
View File
@@ -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"
+3
View File
@@ -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; }
+16
View File
@@ -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
}
+1
View File
@@ -0,0 +1 @@
int locals(void) { volatile int x = 7; return x; }
+51
View File
@@ -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")
+171
View File
@@ -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"
+23
View File
@@ -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;
}
+463
View File
@@ -0,0 +1,463 @@
#!/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>
# _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 3FD645B0" \
'jauc[[:space:]]+0x3fd645b0'
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"
+25
View File
@@ -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
+7
View File
@@ -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; }
+29
View File
@@ -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
+21
View File
@@ -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); }
+16
View File
@@ -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
}
+79
View File
@@ -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")
+223
View File
@@ -0,0 +1,223 @@
#!/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
#
# 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"
+61
View File
@@ -0,0 +1,61 @@
#!/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 is the
# evidence that these entries work. M4 extends the table for XImage/XEvent.
#
# 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 0x3FD645B0
check_sym XSync 0x3FD9F6B0
echo "m3 validate-syms: PASS (four v2.2 Xlib entries in ROM, aligned, prologues valid)"
+43
View File
@@ -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
+18
View File
@@ -0,0 +1,18 @@
#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;
}
+133
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
int add2(a, b) int a; int b; { return a + b; }
+22
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
int locals() { int x; x = 7; return x; }
+22
View File
@@ -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
+16
View File
@@ -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" "$@"
+6
View File
@@ -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" "$@"
+268
View File
@@ -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"
+51
View File
@@ -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"
+4
View File
@@ -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; }
+56
View File
@@ -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
+37
View File
@@ -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;
}
+69
View File
@@ -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
+17
View File
@@ -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" "$@"
+92
View File
@@ -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)"
+15
View File
@@ -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" "$@"
+2 -1
View File
@@ -43,7 +43,8 @@ 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
+31 -3
View File
@@ -4,9 +4,37 @@ 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` contains only the entries already proven by the hand-written
payloads in `vendor/vt1200/dwt/gui/xhello.asm` and the README. The compiler payloads in `vendor/vt1200/dwt/gui/xhello.asm` and the README. **M2 consumes
milestone that first calls Xlib must extend it from the Ghidra project / only `strlen`**: `scripts/m2/gen-rom-imports.sh` emits an ABI-preserving
`ptr_constants.py` and validate each entry on MAME, then on the real unit. `jauc` stub for every entry, and the M2 harness calls `_strlen` at
`0x3FB458D0`. **M3 consumes exactly the four Xlib entries below** through the
same stub generator; the compiled `scripts/m3/xhello.c` is the end-to-end proof.
The table has not been widened beyond those five validated entries (the four
Xlib entries below plus `strlen`). M4 extends it for the
real client (`XImage`/`XEvent` and the rest of the X11R4 calls) from the Ghidra
project / `ptr_constants.py`, and validates each new entry on MAME, then on the
real unit. Do not assume an address here is validated just because it is
listed.
## v2.2 Xlib entries validated
The four Xlib entries used by the compiled client are **validated** by
`vendor/vt1200/dwt/gui/xhello.asm` (the hand-written payload that already runs
against the live DWT X server):
| entry | bit address | evidence |
|---|---|---|
| `XOpenDisplay` | `0x3F9C2570` | `xhello.asm` calls it; `CALLA _XOpenDisplay` reaches the stub in MAME |
| `XCreateSimpleWindow` | `0x3FD41B90` | nine-argument window create; window id marker is nonzero |
| `XMapWindow` | `0x3FD645B0` | mapped window produces the white VRAM pixel |
| `XSync` | `0x3FD9F6B0` | called after mapping; return value is diagnostic only |
`scripts/m3/validate-syms.sh` is the regression check: each entry must be in
the ROM bit range `3F200000h..3FFFFFFFh`, 16-bit aligned, and disassemble to a
plausible function prologue (`MOVE`/`MMTM`/`SETF`/`CMP`/`ADDI`).
`scripts/tests/m3/run.sh` runs it before every MAME run, so the compiled client
cannot pass with a stale stub. It is not a re-extraction; M4 extends the table
for `XImage`/`XEvent` and the rest of the real client.
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.