docs: M3 control flow and first Xlib window
This commit is contained in:
1 parent
09db9e22e9
commit
ed58040fbd
7 files changed
+532
-48
No files matched your search
@@ -3,38 +3,73 @@
|
||||
A modern C toolchain for the DEC VT1200 / TI TMS34010, built as a small fork of
|
||||
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
|
||||
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
|
||||
initialized `.data` and string literals. It is still **assembly-only**: no
|
||||
object writer, no `clang -c`, and no native COFF; GSPA/GSPLNK under DOSBox
|
||||
produce the COFF. Control flow, floats, structs, varargs and function pointers
|
||||
remain M3+.
|
||||
(`RETS 0` without arguments). M3 adds conditional/unconditional branches,
|
||||
loops, integer ALU/shift operations, and the first compiled C Xlib client: a
|
||||
four-call window opened with `XOpenDisplay`, created with
|
||||
`XCreateSimpleWindow`, mapped with `XMapWindow` and synchronized with `XSync`.
|
||||
It is still **assembly-only**: no object writer, no `clang -c`, and no native
|
||||
COFF; GSPA/GSPLNK under DOSBox produce the COFF. XImage/XEvent, the real client,
|
||||
structs by value, function pointers, floats and `i64` remain M4/M5.
|
||||
|
||||
- Design: [`docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md`](docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md)
|
||||
- M0 plan: [`docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md`](docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md)
|
||||
- M1 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md`](docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md)
|
||||
- M2 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md`](docs/superpowers/plans/2026-11-11-tms34010-m2-ti-abi-and-rom-calls.md)
|
||||
- M3 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m3-control-flow-and-xlib-window.md`](docs/superpowers/plans/2026-11-11-tms34010-m3-control-flow-and-xlib-window.md)
|
||||
- Spike: [`spike/REPORT-23.md`](spike/REPORT-23.md) (current, LLVM 23.1.5);
|
||||
[`spike/REPORT.md`](spike/REPORT.md) (historical, 19.1.7)
|
||||
|
||||
## Repositories
|
||||
|
||||
- **Compiler fork:** <https://git.wut.ee/opencode.siil.arti.ee/llvm-tms34010>
|
||||
(branch `m2`) — the full buildable LLVM 23.1.5 tree with the TMS34010
|
||||
target, clang bit-address changes and the M2 backend. Branch `tms34010` is
|
||||
the M1 tip and keeps the original M1 backend and suite.
|
||||
(branch `m3`) — the full buildable LLVM 23.1.5 tree with the TMS34010
|
||||
target, clang bit-address changes and the M2/M3 backend. Branch `m2` (the
|
||||
repository default) is the M2 snapshot; branch `tms34010` is the M1 tip and
|
||||
keeps the original M1 backend and suite.
|
||||
- **This project:** <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>
|
||||
(branch `m2`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
|
||||
`tms-clang`, symbols, the M2 harness and the spike. Branch `m1` is the
|
||||
reproducible M1 snapshot, including the retired M1 harness.
|
||||
(branch `m3`) — plans/specs, VT1200 integration tooling, `tms-bitaddr`,
|
||||
`tms-clang`, symbols, the M2/M3 harnesses and the spike. Branch `m2` (the
|
||||
repository default) is the M2 snapshot and branch `m1` is the reproducible M1
|
||||
snapshot, including the retired M1 harness.
|
||||
|
||||
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
|
||||
does not change it.
|
||||
here is a **frozen M0 mirror** used by the default upstream+patches build; M2/M3
|
||||
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.
|
||||
Loops, 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.
|
||||
- Eight `m3-*.ll` lit tests, for a total of 45 with the M2 suite; the M3
|
||||
end-to-end, dis34, layout and differential gates (below).
|
||||
|
||||
M3 remains an `i32`/pointer milestone. Narrow integer loads/stores and `setcc`
|
||||
are 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.
|
||||
|
||||
## What M2 provides (branch `m2`)
|
||||
|
||||
M2 replaces the M1 test convention with the TI ABI and stops at assembly:
|
||||
|
||||
@@ -67,7 +102,7 @@ M2 replaces the M1 test convention with the TI ABI and stops at assembly:
|
||||
|
||||
## The TI ABI
|
||||
|
||||
| | M1 test convention (branch `m1`) | TI ABI (M2) |
|
||||
| | M1 test convention (branch `m1`) | TI ABI (M2/M3) |
|
||||
|---|---|---|
|
||||
| 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)`, … |
|
||||
@@ -86,9 +121,76 @@ immediately after every `CALLA` (`SETF 16,0,0` then `SETF 32,0,1`), because
|
||||
ROM code may change either field size. FS0 drives `short` loads/stores and FS1
|
||||
drives 32-bit words.
|
||||
|
||||
## Run the 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.
|
||||
|
||||
```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: 45 passed (37 m2-* + 8 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. 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`, `RETS 2`, nine
|
||||
`XCreateSimpleWindow` argument pushes, and the `0x480`/`0x40` bit
|
||||
displacements. 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.
|
||||
|
||||
## Run the M2 gates (default branch)
|
||||
|
||||
These are the M2 commands; they also pass on `m3`. The M1 harness and suite are
|
||||
on branch `m1` and are **not** M2 gates: they encode the retired M1 register
|
||||
convention.
|
||||
|
||||
@@ -175,8 +277,11 @@ _strlen:
|
||||
```
|
||||
|
||||
`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
|
||||
add the Xlib call sequence. Do not add Xlib dependencies to M2.
|
||||
`ja`. M2 uses only `strlen`. M3 adds the four Xlib entries
|
||||
(`XOpenDisplay`, `XCreateSimpleWindow`, `XMapWindow`, `XSync`) and validates
|
||||
their stubs in `scripts/m3/validate-syms.sh` and in MAME; `symbols/README.md`
|
||||
records the evidence. Do not add other Xlib dependencies until they are
|
||||
validated.
|
||||
|
||||
## M2 non-goals
|
||||
|
||||
@@ -190,11 +295,10 @@ M2 deliberately rejects or omits:
|
||||
- **Varargs:** `...` functions and calls (M5).
|
||||
- **Function pointers and indirect calls** (M4).
|
||||
- **Tail calls:** ordinary `tail` markers are demoted to ordinary calls;
|
||||
`musttail` is not implemented (M2 C does not emit it). Real tail calls are
|
||||
M3.
|
||||
- **Narrow ABI types:** `i8`/`i16` parameters, `i64`, and narrow returns; M3
|
||||
adds the widen/truncate rules. Narrow loads/stores inside a function remain
|
||||
supported.
|
||||
`musttail` is not implemented (M2 C does not emit it). Real tail calls remain
|
||||
a later milestone.
|
||||
- **Narrow ABI types:** `i8`/`i16` parameters, `i64`, and narrow returns remain
|
||||
rejected; narrow loads/stores inside a function are supported.
|
||||
- **Object output:** no `MCCodeEmitter`, object writer, `llc -filetype=obj`,
|
||||
`clang -c` or native COFF. GSPA assembles and GSPLNK links the asm.
|
||||
- **Xlib and clients:** no X11 headers, Xlib calls or real VT1200 clients in
|
||||
@@ -203,18 +307,39 @@ M2 deliberately rejects or omits:
|
||||
The optimizer's `returned` argument attribute is accepted because it does not
|
||||
affect the stack-slot ABI; `sret`/`byval`/`inreg`/`nest` remain rejected.
|
||||
|
||||
## M3 non-goals
|
||||
|
||||
M3 deliberately stops at the first window and ordinary integer control flow:
|
||||
|
||||
- **XImage/XEvent and the real client:** no image transfer, event handling,
|
||||
full X11R4 headers or real VT1200 client (M4). M3 uses exactly the four Xlib
|
||||
calls above and no other Xlib entry.
|
||||
- **Aggregates by value:** structs/unions passed or returned by value, `sret`,
|
||||
`byval`, bitfields (M4/M5). Struct *fields* through pointers are supported.
|
||||
- **Function pointers and indirect calls:** the callee must be a direct global
|
||||
symbol (M4).
|
||||
- **Floating point and `i64`:** no float/double or 64-bit integer arguments,
|
||||
returns, arithmetic or branches (M5).
|
||||
- **Object output:** still no `MCCodeEmitter`, object writer,
|
||||
`llc -filetype=obj`, `clang -c` or native COFF; GSPA/GSPLNK produce the COFF.
|
||||
The `JR`/branch-relaxation code-size pass is M5 as well.
|
||||
- **Varargs, `musttail`, narrow ABI parameters/returns:** unchanged from M2.
|
||||
|
||||
## Repository layout
|
||||
|
||||
```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-src/ M0 working fork (ignored)
|
||||
patches/llvm/ frozen M0 patch series against llvmorg-23.1.5
|
||||
tools/tms-bitaddr/ GEP scale/normalize tool
|
||||
scripts/tms-clang IR pipeline wrapper (clang + tms-bitaddr + opt)
|
||||
scripts/m2/ M2 harness: C inputs, entry.asm, link.cmd, stubs, checks
|
||||
scripts/m3/ M3 harness: branch/xhello/layout inputs, Xlib header, entry/link
|
||||
scripts/tests/m2/ M2 gates (run.sh, dis-roundtrip.sh)
|
||||
scripts/tests/m2/diff/ TI golden asm and the differential ABI check
|
||||
scripts/tests/m3/ M3 gates (run.sh, dis-roundtrip.sh, layout.sh)
|
||||
scripts/tests/m3/diff/ TI branch/Xlib goldens and the differential check
|
||||
scripts/m1/ M1 harness (historical; also on branch `m1`, not an M2 gate)
|
||||
scripts/tests/m1/ M1 gates (historical; also on branch `m1`, not an M2 gate)
|
||||
scripts/tests/m0/ M0 integration tests
|
||||
@@ -229,7 +354,7 @@ docs/ design and plans
|
||||
## Prerequisites
|
||||
|
||||
`cmake`, `ninja-build`, `ccache`, a C++17 host compiler (GCC 14 is used here),
|
||||
Python 3, and about 20 GB of disk for the LLVM source and build. The M2 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
|
||||
image, the `dwt` COFF loader, `dis34`, and a built MAME `vt1200` binary
|
||||
(`build/mame-src/vt1200`). The differential golden check needs no
|
||||
@@ -251,13 +376,13 @@ Xlib call sequence, `as34`, `dis34`, SDKs, manuals). It is not vendored:
|
||||
|
||||
## 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:
|
||||
|
||||
```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}
|
||||
# lit driver: build/llvm-clean-build/bin/llvm-lit
|
||||
```
|
||||
@@ -266,7 +391,7 @@ To clone the fork first instead of using an existing checkout:
|
||||
|
||||
```sh
|
||||
LLVM_REPO=gitea@git.wut.ee:opencode.siil.arti.ee/llvm-tms34010.git \
|
||||
LLVM_BRANCH=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
|
||||
@@ -283,22 +408,22 @@ TMS34010; a first build takes roughly 20–40 minutes.
|
||||
|
||||
This mode clones `llvmorg-23.1.5`, applies `patches/llvm/*.patch`, and builds
|
||||
`build/llvm-clean-build`. It owns and resets only a plain `llvm-project` clone:
|
||||
if `build/llvm-clean` has some other origin (for example the M2 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.
|
||||
|
||||
`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
|
||||
|
||||
The M2 pipeline uses `tms-bitaddr` and `tms-clang`:
|
||||
The M2/M3 pipeline uses `tms-bitaddr` and `tms-clang`:
|
||||
|
||||
```sh
|
||||
LLVM_CONFIG=$PWD/build/llvm-clean-build/bin/llvm-config \
|
||||
tools/tms-bitaddr/build.sh
|
||||
```
|
||||
|
||||
M0 regression test (optional in M2):
|
||||
M0 regression test (optional in M2/M3):
|
||||
|
||||
```sh
|
||||
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
|
||||
@@ -334,7 +459,7 @@ object file or a usable COFF for this target.
|
||||
|
||||
## Later milestones
|
||||
|
||||
M3 adds branches/control flow, the Xlib call sequence and a wider validated ROM
|
||||
symbol table. M4 adds the real client plus structs and function pointers. M5
|
||||
adds the native COFF path, floats, varargs, bitfields and `long long`; 2ine
|
||||
r5.01 is a later speed-up for the GSPA/GSPLNK step.
|
||||
M4 adds the real Xlib client (`XImage`/`XEvent`, the full X11R4 headers),
|
||||
structs by value and function pointers. M5 adds the native COFF path, floats,
|
||||
varargs, bitfields, `long long` and the `JR`/branch-relaxation code-size pass;
|
||||
2ine r5.01 is a later speed-up for the GSPA/GSPLNK step.
|
||||
Executable
+191
@@ -0,0 +1,191 @@
|
||||
#!/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 and an unconditional loop back-edge,
|
||||
# result in A8, RETS 2
|
||||
# max2 a conditional branch, result in A8, RETS 2
|
||||
# xhello nine argument pushes for XCreateSimpleWindow (background first,
|
||||
# display last), and the ROM bit displacements 0x480 (Display.screens)
|
||||
# / 0x40 (Screen.root)
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 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_rets2() {
|
||||
require "$1" '^[[:space:]]*RETS[[:space:]]+2([[:space:]]|$)' "$2"
|
||||
}
|
||||
|
||||
# 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_rets2 "$TMP/sumto.body" "$2: sumto ends with RETS 2"
|
||||
require "$TMP/max2.body" "$COND" "$2: max2 has a conditional branch"
|
||||
require_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
|
||||
|
||||
HEX480='(0x480|480h|[^0-9a-f]1152[^0-9a-f])'
|
||||
HEX40='(0x40|40h|[^0-9a-f]64[^0-9a-f])'
|
||||
|
||||
check_offsets() { # check_offsets FILE WHO
|
||||
_f=$1
|
||||
_who=$2
|
||||
body "$_f" xhello > "$TMP/xhello.body"
|
||||
require "$TMP/xhello.body" "$HEX480" "$_who: Display.screens at bit 0x480"
|
||||
require "$TMP/xhello.body" "$HEX40" "$_who: Screen.root at bit 0x40"
|
||||
}
|
||||
|
||||
echo "== ROM bit displacements"
|
||||
check_offsets "$TI_XHELLO" "TI"
|
||||
check_offsets "$TMP/xhello.s.code" "ours"
|
||||
|
||||
echo "M3 diff: 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
|
||||
+11
-9
@@ -7,13 +7,13 @@ stable across firmware revisions; v2.2 is the first target.
|
||||
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
|
||||
`jauc` stub for every entry, and the M2 harness calls `_strlen` at
|
||||
`0x3FB458D0`. The other entries are recorded for later milestones and are not
|
||||
yet exercised by compiled C.
|
||||
|
||||
M3 owns the wider table: it extends the entries from the Ghidra project /
|
||||
`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
|
||||
because it is listed.
|
||||
`0x3FB458D0`. **M3 consumes exactly the four Xlib entries below** through the
|
||||
same stub generator; the compiled `scripts/m3/xhello.c` is the end-to-end proof.
|
||||
The table has not been widened beyond those five entries. M4 extends it for the
|
||||
real client (`XImage`/`XEvent` and the rest of the X11R4 calls) from the Ghidra
|
||||
project / `ptr_constants.py`, and validates each new entry on MAME, then on the
|
||||
real unit. Do not assume an address here is validated just because it is
|
||||
listed.
|
||||
|
||||
## v2.2 Xlib entries validated
|
||||
|
||||
@@ -30,8 +30,10 @@ against the live DWT X server):
|
||||
|
||||
`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`). It is not a
|
||||
re-extraction; M4 extends the table for `XImage`/`XEvent`.
|
||||
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
|
||||
bake them into the compiler: keep them in a per-ROM linker script.
|
||||
Reference in new issue
Block a user