Files
arti eacaab68f6 docs: correct the GEP scaling warning and M1 data limits
Raw clang IR has byte GEP offsets that llc treats as bits, so addresses
are 8x too small; always run tms-clang/tms-bitaddr --scale first.
Document that M1 supports only zero-initialized globals and rejects
.data/string literals loudly, and defer the spec's .data initializer
probe to M2.
2026-11-11 10:15:32 +00:00

240 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tms-c-compiler
A modern C toolchain for the DEC VT1200 / TI TMS34010, built as a small fork of
upstream LLVM.
**Status: M1.** `llc -march=tms34010` emits GSPA-compatible assembly for a
small set of leaf functions, and that assembly assembles, links and runs in
MAME. M1 is an **assembly-only, test-only** snapshot: it uses an A0–A3/A0
register convention, not the TI ABI, and has no object writer, calls or runtime.
- 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)
- 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 `tms34010`) — the full buildable LLVM 23.1.5 tree with the TMS34010
target, clang bit-address changes and the M1 backend.
- **This project:** <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>
— plans/specs, VT1200 integration tooling, `tms-bitaddr`, `tms-clang`,
symbols, the M1 harness and the spike.
The compiler fork is the canonical source for all target code. `patches/llvm`
here is a **frozen M0 mirror** used by the default upstream+patches build; M1
does not change it.
## What M1 provides
M1 fills in the M0 target skeleton and stops at assembly:
- `llc -march=tms34010 -filetype=asm` for leaf 32-bit functions:
integer/pointer arguments in `A0`–`A3`, result in `A0`, return via `RETS 0`.
- `ADD` for `i32`, `MOVI` for constants, `MOVE ..., 1` (FS1) for 32-bit
load/store, `MOVE ..., 0` (FS0) for `i16`, and sign-extending `MOVB` for
`i8` load/store.
- Constant global addresses as `MOVI _sym+bits`; a surviving GEP offset is a
bit offset (`ADDI 24`, not `ADDI 192`).
- Hard errors, not silent fallbacks, for calls (`M1 has no calls; M2 adds the
TI ABI`), stack frames (`M1 has no stack frame`), more than four register
arguments, unsupported signatures (varargs, `i64`/float/aggregate
parameters, `sret`/`byval`/`inreg`/`nest`/`returned`), unsupported return
types, and initialized globals or string literals.
- Seventeen `llvm-lit`/FileCheck tests under
`llvm/test/CodeGen/TMS34010/m1-*.ll` in the compiler fork, including
negative tests that pin the loud refusals.
- An end-to-end path: `scripts/tms-clang` IR → `llc -O0 -filetype=asm` →
**1987 GSPA/GSPLNK 2.00 under DOSBox** → COFF → the VT1200 loader in MAME.
The run checks `add(20, 22) == 42`, `load(&word) == 0x12345678`,
`second() == &arr[1]` (a `symbol+32` bit relocation), `loadb` of a byte
`0x81` returns the sign-extended `0xFFFFFF81`, and `storeb` leaves `0x81`
in memory.
- A `dis34` round-trip that reads the linked `.text` from the COFF and checks
the instructions GSPA encoded at their linked bit addresses.
## M1 is not the TI ABI
| | M1 test convention | TI ABI (M2) |
|---|---|---|
| Integer/pointer arguments | `A0`–`A3`, four maximum | program stack (via `A14` STK) |
| Return value | `A0` | `A8` |
| Frame | none; leaf functions only | `A13` FP, `A14` STK, `A15` system stack |
| Return instruction | `RETS 0` | `RETS 2` |
| Calls, callee-saves, stack args | rejected | required |
| Field select | harness sets FS1/FS0 before the leaf | `FS1 = 32` preserved across calls |
M1 assembly is not ABI-compatible with TI's compiler or the ROM Xlib. M2
replaces the convention; do not build on the M1 register assignment.
## Repository layout
```text
build/llvm-clean/ compiler fork checkout used for M1 (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/m1/ M1 harness: C inputs, entry.asm, link.cmd, checks
scripts/tests/m1/ M1 gates (run.sh, dis-roundtrip.sh)
scripts/tests/m0/ M0 integration tests
scripts/build-llvm.sh fork or upstream+patches build
scripts/fetch-vt1200.sh fetch the VT1200 RE tree at its pinned commit
spike/ measurement corpus and reports
symbols/ per-ROM import tables (v2.2 subset)
vendor/ fetched assets and LLVM sources (ignored)
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 M1 gates
additionally need `dosbox`, `mtools` (`mcopy`), the VT1200 SDK image, the
`dwt` COFF loader, and a built MAME `vt1200` binary
(`build/mame-src/vt1200`). On Debian:
```sh
sudo apt-get install -y cmake ninja-build ccache dosbox mtools
```
## Fetch the VT1200 assets
The compiler contract is checked against the VT1200 tree (TI `hello.s`, the
Xlib call sequence, `as34`, `dis34`, SDKs, manuals). It is not vendored:
```sh
./scripts/fetch-vt1200.sh # -> vendor/vt1200, pinned 17dfd24...
```
## Build the compiler
### Compiler fork (M1, the normal path)
The fork checkout already exists at `build/llvm-clean` on `tms34010`. Build it
in place:
```sh
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=tms34010 ./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
```
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=tms34010 ./scripts/build-llvm.sh
```
Fork mode **never** checks out, resets, cleans or patches the source tree; it
only runs CMake and Ninja. Local commits on the branch are safe. Use
`LLVM_BUILD=...` to keep a separate build directory. It builds `llc`, clang,
opt, `llvm-config`, FileCheck and the test helpers with X86 (host) and
TMS34010; a first build takes roughly 20–40 minutes.
### Upstream + patches (frozen M0 path)
```sh
./scripts/build-llvm.sh
```
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 M1 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.
## Build the M0 support tools
The M1 pipeline still 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 M1):
```sh
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
TMS_OPT=$PWD/build/llvm-clean-build/bin/opt \
TMS_BITADDR=$PWD/tools/tms-bitaddr/tms-bitaddr \
./scripts/tests/m0/run.sh
# -> M0 pipeline: PASS
```
## Run the M1 gates
These are the documented commands; all three must pass:
```sh
# 1. LLVM codegen tests: 17 passed
build/llvm-clean-build/bin/llvm-lit -v \
build/llvm-clean-build/test/CodeGen/TMS34010 --filter='m1-'
# 2. Compile, assemble, link and run in MAME
./scripts/tests/m1/run.sh
# -> M1 MAME: PASS
# 3. Rebuild the COFF and check the GSPA encodings with dis34
./scripts/tests/m1/dis-roundtrip.sh
# -> M1 dis34 round-trip: PASS
```
`run.sh` compiles five C inputs with `scripts/tms-clang -O2`, runs
`llc -march=tms34010 -O0 -filetype=asm` on the normalized IR, concatenates
the result with `scripts/m1/entry.asm` and `scripts/m1/char-test.asm`,
assembles/links with GSPA/GSPLNK 2.00 under DOSBox, then loads the COFF in
MAME. `dis-roundtrip.sh` runs the same build and then disassembles the linked
`.text` at the symbol addresses from the COFF symbol table. Paths are
overridable with `TMS_CLANG`, `TMS_OPT`, `TMS_BITADDR`, `TMS_LLC`, `MAME`,
`DOSBOX`, `SDK_IMG`, `M1_WORK`, `M1_COFF` and `M1_SDK`.
## Emit assembly by hand
```sh
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
TMS_OPT=$PWD/build/llvm-clean-build/bin/opt \
TMS_BITADDR=$PWD/tools/tms-bitaddr/tms-bitaddr \
scripts/tms-clang -O2 -o /tmp/add.ll scripts/m1/add.c
build/llvm-clean-build/bin/llc -march=tms34010 -O0 -filetype=asm \
/tmp/add.ll -o /tmp/add.s
```
Always produce the IR with `scripts/tms-clang` (or run `tms-bitaddr --scale`)
before `llc`: raw clang IR carries byte GEP offsets, but `llc` treats every
surviving GEP offset as a *bit* offset, so addresses would be 8× too small.
Run `llc -O0` on the normalized IR that `tms-clang` already optimized and
scaled. `tms-bitaddr --normalize` consumes that scaled IR and now rejects a
typed (non-`i8`) or vector GEP instead of emitting its byte offset as bits.
The lit tests use `llc -march=tms34010 -O2 -filetype=asm` on hand-written IR
to exercise the selector pipeline directly. Output is assembly only: `llc`
cannot emit an object file or a usable COFF for this target.
## Not yet supported (M2 and later)
- **Data:** only zero-initialized mutable globals (`.bss`) are supported.
Initialized globals, string literals and constant/private data (`.data`,
`.rodata`) make `llc` fail loudly; the `.data` initializer probe moves to
M2.
- **Object emission:** no `MCCodeEmitter`, object writer, `llc -filetype=obj`,
`clang -c`, or native COFF. GSPA assembles and GSPLNK links; the existing
COFF loader loads.
- **TI ABI:** `A8` return, `A13`/`A14`/`A15` dual stack, `RETS 2`, `FS1 = 32`
across calls, callee-saves, stack arguments, more than four arguments.
- **C features:** calls, locals/`alloca`, structures/`byval`, floats, varargs,
bitfields, 64-bit arithmetic, `memcpy`/`alloca` lowering.
- **Runtime and clients:** ROM/Xlib imports, `strlen`/`malloc`, X11 headers,
real VT1200 clients, debug info.
- **2ine:** M1 uses the proven 1987 GSPA/GSPLNK under DOSBox, not the external
2ine r5.01 tools; 2ine is an M2 speed-up.
These are M2–M5 in the design spec, each with its own plan.