Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24f877362b | ||
|
|
2681be26c0 | ||
|
|
ed58040fbd | ||
|
|
09db9e22e9 | ||
|
|
b34016a9f3 | ||
|
|
2187f59b20 | ||
|
|
d1ba1e4d86 |
No files matched your search
@@ -3,38 +3,88 @@
|
|||||||
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: M2.** `llc -march=tms34010` emits GSPA-compatible assembly using the
|
**Status: M3.** `llc -march=tms34010` emits GSPA-compatible assembly using the
|
||||||
TI ABI: integer/pointer arguments are pushed on the program stack, the result is
|
TI ABI: integer/pointer arguments are pushed on the program stack, the result is
|
||||||
returned in `A8`, frames use `A13`/`A14`/`A15`, and functions end in `RETS 2`
|
returned in `A8`, frames use `A13`/`A14`/`A15`, and functions end in `RETS 2`
|
||||||
(`RETS 0` without arguments). M2 can call a ROM entry point (`strlen`) and emit
|
(`RETS 0` without arguments). M3 adds conditional/unconditional branches,
|
||||||
initialized `.data` and string literals. It is still **assembly-only**: no
|
ordinary `while`/`for` loops, integer ALU/shift operations, and the first
|
||||||
object writer, no `clang -c`, and no native COFF; GSPA/GSPLNK under DOSBox
|
compiled C Xlib client: a four-call window opened with `XOpenDisplay`, created with
|
||||||
produce the COFF. Control flow, floats, structs, varargs and function pointers
|
`XCreateSimpleWindow`, mapped with `XMapWindow` and synchronized with `XSync`.
|
||||||
remain M3+.
|
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)
|
- 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 `m2`) — 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 M2 backend. Branch `tms34010` is
|
target, clang bit-address changes and the M2/M3 backend. Branch `m2` (the
|
||||||
the M1 tip and keeps the original M1 backend and suite.
|
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>
|
||||||
(branch `m2`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
|
(branch `m3`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
|
||||||
`tms-clang`, symbols, the M2 harness and the spike. Branch `m1` is the
|
`tms-clang`, symbols, the M2/M3 harnesses and the spike. Branch `m2` (the
|
||||||
reproducible M1 snapshot, including the retired M1 harness.
|
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; M2
|
here is a **frozen M0 mirror** used by the default upstream+patches build; M2/M3
|
||||||
does not change it.
|
do not change it.
|
||||||
|
|
||||||
## What M2 provides
|
## What M3 provides
|
||||||
|
|
||||||
|
M3 adds integer control flow and the first compiled C Xlib client, still
|
||||||
|
assembly-only:
|
||||||
|
|
||||||
|
- Conditional and unconditional branches for `i32` and pointer values:
|
||||||
|
`CMP`/`CMPI` plus `JAEQ`/`JANE`, the signed `JALT`/`JALE`/`JAGT`/`JAGE`, the
|
||||||
|
unsigned `JALO`/`JALS`/`JAHI`/`JAHS`, and `JAUC` for an unconditional jump.
|
||||||
|
Ordinary `while`/`for` loops (subject to the M3 limitations below), if/else
|
||||||
|
and `?:` work; `?:` expands to a compare plus a two-block diamond
|
||||||
|
(`SELECTCC`), and `setcc` becomes that diamond over 1/0.
|
||||||
|
- Integer ALU and shift operations: `SUB`, `AND`, `OR`, `XOR`, `NEG`, `NOT`,
|
||||||
|
`SLL`, `SRL`, `SRA`, plus `ADDI`/`ANDI`/`SLLK`/`SRAK`/`SRLK` immediates.
|
||||||
|
- The first compiled C Xlib window: `XOpenDisplay`, `XCreateSimpleWindow`
|
||||||
|
(nine arguments in the ROM's order: display, root, x, y, width, height,
|
||||||
|
border width, border, background), `XMapWindow` and `XSync`. All four go
|
||||||
|
through validated ROM import stubs.
|
||||||
|
- The ROM's `Display`/`Screen` field displacements are **bit** offsets
|
||||||
|
`0x480` (`Display.screens`) and `0x40` (`Screen.root`), i.e. byte offsets
|
||||||
|
`0x90` and `0x8` in `scripts/m3/vt1200_xlib.h`.
|
||||||
|
- Misaligned accesses: `MOVE`/`MOVB` and the FS0/FS1 field-size forms have no
|
||||||
|
alignment requirement on the TMS34010, so packed and bit-addressed struct
|
||||||
|
fields select directly instead of being split into byte-wide accesses.
|
||||||
|
- Nine `m3-*.ll` lit tests, for a total of 46 with the M2 suite; the M3
|
||||||
|
end-to-end, dis34, layout and differential gates (below).
|
||||||
|
|
||||||
|
M3 remains an `i32`/pointer milestone. `setcc` is supported, but narrow
|
||||||
|
(`i8`/`i16`) parameters and returns are still rejected. Branches on `i64`,
|
||||||
|
floats, aggregates, varargs and indirect calls are not supported. The backend
|
||||||
|
still emits assembly only.
|
||||||
|
|
||||||
|
### M3 limitations
|
||||||
|
|
||||||
|
- **Narrow loads are sign-extending only.** `MOVB` handles signed `char` and
|
||||||
|
the FS0 `MOVE` handles signed `short`; `unsigned char`, `unsigned short` and
|
||||||
|
`_Bool` loads need a zero-extending pattern that M3 does not have. They stop
|
||||||
|
`llc` with a raw `Cannot select`, which `m3-reject-zext.ll` pins. M4 adds the
|
||||||
|
zero-extending loads; do not assume general narrow-load support in M3.
|
||||||
|
- **`-O2` loop exit-value rewriting is not lowered.** IndVarSimplify can turn
|
||||||
|
`sumto`'s loop into a closed-form `i33` multiply, which M3 cannot select. The
|
||||||
|
M3 harness therefore builds `branch.c` with `-replexitval=never`. Ordinary
|
||||||
|
`while`/`for` loops that keep a real back-edge and phi work. M4/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:
|
M2 replaces the M1 test convention with the TI ABI and stops at assembly:
|
||||||
|
|
||||||
@@ -67,7 +117,7 @@ M2 replaces the M1 test convention with the TI ABI and stops at assembly:
|
|||||||
|
|
||||||
## The TI ABI
|
## The TI ABI
|
||||||
|
|
||||||
| | M1 test convention (branch `m1`) | TI ABI (M2) |
|
| | M1 test convention (branch `m1`) | TI ABI (M2/M3) |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Integer/pointer arguments | `A0`–`A3`, four maximum | program stack via `A14` (STK), pushed in reverse |
|
| 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)`, … |
|
| Argument slots | registers | first at `FP(-32)`, second at `FP(-64)`, … |
|
||||||
@@ -86,11 +136,93 @@ 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
|
ROM code may change either field size. FS0 drives `short` loads/stores and FS1
|
||||||
drives 32-bit words.
|
drives 32-bit words.
|
||||||
|
|
||||||
## Run the M2 gates
|
## Run the M3 gates
|
||||||
|
|
||||||
These are the documented commands; all must pass. The M1 harness and suite are
|
These are the documented commands on branch `m3`; all must pass.
|
||||||
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: 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
|
```sh
|
||||||
# 1. Build llc, opt, clang and the test tools
|
# 1. Build llc, opt, clang and the test tools
|
||||||
@@ -175,8 +307,11 @@ _strlen:
|
|||||||
```
|
```
|
||||||
|
|
||||||
`JAUC` is the TMS34010 unconditional absolute jump; GSPA 2.00 rejects bare
|
`JAUC` is the TMS34010 unconditional absolute jump; GSPA 2.00 rejects bare
|
||||||
`ja`. M2 uses only `strlen`; later milestones extend and validate the table and
|
`ja`. M2 uses only `strlen`. M3 adds the four Xlib entries
|
||||||
add the Xlib call sequence. Do not add Xlib dependencies to M2.
|
(`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 non-goals
|
||||||
|
|
||||||
@@ -190,11 +325,12 @@ M2 deliberately rejects or omits:
|
|||||||
- **Varargs:** `...` functions and calls (M5).
|
- **Varargs:** `...` functions and calls (M5).
|
||||||
- **Function pointers and indirect calls** (M4).
|
- **Function pointers and indirect calls** (M4).
|
||||||
- **Tail calls:** ordinary `tail` markers are demoted to ordinary calls;
|
- **Tail calls:** ordinary `tail` markers are demoted to ordinary calls;
|
||||||
`musttail` is not implemented (M2 C does not emit it). Real tail calls are
|
`musttail` is not implemented (M2 C does not emit it). Real tail calls remain
|
||||||
M3.
|
a later milestone.
|
||||||
- **Narrow ABI types:** `i8`/`i16` parameters, `i64`, and narrow returns; M3
|
- **Narrow ABI types:** `i8`/`i16` parameters, `i64`, and narrow returns remain
|
||||||
adds the widen/truncate rules. Narrow loads/stores inside a function remain
|
rejected. Narrow accesses inside a function are sign-extending only: `MOVB`
|
||||||
supported.
|
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`,
|
- **Object output:** no `MCCodeEmitter`, object writer, `llc -filetype=obj`,
|
||||||
`clang -c` or native COFF. GSPA assembles and GSPLNK links the asm.
|
`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
|
- **Xlib and clients:** no X11 headers, Xlib calls or real VT1200 clients in
|
||||||
@@ -203,18 +339,61 @@ M2 deliberately rejects or omits:
|
|||||||
The optimizer's `returned` argument attribute is accepted because it does not
|
The optimizer's `returned` argument attribute is accepted because it does not
|
||||||
affect the stack-slot ABI; `sret`/`byval`/`inreg`/`nest` remain rejected.
|
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 M2 (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/m2/ M2 harness: C inputs, entry.asm, link.cmd, stubs, checks
|
scripts/m2/ M2 harness: C inputs, entry.asm, link.cmd, stubs, checks
|
||||||
|
scripts/m3/ M3 harness: branch/xhello/layout inputs, Xlib header, entry/link
|
||||||
scripts/tests/m2/ M2 gates (run.sh, dis-roundtrip.sh)
|
scripts/tests/m2/ M2 gates (run.sh, dis-roundtrip.sh)
|
||||||
scripts/tests/m2/diff/ TI golden asm and the differential ABI check
|
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/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/m1/ M1 gates (historical; also on branch `m1`, not an M2 gate)
|
||||||
scripts/tests/m0/ M0 integration tests
|
scripts/tests/m0/ M0 integration tests
|
||||||
@@ -229,7 +408,7 @@ 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 M2 MAME
|
Python 3, and about 20 GB of disk for the LLVM source and build. The M2/M3 MAME
|
||||||
and dis34 gates additionally need `dosbox`, `mtools` (`mcopy`), the VT1200 SDK
|
and dis34 gates additionally need `dosbox`, `mtools` (`mcopy`), the VT1200 SDK
|
||||||
image, the `dwt` COFF loader, `dis34`, and a built MAME `vt1200` binary
|
image, the `dwt` COFF loader, `dis34`, and a built MAME `vt1200` binary
|
||||||
(`build/mame-src/vt1200`). The differential golden check needs no
|
(`build/mame-src/vt1200`). The differential golden check needs no
|
||||||
@@ -251,13 +430,13 @@ Xlib call sequence, `as34`, `dis34`, SDKs, manuals). It is not vendored:
|
|||||||
|
|
||||||
## Build the compiler
|
## Build the compiler
|
||||||
|
|
||||||
### Compiler fork (M2, the normal path)
|
### Compiler fork (M3, the normal path)
|
||||||
|
|
||||||
The fork checkout already exists at `build/llvm-clean` on `m2`. Build it in
|
The fork checkout already exists at `build/llvm-clean` on `m3`. Build it in
|
||||||
place:
|
place:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m2 ./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
|
||||||
```
|
```
|
||||||
@@ -266,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=m2 ./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
|
||||||
@@ -283,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 M2 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/M2.
|
`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 M2 pipeline 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 M2):
|
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 \
|
||||||
@@ -334,7 +513,7 @@ object file or a usable COFF for this target.
|
|||||||
|
|
||||||
## Later milestones
|
## Later milestones
|
||||||
|
|
||||||
M3 adds branches/control flow, the Xlib call sequence and a wider validated ROM
|
M4 adds the real Xlib client (`XImage`/`XEvent`, the full X11R4 headers),
|
||||||
symbol table. M4 adds the real client plus structs and function pointers. M5
|
structs by value and function pointers. M5 adds the native COFF path, floats,
|
||||||
adds the native COFF path, floats, varargs, bitfields and `long long`; 2ine
|
varargs, bitfields, `long long` and the `JR`/branch-relaxation code-size pass;
|
||||||
r5.01 is a later speed-up for the GSPA/GSPLNK step.
|
2ine r5.01 is a later speed-up for the GSPA/GSPLNK step.
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include "vt1200_xlib.h"
|
||||||
|
|
||||||
|
#define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n) * 0x20u))
|
||||||
|
|
||||||
|
int sumto(int n) {
|
||||||
|
int s = 0;
|
||||||
|
int i;
|
||||||
|
for (i = 1; i <= n; i++)
|
||||||
|
s += i;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int max2(int a, int b) { return a > b ? a : b; }
|
||||||
|
int lt(int a, int b) { return a < b; }
|
||||||
|
|
||||||
|
int branch_test(void) {
|
||||||
|
MARK(4) = (unsigned int)sumto(10);
|
||||||
|
MARK(5) = (unsigned int)max2(20, 22);
|
||||||
|
MARK(6) = (unsigned int)lt(5, 7);
|
||||||
|
MARK(7) = (unsigned int)layout_screens();
|
||||||
|
MARK(8) = (unsigned int)layout_root();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Executable
+463
@@ -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"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
; entry.asm -- M3 entry, linked at bit 018A0000.
|
||||||
|
;
|
||||||
|
; Calls the compiled branch/loop tests and then the first compiled C Xlib
|
||||||
|
; client. Markers (0x20 bits apart):
|
||||||
|
; 018A8000 Display pointer (xhello)
|
||||||
|
; 018A8020 root window (xhello)
|
||||||
|
; 018A8040 window id (xhello)
|
||||||
|
; 018A8060 XSync diagnostic (xhello)
|
||||||
|
; 018A8080 sumto(10) == 55 (branch_test)
|
||||||
|
; 018A80A0 max2(20,22) == 22 (branch_test)
|
||||||
|
; 018A80C0 lt(5,7) == 1 (branch_test)
|
||||||
|
; 018A80E0 layout_screens() == 0x480
|
||||||
|
; 018A8100 layout_root() == 0x40
|
||||||
|
.text
|
||||||
|
.globl _start
|
||||||
|
_start:
|
||||||
|
setf 16, 0, 0
|
||||||
|
setf 32, 0, 1
|
||||||
|
movi _cstack, a14
|
||||||
|
move a14, a13
|
||||||
|
calla _branch_test
|
||||||
|
calla _xhello
|
||||||
|
rets
|
||||||
|
|
||||||
|
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
struct display { char pad[0x90]; void *screens; };
|
||||||
|
struct screen { char pad[0x8]; unsigned int root; };
|
||||||
|
|
||||||
|
/* The 1987 compiler's &-of-member is a bit address, so these return the
|
||||||
|
ROM's bit offsets 0x480/0x40 directly. */
|
||||||
|
int layout_screens() { return (int)&((struct display *)0)->screens; }
|
||||||
|
int layout_root() { return (int)&((struct screen *)0)->root; }
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
*******************************************************
|
||||||
|
* GSP C COMPILER, Version 2.01, 87.300 *
|
||||||
|
*******************************************************
|
||||||
|
FP .set A13
|
||||||
|
STK .set A14
|
||||||
|
.globl _layout_screens
|
||||||
|
.file "LAYOUT.c"
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _layout_screens
|
||||||
|
******************************************************
|
||||||
|
_layout_screens:
|
||||||
|
MOVE A7,-*SP,1
|
||||||
|
MOVI 1152,A7
|
||||||
|
MOVE A7,A8
|
||||||
|
EPI0_1:
|
||||||
|
MOVE *SP+,A7,1
|
||||||
|
RETS 0
|
||||||
|
.globl _layout_root
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _layout_root
|
||||||
|
******************************************************
|
||||||
|
_layout_root:
|
||||||
|
MOVE A7,-*SP,1
|
||||||
|
MOVI 64,A7
|
||||||
|
MOVE A7,A8
|
||||||
|
EPI0_2:
|
||||||
|
MOVE *SP+,A7,1
|
||||||
|
RETS 0
|
||||||
|
.end
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include "vt1200_xlib.h"
|
||||||
|
|
||||||
|
/* The ROM's indirect displacements are bit offsets (xhello.asm):
|
||||||
|
Display.screens at bit 0x480, Screen.root at bit 0x40. C struct offsets
|
||||||
|
are bytes, so assert the byte offsets and return the ROM's bit offsets. */
|
||||||
|
_Static_assert(__builtin_offsetof(Display, screens) == 0x90,
|
||||||
|
"Display.screens must be at byte 0x90 (bit 0x480)");
|
||||||
|
_Static_assert(__builtin_offsetof(Screen, root) == 0x8,
|
||||||
|
"Screen.root must be at byte 0x8 (bit 0x40)");
|
||||||
|
|
||||||
|
int layout_screens(void) {
|
||||||
|
return (int)__builtin_offsetof(Display, screens) * 8;
|
||||||
|
}
|
||||||
|
int layout_root(void) {
|
||||||
|
return (int)__builtin_offsetof(Screen, root) * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exercise the real access path: vt1200_root() follows Display.screens (bit
|
||||||
|
0x480) and reads Screen.root (bit 0x40). The align-1 loads this generates
|
||||||
|
must select MOVE directly (see m3-unaligned.ll). */
|
||||||
|
unsigned int root_probe(Display *d) { return vt1200_root(d); }
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-a
|
||||||
|
-o M3.OUT
|
||||||
|
-m M3.MAP
|
||||||
|
-e _start
|
||||||
|
M3.obj
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
prog: origin = 018A0000h, length = 8000h
|
||||||
|
ram: origin = 018A9000h, length = 40000h
|
||||||
|
}
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
.text: {} > prog
|
||||||
|
.data: {} > prog
|
||||||
|
.bss: {} > ram
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
-- m3-check.lua -- load the M3 COFF with dwt/loader/loader.lua, read the
|
||||||
|
-- compiled branch/Xlib marker words, and prove a window was mapped by reading
|
||||||
|
-- the white background pixel in VRAM.
|
||||||
|
--
|
||||||
|
-- M3_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua
|
||||||
|
-- LOADER_END loader inspection/exit frame
|
||||||
|
--
|
||||||
|
-- The marker read runs one frame before loader.lua's inspection/exit frame.
|
||||||
|
-- XSync is diagnostic only; the pass requires the visible white pixel.
|
||||||
|
--
|
||||||
|
-- The DWT root window is a fine monochrome weave, so one set bit at (150,150)
|
||||||
|
-- cannot distinguish the mapped client window from the root. Require the
|
||||||
|
-- whole 32-bit VRAM word containing (150,150) to be 0xFFFFFFFF, and check a
|
||||||
|
-- second interior word at (250,200) so the client area is proven solid, not
|
||||||
|
-- just a lucky white stripe.
|
||||||
|
|
||||||
|
dofile(os.getenv("M3_LOADER_LUA"))
|
||||||
|
|
||||||
|
local ENDF = tonumber(os.getenv("LOADER_END") or "3400")
|
||||||
|
|
||||||
|
-- Window: XCreateSimpleWindow(display, root, 100, 100, 400, 200, 2, 0, 1).
|
||||||
|
-- The client area starts at (102,102); sample well inside it.
|
||||||
|
local VRAM_BASE = 0x04000000
|
||||||
|
local VRAM_PITCH = 2048 -- VR319: 2048 bits per row
|
||||||
|
local WHITE_WORD = 0xFFFFFFFF
|
||||||
|
local PX, PY = 150, 150
|
||||||
|
local PX2, PY2 = 250, 200
|
||||||
|
|
||||||
|
local frames = 0
|
||||||
|
local checked = false
|
||||||
|
|
||||||
|
local function vram_word(prog, x, y)
|
||||||
|
local bit = VRAM_BASE + y * VRAM_PITCH + x
|
||||||
|
local word = bit - (bit % 32)
|
||||||
|
return prog:read_u32(word), word, bit
|
||||||
|
end
|
||||||
|
|
||||||
|
emu.register_frame_done(function()
|
||||||
|
frames = frames + 1
|
||||||
|
if frames == ENDF - 1 and not checked then
|
||||||
|
checked = true
|
||||||
|
local prog = manager.machine.devices[':maincpu'].spaces['program']
|
||||||
|
local display = prog:read_u32(0x018A8000)
|
||||||
|
local root = prog:read_u32(0x018A8020)
|
||||||
|
local window = prog:read_u32(0x018A8040)
|
||||||
|
local sync = prog:read_u32(0x018A8060)
|
||||||
|
local sum = prog:read_u32(0x018A8080)
|
||||||
|
local maxv = prog:read_u32(0x018A80A0)
|
||||||
|
local ltv = prog:read_u32(0x018A80C0)
|
||||||
|
local screens = prog:read_u32(0x018A80E0)
|
||||||
|
local rootoff = prog:read_u32(0x018A8100)
|
||||||
|
|
||||||
|
-- The full words containing each sample pixel.
|
||||||
|
local pix, word, bit = vram_word(prog, PX, PY)
|
||||||
|
local pix2, word2, bit2 = vram_word(prog, PX2, PY2)
|
||||||
|
local solid = pix == WHITE_WORD
|
||||||
|
local solid2 = pix2 == WHITE_WORD
|
||||||
|
|
||||||
|
print(string.format(
|
||||||
|
"M3: display=%u root=%u window=%u sync=%d sum=%d max=%d lt=%d screens=0x%x root_off=0x%x",
|
||||||
|
display, root, window, sync, sum, maxv, ltv, screens, rootoff))
|
||||||
|
print(string.format(
|
||||||
|
"M3: pixel(%d,%d) bit=0x%08X word=0x%08X value=0x%08X %s",
|
||||||
|
PX, PY, bit, word, pix, solid and "SOLID-WHITE" or "not-solid-white"))
|
||||||
|
print(string.format(
|
||||||
|
"M3: pixel(%d,%d) bit=0x%08X word=0x%08X value=0x%08X %s",
|
||||||
|
PX2, PY2, bit2, word2, pix2, solid2 and "SOLID-WHITE" or "not-solid-white"))
|
||||||
|
|
||||||
|
local ok = display ~= 0 and root ~= 0 and window ~= 0
|
||||||
|
and sum == 55 and maxv == 22 and ltv == 1
|
||||||
|
and screens == 0x480 and rootoff == 0x40
|
||||||
|
and solid and solid2
|
||||||
|
if ok then
|
||||||
|
print("M3 MAME: PASS")
|
||||||
|
else
|
||||||
|
print("M3 MAME: FAIL")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, "m3-check")
|
||||||
Executable
+223
@@ -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"
|
||||||
Executable
+61
@@ -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)"
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#ifndef VT1200_XLIB_H
|
||||||
|
#define VT1200_XLIB_H
|
||||||
|
|
||||||
|
typedef unsigned int XID;
|
||||||
|
typedef unsigned int Window;
|
||||||
|
|
||||||
|
/* ROM v2.2 layout (xhello.asm). TMS34010 indirect displacements are *bit*
|
||||||
|
offsets: xhello's `*a11(0480h)` / `*a10(040h)` are bit offsets 0x480/0x40,
|
||||||
|
which are byte offsets 0x90/0x8. Cross-checks: the classic 32-bit
|
||||||
|
Screen.root is at byte 8, and the Terminal Manager scales its screen index
|
||||||
|
by 0x280 = sizeof(Screen) (0x50 bytes) * 8. */
|
||||||
|
typedef struct {
|
||||||
|
unsigned char pad[0x90];
|
||||||
|
void *screens;
|
||||||
|
} Display;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char pad[0x8];
|
||||||
|
unsigned int root;
|
||||||
|
} Screen;
|
||||||
|
|
||||||
|
extern Display *XOpenDisplay(const char *name);
|
||||||
|
extern Window XCreateSimpleWindow(Display *display, Window parent,
|
||||||
|
int x, int y, unsigned width,
|
||||||
|
unsigned height, unsigned border_width,
|
||||||
|
unsigned long border,
|
||||||
|
unsigned long background);
|
||||||
|
extern int XMapWindow(Display *display, Window window);
|
||||||
|
extern int XSync(Display *display, int discard);
|
||||||
|
|
||||||
|
/* clang is invoked at -O0 (the optimizer runs separately), which marks every
|
||||||
|
definition noinline. Keep this helper always-inline so layout.c and the
|
||||||
|
Xlib client do not each emit a local _vt1200_root for GSPA/GSPLNK to
|
||||||
|
collide on. */
|
||||||
|
static inline __attribute__((always_inline)) unsigned int
|
||||||
|
vt1200_root(Display *d) {
|
||||||
|
return ((Screen *)((Display *)d)->screens)->root;
|
||||||
|
}
|
||||||
|
|
||||||
|
int layout_screens(void);
|
||||||
|
int layout_root(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,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;
|
||||||
|
}
|
||||||
Executable
+268
@@ -0,0 +1,268 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 differential TI-compiler control-flow/Xlib check.
|
||||||
|
#
|
||||||
|
# scripts/tests/m3/diff/check.sh
|
||||||
|
#
|
||||||
|
# The stored ti-branch.s/ti-xhello.s are one-time outputs of the 1987 TI
|
||||||
|
# compiler (GSP C Compiler 2.01) under DOSBox. CI never runs DOSBox: this
|
||||||
|
# script greps the stored golden files and compares their ABI shape with the
|
||||||
|
# assembly our compiler emits for the equivalent C.
|
||||||
|
#
|
||||||
|
# The oracle is the ABI shape, not instruction selection. Each requirement
|
||||||
|
# accepts the TI spellings and ours:
|
||||||
|
#
|
||||||
|
# sumto a conditional branch, an unconditional loop back-edge, and a last
|
||||||
|
# RETS of RETS 2
|
||||||
|
# max2 a conditional branch, and a last RETS of RETS 2
|
||||||
|
# xhello nine argument pushes before _XCreateSimpleWindow, and the ROM bit
|
||||||
|
# displacements 0x480 (Display.screens) / 0x40 (Screen.root)
|
||||||
|
#
|
||||||
|
# This check does not verify the result register, the argument order, or the
|
||||||
|
# "background first, display last" sequence: the MAME gate checks the returned
|
||||||
|
# values, and dis-roundtrip.sh checks the push order.
|
||||||
|
#
|
||||||
|
# TI emits JRLE/JRUC where we emit JAxx/JAUC; both are accepted. TI prints the
|
||||||
|
# displacements as decimal 1152/64, so 480h/40h are accepted too. A
|
||||||
|
# displacement only counts inside the load that uses it: a bare 64/1152
|
||||||
|
# elsewhere in the body (an unrelated local at *FP(64), the constant 0x400,
|
||||||
|
# an 0x4800 that merely shares a prefix) is not evidence.
|
||||||
|
#
|
||||||
|
# Our branch.c is built exactly like the M3 gate: tms-clang -O2 with the
|
||||||
|
# -replexitval=never optimizer (that rewrite would turn sumto's loop into an
|
||||||
|
# i33 multiply, which the target cannot select), then llc -O0. Dropping
|
||||||
|
# -replexitval=never makes this check fail to compile the loop, not pass.
|
||||||
|
#
|
||||||
|
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC TI_BRANCH TI_XHELLO
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../../../.." && pwd)
|
||||||
|
DIR=$ROOT/scripts/tests/m3/diff
|
||||||
|
SRC=$ROOT/scripts/m3
|
||||||
|
|
||||||
|
LLVM_BIN=${LLVM_BIN:-$ROOT/build/llvm-clean-build/bin}
|
||||||
|
TMS_CLANG=${TMS_CLANG:-$LLVM_BIN/clang}
|
||||||
|
TMS_OPT=${TMS_OPT:-$LLVM_BIN/opt}
|
||||||
|
TMS_BITADDR=${TMS_BITADDR:-$ROOT/tools/tms-bitaddr/tms-bitaddr}
|
||||||
|
TMS_LLC=${TMS_LLC:-$LLVM_BIN/llc}
|
||||||
|
TI_BRANCH=${TI_BRANCH:-$DIR/ti-branch.s}
|
||||||
|
TI_XHELLO=${TI_XHELLO:-$DIR/ti-xhello.s}
|
||||||
|
export TMS_CLANG TMS_OPT TMS_BITADDR
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "M3 diff: FAIL: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do
|
||||||
|
[ -x "$tool" ] || fail "missing tool: $tool"
|
||||||
|
done
|
||||||
|
[ -f "$TI_BRANCH" ] || fail "stored TI golden missing: $TI_BRANCH"
|
||||||
|
[ -f "$TI_XHELLO" ] || fail "stored TI golden missing: $TI_XHELLO"
|
||||||
|
|
||||||
|
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m3-diff.XXXXXX")
|
||||||
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||||
|
|
||||||
|
# branch.c is generated through a wrapper so only this file keeps its loop.
|
||||||
|
OPT_NOREPLEXIT=$TMP/opt-noreplexit
|
||||||
|
printf '#!/bin/sh\nexec "%s" -replexitval=never "$@"\n' "$TMS_OPT" > "$OPT_NOREPLEXIT"
|
||||||
|
chmod +x "$OPT_NOREPLEXIT"
|
||||||
|
|
||||||
|
echo "== compile our branch.s and xhello.s"
|
||||||
|
TMS_OPT=$OPT_NOREPLEXIT "$ROOT/scripts/tms-clang" -O2 -I "$SRC" \
|
||||||
|
-o "$TMP/branch.ll" "$SRC/branch.c"
|
||||||
|
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/branch.ll" -o "$TMP/branch.s"
|
||||||
|
"$ROOT/scripts/tms-clang" -O2 -I "$SRC" \
|
||||||
|
-o "$TMP/xhello.ll" "$SRC/xhello.c"
|
||||||
|
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm "$TMP/xhello.ll" -o "$TMP/xhello.s"
|
||||||
|
echo " branch.s: $(grep -c . "$TMP/branch.s") lines"
|
||||||
|
echo " xhello.s: $(grep -c . "$TMP/xhello.s") lines"
|
||||||
|
|
||||||
|
# Our llc output has `;` end-of-line comments (`; %bb.0`). Strip them so a
|
||||||
|
# comment cannot satisfy a check; TI asm has none.
|
||||||
|
for f in "$TMP/branch.s" "$TMP/xhello.s"; do
|
||||||
|
sed 's/;.*//' "$f" > "$f.code"
|
||||||
|
done
|
||||||
|
|
||||||
|
# body FILE FUNC: from `_func:` to the next .globl/.end.
|
||||||
|
body() {
|
||||||
|
awk -v want="_$2:" '
|
||||||
|
$1 == want { infn = 1; next }
|
||||||
|
infn && ($1 == ".globl" || $1 == ".end") { exit }
|
||||||
|
infn { print }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
require() {
|
||||||
|
_file=$1
|
||||||
|
_pat=$2
|
||||||
|
_what=$3
|
||||||
|
if grep -Eiq "$_pat" "$_file"; then
|
||||||
|
echo " ok $_what"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "M3 diff: FAIL: missing $_what" >&2
|
||||||
|
echo " pattern: $_pat" >&2
|
||||||
|
sed 's/^/ /' "$_file" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# require_last_rets2 FILE WHAT: the last RETS instruction in the body (the
|
||||||
|
# epilogue) is RETS 2. Matching any RETS 2 elsewhere would let a wrong
|
||||||
|
# epilogue through.
|
||||||
|
require_last_rets2() {
|
||||||
|
if awk '
|
||||||
|
/^[[:space:]]*RETS([[:space:]]|$)/ { last = $0; seen = 1 }
|
||||||
|
END {
|
||||||
|
if (!seen)
|
||||||
|
exit 1
|
||||||
|
exit (last ~ /^[[:space:]]*RETS[[:space:]]+2[[:space:]]*$/) ? 0 : 1
|
||||||
|
}
|
||||||
|
' "$1"; then
|
||||||
|
echo " ok $2"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "M3 diff: FAIL: $2 (last RETS is not RETS 2)" >&2
|
||||||
|
sed 's/^/ /' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# A conditional branch: JA/JR plus a condition suffix. JAUC/JRUC/JRU do not
|
||||||
|
# match because their suffix is U/UC.
|
||||||
|
COND='^[[:space:]]*J[A-Z]*(EQ|NE|LT|LE|GT|GE|V|NV|P|N|Z|NZ|PL|MI)[[:space:]]'
|
||||||
|
# An unconditional branch: our JAUC, TI's JRUC (and bare JRU).
|
||||||
|
UNCOND='^[[:space:]]*J(AUC|RUC|RU|UMP)[[:space:]]'
|
||||||
|
|
||||||
|
# has_backedge FILE: true when a branch targets a label defined earlier in the
|
||||||
|
# same function body. TI closes the loop with the conditional JRLE L4; we
|
||||||
|
# close it with the unconditional JAUC LBB0_1, so either form counts.
|
||||||
|
has_backedge() {
|
||||||
|
awk '
|
||||||
|
/^[A-Za-z_][A-Za-z0-9_]*:/ {
|
||||||
|
lbl = $1
|
||||||
|
sub(/:$/, "", lbl)
|
||||||
|
pos[lbl] = NR
|
||||||
|
next
|
||||||
|
}
|
||||||
|
/^[[:space:]]*J/ {
|
||||||
|
if ($2 in pos && pos[$2] < NR)
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
END { exit found ? 0 : 1 }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_branch() { # check_branch FILE LABEL
|
||||||
|
_f=$1
|
||||||
|
body "$_f" sumto > "$TMP/sumto.body"
|
||||||
|
body "$_f" max2 > "$TMP/max2.body"
|
||||||
|
require "$TMP/sumto.body" "$COND" "$2: sumto has a conditional branch"
|
||||||
|
require "$TMP/sumto.body" "$UNCOND" "$2: sumto has an unconditional branch"
|
||||||
|
if ! has_backedge "$TMP/sumto.body"; then
|
||||||
|
echo "M3 diff: FAIL: $2: sumto has no loop back-edge" >&2
|
||||||
|
sed 's/^/ /' "$TMP/sumto.body" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ok $2: sumto branches back to the loop"
|
||||||
|
require_last_rets2 "$TMP/sumto.body" "$2: sumto ends with RETS 2"
|
||||||
|
require "$TMP/max2.body" "$COND" "$2: max2 has a conditional branch"
|
||||||
|
require_last_rets2 "$TMP/max2.body" "$2: max2 ends with RETS 2"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "== ABI shape: TI golden"
|
||||||
|
check_branch "$TI_BRANCH" "TI"
|
||||||
|
echo "== ABI shape: our compiler"
|
||||||
|
check_branch "$TMP/branch.s.code" "ours"
|
||||||
|
|
||||||
|
# Count the argument pushes since the previous CALLA, then require nine for
|
||||||
|
# XCreateSimpleWindow. Both compilers push the last argument first: display,
|
||||||
|
# root, x, y, width, height, border_width, border, background.
|
||||||
|
count_xcreate_pushes() {
|
||||||
|
awk '
|
||||||
|
/^_xhello:/ { infn = 1; n = 0; next }
|
||||||
|
!infn { next }
|
||||||
|
/CALLA[[:space:]]+_XCreateSimpleWindow/ { print n; exit }
|
||||||
|
/CALLA/ { n = 0; next }
|
||||||
|
/MOVE[[:space:]]+[^,]+,[[:space:]]*\*(STK|A14)\+/ { n++ }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
for pair in "$TI_XHELLO:TI" "$TMP/xhello.s.code:ours"; do
|
||||||
|
f=${pair%%:*}
|
||||||
|
who=${pair##*:}
|
||||||
|
pushes=$(count_xcreate_pushes "$f")
|
||||||
|
[ -n "$pushes" ] || fail "$who xhello has no CALLA _XCreateSimpleWindow"
|
||||||
|
[ "$pushes" = "9" ] \
|
||||||
|
|| fail "$who XCreateSimpleWindow has $pushes argument pushes, want 9"
|
||||||
|
echo " ok $who: XCreateSimpleWindow takes nine argument pushes"
|
||||||
|
done
|
||||||
|
|
||||||
|
# TI: Display.screens is the indexed load `MOVE *A7(1152),A7,1`; Screen.root
|
||||||
|
# is loaded by `MOVE *A7(64),*FP(32),1`. The closing paren and the comma keep
|
||||||
|
# the boundary exact, so 0x4800/0x400 and decimal 11520/640 do not match; the
|
||||||
|
# register names may vary.
|
||||||
|
TI_SCREENS='^[[:space:]]*MOVE[[:space:]]+\*(A[0-9]+|FP|SP)\((1152|0x480|480h)\),'
|
||||||
|
TI_ROOT='^[[:space:]]*MOVE[[:space:]]+\*(A[0-9]+|FP|SP)\((64|0x40|40h)\),[[:space:]]*\*(A[0-9]+|FP|SP)\([0-9]+\)'
|
||||||
|
|
||||||
|
# has_addi_load FILE screens|root: our displacement is materialised by
|
||||||
|
# `ADDI <disp>, A<n>` and then used by a load through that same register
|
||||||
|
# (`MOVE *A<n>,...`). Only that pairing counts, not any ADDI in the body.
|
||||||
|
has_addi_load() {
|
||||||
|
awk -v kind="$2" '
|
||||||
|
function disp_ok(d) {
|
||||||
|
if (kind == "screens")
|
||||||
|
return d == "1152" || d == "0x480" || d == "480h"
|
||||||
|
return d == "64" || d == "0x40" || d == "40h"
|
||||||
|
}
|
||||||
|
/^[[:space:]]*ADDI[[:space:]]/ {
|
||||||
|
line = $0
|
||||||
|
sub(/^[[:space:]]*ADDI[[:space:]]+/, "", line)
|
||||||
|
split(line, f, /[[:space:]]*,[[:space:]]*/)
|
||||||
|
disp = f[1]
|
||||||
|
reg = f[2]
|
||||||
|
if (getline nxt <= 0)
|
||||||
|
next
|
||||||
|
while (nxt ~ /^[[:space:]]*$/) {
|
||||||
|
if (getline nxt <= 0)
|
||||||
|
next
|
||||||
|
}
|
||||||
|
if (disp_ok(disp) &&
|
||||||
|
nxt ~ ("^[[:space:]]*MOVE[[:space:]]+\\*" reg "([[:space:],]|$)"))
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
END { exit found ? 0 : 1 }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_offsets() { # check_offsets FILE WHO ti|ours
|
||||||
|
_f=$1
|
||||||
|
_who=$2
|
||||||
|
_side=$3
|
||||||
|
body "$_f" xhello > "$TMP/xhello.body"
|
||||||
|
if [ "$_side" = ti ]; then
|
||||||
|
require "$TMP/xhello.body" "$TI_SCREENS" \
|
||||||
|
"$_who: Display.screens is loaded at bit 0x480 (*A?(1152))"
|
||||||
|
require "$TMP/xhello.body" "$TI_ROOT" \
|
||||||
|
"$_who: Screen.root is loaded at bit 0x40 (*A?(64),*A?(...))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if has_addi_load "$TMP/xhello.body" screens; then
|
||||||
|
echo " ok $_who: Display.screens is loaded at bit 0x480 (ADDI 1152 + load)"
|
||||||
|
else
|
||||||
|
echo "M3 diff: FAIL: missing $_who: Display.screens load at bit 0x480" >&2
|
||||||
|
sed 's/^/ /' "$TMP/xhello.body" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if has_addi_load "$TMP/xhello.body" root; then
|
||||||
|
echo " ok $_who: Screen.root is loaded at bit 0x40 (ADDI 64 + load)"
|
||||||
|
else
|
||||||
|
echo "M3 diff: FAIL: missing $_who: Screen.root load at bit 0x40" >&2
|
||||||
|
sed 's/^/ /' "$TMP/xhello.body" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "== ROM bit displacements"
|
||||||
|
check_offsets "$TI_XHELLO" "TI" ti
|
||||||
|
check_offsets "$TMP/xhello.s.code" "ours" ours
|
||||||
|
|
||||||
|
echo "M3 diff: PASS"
|
||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Negative controls for scripts/tests/m3/diff/check.sh.
|
||||||
|
#
|
||||||
|
# scripts/tests/m3/diff/negative-control.sh
|
||||||
|
#
|
||||||
|
# The differential check must reject a TI golden whose real Screen.root or
|
||||||
|
# Display.screens displacement is wrong. This is a guard on the oracle
|
||||||
|
# itself: an earlier check matched `64`/`1152` anywhere in the function body,
|
||||||
|
# so the unrelated local `*FP(64)` (and the prefix `0x400`) satisfied it even
|
||||||
|
# after the real load was changed to 512. Each mutation below must make
|
||||||
|
# check.sh fail; the stored goldens are never modified.
|
||||||
|
#
|
||||||
|
# Overridable: the same variables as check.sh.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m3-diff-neg.XXXXXX")
|
||||||
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||||
|
|
||||||
|
expect_fail() { # expect_fail WHAT MUTATED_GOLDEN
|
||||||
|
if TI_XHELLO=$2 "$DIR/check.sh" > "$TMP/out" 2>&1; then
|
||||||
|
echo "M3 diff negative control: FAIL: check.sh accepted $1" >&2
|
||||||
|
sed 's/^/ /' "$TMP/out" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ok negative control: $1 rejected"
|
||||||
|
grep 'M3 diff: FAIL' "$TMP/out" | sed 's/^/ /'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Screen.root: *A7(64),*FP(32),1 -> *A7(512),*FP(32),1
|
||||||
|
sed 's/\*A7(64),\*FP(32)/*A7(512),*FP(32)/' \
|
||||||
|
"$DIR/ti-xhello.s" > "$TMP/root-512.s"
|
||||||
|
grep -q '\*A7(512),\*FP(32)' "$TMP/root-512.s" \
|
||||||
|
|| { echo "M3 diff negative control: mutation did not apply" >&2; exit 1; }
|
||||||
|
expect_fail "Screen.root displacement 512" "$TMP/root-512.s"
|
||||||
|
|
||||||
|
# Display.screens: *A7(1152) -> *A7(512)
|
||||||
|
sed 's/\*A7(1152)/*A7(512)/' \
|
||||||
|
"$DIR/ti-xhello.s" > "$TMP/screens-512.s"
|
||||||
|
grep -q '\*A7(512)' "$TMP/screens-512.s" \
|
||||||
|
|| { echo "M3 diff negative control: mutation did not apply" >&2; exit 1; }
|
||||||
|
expect_fail "Display.screens displacement 512" "$TMP/screens-512.s"
|
||||||
|
|
||||||
|
# Boundary: *A7(1152) -> *A7(0x4800), which shares the 0x480 prefix.
|
||||||
|
sed 's/\*A7(1152)/*A7(0x4800)/' \
|
||||||
|
"$DIR/ti-xhello.s" > "$TMP/screens-4800.s"
|
||||||
|
grep -q '\*A7(0x4800)' "$TMP/screens-4800.s" \
|
||||||
|
|| { echo "M3 diff negative control: mutation did not apply" >&2; exit 1; }
|
||||||
|
expect_fail "Display.screens displacement 0x4800 (hex boundary)" "$TMP/screens-4800.s"
|
||||||
|
|
||||||
|
echo "M3 diff negative controls: PASS"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/* K&R source for the M3 differential golden: the same two functions as
|
||||||
|
scripts/m3/branch.c, compiled by the 1987 TI compiler. */
|
||||||
|
int sumto(n) int n; { int s; int i; s = 0; for (i = 1; i <= n; i++) s = s + i; return s; }
|
||||||
|
int max2(a, b) int a; int b; { if (a > b) return a; return b; }
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
*******************************************************
|
||||||
|
* GSP C COMPILER, Version 2.01, 87.300 *
|
||||||
|
*******************************************************
|
||||||
|
FP .set A13
|
||||||
|
STK .set A14
|
||||||
|
.globl _sumto
|
||||||
|
.file "BRANCH.c"
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _sumto
|
||||||
|
******************************************************
|
||||||
|
_sumto:
|
||||||
|
MMTM SP,A5,A7,A12,FP
|
||||||
|
MOVE STK,FP
|
||||||
|
ADDI 64,STK
|
||||||
|
CLR A12
|
||||||
|
MOVE A12,*FP,1
|
||||||
|
ADDK 1,A12
|
||||||
|
MOVE A12,*FP(32),1
|
||||||
|
JRUC L1
|
||||||
|
L4:
|
||||||
|
MOVE *FP(32),A7,1
|
||||||
|
MOVE *FP,A5,1
|
||||||
|
ADD A7,A5
|
||||||
|
MOVE A5,*FP,1
|
||||||
|
ADDK 1,A7
|
||||||
|
MOVE A7,*FP(32),1
|
||||||
|
L1:
|
||||||
|
MOVE *FP(32),A7,1
|
||||||
|
MOVE *FP(-32),A5,1
|
||||||
|
CMP A5,A7
|
||||||
|
JRLE L4
|
||||||
|
MOVE *FP,A8,1
|
||||||
|
EPI0_1:
|
||||||
|
MOVE *SP(160),STK,1
|
||||||
|
MMFM SP,A5,A7,A12,FP
|
||||||
|
RETS 2
|
||||||
|
.globl _max2
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _max2
|
||||||
|
******************************************************
|
||||||
|
_max2:
|
||||||
|
MMTM SP,A5,A7,FP
|
||||||
|
MOVE STK,FP
|
||||||
|
MOVE *FP(-32),A7,1
|
||||||
|
MOVE *FP(-64),A5,1
|
||||||
|
CMP A5,A7
|
||||||
|
JRLE L5
|
||||||
|
MOVE A7,A8
|
||||||
|
JRUC EPI0_2
|
||||||
|
L5:
|
||||||
|
MOVE A5,A8
|
||||||
|
EPI0_2:
|
||||||
|
MOVE *SP(128),STK,1
|
||||||
|
MMFM SP,A5,A7,FP
|
||||||
|
RETS 2
|
||||||
|
.end
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/* K&R source for the M3 differential golden: the same four-call Xlib client
|
||||||
|
as scripts/m3/xhello.c, compiled by the 1987 TI compiler.
|
||||||
|
|
||||||
|
The ROM's Display/Screen fields are at bit displacements 0x480/0x40, i.e.
|
||||||
|
byte offsets 0x90/0x8; the 1987 compiler's &-of-member arithmetic is in
|
||||||
|
bits, so it emits 1152 and 64 for these pads. */
|
||||||
|
struct display {
|
||||||
|
char pad[0x90];
|
||||||
|
void *screens;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct screen {
|
||||||
|
char pad[0x8];
|
||||||
|
unsigned int root;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct display *XOpenDisplay();
|
||||||
|
extern int XCreateSimpleWindow();
|
||||||
|
extern int XMapWindow();
|
||||||
|
extern int XSync();
|
||||||
|
|
||||||
|
int xhello()
|
||||||
|
{
|
||||||
|
struct display *d;
|
||||||
|
unsigned int root;
|
||||||
|
int w;
|
||||||
|
int s;
|
||||||
|
|
||||||
|
d = XOpenDisplay("0::0");
|
||||||
|
if (!d)
|
||||||
|
return -1;
|
||||||
|
root = ((struct screen *)d->screens)->root;
|
||||||
|
w = XCreateSimpleWindow(d, root, 100, 100, 400, 200, 2, 0, 1);
|
||||||
|
XMapWindow(d, w);
|
||||||
|
s = XSync(d, 0);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
*******************************************************
|
||||||
|
* GSP C COMPILER, Version 2.01, 87.300 *
|
||||||
|
*******************************************************
|
||||||
|
FP .set A13
|
||||||
|
STK .set A14
|
||||||
|
.globl _XOpenDisplay
|
||||||
|
.globl _XCreateSimpleWindow
|
||||||
|
.globl _XMapWindow
|
||||||
|
.globl _XSync
|
||||||
|
.globl _xhello
|
||||||
|
.file "XHELLO.c"
|
||||||
|
******************************************************
|
||||||
|
* FUNCTION DEF : _xhello
|
||||||
|
******************************************************
|
||||||
|
_xhello:
|
||||||
|
MMTM SP,A7,FP
|
||||||
|
MOVE STK,FP
|
||||||
|
ADDI 128,STK
|
||||||
|
MOVE STK,-*SP,1
|
||||||
|
MOVI SL1,A8
|
||||||
|
MOVE A8,*STK+,1
|
||||||
|
CALLA _XOpenDisplay
|
||||||
|
MOVE A8,*FP,1
|
||||||
|
MOVE *FP,A7,1
|
||||||
|
JRNZ L1
|
||||||
|
MOVI -1,A8
|
||||||
|
JRUC EPI0_1
|
||||||
|
L1:
|
||||||
|
MOVE *A7(1152),A7,1
|
||||||
|
MOVE *A7(64),*FP(32),1
|
||||||
|
MOVE STK,-*SP,1
|
||||||
|
MOVK 1,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
SUB A7,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
ADDK 2,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVI 200,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVI 400,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVI 100,A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVE *FP(32),*STK+,1
|
||||||
|
MOVE *FP(0),*STK+,1
|
||||||
|
CALLA _XCreateSimpleWindow
|
||||||
|
MOVE A8,*FP(64),1
|
||||||
|
MOVE STK,-*SP,1
|
||||||
|
MOVE *FP(64),*STK+,1
|
||||||
|
MOVE *FP(0),*STK+,1
|
||||||
|
CALLA _XMapWindow
|
||||||
|
MOVE STK,-*SP,1
|
||||||
|
CLR A7
|
||||||
|
MOVE A7,*STK+,1
|
||||||
|
MOVE *FP(0),*STK+,1
|
||||||
|
CALLA _XSync
|
||||||
|
MOVE A8,*FP(96),1
|
||||||
|
MOVE *FP(64),A8,1
|
||||||
|
EPI0_1:
|
||||||
|
ADDI -128,STK
|
||||||
|
MMFM SP,A7,FP
|
||||||
|
RETS 0
|
||||||
|
******************************************************
|
||||||
|
* DEFINE STRING CONSTANTS *
|
||||||
|
******************************************************
|
||||||
|
.text
|
||||||
|
SL1: .string "0::0",0
|
||||||
|
.end
|
||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 test gate: rebuild the linked COFF through the normal GSPA/GSPLNK path,
|
||||||
|
# then verify dis34 decodes the Xlib call sequence, branch code and old-STK
|
||||||
|
# restore offsets at the linked addresses.
|
||||||
|
set -eu
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||||
|
|
||||||
|
# Always rebuild: a stale build/m3/m3.coff would let an assembler/compiler
|
||||||
|
# regression pass without ever being reassembled. Keep dis-roundtrip.sh
|
||||||
|
# pointed at the file this run produces, including when M3_WORK/M3_COFF are
|
||||||
|
# overridden.
|
||||||
|
WORK=${M3_WORK:-$ROOT/build/m3}
|
||||||
|
M3_COFF=${M3_COFF:-$WORK/m3.coff}
|
||||||
|
export M3_COFF
|
||||||
|
|
||||||
|
"$ROOT/scripts/m3/run.sh" "$@"
|
||||||
|
exec "$ROOT/scripts/m3/dis-roundtrip.sh" "$@"
|
||||||
Executable
+92
@@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 layout gate: Display.screens and Screen.root must be at the ROM's bit
|
||||||
|
# displacements 0x480 and 0x40.
|
||||||
|
#
|
||||||
|
# The ROM's indirect displacements are *bit* offsets (TMS34010), so those
|
||||||
|
# displacements are byte offsets 0x90 and 0x8. layout.c asserts the byte
|
||||||
|
# layout and returns the bit displacements; layout-ti.c does the same through
|
||||||
|
# the 1987 compiler's bit-addressed &-of-member. Both asm files must carry
|
||||||
|
# 0x480 / 0x40 (480h, 1152, ...).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||||
|
LLVM_BIN=${LLVM_BIN:-$ROOT/build/llvm-clean-build/bin}
|
||||||
|
TMS_CLANG=${TMS_CLANG:-$LLVM_BIN/clang}
|
||||||
|
TMS_OPT=${TMS_OPT:-$LLVM_BIN/opt}
|
||||||
|
TMS_LLC=${TMS_LLC:-$LLVM_BIN/llc}
|
||||||
|
TMS_BITADDR=${TMS_BITADDR:-$ROOT/tools/tms-bitaddr/tms-bitaddr}
|
||||||
|
WORK=${M3_LAYOUT_WORK:-$ROOT/build/m3-layout}
|
||||||
|
GOLDEN=$ROOT/scripts/m3/layout-ti.s
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "layout.sh: FAIL: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_LLC" "$TMS_BITADDR"; do
|
||||||
|
[ -x "$tool" ] || fail "missing tool: $tool"
|
||||||
|
done
|
||||||
|
[ -f "$GOLDEN" ] || fail "missing TI golden: $GOLDEN"
|
||||||
|
|
||||||
|
mkdir -p "$WORK"
|
||||||
|
|
||||||
|
# Exit 0 here means the _Static_asserts in layout.c held on the target.
|
||||||
|
TMS_CLANG="$TMS_CLANG" TMS_OPT="$TMS_OPT" TMS_BITADDR="$TMS_BITADDR" \
|
||||||
|
"$ROOT/scripts/tms-clang" -O0 -o "$WORK/layout.ll" \
|
||||||
|
"$ROOT/scripts/m3/layout.c" \
|
||||||
|
|| fail "target compile failed (Display/Screen static asserts?)"
|
||||||
|
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm \
|
||||||
|
"$WORK/layout.ll" -o "$WORK/layout.s" \
|
||||||
|
|| fail "llc failed for layout.ll"
|
||||||
|
|
||||||
|
# Print the body of one function, from its label to the next .globl/.end.
|
||||||
|
body() { # body FILE FUNC
|
||||||
|
awk -v want="_$2:" '
|
||||||
|
$1 == want { infn = 1; next }
|
||||||
|
infn && ($1 == ".globl" || $1 == ".end") { exit }
|
||||||
|
infn { print }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Allowed spellings for a constant: C hex, assembler hex with `h`, decimal.
|
||||||
|
has_const() { # has_const FILE FUNC ERE
|
||||||
|
body "$1" "$2" | tr 'A-Z' 'a-z' | grep -Eq "$3"
|
||||||
|
}
|
||||||
|
|
||||||
|
check() { # check FILE FUNC ERE WHAT
|
||||||
|
if ! has_const "$1" "$2" "$3"; then
|
||||||
|
echo "layout.sh: $1: _$2 body:" >&2
|
||||||
|
body "$1" "$2" >&2
|
||||||
|
fail "_$2 in $1 does not contain $4"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
HEX480='(0x480|480h|[^0-9a-f]1152[^0-9a-f])'
|
||||||
|
HEX40='(0x40|40h|[^0-9a-f]64[^0-9a-f])'
|
||||||
|
|
||||||
|
check "$WORK/layout.s" layout_screens "$HEX480" "0x480 (Display.screens)"
|
||||||
|
check "$WORK/layout.s" layout_root "$HEX40" "0x40 (Screen.root)"
|
||||||
|
check "$GOLDEN" layout_screens "$HEX480" "0x480 (Display.screens)"
|
||||||
|
check "$GOLDEN" layout_root "$HEX40" "0x40 (Screen.root)"
|
||||||
|
|
||||||
|
# root_probe exercises the real access path: vt1200_root() follows
|
||||||
|
# Display.screens (bit 0x480) and reads Screen.root (bit 0x40). The helper is
|
||||||
|
# always_inline, so the incoming align-1 loads select MOVE directly in
|
||||||
|
# root_probe (see m3-unaligned.ll). Require both displacements and reject a
|
||||||
|
# MOVB byte split.
|
||||||
|
check "$WORK/layout.s" root_probe "$HEX480" "0x480 (Display.screens)"
|
||||||
|
check "$WORK/layout.s" root_probe "$HEX40" "0x40 (Screen.root)"
|
||||||
|
if body "$WORK/layout.s" root_probe | grep -q 'MOVB'; then
|
||||||
|
fail "root_probe contains MOVB: align-1 field loads did not select MOVE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Guard against the old byte-pad mistake: 0x480*8 / 0x40*8 (9216 / 512)
|
||||||
|
# means the struct was padded in bytes past the ROM's bit displacements.
|
||||||
|
if body "$GOLDEN" layout_screens | tr 'A-Z' 'a-z' |
|
||||||
|
grep -Eq '(^|[^0-9])(9216|2400h)([^0-9a-f]|$)' ||
|
||||||
|
body "$GOLDEN" layout_root | tr 'A-Z' 'a-z' |
|
||||||
|
grep -Eq '(^|[^0-9])(512|200h)([^0-9a-f]|$)'; then
|
||||||
|
fail "TI golden carries byte-pad offsets (9216/512); ROM displacements are bits 0x480/0x40"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "layout.sh: PASS (Display.screens=0x480, Screen.root=0x40; target + TI golden)"
|
||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# M3 test gate: validate the Display/Screen layout and ROM entries, compile the
|
||||||
|
# branch/loop tests and the first C Xlib client, assemble/link with GSPA/GSPLNK,
|
||||||
|
# load the COFF in MAME, and require the branch markers plus a solid white
|
||||||
|
# window area.
|
||||||
|
set -eu
|
||||||
|
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||||
|
|
||||||
|
# These two checks are part of the gate, not manual steps: layout.sh proves the
|
||||||
|
# 0x480/0x40 root probe still selects the ROM displacements, and
|
||||||
|
# validate-syms.sh proves the four Xlib imports are still the v2.2 entries.
|
||||||
|
"$ROOT/scripts/tests/m3/layout.sh"
|
||||||
|
"$ROOT/scripts/m3/validate-syms.sh"
|
||||||
|
|
||||||
|
exec "$ROOT/scripts/m3/run.sh" "$@"
|
||||||
+27
-6
@@ -7,13 +7,34 @@ stable across firmware revisions; v2.2 is the first target.
|
|||||||
payloads in `vendor/vt1200/dwt/gui/xhello.asm` and the README. **M2 consumes
|
payloads in `vendor/vt1200/dwt/gui/xhello.asm` and the README. **M2 consumes
|
||||||
only `strlen`**: `scripts/m2/gen-rom-imports.sh` emits an ABI-preserving
|
only `strlen`**: `scripts/m2/gen-rom-imports.sh` emits an ABI-preserving
|
||||||
`jauc` stub for every entry, and the M2 harness calls `_strlen` at
|
`jauc` stub for every entry, and the M2 harness calls `_strlen` at
|
||||||
`0x3FB458D0`. The other entries are recorded for later milestones and are not
|
`0x3FB458D0`. **M3 consumes exactly the four Xlib entries below** through the
|
||||||
yet exercised by compiled C.
|
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.
|
||||||
|
|
||||||
M3 owns the wider table: it extends the entries from the Ghidra project /
|
## v2.2 Xlib entries validated
|
||||||
`ptr_constants.py`, adds the Xlib call sequence, and validates each entry on
|
|
||||||
MAME, then on the real unit. Do not assume an address here is validated just
|
The four Xlib entries used by the compiled client are **validated** by
|
||||||
because it is listed.
|
`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.
|
||||||
Reference in new issue
Block a user