docs: TMS34010 LLVM design, spike, and workspace setup
This commit is contained in:
commit
da553739c5
14 files changed
+2430
No files matched your search
@@ -0,0 +1,8 @@
|
||||
# LLVM source pulled for the spike; not part of this project.
|
||||
vendor/
|
||||
# Spike build artefacts.
|
||||
spike/bitscale
|
||||
spike/*.ll
|
||||
spike/*.o
|
||||
build/
|
||||
tools/tms-bitaddr/tms-bitaddr
|
||||
@@ -0,0 +1,660 @@
|
||||
# TMS34010 M0 — Fork and Bit-Address Semantics 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:** Make clang 23.1.5 understand TMS34010 addresses as bits: a `tms34010-unknown-none` target with TI's data model, pointer arithmetic scaled by 8, pointer differences divided by 8, and a verified early-GEP / late-normalization pipeline. No machine-code generation yet.
|
||||
|
||||
**Architecture:** A pinned LLVM 23.1.5 fork adds the triple, clang target info, a stub target machine, and one pointer-difference patch. GEP scaling and late normalization are a project tool (`tools/tms-bitaddr`), not an LLVM change, so the fork stays small. A wrapper (`scripts/tms-clang`) runs clang at `-O0`, scales, optimizes, then normalizes and verifies.
|
||||
|
||||
**Tech Stack:** LLVM/Clang 23.1.5, C++17, CMake/Ninja, `llvm-lit`/FileCheck, POSIX shell.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md`
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- LLVM tag: `llvmorg-23.1.5`. No LLVM source is vendored in the project; the fork lives in `build/llvm-src`, and `patches/llvm/*.patch` is the committed delta.
|
||||
- Target triple: `tms34010-unknown-none`. Little-endian, 32-bit pointers, 8-bit signed `char`, 16-bit `short`, 32-bit `int`/`long`/`size_t`/`ptrdiff_t`, 32-bit `wchar_t`, 32-bit `float`, 64-bit `double`/`long double`.
|
||||
- Data layout: `e-m:e-p:32:16-i8:8-i16:16-i32:16-i64:16-f32:16-f64:16-n8:16:32`; all struct types are packed (`Opts.PackStruct = 1` in `TargetInfo::adjust`).
|
||||
- Addresses are bit addresses after the GEP pass; `sizeof`, `offsetof`, `memcpy`/`memset` lengths and `alloca` sizes stay in bytes.
|
||||
- Pointer difference: divide by `sizeof(T) * 8`; `(int)(p + n) - (int)p` must stay an ordinary integer subtraction.
|
||||
- GEP contract: after the pass, a GEP offset is in bits; the backend must never apply `DataLayout` byte scaling to it (§7.3.1 of the spec).
|
||||
- M0 stops at LLVM IR and the `opt`-level normalizer. No `llc`, no ABI, no ROM calls, no Xlib. The `llc` half of the GEP-contract test is M1.
|
||||
- Build with `X86` (host tools) and `TMS34010`; Release with assertions; `clang`, `opt`, `llvm-lit`, `llvm-config` are the build outputs.
|
||||
- Test inputs are the spike corpus (`spike/xclient.c`, `spike/ptrdiff.c`, `spike/stress.c`) plus the new `scripts/tests/m0/*` files. No real hardware in M0.
|
||||
- VT1200 assets come from `scripts/fetch-vt1200.sh` into `vendor/vt1200`; they are ignored by git.
|
||||
- Every task ends with a commit. LLVM changes are committed in `build/llvm-src`; the project commit contains the re-exported patch (and any project files).
|
||||
|
||||
## Review Focus
|
||||
|
||||
1. `p - q` versus `(int)p - (int)q`: the first must divide by 8, the second must not. Tests in Task 3 and Task 5.
|
||||
2. Late GEPs created after `opt` (by InstCombine/CodeGenPrepare) must not be scaled a second time. Tests in Task 4 (`--normalize`) and Task 5 (`-O2` then normalize).
|
||||
3. A constant GEP (`"str" + 2`, `&arr[1]`) must become `symbol + bit_addend`, not a byte addend. Test in Task 4.
|
||||
4. Packed struct layout and `_Alignof`: `sizeof(struct{char;int;void*}) == 9`, offsets 1/5, `_Alignof(int) == 2`, `_Alignof(struct) == 1`. Test in Task 2.
|
||||
5. `char` signedness and Xlib pointer fields: `(char)-1 < 0`; the Xlib client's `Display->screens` and `Screen` stride survive scaling. Tests in Task 2 and Task 5.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```text
|
||||
build/llvm-src/ local LLVM clone at the tag; edited fork; ignored
|
||||
build/llvm-build/ Ninja build tree; ignored
|
||||
patches/llvm/ exported fork delta; committed
|
||||
tools/tms-bitaddr/ GEP scale/normalize tool; committed
|
||||
scripts/ build, wrapper, tests, fetch; committed
|
||||
spike/ measurement corpus and reports; committed
|
||||
docs/ design and plan; committed
|
||||
symbols/ per-ROM import tables; committed
|
||||
vendor/ fetched VT1200 tree; ignored
|
||||
```
|
||||
|
||||
## Preflight
|
||||
|
||||
- [ ] `git init` in `/home/arti/code/tms-c-compiler` if it is not a repo.
|
||||
- [ ] Ensure `.gitignore` contains `build/`, `vendor/`, `spike/*.ll`, `spike/bitscale`, `tools/tms-bitaddr/tms-bitaddr`.
|
||||
- [ ] Run `scripts/fetch-vt1200.sh` and confirm `vendor/vt1200/harness/c-hello/build/hello.s` exists.
|
||||
- [ ] Install `cmake`, `ninja-build`, `ccache` (`sudo apt-get install -y cmake ninja-build ccache`).
|
||||
- [ ] Commit the design doc, spike, scripts, and symbols as the initial state:
|
||||
```sh
|
||||
git add docs spike scripts symbols .gitignore
|
||||
git commit -m "docs: TMS34010 LLVM design, spike, and workspace setup"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Pinned fork, triple, clang target info, stub target machine
|
||||
|
||||
**Files:**
|
||||
- Create: `build/llvm-src/clang/test/CodeGen/TMS34010/triple.c` (fork)
|
||||
- Modify: `build/llvm-src/llvm/include/llvm/TargetParser/Triple.h` (fork)
|
||||
- Modify: `build/llvm-src/llvm/lib/TargetParser/Triple.cpp` (fork)
|
||||
- Create: `build/llvm-src/llvm/lib/Target/TMS34010/` (fork: `CMakeLists.txt`, `TMS34010TargetMachine.{h,cpp}`, `TMS34010Subtarget.{h,cpp}`, `TargetInfo/`, `MCTargetDesc/`)
|
||||
- Modify: `build/llvm-src/llvm/lib/Target/CMakeLists.txt`, `build/llvm-src/llvm/CMakeLists.txt` (fork)
|
||||
- Create: `build/llvm-src/clang/lib/Basic/Targets/TMS34010.{h,cpp}` (fork)
|
||||
- Modify: `build/llvm-src/clang/lib/Basic/Targets.cpp`, `build/llvm-src/clang/lib/Basic/Targets/CMakeLists.txt` (fork)
|
||||
- Create: `scripts/export-patches.sh` (project)
|
||||
- Create: `patches/llvm/0001-triple.patch`, `patches/llvm/0002-target-skeleton.patch` (project, generated)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: nothing.
|
||||
- Produces: `llvm::Triple::tms34010`; `clang::TMS34010TargetInfo`; `llvm::TMS34010TargetMachine` (stub; `addPassesToEmitFile` returns `true`); target registration symbols `LLVMInitializeTMS34010TargetInfo`, `LLVMInitializeTMS34010Target`, `LLVMInitializeTMS34010TargetMC`.
|
||||
|
||||
- [ ] **Step 1: Clone and build the pinned tree**
|
||||
|
||||
```sh
|
||||
mkdir -p build
|
||||
git clone --depth 1 --branch llvmorg-23.1.5 \
|
||||
https://github.com/llvm/llvm-project.git build/llvm-src
|
||||
cmake -S build/llvm-src/llvm -B build/llvm-build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON \
|
||||
-DLLVM_ENABLE_PROJECTS=clang \
|
||||
-DLLVM_TARGETS_TO_BUILD=X86 \
|
||||
-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=TMS34010 \
|
||||
-DLLVM_INCLUDE_TESTS=ON -DLLVM_INCLUDE_EXAMPLES=OFF \
|
||||
-DLLVM_INCLUDE_BENCHMARKS=OFF
|
||||
ninja -C build/llvm-build clang opt llvm-lit llvm-config FileCheck
|
||||
```
|
||||
|
||||
Expected: build completes; `build/llvm-build/bin/clang --version` reports 23.1.5.
|
||||
|
||||
- [ ] **Step 2: Write the failing lit test**
|
||||
|
||||
`build/llvm-src/clang/test/CodeGen/TMS34010/triple.c`:
|
||||
|
||||
```c
|
||||
// RUN: %clang_cc1 -triple tms34010-unknown-none -ffreestanding -emit-llvm -o - %s | FileCheck %s
|
||||
// CHECK: target triple = "tms34010-unknown-none"
|
||||
// CHECK: target datalayout = "e-m:e-p:32:16-i8:8-i16:16-i32:16-i64:16-f32:16-f64:16-n8:16:32"
|
||||
int add(int a, int b) { return a + b; }
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run it to verify it fails**
|
||||
|
||||
Run:
|
||||
```sh
|
||||
build/llvm-build/bin/llvm-lit -v \
|
||||
build/llvm-build/tools/clang/test --filter='TMS34010'
|
||||
```
|
||||
Expected: FAIL with `unknown target triple 'tms34010-unknown-none'` (or `unknown target`).
|
||||
|
||||
- [ ] **Step 4: Implement the triple and target skeleton**
|
||||
|
||||
In `Triple.h`, add `tms34010` to `enum ArchType` (next to the other 32-bit CISC entries). In `Triple.cpp`, add `"tms34010"` to the arch-name/parse/print paths, make `getArchPointerBitWidth` return 32, `isArch32Bit` return true, and `getDefaultFormat` return `ELF` for now (M0 does not emit objects).
|
||||
|
||||
Add the target directory with the minimum LLVM target:
|
||||
|
||||
- `TMS34010TargetMachine` constructor and `getSubtargetImpl`; `addPassesToEmitFile` returns `true` (not implemented in M0).
|
||||
- `TargetInfo/TMS34010TargetInfo.cpp` registers the target for `Triple::tms34010` with `RegisterTarget<Triple::tms34010, /*HasJIT=*/false>`.
|
||||
- `MCTargetDesc/TMS34010MCTargetDesc.{h,cpp}` with `TMS34010MCAsmInfo`, `TMS34010MCInstrInfo`, `TMS34010MCRegisterInfo`, `TMS34010MCSubtargetInfo` and the init functions.
|
||||
- Add the directory to `llvm/lib/Target/CMakeLists.txt` and the target name to `llvm/CMakeLists.txt` (`LLVM_ALL_TARGETS` / experimental list).
|
||||
|
||||
Add `clang/lib/Basic/Targets/TMS34010.{h,cpp}`:
|
||||
|
||||
```cpp
|
||||
class LLVM_LIBRARY_VISIBILITY TMS34010TargetInfo : public TargetInfo {
|
||||
public:
|
||||
TMS34010TargetInfo(const llvm::Triple &Triple, const TargetOptions &Opts);
|
||||
void getTargetDefines(const LangOptions &Opts,
|
||||
MacroBuilder &Builder) const override;
|
||||
bool hasFeature(StringRef Feature) const override { return false; }
|
||||
};
|
||||
```
|
||||
|
||||
Set `DataLayoutString` / `DescriptionString` to the Global Constraints layout, and register the target in the `clang/lib/Basic/Targets.cpp` switch and CMake list. Add defines `__tms34010__`, `__TMS34010__`, `__CHAR_BIT__ 8`, `__SIZEOF_POINTER__ 4`, `__ILP32__ 1`.
|
||||
|
||||
Rebuild: `ninja -C build/llvm-build clang opt llvm-lit`.
|
||||
|
||||
- [ ] **Step 5: Run the test to verify it passes**
|
||||
|
||||
Run:
|
||||
```sh
|
||||
build/llvm-build/bin/llvm-lit -v \
|
||||
build/llvm-build/tools/clang/test --filter='TMS34010'
|
||||
```
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 6: Export the fork delta and commit**
|
||||
|
||||
`scripts/export-patches.sh`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
SRC=${LLVM_SRC:-build/llvm-src}
|
||||
TAG=llvmorg-23.1.5
|
||||
OUT=${1:-patches/llvm}
|
||||
mkdir -p "$OUT"
|
||||
git -C "$SRC" diff "$TAG" -- \
|
||||
llvm/include/llvm/TargetParser llvm/lib/TargetParser \
|
||||
> "$OUT/0001-triple.patch"
|
||||
git -C "$SRC" diff "$TAG" -- \
|
||||
llvm/lib/Target/TMS34010 llvm/lib/Target/CMakeLists.txt llvm/CMakeLists.txt \
|
||||
> "$OUT/0002-target-skeleton.patch"
|
||||
git -C "$SRC" diff "$TAG" -- \
|
||||
clang/lib/Basic/Targets clang/lib/Basic/Targets.cpp \
|
||||
clang/lib/Basic/Targets/CMakeLists.txt clang/test/CodeGen/TMS34010 \
|
||||
> "$OUT/0003-clang-target.patch"
|
||||
git -C "$SRC" diff "$TAG" -- clang/lib/CodeGen/CGExprScalar.cpp \
|
||||
> "$OUT/0004-clang-ptrdiff.patch"
|
||||
find "$OUT" -type f -empty -delete
|
||||
```
|
||||
|
||||
Run it, then:
|
||||
```sh
|
||||
git add scripts/export-patches.sh patches/llvm
|
||||
git commit -m "llvm: add tms34010 triple, clang target info, and target skeleton"
|
||||
```
|
||||
|
||||
### Task 2: Data model and packed struct tests
|
||||
|
||||
**Files:**
|
||||
- Create: `build/llvm-src/clang/test/CodeGen/TMS34010/layout.c` (fork)
|
||||
- Modify: `build/llvm-src/clang/lib/Basic/Targets/TMS34010.{h,cpp}` (fork)
|
||||
- Modify: `patches/llvm/0003-clang-target.patch` (project, regenerated)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `clang::TMS34010TargetInfo` from Task 1.
|
||||
- Produces: the type model and `TMS34010TargetInfo::adjust(DiagnosticsEngine &, LangOptions &Opts, const TargetInfo *Aux)` setting `Opts.PackStruct = 1`.
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
`build/llvm-src/clang/test/CodeGen/TMS34010/layout.c`:
|
||||
|
||||
```c
|
||||
// RUN: %clang_cc1 -triple tms34010-unknown-none -ffreestanding -fsyntax-only %s
|
||||
struct S { char c; int i; void *p; };
|
||||
enum E { E0 = 0, E1 = 1 };
|
||||
|
||||
_Static_assert(sizeof(struct S) == 9, "packed struct");
|
||||
_Static_assert(__builtin_offsetof(struct S, i) == 1, "packed i");
|
||||
_Static_assert(__builtin_offsetof(struct S, p) == 5, "packed p");
|
||||
_Static_assert(_Alignof(struct S) == 1, "packed struct alignment");
|
||||
_Static_assert(_Alignof(int) == 2, "object alignment");
|
||||
_Static_assert(sizeof(int) == 4, "int");
|
||||
_Static_assert(sizeof(long) == 4, "long");
|
||||
_Static_assert(sizeof(void *) == 4, "pointer");
|
||||
_Static_assert(sizeof(short) == 2, "short");
|
||||
_Static_assert(sizeof(long double) == 8, "long double");
|
||||
_Static_assert(sizeof(float) == 4, "float");
|
||||
_Static_assert(sizeof(double) == 8, "double");
|
||||
_Static_assert(sizeof(long long) == 8, "long long");
|
||||
_Static_assert(sizeof(_Bool) == 1, "_Bool");
|
||||
_Static_assert(sizeof(__WCHAR_TYPE__) == 4, "wchar_t");
|
||||
_Static_assert(sizeof(__SIZE_TYPE__) == 4, "size_t");
|
||||
_Static_assert(sizeof(__PTRDIFF_TYPE__) == 4, "ptrdiff_t");
|
||||
_Static_assert((char)-1 < 0, "char is signed");
|
||||
_Static_assert(_Alignof(char) == 1, "char align");
|
||||
_Static_assert(_Alignof(short) == 2, "short align");
|
||||
_Static_assert(_Alignof(void *) == 2, "pointer align");
|
||||
_Static_assert(_Alignof(double) == 2, "double align");
|
||||
_Static_assert(sizeof(enum E) == 4, "enum");
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run it to verify it fails**
|
||||
|
||||
Run:
|
||||
```sh
|
||||
build/llvm-build/bin/llvm-lit -v \
|
||||
build/llvm-build/tools/clang/test --filter='TMS34010'
|
||||
```
|
||||
Expected: FAIL on the packed-struct assertions (currently 12/4/8) and/or enum/alignment.
|
||||
|
||||
- [ ] **Step 3: Complete the target info**
|
||||
|
||||
Set the `TargetInfo` widths and alignments from Global Constraints: `CharWidth=8`, `CharAlign=8`, `CharIsSigned=true`; `ShortWidth=16`; `IntWidth=32`; `LongWidth=32`; `LongLongWidth=64`; `PointerWidth=32`, `PointerAlign=16`; `FloatWidth=32`, `DoubleWidth=64`, `LongDoubleWidth=64`; `WCharType=SignedInt`, `WCharWidth=32`; `SizeType=UnsignedInt`, `PtrDiffType=SignedInt`, `IntMaxType=SignedLongLong`; `BoolWidth=8`, `BoolAlign=16`; scalar alignments (Int/Long/Float/Double/LongDouble) 16 bits.
|
||||
|
||||
Implement `adjust(DiagnosticsEngine &Diags, LangOptions &Opts, const TargetInfo *Aux) override { Opts.PackStruct = 1; }`.
|
||||
|
||||
Enums are 32-bit; the test gates `sizeof(enum E) == 4`. TI makes enums unsigned; if a later ABI test depends on the signedness, add the target's enum-type hook then.
|
||||
|
||||
- [ ] **Step 4: Run the test to verify it passes**
|
||||
|
||||
Run:
|
||||
```sh
|
||||
build/llvm-build/bin/llvm-lit -v \
|
||||
build/llvm-build/tools/clang/test --filter='TMS34010'
|
||||
```
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Regenerate the patch and commit**
|
||||
|
||||
```sh
|
||||
./scripts/export-patches.sh
|
||||
git add patches/llvm
|
||||
git commit -m "clang: tms34010 data model and packed structs"
|
||||
```
|
||||
|
||||
### Task 3: Address-unit hook and pointer-difference scaling
|
||||
|
||||
**Files:**
|
||||
- Create: `build/llvm-src/clang/test/CodeGen/TMS34010/ptrdiff.c` (fork)
|
||||
- Modify: `build/llvm-src/clang/include/clang/Basic/TargetInfo.h` (fork)
|
||||
- Modify: `build/llvm-src/clang/lib/Basic/Targets/TMS34010.h` (fork)
|
||||
- Modify: `build/llvm-src/clang/lib/CodeGen/CGExprScalar.cpp` (fork)
|
||||
- Modify: `patches/llvm/0003-clang-target.patch`, `patches/llvm/0004-clang-ptrdiff.patch` (project, regenerated)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `TMS34010TargetInfo` from Task 1/2.
|
||||
- Produces: `virtual unsigned TargetInfo::getAddressUnitBits() const { return 8; }`; `TMS34010TargetInfo::getAddressUnitBits() const override { return 1; }`; pointer-difference IR of `sdiv exact ... sizeof(T)*8`.
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
`build/llvm-src/clang/test/CodeGen/TMS34010/ptrdiff.c`:
|
||||
|
||||
```c
|
||||
// RUN: %clang_cc1 -triple tms34010-unknown-none -O0 -emit-llvm -o - %s | FileCheck %s
|
||||
struct S { char c; int i; void *p; };
|
||||
|
||||
long pd_char(char *a, char *b) { return a - b; }
|
||||
long pd_int(int *a, int *b) { return a - b; }
|
||||
long pd_struct(struct S *a, struct S *b) { return a - b; }
|
||||
long pd_vla(int n, char (*a)[n], char (*b)[n]) { return a - b; }
|
||||
int int_diff(char *p) { return (int)(p + 1) - (int)p; }
|
||||
int later(void) { return 0; }
|
||||
|
||||
// CHECK-LABEL: @pd_char
|
||||
// CHECK: sub.ptr.sub
|
||||
// CHECK: sdiv exact i32 %{{.*}}, 8
|
||||
// CHECK-LABEL: @pd_int
|
||||
// CHECK: sdiv exact i32 %{{.*}}, 32
|
||||
// CHECK-LABEL: @pd_struct
|
||||
// CHECK: sdiv exact i32 %{{.*}}, 72
|
||||
// CHECK-LABEL: @pd_vla
|
||||
// CHECK: mul {{.*}}, 8
|
||||
// CHECK-LABEL: @int_diff
|
||||
// CHECK-NOT: sdiv
|
||||
// CHECK-LABEL: @later
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run it to verify it fails**
|
||||
|
||||
Run:
|
||||
```sh
|
||||
build/llvm-build/bin/llvm-lit -v \
|
||||
build/llvm-build/tools/clang/test --filter='TMS34010'
|
||||
```
|
||||
Expected: FAIL — `pd_char` has no divide, `pd_int` divides by 4, `pd_struct` by 9.
|
||||
|
||||
- [ ] **Step 3: Implement the hook and the EmitSub change**
|
||||
|
||||
Add the virtual hook to `TargetInfo.h` next to `getCharWidth()`:
|
||||
|
||||
```cpp
|
||||
virtual unsigned getAddressUnitBits() const { return 8; }
|
||||
```
|
||||
|
||||
Override it in `TMS34010TargetInfo` to return 1.
|
||||
|
||||
In `ScalarExprEmitter::EmitSub` (pointer-difference branch, around the `diffInChars` and `elementSize` code), compute `unsigned Scale = 8 / CGF.getTarget().getAddressUnitBits();` and:
|
||||
|
||||
- Do not early-return for `elementSize.isOne()` unless `Scale == 1`.
|
||||
- `divisor = CGF.CGM.getSize(elementSize * Scale);`
|
||||
- For the VLA branch, multiply the VLA element-size divisor by `Scale` as well.
|
||||
- Keep `CreateExactSDiv(diffInChars, divisor, "sub.ptr.div")`.
|
||||
|
||||
Do not touch the integer `sub` path; `(int)(p + 1) - (int)p` must keep its plain subtraction.
|
||||
|
||||
Rebuild: `ninja -C build/llvm-build clang opt llvm-lit`.
|
||||
|
||||
- [ ] **Step 4: Run the test to verify it passes**
|
||||
|
||||
Run:
|
||||
```sh
|
||||
build/llvm-build/bin/llvm-lit -v \
|
||||
build/llvm-build/tools/clang/test --filter='TMS34010'
|
||||
```
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Regenerate the patches and commit**
|
||||
|
||||
```sh
|
||||
./scripts/export-patches.sh
|
||||
git add patches/llvm
|
||||
git commit -m "clang: bit-address pointer difference scaling"
|
||||
```
|
||||
|
||||
### Task 4: `tms-bitaddr` scale and normalize tool
|
||||
|
||||
**Files:**
|
||||
- Create: `tools/tms-bitaddr/bitscale.cpp`
|
||||
- Create: `tools/tms-bitaddr/build.sh`
|
||||
- Create: `tools/tms-bitaddr/tests/scale.ll`
|
||||
- Create: `tools/tms-bitaddr/tests/const-gep.ll`
|
||||
- Create: `tools/tms-bitaddr/tests/normalize.ll`
|
||||
- Create: `tools/tms-bitaddr/tests/run.sh`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: the fork's `llvm-config`, `opt`, `FileCheck`.
|
||||
- Produces: `tms-bitaddr --scale IN.ll OUT.ll` and `tms-bitaddr --normalize IN.ll OUT.ll`; `--scale` rewrites every non-vector GEP (instruction and constant expression) as `base + (byte_offset << 3)`; `--normalize` rewrites every remaining GEP (instruction and constant expression) as `base + offset` with the offset treated as bits (no further scaling).
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
|
||||
Also create `tools/tms-bitaddr/tests/run.sh`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
TOOL=${TMS_BITADDR:-$ROOT/tms-bitaddr}
|
||||
FILECHECK=${FILECHECK:-$ROOT/../../build/llvm-build/bin/FileCheck}
|
||||
"$TOOL" --scale "$ROOT/tests/scale.ll" | "$FILECHECK" "$ROOT/tests/scale.ll"
|
||||
"$TOOL" --scale "$ROOT/tests/const-gep.ll" | "$FILECHECK" "$ROOT/tests/const-gep.ll"
|
||||
"$TOOL" --normalize "$ROOT/tests/normalize.ll" | "$FILECHECK" "$ROOT/tests/normalize.ll"
|
||||
```
|
||||
|
||||
`tools/tms-bitaddr/tests/scale.ll`:
|
||||
|
||||
```llvm
|
||||
; RUN: %tms-bitaddr --scale %s | FileCheck %s
|
||||
define ptr @gep(ptr %p) {
|
||||
%q = getelementptr i8, ptr %p, i32 3
|
||||
ret ptr %q
|
||||
}
|
||||
; CHECK-LABEL: @gep
|
||||
; CHECK: %bit.addr = add i32 %bit.base, 24
|
||||
; CHECK-NOT: getelementptr
|
||||
```
|
||||
|
||||
`tools/tms-bitaddr/tests/const-gep.ll`:
|
||||
|
||||
```llvm
|
||||
; RUN: %tms-bitaddr --scale %s | FileCheck %s
|
||||
@g = global [4 x i8] zeroinitializer
|
||||
@s = global ptr getelementptr ([4 x i8], ptr @g, i32 0, i32 2)
|
||||
define ptr @c() {
|
||||
ret ptr getelementptr ([4 x i8], ptr @g, i32 0, i32 2)
|
||||
}
|
||||
; CHECK: @s = global ptr inttoptr (i32 add (i32 ptrtoint (ptr @g to i32), i32 16) to ptr)
|
||||
; CHECK: inttoptr (i32 add (i32 ptrtoint (ptr @g to i32), i32 16) to ptr)
|
||||
```
|
||||
|
||||
`tools/tms-bitaddr/tests/normalize.ll`:
|
||||
|
||||
```llvm
|
||||
; RUN: %tms-bitaddr --normalize %s | FileCheck %s
|
||||
define ptr @n(ptr %p) {
|
||||
%q = getelementptr i8, ptr %p, i32 16
|
||||
ret ptr %q
|
||||
}
|
||||
; CHECK-LABEL: @n
|
||||
; CHECK: %bit.addr = add i32 %bit.base, 16
|
||||
; CHECK-NOT: getelementptr
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the tests to verify they fail**
|
||||
|
||||
Run `tools/tms-bitaddr/tests/run.sh` (the tool does not exist yet). Expected: FAIL / command not found.
|
||||
|
||||
- [ ] **Step 3: Implement the tool**
|
||||
|
||||
Adapt `spike/bitscale.cpp` to LLVM 23.1.5. Keep the two modes and the stats output. `--scale` uses the existing constant-GEP rewrite and instruction GEP rewrite; `--normalize` rewrites the GEP to `inttoptr(add(ptrtoint(base), offset))` without shifting the offset. `build.sh`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
LLVM_CONFIG=${LLVM_CONFIG:-build/llvm-build/bin/llvm-config}
|
||||
cd "$(dirname "$0")"
|
||||
c++ -std=c++17 $("$LLVM_CONFIG" --cxxflags) bitscale.cpp \
|
||||
$("$LLVM_CONFIG" --ldflags --libs core irreader support --system-libs) \
|
||||
-o tms-bitaddr
|
||||
```
|
||||
|
||||
`tests/run.sh` is the driver above; it invokes the tool and `FileCheck` for each `.ll` file. The `RUN:` lines in the `.ll` files are documentation only in M0.
|
||||
|
||||
- [ ] **Step 4: Build and run the tests**
|
||||
|
||||
```sh
|
||||
LLVM_CONFIG=build/llvm-build/bin/llvm-config tools/tms-bitaddr/build.sh
|
||||
tools/tms-bitaddr/tests/run.sh
|
||||
```
|
||||
Expected: all three tests PASS; the scale test leaves zero GEPs; the constant GEP has a 16-bit addend; normalize adds 16, not 128.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```sh
|
||||
git add tools/tms-bitaddr
|
||||
git commit -m "tools: add tms-bitaddr GEP scale and normalize tool"
|
||||
```
|
||||
|
||||
### Task 5: Pipeline wrapper and integration tests
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/tms-clang`
|
||||
- Create: `scripts/tests/m0/ptrdiff.c`
|
||||
- Create: `scripts/tests/m0/run.sh`
|
||||
- Modify: `.gitignore` if needed
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `clang`, `opt`, `tms-bitaddr` from Tasks 1–4.
|
||||
- Produces: `scripts/tms-clang [-O0|-O2] [-I dir] -o OUT.ll IN.c`, which runs clang `-O0`, `tms-bitaddr --scale`, `opt -passes='default<O2>'` (unless `-O0`), and `tms-bitaddr --normalize`.
|
||||
|
||||
- [ ] **Step 1: Write the failing integration test**
|
||||
|
||||
`scripts/tests/m0/ptrdiff.c`:
|
||||
|
||||
```c
|
||||
int int_diff(char *p) { return (int)(p + 1) - (int)p; }
|
||||
int int_diff4(int *p) { return (int)(p + 1) - (int)p; }
|
||||
long pd_char(char *a, char *b) { return a - b; }
|
||||
long pd_int(int *a, int *b) { return a - b; }
|
||||
```
|
||||
|
||||
`scripts/tests/m0/run.sh`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
OUT=${TMPDIR:-/tmp}/tms-m0
|
||||
mkdir -p "$OUT"
|
||||
TMS_CLANG=${TMS_CLANG:-$ROOT/build/llvm-build/bin/clang}
|
||||
TMS_OPT=${TMS_OPT:-$ROOT/build/llvm-build/bin/opt}
|
||||
TMS_BITADDR=${TMS_BITADDR:-$ROOT/tools/tms-bitaddr/tms-bitaddr}
|
||||
export TMS_CLANG TMS_OPT TMS_BITADDR
|
||||
|
||||
"$ROOT/scripts/tms-clang" -O2 -o "$OUT/pdiff.ll" "$ROOT/scripts/tests/m0/ptrdiff.c"
|
||||
grep -A2 'define.*@int_diff(' "$OUT/pdiff.ll" | grep -q 'ret i32 8'
|
||||
grep -A2 'define.*@int_diff4' "$OUT/pdiff.ll" | grep -q 'ret i32 32'
|
||||
grep -A6 'define.*@pd_char' "$OUT/pdiff.ll" | grep -Eq 'sdiv|ashr|udiv'
|
||||
grep -A6 'define.*@pd_int' "$OUT/pdiff.ll" | grep -Eq 'sdiv|ashr|udiv'
|
||||
"$TMS_OPT" -passes=verify -disable-output "$OUT/pdiff.ll"
|
||||
|
||||
"$ROOT/scripts/tms-clang" -O2 -I "$ROOT/spike" -o "$OUT/xclient.ll" "$ROOT/spike/xclient.c"
|
||||
test "$(grep -c getelementptr "$OUT/xclient.ll")" -eq 0
|
||||
grep -Eq 'add i32 %bit.base[0-9]*, 1056' "$OUT/xclient.ll"
|
||||
grep -Eq 'mul i32 [^,]+, 640' "$OUT/xclient.ll"
|
||||
"$TMS_OPT" -passes=verify -disable-output "$OUT/xclient.ll"
|
||||
|
||||
"$ROOT/scripts/tms-clang" -O0 -o "$OUT/pdiff.o0.ll" "$ROOT/scripts/tests/m0/ptrdiff.c"
|
||||
test "$(grep -c getelementptr "$OUT/pdiff.o0.ll")" -eq 0
|
||||
echo "M0 pipeline: PASS"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run it to verify it fails**
|
||||
|
||||
Run `scripts/tests/m0/run.sh`. Expected: FAIL (wrapper missing).
|
||||
|
||||
- [ ] **Step 3: Implement the wrapper**
|
||||
|
||||
`scripts/tms-clang` must:
|
||||
|
||||
1. Parse `-O0`/`-O2` (default `-O2` after scaling) and `-I` passes; pass `-ffreestanding -nostdinc` plus the include paths to clang.
|
||||
2. Run `"$TMS_CLANG" --target=tms34010-unknown-none -O0 -Xclang -disable-O0-optnone -S -emit-llvm -o tmp.ll "$IN"`.
|
||||
3. Run `"$TMS_BITADDR" --scale tmp.ll tmp.scaled.ll`.
|
||||
4. Unless `-O0`, run `"$TMS_OPT" -passes='default<O2>' -S tmp.scaled.ll -o tmp.opt.ll`.
|
||||
5. Run `"$TMS_BITADDR" --normalize tmp.input.ll "$OUT"`.
|
||||
6. Leave temporary files under `${TMPDIR:-/tmp}` and clean them up.
|
||||
|
||||
Do not invoke `llc`; M0 output is IR.
|
||||
|
||||
- [ ] **Step 4: Run the integration test to verify it passes**
|
||||
|
||||
Run `scripts/tests/m0/run.sh`. Expected: `M0 pipeline: PASS`.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```sh
|
||||
git add scripts/tms-clang scripts/tests/m0 .gitignore
|
||||
git commit -m "scripts: add M0 clang/scale/opt/normalize wrapper and tests"
|
||||
```
|
||||
|
||||
### Task 6: Re-run the spike on 23.1.5 and record it
|
||||
|
||||
**Files:**
|
||||
- Modify: `spike/run.sh` (accept `CLANG`, `OPT`, `LLVMCONFIG` from the environment)
|
||||
- Create: `spike/REPORT-23.md`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: the Task 5 wrapper/tool and the 23.1.5 build.
|
||||
- Produces: a recorded 23.1.5 measurement, replacing the 19.1.7-only evidence as the current result.
|
||||
|
||||
- [ ] **Step 1: Make `spike/run.sh` use the environment**
|
||||
|
||||
Default `CLANG=clang-19`, `OPT=opt-19`, `LLVMCONFIG=llvm-config-19`, `CXX=clang++-19`; allow the caller to override all four. Use `TMS_BITADDR` if set, otherwise build `spike/bitscale` as today.
|
||||
|
||||
- [ ] **Step 2: Run the spike against 23.1.5**
|
||||
|
||||
```sh
|
||||
CLANG=$PWD/build/llvm-build/bin/clang \
|
||||
OPT=$PWD/build/llvm-build/bin/opt \
|
||||
LLVMCONFIG=$PWD/build/llvm-build/bin/llvm-config \
|
||||
CXX=$PWD/build/llvm-build/bin/clang++ \
|
||||
TMS_BITADDR=$PWD/tools/tms-bitaddr/tms-bitaddr \
|
||||
./spike/run.sh
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Record the results**
|
||||
|
||||
Create `spike/REPORT-23.md` with: LLVM 23.1.5, the same table as `REPORT.md`, the 35 → 0 Xlib GEP result, `int_diff` = 8, `int_diff4` = 32, `pd_char` = bytes, and any differences from 19.1.7. State explicitly that this is the current measured result and 19.1.7 is historical.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```sh
|
||||
git add spike/run.sh spike/REPORT-23.md
|
||||
git commit -m "spike: re-run on LLVM 23.1.5"
|
||||
```
|
||||
|
||||
### Task 7: Clean-build patch series and documentation
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/build-llvm.sh`
|
||||
- Create: `README.md` (or `docs/build.md`)
|
||||
- Modify: `patches/llvm/*.patch` (final export)
|
||||
- Modify: `.gitignore`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: all previous tasks.
|
||||
- Produces: a clean clone + patch + build path and a documented M0 test command.
|
||||
|
||||
- [ ] **Step 1: Write the clean-build script**
|
||||
|
||||
`scripts/build-llvm.sh`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
cd "$ROOT"
|
||||
mkdir -p build
|
||||
SRC=${LLVM_SRC:-build/llvm-clean}
|
||||
BUILD=${LLVM_BUILD:-build/llvm-clean-build}
|
||||
TAG=llvmorg-23.1.5
|
||||
if [ ! -d "$SRC/.git" ]; then
|
||||
git clone --depth 1 --branch "$TAG" \
|
||||
https://github.com/llvm/llvm-project.git "$SRC"
|
||||
fi
|
||||
git -C "$SRC" checkout --detach "$TAG"
|
||||
git -C "$SRC" reset --hard
|
||||
PATCHROOT=$(pwd)/patches/llvm
|
||||
for p in "$PATCHROOT"/*.patch; do
|
||||
[ -e "$p" ] || continue
|
||||
git -C "$SRC" apply "$p"
|
||||
done
|
||||
cmake -S "$SRC/llvm" -B "$BUILD" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON \
|
||||
-DLLVM_ENABLE_PROJECTS=clang \
|
||||
-DLLVM_TARGETS_TO_BUILD=X86 \
|
||||
-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=TMS34010 \
|
||||
-DLLVM_INCLUDE_TESTS=ON -DLLVM_INCLUDE_EXAMPLES=OFF \
|
||||
-DLLVM_INCLUDE_BENCHMARKS=OFF
|
||||
ninja -C "$BUILD" clang opt llvm-lit llvm-config FileCheck
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the clean build**
|
||||
|
||||
```sh
|
||||
./scripts/build-llvm.sh
|
||||
```
|
||||
Expected: patches apply cleanly; build succeeds. If a patch does not apply, regenerate it with `scripts/export-patches.sh` and retry.
|
||||
|
||||
- [ ] **Step 3: Run the M0 tests against the clean build**
|
||||
|
||||
```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
|
||||
```
|
||||
Expected: `M0 pipeline: PASS`, plus the lit tests:
|
||||
```sh
|
||||
build/llvm-clean-build/bin/llvm-lit -v \
|
||||
build/llvm-clean-build/tools/clang/test --filter='TMS34010'
|
||||
```
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 4: Document**
|
||||
|
||||
`README.md` (or `docs/build.md`) must state: the pinned tag, the repository layout, the build command, how to run the M0 tests, the fact that `vendor/` is fetched and ignored, and that M1 (backend/ABI) follows. Do not claim code generation works.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```sh
|
||||
git add scripts/build-llvm.sh README.md docs .gitignore patches/llvm
|
||||
git commit -m "build: clean LLVM fork build from pinned tag and patches"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
This plan is M0 only. M1–M5 (backend, TI ABI/ROM calls, Xlib, client, robustness) are separate plans because their tasks depend on what M0/M1 measure; the spec's milestones define their boundaries.
|
||||
|
||||
After implementation, the expected state is: a clean LLVM 23.1.5 build with the `tms34010-unknown-none` clang target; correct IR for pointer arithmetic and pointer differences; a scale/normalize tool; and an integration test proving the Xlib client's IR is bit-addressed — no TMS machine code yet.
|
||||
@@ -0,0 +1,495 @@
|
||||
# TMS34010 LLVM toolchain — design
|
||||
|
||||
**Status:** proposed for approval, 2026-11-10.
|
||||
**Scope:** a modern C toolchain for the DEC VT1200 (TI TMS34010), built as a
|
||||
minimal fork of upstream LLVM 23.1.5.
|
||||
|
||||
The working directory is `/home/arti/code/tms-c-compiler`. The bit-address
|
||||
spike lives in `spike/`; its report is `spike/REPORT.md`. The spike used LLVM
|
||||
19.1.7 and is reference only; the implementation pins **LLVM 23.1.5**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Intent
|
||||
|
||||
The VT1200 runs its X server, terminal manager, VT emulator and network stack
|
||||
from ROM on a TMS34010. Its only C compilers are TI's 1987 K&R compiler and the
|
||||
1990 r5.01 OS/2 tools; both are awkward to run and cannot express modern C.
|
||||
|
||||
We want `clang` to compile C for the TMS34010 with the **TI ABI**, so that a
|
||||
compiled program can call the ROM's `XOpenDisplay`, `XCreateSimpleWindow`,
|
||||
`malloc`, `strlen`, `XSync`, and so on, and can be loaded by the existing COFF
|
||||
loader. The fork must stay small against upstream: a new target machine, a
|
||||
small generic address-unit hook in clang, one pointer-difference fix, and an
|
||||
early GEP-scaling pass.
|
||||
|
||||
## 2. Goals
|
||||
|
||||
- `clang --target=tms34010-unknown-none -O2` produces code that runs on a real
|
||||
VT1200 and in MAME.
|
||||
- TI ABI compatibility: call and be called by ROM Xlib, `rts.lib`, `flib.lib`
|
||||
and `malloc`; match data layout, calling convention, stacks, field-size
|
||||
state and struct layout.
|
||||
- Ordinary C11/C17 (whatever clang supports); no C++ initially.
|
||||
- Minimal fork: TMS34010 target under `llvm/lib/Target/TMS34010`, clang target
|
||||
info, one generic `TargetInfo` hook, one pointer-difference patch, one
|
||||
bit-address IR pass.
|
||||
- Reproducible Linux build from a pinned LLVM tag; no DOSBox or OS/2 in the
|
||||
compiler path.
|
||||
- First output path: GSPA-compatible assembly → `gspa`/`gsplnk` under 2ine →
|
||||
COFF → existing loader. Native LLVM COFF later.
|
||||
- Test on MAME and real hardware; use the TI compiler as an oracle.
|
||||
|
||||
## 3. Non-goals
|
||||
|
||||
- Rebuilding the ROM/firmware or a complete OS.
|
||||
- C++, exceptions, RTTI, threads, dynamic linking, MMU support.
|
||||
- DWARF/GDB in the first milestone; symbol map and line info first.
|
||||
- A new libc; reuse the ROM runtime and `rts.lib`/`flib.lib`.
|
||||
- Bitfields, varargs and floating point in the first milestone (needed later;
|
||||
a minimal Xlib client does not use them).
|
||||
- Upstream acceptance; the target may remain out-of-tree.
|
||||
- Any byte-address workaround that breaks Xlib structs or binary compatibility.
|
||||
|
||||
## 4. Constraints and facts
|
||||
|
||||
### 4.1 CPU and C model
|
||||
|
||||
- TMS34010: 32-bit CISC, bit-addressed, 16-bit instruction words, 1–3-word
|
||||
instructions, 256-byte instruction cache, no MMU, 2–4 MB RAM, little-endian.
|
||||
- Pointers are 32-bit **bit** addresses: `char *p; p + 1` adds 8.
|
||||
- `char` is **signed** 8 bits; `unsigned char` 8; `short` 16; `int` and `long`
|
||||
are 32; `double` and `long double` are 64; `float` 32; `enum` is unsigned
|
||||
32-bit; `wchar_t` is 32; `size_t`/`ptrdiff_t` 32. (C guide §4.2, §6.)
|
||||
- All objects except struct/array members are 16-bit aligned. Structure and
|
||||
array members are **fully packed**: `sizeof(struct { char c; int i; void *p; })`
|
||||
is 9, offsets 1 and 5. Only the first struct in an array is 16-bit aligned.
|
||||
(C guide §4.3, §5.1.6.)
|
||||
- Structures passed by value are rounded up to the next 16-bit boundary.
|
||||
|
||||
### 4.2 ABI
|
||||
|
||||
- Registers: A0–A12 general, A8 return value, A13 FP, A14 STK (program stack),
|
||||
A15 system stack. B0–B14 are unused by TI's C compiler.
|
||||
- Caller: if arguments are passed or a float/double is returned, push the old
|
||||
STK on the system stack (`MOVE STK,-*SP`); push arguments on the program
|
||||
stack in reverse order, widened to 32 bits; structs by value rounded to
|
||||
16 bits; struct return address pushed on the program stack; call with
|
||||
`CALLA` (direct) or `CALL Rs` (indirect).
|
||||
- Callee: save used A registers except A8 on the system stack (`MMTM SP,…`);
|
||||
set `FP = STK`; `ADDI frame_size,STK`; locals at positive FP offsets, first
|
||||
argument at `FP(-32)`; restore STK from `*SP(offset)`; `MMFM`; `RETS 2` if
|
||||
arguments or stack return, `RETS 0` otherwise.
|
||||
- `FS1` must be 32 inside C code and `FE0` must be 0; `FS0` may be changed
|
||||
without restoring. Xlib clobbers the field-size state, so FS1 must be
|
||||
re-established after every call.
|
||||
- Struct return: caller allocates, pushes the address; callee copies. Float/
|
||||
double return: caller allocates space on the program stack, callee copies.
|
||||
(C guide §5.3.)
|
||||
- Symbols use a leading underscore (`_main`); sections are `.text`, `.data`,
|
||||
`.bss`; code is position-dependent and linked at fixed bit addresses.
|
||||
|
||||
### 4.3 Toolchain and loader
|
||||
|
||||
- Section origins and sizes, and assembler/linker location counters, are in
|
||||
**bits**. `SECTIONS { .text 3F208000h: {} }` is a bit address.
|
||||
- The existing COFF loader places sections at header addresses, zeroes `.bss`,
|
||||
and enters at the header entry point. Payloads are loaded into free RAM at
|
||||
fixed bit addresses.
|
||||
- GSPA/gsplnk under 2ine produce COFF and run in ~50 ms. `as34`/`ld34` are
|
||||
native but emit a custom `linkhdr`, not COFF.
|
||||
- The VT1200 tree is fetched by `scripts/fetch-vt1200.sh` at pinned commit
|
||||
`17dfd24340fe3346433f90457cb48cb8786dff39` into `vendor/vt1200` (it is not
|
||||
vendored; `vendor/` is ignored). It provides `harness/c-hello` including
|
||||
`build/hello.s`, `dwt/loader`, `dwt/gui/xhello.asm`, `tools/as34`,
|
||||
`tools/smart34010/dis34.c`, `tools/tms34010-sdk`, `docs/TMS34010-users-guide.txt`
|
||||
and the Ghidra notes. MAME itself is an external build; the repo contains the
|
||||
driver and run scripts, not the emulator binary.
|
||||
- Xlib headers are not usable files in that tree (`reference/x11/r4` and `/r5`
|
||||
are gitlinks without a URL). Use X11R4 headers from X.Org, or the system
|
||||
X11R7 headers, and verify the core `Display`/`Screen`/`XImage`/`XEvent`
|
||||
layouts against the ROM before relying on them.
|
||||
- ROM import symbols live in `symbols/vt1200-v22.syms`. It contains only the
|
||||
entries already proven by `xhello.asm`/the README; the rest must be extracted
|
||||
from the Ghidra project or `ptr_constants.py` and validated before the first
|
||||
Xlib call. Addresses are per-ROM-revision; do not assume they are stable
|
||||
across v2.1/v2.2 or across units.
|
||||
|
||||
### 4.4 LLVM
|
||||
|
||||
- Pin `llvmorg-23.1.5` (latest stable, 2026-11-03). Build with
|
||||
`LLVM_TARGETS_TO_BUILD="X86;TMS34010"` (or the experimental-target variable),
|
||||
assertions on, clang enabled.
|
||||
- The spike on 19.1.7 showed: a 30-line GEP-scaling pass scales all GEPs in a
|
||||
real Xlib client (35 → 0), verifies, and survives `-O2`; pointer subtraction
|
||||
needs a frontend patch; `-fpack-struct=1` matches TI's packed layout; the
|
||||
dual-stack ABI is the larger backend job. Re-validate on 23.1.5 first.
|
||||
- The pinned source is `vendor/llvm-project-23.1.5`. That checkout is sparse
|
||||
(IR/CodeGen/target/CodeGen dirs only); a real build needs a full checkout or
|
||||
a fresh clone at the same tag.
|
||||
|
||||
## 5. Prior art
|
||||
|
||||
- **TI 2.01 K&R and 5.01 ANSI C**: the ABI and runtime we must match; use as a
|
||||
differential oracle for layout and call behaviour.
|
||||
- **Andrew Morton's GCC 2.5.8 port**: proves C/C++ on the CPU, but uses a
|
||||
different ABI (A15 stack, A14 FP, A0 return, FS0=32/FS1=16) and about 110
|
||||
`AKPM` changes spread through GCC. It cannot link `rts.lib` or call Xlib
|
||||
as-is. Reference for bit-address pitfalls, not a base.
|
||||
- **as34/ld34**: native assembler/linker from Morton's toolchain; custom
|
||||
object format, not COFF.
|
||||
- **GSPA/gsplnk**: TI assembler/linker, COFF output, runs under 2ine.
|
||||
- **smart34010/dis34**: disassembler with two encoding fixes, validated
|
||||
against GSPA by byte-exact ROM round-trip.
|
||||
- **MAME `34010ops.hxx`**: compact opcode table with encodings; useful to
|
||||
generate and cross-check the LLVM instruction definitions.
|
||||
- **Spike 2026-11-10**: see `spike/REPORT.md`.
|
||||
|
||||
## 6. Approaches considered
|
||||
|
||||
1. **LLVM fork — chosen.** Best optimizer and C/C++ frontend; existing MC,
|
||||
asm-printer and object infrastructure; the target is a clean new directory.
|
||||
Cost: a real backend plus a small clang change.
|
||||
2. **GCC port — rejected.** Forward-porting Morton's 2.5.8, or a new GCC 15
|
||||
target, repeats the bit-address hacks across a compiler whose interfaces
|
||||
are less modular, and the old port's ABI is not TI's. Similar effort for
|
||||
less maintainability.
|
||||
3. **Small custom backend / QBE / vbcc — rejected for now.** Fastest to a
|
||||
first binary and smallest codebase, but loses clang's C11/C++ and the LLVM
|
||||
optimizer, and we would own the frontend. Keep as a fallback if the LLVM
|
||||
target proves impractical.
|
||||
4. **LLVM with byte addresses + boundary conversions — rejected.** Xlib's
|
||||
`Display`, `Screen`, `XImage`, `XEvent`, `XTextProperty` and `XClassHint`
|
||||
contain pointers; standard macros (`ScreenOfDisplay`, `RootWindow`,
|
||||
`DefaultVisual`) traverse them. A byte-address view would need conversions
|
||||
at every pointer field, or custom wrapper macros.
|
||||
|
||||
## 7. Design
|
||||
|
||||
### 7.1 Fork and build
|
||||
|
||||
- Pin `llvmorg-23.1.5`; keep a fork remote and an exported patch series so the
|
||||
delta is reviewable (the `tools/as34` pattern). Do not vendor the LLVM tree
|
||||
in the final repo; `vendor/` is spike-only.
|
||||
- Install `cmake`, `ninja-build`, `ccache`; build X86 (host tools) plus
|
||||
TMS34010; Release with assertions.
|
||||
- Patch sets:
|
||||
1. clang: `TargetInfo::getAddressUnitBits()` (new; default 8; TMS34010 returns
|
||||
1) and the TMS34010 `TargetInfo`. The hook is a scalar used **only** by the
|
||||
pointer-difference divisor. It does not change `CharUnits`, `sizeof`,
|
||||
GEP emission or `DataLayout`; object sizes and `memcpy` lengths stay in
|
||||
bytes.
|
||||
2. clang: pointer-difference scaling in `CGExprScalar.cpp`.
|
||||
3. project tool `tools/tms-bitaddr` (GEP scaling and late normalization),
|
||||
run by the driver wrapper; not an LLVM patch initially, so the fork stays
|
||||
smaller.
|
||||
4. llvm: the TMS34010 target.
|
||||
- Keep all TMS-specific code inside the target and the target-info files; the
|
||||
only generic clang change is the address-unit query and the pointer-difference
|
||||
scale, both small enough to be upstreamable independently. If the hook is
|
||||
rejected upstream, the pointer-difference patch can read the triple directly.
|
||||
|
||||
### 7.2 Target description
|
||||
|
||||
- Triple: `tms34010-unknown-none` (bare metal).
|
||||
- Little-endian; 32-bit pointers; 8-bit `char`; 16-bit `short`; 32-bit `int`,
|
||||
`long`, `size_t`, `ptrdiff_t`; 32-bit `wchar_t`; 32-bit `float`; 64-bit
|
||||
`double`/`long double`.
|
||||
- Data layout: scalars 16-bit aligned (`i32:16`, `i64:16`, `f32:16`, `f64:16`,
|
||||
`p:32:16`) with **packed struct types** (`<{ … }>`), not globally 1-byte
|
||||
scalar alignment. `-fpack-struct=1` is the spike's approximation; the target
|
||||
should make packed structs the default.
|
||||
- `char` signed; `enum` unsigned 32-bit; `_Bool` 8-bit (object alignment 2,
|
||||
member alignment 1). `long long` is 64-bit but has no TI oracle.
|
||||
- `_Alignof`/`alignof`: scalar objects report 2 bytes; a packed struct reports
|
||||
1 in the frontend because its LLVM type is `<{...}>`. The backend still aligns
|
||||
standalone globals, allocas and frame objects to 2 bytes. TI C89 has no
|
||||
`_Alignof`, so this is a documented modern-C difference, not an ABI break for
|
||||
ROM code; a custom AST layout hook can revisit it if user code depends on
|
||||
`_Alignof(struct)`.
|
||||
- `Bool` in Xlib is `int`, unaffected.
|
||||
|
||||
### 7.3 Bit-address semantics in clang/LLVM
|
||||
|
||||
The spike established the split:
|
||||
|
||||
- **GEPs and pointer arithmetic**: a `tms-bitaddr` project tool rewrites every
|
||||
`getelementptr` instruction and constant-expression GEP to
|
||||
`base + (byte_offset << 3)`. It must run before clang's optimizer; start with
|
||||
a driver step (`clang -O0 -emit-llvm` → `tms-bitaddr --scale` → `opt -O2`
|
||||
→ `tms-bitaddr --normalize`) and integrate at an early pass-builder extension
|
||||
point later.
|
||||
- **Pointer difference**: patch `ScalarExprEmitter::EmitSub` in
|
||||
`clang/lib/CodeGen/CGExprScalar.cpp`. The raw difference of two bit addresses
|
||||
is a bit count; the divisor must be `sizeof(T) * 8`, including `char` where
|
||||
the byte-machine division disappears. A pure IR pass cannot distinguish
|
||||
`p - q` from `(int)p - (int)q`, so this must happen where the AST still knows
|
||||
the expression is a pointer subtraction.
|
||||
- **Sizes stay in bytes**: `sizeof`, `offsetof`, `memcpy`/`memset` lengths,
|
||||
`alloca` sizes and `lifetime` sizes are byte quantities. The backend
|
||||
multiplies by 8 when converting them to bit counts or bit addresses.
|
||||
- **Pointer-to-integer casts** keep their bit-address values; no scaling.
|
||||
|
||||
Tests: `p + 1`, `p - q`, `(int)(p + 1) - (int)p`, arrays, structs, function
|
||||
pointers, string literals, global initializers. FileCheck the IR, then execute
|
||||
the same cases on MAME.
|
||||
|
||||
#### 7.3.1 GEP contract after the pass
|
||||
|
||||
The pass runs before optimization, and later passes can recreate GEPs.
|
||||
`InstCombine::visitIntToPtr` turns `inttoptr(add(ptrtoint Base, Off))` back into
|
||||
`getelementptr i8, Base, Off` when the pointer is only compared or cast, and
|
||||
`CodeGenPrepare` can sink addresses into GEPs. Those GEPs carry an offset that
|
||||
is already in **bits**, not bytes. The design therefore fixes one contract:
|
||||
|
||||
- After the pass, every IR address is a bit address. A GEP offset is an address
|
||||
offset **in bits**. The backend must never apply `DataLayout` type-size
|
||||
scaling to a GEP offset.
|
||||
- The target overrides `TargetSubtargetInfo::addrSinkUsingGEPs()` to `false`, so
|
||||
`CodeGenPrepare` does not introduce byte-offset GEPs.
|
||||
- A late pre-ISel normalization pass canonicalizes any remaining GEP (including
|
||||
ones created by InstCombine) into `base + offset` with the offset treated as
|
||||
bits, and verifies that no typed GEP with a byte addend survives.
|
||||
- ISel lowers a GEP as `base + offset`, not via
|
||||
`DataLayout::getIndexedOffsetInType` byte arithmetic.
|
||||
|
||||
This must be tested with `llc` on pointer-arithmetic programs, not only with
|
||||
`opt`; the spike stopped before ISel. A wrong choice here miscompiles every
|
||||
non-constant pointer expression by 8×.
|
||||
|
||||
### 7.4 ABI implementation
|
||||
|
||||
- Custom calling convention `CC_TMS34010`.
|
||||
- `LowerFormalArguments` / `LowerCall` / `LowerReturn` implement the caller and
|
||||
callee rules in §4.2 exactly; `sret` and `byval` follow the TI layout.
|
||||
- **Explicit arguments**: pushed on the program stack (A14) in reverse order,
|
||||
each widened to 32 bits. The first explicit argument is at `FP(-32)`, the
|
||||
second at `FP(-64)`, etc.
|
||||
- **Struct return**: the caller pushes the return-space address **after** the
|
||||
explicit arguments, so it is the top program-stack slot: the callee reads it
|
||||
at `FP(-32)` and the first explicit argument is at `FP(-64)`.
|
||||
- **Old STK**: the caller saves A14 on the system stack only when it passes
|
||||
arguments or the callee returns a float/double. A no-argument function with
|
||||
locals has no saved old STK; the callee restores STK from FP before `RETS 0`.
|
||||
- **System stack layout** (grows down), after the callee's `MMTM` of `n`
|
||||
registers:
|
||||
|
||||
```text
|
||||
SP + (n+1)*32 old STK (if saved)
|
||||
SP + n*32 return PC
|
||||
SP + ... saved registers
|
||||
SP top of system stack
|
||||
```
|
||||
|
||||
`RETS N` loads PC and advances SP by `32 + 16N` bits in total (the 32 is the
|
||||
PC itself); `RETS 2` is the PC + old-STK case (64 bits), `RETS 0` is PC only
|
||||
(32 bits).
|
||||
- **Frame**: `FP = STK`; `ADDI frame_bits,STK`; locals at positive FP offsets,
|
||||
arguments at negative offsets. Frame size, saved-register offsets and `ADDI`
|
||||
immediates are in bits. Prologue/epilogue use `MMTM`/`MMFM` and `RETS`.
|
||||
- `FS1 = 32` is an invariant at function entry and after calls; a target pass
|
||||
or pseudo-instruction inserts `SETF 32,0,1` after call sites and before
|
||||
field-size-dependent operations when the state is unknown.
|
||||
- `CALL`/`CALLA`/`CALLR` selection: direct calls to local functions may use
|
||||
`CALLR`; external/ROM calls use `CALLA`; function pointers use `CALL Rs`.
|
||||
- Symbol prefix `_`; sections `.text`/`.data`/`.bss`; initialized globals go to
|
||||
`.data` for the payload path (the loader copies sections), so `.cinit` is
|
||||
not required initially. ROM builds may add `.cinit` later.
|
||||
- ROM import library: `symbols/vt1200-v22.syms` maps `_XOpenDisplay`,
|
||||
`_XCreateSimpleWindow`, `_XMapWindow`, `_XSync`, `_strlen`, `_malloc`, `_free`
|
||||
and later entries to bit addresses, as linker absolute symbols or
|
||||
ABI-preserving stubs. The file starts with the entries already proven by
|
||||
`xhello.asm`; the M3 task extends it from Ghidra/`ptr_constants.py` and
|
||||
validates each one. v2.1 and other revisions get separate tables; addresses
|
||||
are not assumed stable.
|
||||
|
||||
### 7.5 Backend
|
||||
|
||||
`llvm/lib/Target/TMS34010/`:
|
||||
|
||||
- Target machine, subtarget, register info, instruction info, frame lowering,
|
||||
ISel lowering and DAG-to-DAG selection, asm printer, MC instruction lowering,
|
||||
`MCTargetDesc` (MCAsmInfo, MCInstrInfo, MCRegisterInfo, MCSubtargetInfo,
|
||||
MCCodeEmitter, MCAsmBackend), target-info registration.
|
||||
- TableGen: `TMS34010.td`, `TMS34010RegisterInfo.td`, `TMS34010InstrInfo.td`,
|
||||
`TMS34010CallingConv.td`.
|
||||
- M1 instruction subset: `MOVI`, `MOVE`, `MOVB`, `ADD`, `SUB`, `AND`, `OR`,
|
||||
`XOR`, `CMP`, `ADDK`, `SUBK`, `SLL`, `SRL`, `SRA`, `MPYS`, `MPYU`, `DIVS`,
|
||||
`DIVU`, `JR`, `JA`, `CALL`, `CALLA`, `CALLR`, `RETS`, `SETF`, `MMTM`,
|
||||
`MMFM`, `CLR`, `NOP`.
|
||||
- Encodings are 1–3 16-bit words; implement a custom MC encoder/decoder where
|
||||
TableGen's fixed-width assumptions do not fit. Validate against `dis34`,
|
||||
MAME, and the byte-exact ROM round-trip.
|
||||
- Loads/stores: misaligned accesses allowed; `MOVB` for i8, `MOVE` with FS0 for
|
||||
i16, `MOVE` with FS1 for i32/pointers; i64 as two 32-bit accesses. Ensure
|
||||
FS1 is 32; use FS0 for 16-bit.
|
||||
- `memcpy`/`memmove`/`memset`: lower to byte/word loops; lengths are bytes,
|
||||
addresses bits; overlap semantics for `memmove`.
|
||||
- `alloca`/VLA: size ×8; 16-bit alignment for standalone objects.
|
||||
- `byval`: copy `sizeof` rounded to 16 bits.
|
||||
- Multiplication/division: 16×16 `MPYS`/`MPYU`; 32×32 and 64-bit via libcalls
|
||||
or sequences. Float/double via libcalls to `flib.lib` or compiler-rt later.
|
||||
- Branches: `JR` range, `JA` absolute; branch relaxation.
|
||||
- Constants: `MOVI` long/short, `ADDK`/`SUBK` small immediates, constant pool
|
||||
for large/float constants.
|
||||
- varargs and bitfields are out of M1; add once the Xlib path works.
|
||||
|
||||
### 7.6 Output path
|
||||
|
||||
- M1: emit GSPA-compatible assembly; assemble and link with `gspa`/`gsplnk`
|
||||
under 2ine; feed the existing COFF loader.
|
||||
- Respect GSPA syntax: hex constants `0…h`, `movi` long/short forms, `sp` vs
|
||||
`b15`, `JR` vs `JA` word counts (see `tools/tms34010-sdk/` notes and
|
||||
`harness/sdk-scratch/dis2as34.py`). Emit section sizes and labels in bits.
|
||||
- Linker command files are CRLF; `SECTIONS` origins are bit addresses.
|
||||
- **Constant GEPs** become `symbol + bit_addend` (for example
|
||||
`inttoptr(add(ptrtoint(@.str), 16))`). Before M1 relies on it, verify that
|
||||
GSPA/gsplnk can carry and relocate a bit addend in both a `MOVI` immediate
|
||||
and a `.data` initializer. Fallbacks if they cannot: leave `symbol+addend`
|
||||
entirely to link time, materialize the addend at runtime (`MOVI symbol;
|
||||
ADDI addend`), or pre-resolve in the linker script. Test a string literal
|
||||
with an offset, `&arr[1]`, and a global pointer initializer.
|
||||
- Later: native `as34`/`ld34` plus a COFF converter/loader extension, or an
|
||||
LLVM MC COFF writer. `ld34` output is not COFF, so this is a separate step.
|
||||
|
||||
### 7.7 Runtime and libraries
|
||||
|
||||
- Reuse `rts.lib`/`flib.lib` and the ROM Xlib; do not write a libc first.
|
||||
- Xlib headers: X11R4-compatible; `XLIB_ILLEGAL_ACCESS`/`_XPrivDisplay` exposes
|
||||
`Display` layout for `ScreenOfDisplay`; `Screen`/`XImage`/`XEvent` definitions
|
||||
must match the ROM. Core Xlib structs are all 4-byte fields, so packed and
|
||||
4-byte alignment give the same offsets; verify with the TI guide and a
|
||||
compiled offset test.
|
||||
- ROM imports: Xlib, `strlen`, `malloc`, `free`, etc. at fixed bit addresses.
|
||||
`malloc` returns a bit address. `XOpenDisplay` must be called from a
|
||||
`create_process` child, not the rdebug dispatcher (repo evidence).
|
||||
- Startup for payloads: `entry.asm`-style `_start` sets STK/FP, calls `_main`,
|
||||
`rets`; no `.cinit`. Standalone `_c_int00`/`.cinit` later.
|
||||
- Initialized globals go in `.data`; the loader copies them.
|
||||
|
||||
### 7.8 Tooling
|
||||
|
||||
- `tms-clang` wrapper: clang → bit-address pass → optimizer → asm → GSPA/gsplnk
|
||||
→ COFF; `tms-run` for MAME or rdebug.
|
||||
- Build scripts and patch series; CI builds LLVM/clang once, caches it, runs lit
|
||||
and MAME smoke tests.
|
||||
- Symbol map and line-table output first; DWARF/GDB later.
|
||||
|
||||
### 7.9 Testing
|
||||
|
||||
- `lit`: clang IR semantics, llc instruction selection/encoding, asm printer.
|
||||
- Encoder/decoder round-trip against `dis34`; byte-exact reassembly of a ROM
|
||||
region.
|
||||
- **GEP-contract test**: M0 gets an `opt`-level late-GEP normalization and
|
||||
verifier test; M1 adds the same program through `llc`, checking that late GEPs
|
||||
carry bit offsets and are not scaled twice (§7.3.1). The spike only reached
|
||||
`opt`; this is a new gate.
|
||||
- **Constant-GEP relocation test**: `symbol + bit_addend` in `MOVI` and in
|
||||
`.data`, plus `&arr[1]` and a global pointer initializer.
|
||||
- MAME end-to-end: compile a C file, assemble/link, load via
|
||||
`dwt/loader/loader.lua`, check markers and frame. This is the CI gate.
|
||||
- Real unit: `dwt/loader/hostload.py`, rdebug. Periodic/optional confirmation,
|
||||
not a CI dependency; the unit is not always available.
|
||||
- Differential: compile the same C with TI 5.01 (and 2.01) **offline**, store
|
||||
`.s`/`.map`/behaviour as golden files, and compare sizes, struct offsets and
|
||||
call results in CI. CI does not need DOSBox or 2ine.
|
||||
- ABI conformance: call ROM `strlen`, `malloc`, `XOpenDisplay`; be called from
|
||||
a ROM-created process; pass structs/pointers.
|
||||
- Xlib: `XOpenDisplay`, `XCreateSimpleWindow`, `XMapWindow`, `XSync`, `XEvent`,
|
||||
`XImage`, `XTextProperty`; verify struct offsets against a TI-compiled
|
||||
offset test before the first call.
|
||||
|
||||
## 8. Milestones
|
||||
|
||||
**M0 — fork and semantics (1–2 weeks).** LLVM 23.1.5 builds; clang target emits
|
||||
IR; bit-address pass and pointer-difference patch pass lit tests; spike re-run
|
||||
on 23.1.5. Acceptance: `int_diff` = 8, `int_diff4` = 32, `p - q` = bytes,
|
||||
Xlib client IR has no GEPs and verifies; an `opt`-level late-GEP normalization
|
||||
test confirms the §7.3.1 contract. The `llc` half of that contract test lands in
|
||||
M1, when the backend exists.
|
||||
|
||||
**M1 — minimal backend (2–4 weeks).** `int add(int,int)` and `ret`; llc emits
|
||||
assembly; assemble/link/load in MAME; `int load(int*)`; `char` load/store;
|
||||
constant-GEP relocation probe. Acceptance: marker word and return value correct
|
||||
in MAME.
|
||||
|
||||
**M2 — ABI and ROM calls (2–4 weeks).** Arguments/locals, callee saves, FP/STK,
|
||||
`RETS`, FS1; call ROM `strlen`; call a function with two arguments; no-arg
|
||||
function with locals. Acceptance: `strlen("0::0") == 4` in MAME (real unit
|
||||
when available).
|
||||
|
||||
**M3 — Xlib (2–4 weeks).** Extend and validate `symbols/vt1200-v22.syms` from
|
||||
Ghidra; `XOpenDisplay`, `XCreateSimpleWindow`, `XMapWindow`, `XSync`;
|
||||
`Display`/`Screen` macros; X11 header offset check. Acceptance: a window
|
||||
appears in MAME (real unit when available).
|
||||
|
||||
**M4 — real client (2–4 weeks).** `hello.c` and a small `xshow`-like client;
|
||||
`XImage`/`XEvent`; structs and function pointers. Acceptance: image on screen,
|
||||
no X errors.
|
||||
|
||||
**M5 — robustness and native path.** `memcpy`, `alloca`, `byval`, varargs
|
||||
(`sprintf`), floats, bitfields, `long long`; native COFF and debug info.
|
||||
|
||||
## 9. Risks and mitigations
|
||||
|
||||
| risk | mitigation |
|
||||
|---|---|
|
||||
| clang fork drifts | pin 23.1.5; keep the generic hook and pointer patch small; export patches |
|
||||
| late GEPs / double scaling | §7.3.1 contract: bit offsets only, disable `addrSinkUsingGEPs`, late normalization + `llc` verifier |
|
||||
| constant-GEP relocation (`symbol+bit addend`) | M1 probe; fallbacks: leave to link time, runtime add, or pre-resolve |
|
||||
| dual-stack frame lowering | prototype ABI in M2 with a ROM call before Xlib |
|
||||
| FS1 clobbering | model it as a pseudo-instruction/after-call reset; ABI test |
|
||||
| instruction encoding errors | byte-exact ROM round-trip plus `dis34`/MAME |
|
||||
| packed struct/bitfield mismatch | offset tests against TI; defer bitfields |
|
||||
| X11 header mismatch | X11R4 headers or system headers; compiled offset test before M3 |
|
||||
| ROM symbol extraction | `symbols/vt1200-v22.syms` starts with proven entries; extend/validate in M3 |
|
||||
| missing assets/oracle | `scripts/fetch-vt1200.sh`; TI oracle is offline/golden files; hardware is periodic |
|
||||
| GSPA/2ine dependency | acceptable initially; native `as34`/`ld34` + converter fallback |
|
||||
| ROM address changes | versioned import tables; v2.2 first |
|
||||
| LLVM 23 API changes | pin; spike on 19.1.7 is reference only |
|
||||
| code size/performance | measure `-O2`/`-Os` against TI `-o2`; tune later |
|
||||
|
||||
## 10. Open decisions left to the builder
|
||||
|
||||
- Exact bitfield layout (defer).
|
||||
- Whether to add a custom AST/record-layout hook so `_Alignof(struct)` reports
|
||||
TI's 2-byte object alignment instead of LLVM's packed 1.
|
||||
- X11R4 headers from X.Org vs. the system X11R7 headers.
|
||||
- Scope of ROM symbol extraction for M3 (known Xlib entries vs. full table).
|
||||
- B-register policy: A-only initially; later allocate B0–B14 as caller-saved.
|
||||
- `CALLR` vs `CALLA` for local calls.
|
||||
- `.data` vs `.cinit` on the ROM-image path.
|
||||
- Native COFF timeline and object-writer scope.
|
||||
- Debug-info format/scope.
|
||||
- Varargs/float priority after M4.
|
||||
- Whether to attempt upstreaming the address-unit hook.
|
||||
|
||||
## 11. Size of work
|
||||
|
||||
This is a project, not a small change: multiple months for a complete toolchain.
|
||||
A first useful Xlib client is realistically 2–3 months with one experienced
|
||||
LLVM engineer; M1/M2 (backend skeleton and dual-stack ABI) carry most of the
|
||||
risk. The bit-address work is the smallest part; the backend and ABI are the
|
||||
bulk.
|
||||
|
||||
## 12. References
|
||||
|
||||
- TI, *TMS34010 C Compiler Reference Guide* (SPVU005A, 1988-08), §4.2, §4.3,
|
||||
§5.1.6, §5.2, §5.3.
|
||||
- `scripts/fetch-vt1200.sh`; paths below are relative to `vendor/vt1200`
|
||||
(pinned commit `17dfd24…`).
|
||||
- `harness/c-hello/build/hello.s` (TI 2.01 codegen), `dwt/gui/xhello.asm`
|
||||
(Xlib call sequence), `harness/c-hello/README.md` (pointer trap).
|
||||
- `spike/REPORT.md`, `spike/bitscale.cpp`, `spike/run.sh`.
|
||||
- `tools/as34/README.md`, `tools/tms34010-sdk/OS2-501.md`,
|
||||
`tools/tms34010-sdk/2INE-501.md`, `reference/tms34010-sdk.md`,
|
||||
`docs/TMS34010-users-guide.txt`.
|
||||
- `symbols/vt1200-v22.syms`, `symbols/README.md`.
|
||||
- X11R4 headers: X.Org release, or system X11R7 headers; validate the core
|
||||
struct layouts.
|
||||
- LLVM 23.1.5: `clang/lib/CodeGen/CGExprScalar.cpp`
|
||||
(`EmitPointerArithmetic`, `EmitSub`), `clang/lib/CodeGen/CGExpr.cpp`
|
||||
(`EmitArraySubscriptExpr`, GEP emission), `llvm/include/llvm/IR/DataLayout.h`,
|
||||
`llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp` and
|
||||
`llvm/lib/CodeGen/CodeGenPrepare.cpp` for the late-GEP contract.
|
||||
- MAME `src/devices/cpu/tms34010/34010ops.hxx`, `tools/smart34010/dis34.c`.
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
# Fetch the VT1200 reverse-engineering tree at the pinned commit.
|
||||
#
|
||||
# The compiler design uses this tree as its ABI/oracle/loader source:
|
||||
# harness/c-hello TI C -> COFF -> real unit
|
||||
# dwt/loader COFF loader and hostload
|
||||
# dwt/gui xhello.asm, the Xlib call sequence
|
||||
# tools/as34 native assembler/linker
|
||||
# tools/smart34010 dis34
|
||||
# tools/tms34010-sdk TI SDKs
|
||||
# reference, docs manuals and notes
|
||||
#
|
||||
# It is NOT vendored into the compiler repo; this script is the reproducible
|
||||
# way to materialise it. Override the destination with $1.
|
||||
set -eu
|
||||
|
||||
DEST=${1:-vendor/vt1200}
|
||||
REPO=https://git.k-space.ee/arti/vt1200
|
||||
COMMIT=17dfd24340fe3346433f90457cb48cb8786dff39
|
||||
|
||||
if [ ! -d "$DEST/.git" ]; then
|
||||
mkdir -p "$(dirname "$DEST")"
|
||||
git clone --filter=blob:none --no-checkout "$REPO" "$DEST"
|
||||
fi
|
||||
|
||||
cd "$DEST"
|
||||
git fetch --depth 1 origin "$COMMIT" 2>/dev/null || true
|
||||
git checkout --detach "$COMMIT"
|
||||
git sparse-checkout init --cone
|
||||
git sparse-checkout set docs reference tools harness dwt
|
||||
printf '%s\n' "$COMMIT" > VENDORED_AT
|
||||
|
||||
echo "vt1200 tree at $DEST, commit $COMMIT"
|
||||
du -sh .
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
# Bit-addressed pointers in LLVM — throwaway spike
|
||||
|
||||
**Status: spike only. Nothing here is a compiler, and nothing was run on a
|
||||
TMS34010.** The purpose was to measure one question: if a TMS34010 target keeps
|
||||
TI's pointer representation (a pointer is a 32-bit *bit* address), how much of
|
||||
LLVM has to change, and where does it hurt?
|
||||
|
||||
Measured 2026-11-10 against LLVM 19.1.7 on x86-64. The LLVM source is checked
|
||||
out in `vendor/llvm-project` at tag `llvmorg-19.1.7` (commit `cd70802`), sparse
|
||||
(134 MB); the host toolchain is Debian's `clang-19`/`llvm-19-dev`.
|
||||
|
||||
## The question, restated
|
||||
|
||||
TI's C ABI does not use byte addresses. From the 1988 *TMS34010 C Compiler
|
||||
Reference Guide*:
|
||||
|
||||
> All objects except structure members and array members are aligned on a
|
||||
> 16-bit (one-word) boundary. ... Structure or array members are not aligned to
|
||||
> 16-bit boundaries. (§4.3)
|
||||
|
||||
and the generated code in `harness/c-hello/build/hello.s` shows the arithmetic:
|
||||
a `char` step is `SLL 3` (×8), a 32-byte array row is `SLL 8` (×256), and
|
||||
`.bss STATIC_3,18432,16` is 18432 **bits** for 2304 bytes.
|
||||
|
||||
LLVM's IR and `DataLayout` define the address unit as the 8-bit byte:
|
||||
`getelementptr i8, ptr %p, i64 1` advances one unit. There is no `DataLayout`
|
||||
knob for "one bit per address unit" while keeping `CHAR_BIT == 8`.
|
||||
|
||||
## What was built
|
||||
|
||||
* `xclient.c` — a small but pointer-heavy Xlib client: `XOpenDisplay`,
|
||||
`XCreateWindow`, `XCreateImage`, `XPutImage`, `XNextEvent`,
|
||||
`XLookupString`, `XGetWMName`/`XmbTextPropertyToTextList`, `XSetClassHint`,
|
||||
`XSetWMProperties`, plus the standard `RootWindow`/`DefaultVisual`/
|
||||
`DefaultGC`/`BlackPixel` macros. It touches `Display->screens`,
|
||||
`Screen->root_visual`/`root`/`default_gc`, `XImage->data` and
|
||||
`XImage->f.put_pixel`, `XEvent.xkey.display`, `XTextProperty.value`, and
|
||||
`XClassHint.res_name`/`res_class`.
|
||||
* `stress.c` — the IR shapes the client does not have: a constant-expression
|
||||
GEP (`"hello" + 2`), a by-value struct argument, varargs, a VLA, a
|
||||
jump-table `switch`, and a 64-bit divide.
|
||||
* `ptrdiff.c` — pointer arithmetic edge cases.
|
||||
* `layout.c` — TI's packed struct layout.
|
||||
* `bitscale.cpp` — a standalone LLVM IR tool that rewrites every
|
||||
non-constant GEP to `base + 8*(byte offset)`, including constant-expression
|
||||
GEPs. It leaves loads/stores, `alloca`, `memcpy` and `lifetime` alone.
|
||||
* `xlib_min.h` — minimal Xlib declarations (32-bit `long` and pointers,
|
||||
packed structs where appropriate).
|
||||
|
||||
Build and run: `./run.sh` (or see the commands at the end).
|
||||
|
||||
## Result 1 — the GEP transform is small and works
|
||||
|
||||
`bitscale` is about 30 lines of real logic:
|
||||
|
||||
```c++
|
||||
// for every getelementptr: base_int + (byte_offset << 3)
|
||||
Value *BaseInt = B.CreatePtrToInt(Base, i32);
|
||||
Value *BitOff = B.CreateShl(ByteOffset, 3);
|
||||
Value *NewPtr = B.CreateIntToPtr(B.CreateAdd(BaseInt, BitOff), ptr);
|
||||
```
|
||||
|
||||
On `xclient.c`:
|
||||
|
||||
| IR | GEPs | allocas | memintrinsics | loads/stores | ptr subtractions |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| `xclient.O0.ll` | 61 | 35 | 1 | 155 / 54 | 0 |
|
||||
| `xclient.O2.ll` | 35 | 10 | 3 | 49 / 24 | 0 |
|
||||
| `xclient.packed.O2.ll` | 35 | 10 | 3 | 49 / 24 | 0 |
|
||||
| after `bitscale` | **0** | 10 | 3 | 49 / 24 | 0 |
|
||||
|
||||
`opt -passes=verify` accepts the result. The Xlib object chain survives as
|
||||
expected. For `DefaultScreen`/`RootWindow`/`DefaultVisual`, the IR becomes:
|
||||
|
||||
```llvm
|
||||
%bit.base = ptrtoint ptr %dpy to i32
|
||||
%bit.screens = add i32 %bit.base, 1056 ; Display->screens at byte 132, ×8
|
||||
%p = inttoptr i32 %bit.screens to ptr
|
||||
%scr = load ptr, ptr %p
|
||||
%bit.base2 = ptrtoint ptr %scr to i32
|
||||
%off = mul i32 %scr_index, 640 ; sizeof(Screen)=80 bytes, ×8
|
||||
%rootv = add i32 %bit.base2, %off
|
||||
... + 320 for Screen->root_visual (byte 40), +64 for root (byte 8), ...
|
||||
```
|
||||
|
||||
Those constants are exactly TI's bit addresses: `Display->screens` is byte 132
|
||||
→ bit 1056; `Screen` is 80 bytes → 640 bits; `root_visual` is byte 40 → 320
|
||||
bits. `instcombine` at `-O2` does **not** reintroduce GEPs in this corpus, and
|
||||
the scaled constants survive.
|
||||
|
||||
## Result 2 — the optimizer order matters, and pointer subtraction is the trap
|
||||
|
||||
The GEP transform must run **before** clang's optimizer, not after. `ptrdiff.c`
|
||||
shows why. The practical sequence is `clang -O0 -emit-llvm` (with
|
||||
`-Xclang -disable-O0-optnone`) → `bitscale` → `opt -O2`, or a clang pass plugin
|
||||
at an early extension point. `xclient.c` also survives the late order because
|
||||
it has no pointer casts or subtractions, but that is not a general guarantee.
|
||||
|
||||
```c
|
||||
int int_diff(char *p) { return (int)(p + 1) - (int)p; }
|
||||
int int_diff4(int *p) { return (int)(p + 1) - (int)p; }
|
||||
long ptr_diff(char *a, char *b) { return a - b; }
|
||||
```
|
||||
|
||||
* At `-O2` **before** scaling, clang folds `int_diff` to `1` and `int_diff4` to
|
||||
`4`; the GEP is already gone, so a late pass cannot fix it.
|
||||
* At `-O0` **then** `bitscale` **then** `-O2`, they become **8** and **32** —
|
||||
correct.
|
||||
* `addr_of_next` becomes `base + 8` — correct.
|
||||
|
||||
That is the good news. The bad news is pointer subtraction:
|
||||
|
||||
```llvm
|
||||
; ptr_diff(char *a, char *b), after bitscale + -O2
|
||||
%a = ptrtoint ptr %0 to i32
|
||||
%b = ptrtoint ptr %1 to i32
|
||||
%s = sub i32 %a, %b
|
||||
ret i32 %s ; bits, but C wants bytes
|
||||
```
|
||||
|
||||
```llvm
|
||||
; pdiff_int(int *a, int *b), after bitscale + -O2
|
||||
%s = sub i32 %a, %b
|
||||
%d = ashr exact i32 %s, 2 ; clang's /4; TI needs /32
|
||||
```
|
||||
|
||||
`bitscale` cannot repair these, and a pure IR pass **cannot reliably tell them
|
||||
apart from an integer cast**:
|
||||
|
||||
```c
|
||||
(int)(p + 1) - (int)p // same IR shape: sub(ptrtoint, ptrtoint)
|
||||
p - q // C pointer difference: must be divided by 8
|
||||
```
|
||||
|
||||
There is also the constant-expression case: `stride()` in `layout.c` is
|
||||
`(char *)&arr[1] - (char *)&arr[0]`; clang folds it to `9` at `-O2` (correct),
|
||||
but if you scale the constant GEP first it becomes `72`, and only a
|
||||
frontend-aware pointer-difference rewrite can turn it back into `9`.
|
||||
|
||||
**Consequence:** a GEP-scaling pass is enough for a client like `xclient.c`,
|
||||
which contains **zero** pointer subtractions. A general C compiler needs either:
|
||||
|
||||
1. a small **clang CodeGen change** that scales GEPs and emits the pointer
|
||||
difference divisor as `sizeof(T) * 8` (the clean solution), or
|
||||
2. a **frontend-generated marker/metadata** on pointer-difference `sub`s so a
|
||||
later IR pass can distinguish them from explicit integer casts, or
|
||||
3. a documented dialect restriction that `(int)(p+n) - (int)p` is undefined /
|
||||
unsupported (not acceptable for ordinary C).
|
||||
|
||||
This is the modern analogue of Andrew Morton's warning: the frontend, not just
|
||||
the backend, assumes `(char *)p + 1 == (long)p + 1`. In LLVM the damage is
|
||||
concentrated in `clang/lib/CodeGen/CGExpr.cpp` (GEP and pointer-difference
|
||||
emission), not spread across 110 GCC 2.5.8 sites.
|
||||
|
||||
## Result 3 — struct layout is solvable, and TI's rule is fully packed
|
||||
|
||||
TI packs structure members at bit granularity and aligns only the structure
|
||||
itself to 16 bits. From `layout.c`:
|
||||
|
||||
| | `sizeof(S)` | `offsetof(i)` | `offsetof(p)` | stride of `S[2]` |
|
||||
|---|---:|---:|---:|---:|
|
||||
| clang i386 default | 12 | 4 | 8 | 12 |
|
||||
| `clang -fpack-struct=1` | **9** | **1** | **5** | **9** |
|
||||
| TI guide (§4.3, §5.1.6) | 9 | 1 | 5 | 9 |
|
||||
|
||||
So the correct LLVM model is **packed struct types** (`%struct.S = type <{ i8,
|
||||
i32, ptr }>`) with scalar object alignment of 16 bits — not "make every scalar
|
||||
1-byte aligned". `-fpack-struct=1` reproduces it for the spike; a real target
|
||||
sets the same policy in its `TargetInfo`/`ASTContext` (and must decide what
|
||||
`_Alignof(int)` reports, because TI aligns objects, not members).
|
||||
|
||||
For the Xlib structs in this client (`Display`, `Screen`, `XImage`, `XEvent`,
|
||||
`XTextProperty`, `XClassHint`, `XSizeHints`) all members are 4 bytes, so packed
|
||||
and 4-byte alignments give the same offsets. User structs and X11 extensions
|
||||
with a `char` before an `int` will differ.
|
||||
|
||||
## Result 4 — the TI ABI itself is the non-standard part
|
||||
|
||||
The pointer model is only half of "TI-compatible". From the C guide §5.2–5.3
|
||||
and `dwt/gui/xhello.asm`:
|
||||
|
||||
* Return value in **A8**; **A13 = FP**, **A14 = STK** (program stack), **A15 =
|
||||
system stack** (interrupt stack).
|
||||
* Arguments are pushed **upward through A14** in reverse order and widened to
|
||||
32 bits. The caller first pushes the old A14 on **A15**; `CALLA` pushes the
|
||||
return address on **A15**; `RETS 2` pops the return address and old STK.
|
||||
Callee-saved registers are saved on **A15**, locals live on **A14**.
|
||||
* **FS1 must be 32 inside C code**; Xlib clobbers the field-size state, so the
|
||||
caller must re-establish `FS1=32` after every external call. `FS0` may be
|
||||
changed without restoring.
|
||||
* Structures are packed for layout but **rounded up to a word boundary when
|
||||
passed by value**.
|
||||
|
||||
This dual-stack convention is not a standard LLVM calling convention. LLVM's
|
||||
`LowerCall`/`LowerReturn` and frame lowering can be customized for it, but the
|
||||
backend must model A15 as a second stack (or use custom prologue/epilogue
|
||||
nodes), and `FS1` behaves like a call-clobbered status register. That is more
|
||||
work than the pointer scaling, but it is bounded and well documented.
|
||||
|
||||
## What remains after the pass
|
||||
|
||||
`bitscale` deliberately leaves these alone; each is a backend boundary:
|
||||
|
||||
| IR construct | `xclient` count | what the backend must do |
|
||||
|---|---:|---|
|
||||
| `llvm.memcpy` of `XEvent` | 3 (96 bytes) | copy 96 × 8 bits |
|
||||
| `alloca` | 10 | allocate `size × 8` bits, frame offsets in bits |
|
||||
| `byval(%struct.S)` | 0 here, 1 in `stress.c` | copy `sizeof` bytes rounded to 16 bits |
|
||||
| `llvm.va_start` | 0 here, 1 in `stress.c` | TI-compatible `va_list` if calling `sprintf` |
|
||||
| indirect call `img->f.put_pixel` | 1 | function pointers are bit addresses; no change |
|
||||
| constant-expression GEP | 0 here, 1 in `stress.c` | rewritten by `bitscale` |
|
||||
| `align 4` hints | many | ignore or lower to TI's 2-byte object alignment |
|
||||
| 64-bit divide | 0 here, 1 in `stress.c` | libcall or sequence (no 64-bit divide) |
|
||||
|
||||
Loads and stores themselves are the easy part on this CPU: `MOVB` moves 8 bits
|
||||
at any bit address, and `MOVE ...,FS1` moves 32 bits at any bit address, so a
|
||||
bit-address `load i8`/`load i32` is one instruction, not a shift/mask sequence.
|
||||
|
||||
## Verdict
|
||||
|
||||
**Not horrible at the pointer level.** A 30-line IR pass scales every GEP in a
|
||||
real Xlib client, LLVM verifies it, `-O2` preserves it, and the result is
|
||||
directly interoperable with Xlib's bit-addressed structs. A byte-address model
|
||||
would instead need conversions at every pointer field the standard macros
|
||||
traverse (`screens`, `root_visual`, `XImage->data`, `put_pixel`,
|
||||
`XEvent.xkey.display`, `XTextProperty.value`, …), which is worse.
|
||||
|
||||
**The three real costs are:**
|
||||
|
||||
1. **A clang CodeGen change for pointer difference** (and the GEP scaling, if
|
||||
you want it before clang's optimizer). A pure post-IR pass cannot do it
|
||||
correctly. This is a fork of clang, not just an LLVM backend.
|
||||
2. **The dual-stack / FS1 TI ABI.** Custom call, return and frame lowering;
|
||||
more work than the pointer model.
|
||||
3. **The backend itself** — instruction selection, register allocation, asm
|
||||
printer/relocations to `as34`/GSPA, `memcpy`/byval/varargs, and a C library
|
||||
(reuse `rts.lib`/Xlib or build one). This is the usual target-port scale.
|
||||
|
||||
None of these is a blocker. The spike says the bit-address question is answerable
|
||||
with a small, well-localized change, and the remaining project is an ordinary
|
||||
(if unusual) LLVM target plus the TI ABI.
|
||||
|
||||
## Reproduce
|
||||
|
||||
```sh
|
||||
cd spike
|
||||
clang++-19 -std=c++17 $(llvm-config-19 --cxxflags) bitscale.cpp \
|
||||
$(llvm-config-19 --ldflags --libs core irreader support --system-libs) -o bitscale
|
||||
|
||||
clang-19 -target i386-unknown-none -ffreestanding -nostdinc -fno-builtin \
|
||||
-fpack-struct=1 -O2 -S -emit-llvm xclient.c -o xclient.packed.O2.ll
|
||||
./bitscale xclient.packed.O2.ll xclient.packed.O2.bits.ll
|
||||
opt-19 -passes=verify -disable-output xclient.packed.O2.bits.ll
|
||||
opt-19 -passes='default<O2>' -S xclient.packed.O2.bits.ll -o /tmp/x.ll
|
||||
grep -c getelementptr /tmp/x.ll # 0
|
||||
grep -o 'add i32 %bit.base[0-9]*, [0-9]*' /tmp/x.ll | head
|
||||
|
||||
# early scaling is required for (int)(p+1) - (int)p:
|
||||
clang-19 -target i386-unknown-none -ffreestanding -nostdinc -fno-builtin \
|
||||
-Xclang -disable-O0-optnone -O0 -S -emit-llvm ptrdiff.c -o ptrdiff.O0.ll
|
||||
./bitscale ptrdiff.O0.ll ptrdiff.O0.bits.ll
|
||||
opt-19 -passes='default<O2>' -S ptrdiff.O0.bits.ll -o /tmp/p.ll
|
||||
grep -A2 'define.*@int_diff' /tmp/p.ll # ret i32 8
|
||||
grep -A2 'define.*@int_diff4' /tmp/p.ll # ret i32 32
|
||||
grep -A6 'define.*@ptr_diff' /tmp/p.ll # still bits: needs frontend fix
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
* TI, *TMS34010 C Compiler Reference Guide* (SPVU005A, 1988-08), §4.3, §5.1.6,
|
||||
§5.2.1, §5.3.
|
||||
* `harness/c-hello/build/hello.s` (TI 2.01 output: `SLL 3`, `SLL 8`, `.bss` in
|
||||
bits).
|
||||
* `dwt/gui/xhello.asm` (A8/A13/A14, `CALLA`, `FS0=16`/`FS1=32` after each call).
|
||||
* LLVM 19.1.7 `llvm/include/llvm/IR/DataLayout.h` (`getTypeAllocSize` returns
|
||||
bytes; `getIndexedOffsetInType` is byte-based) and
|
||||
`clang/lib/CodeGen/CGExpr.cpp` (GEP and pointer-difference emission).
|
||||
@@ -0,0 +1,292 @@
|
||||
// bitscale -- throwaway spike tool.
|
||||
//
|
||||
// Rewrites every non-constant getelementptr so that its byte offset is
|
||||
// multiplied by 8. The result is an IR in which every pointer value is a
|
||||
// *bit* address, while loads/stores still carry their normal LLVM byte-sized
|
||||
// types. A real TMS34010 backend would lower "load i8 at bit address A" as
|
||||
// MOVB [A] and "load i32 at bit address A" as MOVE [A],FS0-in-32-bit-mode.
|
||||
//
|
||||
// Build:
|
||||
// clang++-19 -std=c++17 $(llvm-config-19 --cxxflags) bitscale.cpp \
|
||||
// $(llvm-config-19 --ldflags --libs core irreader support) -o bitscale
|
||||
|
||||
#include "llvm/IR/Constants.h"
|
||||
#include "llvm/IR/IRBuilder.h"
|
||||
#include "llvm/IR/DataLayout.h"
|
||||
#include "llvm/IR/GetElementPtrTypeIterator.h"
|
||||
#include "llvm/IR/Instructions.h"
|
||||
#include "llvm/IR/LLVMContext.h"
|
||||
#include "llvm/IR/Module.h"
|
||||
#include "llvm/IR/Operator.h"
|
||||
#include "llvm/IRReader/IRReader.h"
|
||||
#include "llvm/Support/SourceMgr.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
struct Stats {
|
||||
unsigned geps = 0;
|
||||
unsigned const_geps = 0;
|
||||
unsigned vector_geps = 0;
|
||||
unsigned allocas = 0;
|
||||
unsigned memintrinsics = 0;
|
||||
unsigned vaarg = 0;
|
||||
unsigned byval_args = 0;
|
||||
unsigned indirect_calls = 0;
|
||||
unsigned loads = 0;
|
||||
unsigned stores = 0;
|
||||
unsigned ptr_subs = 0;
|
||||
unsigned const_ptr_subs = 0;
|
||||
};
|
||||
|
||||
static void count_constants(Value *V, Stats &S, unsigned depth = 0)
|
||||
{
|
||||
if (!V || depth > 64)
|
||||
return;
|
||||
auto *C = dyn_cast<Constant>(V);
|
||||
if (!C)
|
||||
return;
|
||||
if (auto *CE = dyn_cast<ConstantExpr>(C)) {
|
||||
if (CE->getOpcode() == Instruction::Sub) {
|
||||
auto *L = dyn_cast<ConstantExpr>(CE->getOperand(0));
|
||||
auto *R = dyn_cast<ConstantExpr>(CE->getOperand(1));
|
||||
if (L && R && L->getOpcode() == Instruction::PtrToInt &&
|
||||
R->getOpcode() == Instruction::PtrToInt)
|
||||
++S.const_ptr_subs;
|
||||
}
|
||||
}
|
||||
if (auto *GEP = dyn_cast<GEPOperator>(C)) {
|
||||
++S.const_geps;
|
||||
count_constants(GEP->getPointerOperand(), S, depth + 1);
|
||||
for (auto &Op : GEP->indices())
|
||||
count_constants(Op.get(), S, depth + 1);
|
||||
return;
|
||||
}
|
||||
for (auto &Op : C->operands())
|
||||
count_constants(Op.get(), S, depth + 1);
|
||||
}
|
||||
|
||||
static void count_constant_geps(const Constant *C, Stats &S, unsigned depth = 0)
|
||||
{
|
||||
count_constants(const_cast<Constant *>(C), S, depth);
|
||||
}
|
||||
|
||||
static Stats collect(Module &M)
|
||||
{
|
||||
Stats S;
|
||||
for (Function &F : M) {
|
||||
for (Argument &A : F.args())
|
||||
if (A.hasAttribute(Attribute::ByVal))
|
||||
++S.byval_args;
|
||||
for (BasicBlock &BB : F) {
|
||||
for (Instruction &I : BB) {
|
||||
if (auto *GEP = dyn_cast<GetElementPtrInst>(&I)) {
|
||||
if (GEP->getType()->isVectorTy())
|
||||
++S.vector_geps;
|
||||
else
|
||||
++S.geps;
|
||||
}
|
||||
if (isa<AllocaInst>(&I))
|
||||
++S.allocas;
|
||||
if (isa<LoadInst>(&I))
|
||||
++S.loads;
|
||||
if (isa<StoreInst>(&I))
|
||||
++S.stores;
|
||||
if (auto *BO = dyn_cast<BinaryOperator>(&I)) {
|
||||
if (BO->getOpcode() == Instruction::Sub &&
|
||||
isa<PtrToIntOperator>(BO->getOperand(0)) &&
|
||||
isa<PtrToIntOperator>(BO->getOperand(1)))
|
||||
++S.ptr_subs;
|
||||
}
|
||||
if (auto *CB = dyn_cast<CallBase>(&I)) {
|
||||
if (Function *Callee = CB->getCalledFunction()) {
|
||||
if (Callee->isIntrinsic()) {
|
||||
StringRef N = Callee->getName();
|
||||
if (N.starts_with("llvm.memcpy") || N.starts_with("llvm.memmove") ||
|
||||
N.starts_with("llvm.memset"))
|
||||
++S.memintrinsics;
|
||||
if (N.starts_with("llvm.va_start") || N.starts_with("llvm.va_arg"))
|
||||
++S.vaarg;
|
||||
}
|
||||
} else {
|
||||
++S.indirect_calls;
|
||||
}
|
||||
for (unsigned i = 0; i < CB->arg_size(); ++i)
|
||||
if (CB->paramHasAttr(i, Attribute::ByVal))
|
||||
++S.byval_args;
|
||||
}
|
||||
for (Value *Op : I.operands())
|
||||
count_constants(Op, S);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (GlobalVariable &G : M.globals())
|
||||
if (G.hasInitializer())
|
||||
count_constant_geps(G.getInitializer(), S);
|
||||
return S;
|
||||
}
|
||||
|
||||
static bool scale_constant_gep(ConstantExpr *CE, const DataLayout &DL)
|
||||
{
|
||||
if (CE->getType()->isVectorTy())
|
||||
return false;
|
||||
|
||||
auto *GEP = cast<GEPOperator>(CE);
|
||||
SmallVector<Value *, 8> Indices;
|
||||
for (auto &Op : GEP->indices())
|
||||
Indices.push_back(Op.get());
|
||||
for (Value *Idx : Indices)
|
||||
if (!isa<ConstantInt>(Idx))
|
||||
return false; // cannot fold a non-constant constant-expression GEP
|
||||
|
||||
int64_t Off = DL.getIndexedOffsetInType(GEP->getSourceElementType(), Indices);
|
||||
Constant *Base = cast<Constant>(GEP->getPointerOperand());
|
||||
Constant *BaseInt = ConstantExpr::getPtrToInt(Base, Type::getInt32Ty(CE->getContext()));
|
||||
Constant *BitOff = ConstantInt::get(Type::getInt32Ty(CE->getContext()), Off * 8);
|
||||
Constant *BitAddr = ConstantExpr::getAdd(BaseInt, BitOff);
|
||||
Constant *New = ConstantExpr::getIntToPtr(BitAddr, CE->getType());
|
||||
CE->replaceAllUsesWith(New);
|
||||
return true;
|
||||
}
|
||||
|
||||
// base_int + (byte offset computed from the GEP's indices) << 3
|
||||
static bool scale_gep(GetElementPtrInst *GEP)
|
||||
{
|
||||
if (GEP->getType()->isVectorTy())
|
||||
return false;
|
||||
Value *Base = GEP->getPointerOperand();
|
||||
Type *PtrTy = GEP->getType();
|
||||
const DataLayout &DL = GEP->getModule()->getDataLayout();
|
||||
|
||||
IRBuilder<> B(GEP);
|
||||
Value *BaseInt = B.CreatePtrToInt(Base, B.getInt32Ty(), "bit.base");
|
||||
Value *ByteOff = ConstantInt::get(B.getInt32Ty(), 0);
|
||||
|
||||
for (auto GTI = gep_type_begin(GEP), GTE = gep_type_end(GEP); GTI != GTE; ++GTI) {
|
||||
Value *Idx = GTI.getOperand();
|
||||
Value *Idx32 = B.CreateSExtOrTrunc(Idx, B.getInt32Ty());
|
||||
if (StructType *STy = GTI.getStructTypeOrNull()) {
|
||||
unsigned FieldNo = cast<ConstantInt>(Idx)->getZExtValue();
|
||||
uint64_t FieldOff = DL.getStructLayout(STy)->getElementOffset(FieldNo);
|
||||
ByteOff = B.CreateAdd(ByteOff, ConstantInt::get(B.getInt32Ty(), FieldOff));
|
||||
} else {
|
||||
uint64_t Stride = GTI.getSequentialElementStride(DL);
|
||||
Value *Scaled = B.CreateMul(Idx32, ConstantInt::get(B.getInt32Ty(), Stride));
|
||||
ByteOff = B.CreateAdd(ByteOff, Scaled);
|
||||
}
|
||||
}
|
||||
|
||||
Value *BitOff = B.CreateShl(ByteOff, 3, "bit.off");
|
||||
Value *BitAddr = B.CreateAdd(BaseInt, BitOff, "bit.addr");
|
||||
Value *NewPtr = B.CreateIntToPtr(BitAddr, PtrTy, "bit.ptr");
|
||||
|
||||
GEP->replaceAllUsesWith(NewPtr);
|
||||
GEP->eraseFromParent();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void collect_const_geps(Value *V, std::vector<ConstantExpr *> &Out,
|
||||
unsigned depth = 0)
|
||||
{
|
||||
if (!V || depth > 64)
|
||||
return;
|
||||
auto *C = dyn_cast<Constant>(V);
|
||||
if (!C)
|
||||
return;
|
||||
if (auto *CE = dyn_cast<ConstantExpr>(C)) {
|
||||
if (CE->getOpcode() == Instruction::GetElementPtr && !CE->getType()->isVectorTy()) {
|
||||
Out.push_back(CE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (auto &Op : C->operands())
|
||||
collect_const_geps(Op.get(), Out, depth + 1);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 3) {
|
||||
errs() << "usage: " << argv[0] << " input.ll output.ll\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
LLVMContext Ctx;
|
||||
SMDiagnostic Err;
|
||||
std::unique_ptr<Module> M = parseIRFile(argv[1], Err, Ctx);
|
||||
if (!M) {
|
||||
Err.print(argv[0], errs());
|
||||
return 1;
|
||||
}
|
||||
|
||||
Stats Before = collect(*M);
|
||||
|
||||
// Constant-expression GEPs (e.g. `"hello" + 2`) cannot be replaced by an
|
||||
// instruction, so rewrite them as constant inttoptr(add(ptrtoint, 8*off)).
|
||||
unsigned ConstScaled = 0;
|
||||
const DataLayout &DL = M->getDataLayout();
|
||||
bool Changed = true;
|
||||
while (Changed) {
|
||||
Changed = false;
|
||||
std::vector<ConstantExpr *> Work;
|
||||
for (GlobalVariable &G : M->globals())
|
||||
if (G.hasInitializer())
|
||||
collect_const_geps(G.getInitializer(), Work);
|
||||
for (Function &F : *M)
|
||||
for (BasicBlock &BB : F)
|
||||
for (Instruction &I : BB)
|
||||
for (Value *Op : I.operands())
|
||||
collect_const_geps(Op, Work);
|
||||
|
||||
std::set<ConstantExpr *> Seen;
|
||||
for (ConstantExpr *CE : Work) {
|
||||
if (!Seen.insert(CE).second)
|
||||
continue;
|
||||
if (scale_constant_gep(CE, DL)) {
|
||||
++ConstScaled;
|
||||
Changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned Scaled = 0;
|
||||
for (Function &F : *M) {
|
||||
std::vector<GetElementPtrInst *> Work;
|
||||
for (BasicBlock &BB : F)
|
||||
for (Instruction &I : BB)
|
||||
if (auto *GEP = dyn_cast<GetElementPtrInst>(&I))
|
||||
Work.push_back(GEP);
|
||||
for (GetElementPtrInst *GEP : Work)
|
||||
if (scale_gep(GEP))
|
||||
++Scaled;
|
||||
}
|
||||
Stats After = collect(*M);
|
||||
|
||||
errs() << "bitscale: scaled " << Scaled << " GEPs, " << ConstScaled
|
||||
<< " constant GEPs\n";
|
||||
errs() << " before: geps=" << Before.geps << " const_geps=" << Before.const_geps
|
||||
<< " vector_geps=" << Before.vector_geps << " allocas=" << Before.allocas
|
||||
<< " memintrinsics=" << Before.memintrinsics << " va=" << Before.vaarg
|
||||
<< " byval=" << Before.byval_args << " indirect=" << Before.indirect_calls
|
||||
<< " loads=" << Before.loads << " stores=" << Before.stores
|
||||
<< " ptr_subs=" << Before.ptr_subs << " const_ptr_subs=" << Before.const_ptr_subs << "\n";
|
||||
errs() << " after: geps=" << After.geps << " const_geps=" << After.const_geps
|
||||
<< " vector_geps=" << After.vector_geps << " allocas=" << After.allocas
|
||||
<< " memintrinsics=" << After.memintrinsics << " va=" << After.vaarg
|
||||
<< " byval=" << After.byval_args << " indirect=" << After.indirect_calls
|
||||
<< " loads=" << After.loads << " stores=" << After.stores
|
||||
<< " ptr_subs=" << After.ptr_subs << " const_ptr_subs=" << After.const_ptr_subs << "\n";
|
||||
|
||||
std::error_code EC;
|
||||
raw_fd_ostream Out(argv[2], EC);
|
||||
if (EC) {
|
||||
errs() << "cannot write " << argv[2] << ": " << EC.message() << "\n";
|
||||
return 1;
|
||||
}
|
||||
M->print(Out, nullptr);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
struct S {
|
||||
char c;
|
||||
int i;
|
||||
void *p;
|
||||
};
|
||||
|
||||
struct S arr[2];
|
||||
|
||||
int size_S(void) { return (int)sizeof(struct S); }
|
||||
int off_i(void) { return (int)__builtin_offsetof(struct S, i); }
|
||||
int off_p(void) { return (int)__builtin_offsetof(struct S, p); }
|
||||
int stride(void) { return (int)((char *)&arr[1] - (char *)&arr[0]); }
|
||||
@@ -0,0 +1,4 @@
|
||||
int int_diff(char *p) { return (int)(p + 1) - (int)p; }
|
||||
int int_diff4(int *p) { return (int)(p + 1) - (int)p; }
|
||||
long ptr_diff(char *a, char *b) { return a - b; }
|
||||
int addr_of_next(char *p) { return (int)(p + 1); }
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/sh
|
||||
# Reproduce the bit-address spike. Throwaway: build artefacts are not
|
||||
# interesting beyond the numbers in REPORT.md.
|
||||
set -eu
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
CC=clang-19
|
||||
CXX=clang++-19
|
||||
OPT=opt-19
|
||||
LLVMCONFIG=llvm-config-19
|
||||
|
||||
echo "== build bitscale"
|
||||
$CXX -std=c++17 $($LLVMCONFIG --cxxflags) bitscale.cpp \
|
||||
$($LLVMCONFIG --ldflags --libs core irreader support --system-libs) \
|
||||
-o bitscale
|
||||
|
||||
COMMON="-target i386-unknown-none -ffreestanding -nostdinc -fno-builtin -fno-discard-value-names"
|
||||
|
||||
echo "== Xlib client"
|
||||
$CC $COMMON -fpack-struct=1 -O2 -S -emit-llvm xclient.c -o xclient.packed.O2.ll
|
||||
./bitscale xclient.packed.O2.ll xclient.packed.O2.bits.ll
|
||||
$OPT -passes=verify -disable-output xclient.packed.O2.bits.ll
|
||||
$OPT -passes='default<O2>' -S xclient.packed.O2.bits.ll -o /tmp/xclient.bits.O2.ll
|
||||
printf 'GEPs after scaling+O2: '
|
||||
grep -c getelementptr /tmp/xclient.bits.O2.ll || true
|
||||
printf 'memcpy count: '
|
||||
grep -c 'llvm.memcpy' /tmp/xclient.bits.O2.ll || true
|
||||
|
||||
echo "== early scaling is required for pointer arithmetic"
|
||||
$CC $COMMON -Xclang -disable-O0-optnone -O0 -S -emit-llvm ptrdiff.c -o ptrdiff.O0.ll
|
||||
./bitscale ptrdiff.O0.ll ptrdiff.O0.bits.ll
|
||||
$OPT -passes='default<O2>' -S ptrdiff.O0.bits.ll -o /tmp/ptrdiff.bits.O2.ll
|
||||
grep -A2 'define.*@int_diff(' /tmp/ptrdiff.bits.O2.ll
|
||||
grep -A2 'define.*@int_diff4' /tmp/ptrdiff.bits.O2.ll
|
||||
echo "-- pointer difference still in bit units (needs frontend fix):"
|
||||
grep -A6 'define.*@ptr_diff' /tmp/ptrdiff.bits.O2.ll
|
||||
|
||||
echo "== constant GEP, byval, varargs, VLA"
|
||||
$CC $COMMON -O2 -S -emit-llvm stress.c -o stress.O2.ll 2>/dev/null
|
||||
./bitscale stress.O2.ll stress.O2.bits.ll
|
||||
$OPT -passes=verify -disable-output stress.O2.bits.ll
|
||||
|
||||
echo "== TI struct layout"
|
||||
$CC $COMMON -O2 -S -emit-llvm layout.c -o layout.O2.ll
|
||||
$CC $COMMON -fpack-struct=1 -O2 -S -emit-llvm layout.c -o layout.packed.O2.ll
|
||||
echo "-- default:"
|
||||
grep -A2 'define.*@size_S' layout.O2.ll | tail -1
|
||||
grep -A2 'define.*@off_i' layout.O2.ll | tail -1
|
||||
grep -A2 'define.*@stride' layout.O2.ll | tail -1
|
||||
echo "-- packed:"
|
||||
grep -A2 'define.*@size_S' layout.packed.O2.ll | tail -1
|
||||
grep -A2 'define.*@off_i' layout.packed.O2.ll | tail -1
|
||||
grep -A2 'define.*@stride' layout.packed.O2.ll | tail -1
|
||||
|
||||
echo "done"
|
||||
@@ -0,0 +1,81 @@
|
||||
/* Extra IR shapes that the Xlib client does not contain. */
|
||||
typedef __builtin_va_list va_list;
|
||||
#define va_start(ap, last) __builtin_va_start(ap, last)
|
||||
#define va_arg(ap, type) __builtin_va_arg(ap, type)
|
||||
#define va_end(ap) __builtin_va_end(ap)
|
||||
|
||||
struct S {
|
||||
char c;
|
||||
int i;
|
||||
void *p;
|
||||
};
|
||||
|
||||
static char *file_ptr = "hello" + 2; /* constant-expression GEP */
|
||||
static char *tab[] = { "a", "bb", "ccc" }; /* pointer relocations */
|
||||
static int (*fns[])(int) = { 0, 0 }; /* function pointer table */
|
||||
|
||||
int take(struct S s) /* byval argument */
|
||||
{
|
||||
return s.c + s.i + (s.p != 0);
|
||||
}
|
||||
|
||||
struct S make(int i)
|
||||
{
|
||||
struct S s;
|
||||
s.c = (char)i;
|
||||
s.i = i;
|
||||
s.p = tab[i & 1];
|
||||
return s; /* aggregate return */
|
||||
}
|
||||
|
||||
int sum(int n, ...) /* varargs */
|
||||
{
|
||||
va_list ap;
|
||||
int s = 0;
|
||||
va_start(ap, n);
|
||||
while (n-- > 0)
|
||||
s += va_arg(ap, int);
|
||||
va_end(ap);
|
||||
return s;
|
||||
}
|
||||
|
||||
int vla(int n) /* dynamic alloca */
|
||||
{
|
||||
int a[n];
|
||||
int i;
|
||||
for (i = 0; i < n; i++)
|
||||
a[i] = i * i;
|
||||
return a[n - 1];
|
||||
}
|
||||
|
||||
long long div64(long long a, long long b) /* libcall */
|
||||
{
|
||||
return a / b;
|
||||
}
|
||||
|
||||
int sw(int n) /* jump table */
|
||||
{
|
||||
switch (n) {
|
||||
case 0: return 1;
|
||||
case 1: return 2;
|
||||
case 2: return 3;
|
||||
case 3: return 5;
|
||||
case 4: return 7;
|
||||
case 5: return 11;
|
||||
case 6: return 13;
|
||||
case 7: return 17;
|
||||
case 8: return 19;
|
||||
case 9: return 23;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
char *get_file_ptr(void)
|
||||
{
|
||||
return file_ptr;
|
||||
}
|
||||
|
||||
char *pick(int i)
|
||||
{
|
||||
return tab[i];
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* A small but pointer-heavy Xlib client, used only to measure what
|
||||
* bit-addressed pointers do to LLVM IR.
|
||||
*
|
||||
* It deliberately touches:
|
||||
* - Display->screens[] and Screen->root_visual, root, default_gc
|
||||
* - XImage->data and the XImage->f.put_pixel function pointer
|
||||
* - XEvent->xkey.display and XEvent->xkey.keycode
|
||||
* - XTextProperty.value and XClassHint.res_name/res_class
|
||||
* - char * arithmetic over a scanline
|
||||
* - a struct copy (llvm.memcpy) and an indirect call
|
||||
*/
|
||||
#include "xlib_min.h"
|
||||
|
||||
_Static_assert(sizeof(void *) == 4, "spike wants 32-bit pointers");
|
||||
_Static_assert(sizeof(long) == 4, "TI long is 32 bits");
|
||||
_Static_assert(sizeof(int) == 4, "TI int is 32 bits");
|
||||
_Static_assert(sizeof(char) == 1, "TI char is 8 bits");
|
||||
|
||||
static void pattern(char *data, int bpl, int w, int h)
|
||||
{
|
||||
int x, y;
|
||||
|
||||
memset(data, 0, (unsigned long)bpl * h);
|
||||
for (y = 0; y < h; y++) {
|
||||
char *row = data + y * bpl;
|
||||
for (x = 0; x < w; x++)
|
||||
row[x >> 3] |= (char)(1 << (x & 7));
|
||||
}
|
||||
}
|
||||
|
||||
static int handle(Display *dpy, XEvent *ev)
|
||||
{
|
||||
char buf[8];
|
||||
KeySym ks;
|
||||
|
||||
switch (ev->type) {
|
||||
case Expose:
|
||||
return ev->xexpose.width * ev->xexpose.height;
|
||||
case KeyPress:
|
||||
XLookupString(&ev->xkey, buf, sizeof buf, &ks, 0);
|
||||
if (ev->xkey.display != dpy)
|
||||
return -1;
|
||||
return (int)ev->xkey.keycode + buf[0] + (int)ks;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
Display *dpy;
|
||||
int scr, w = 640, h = 480, bpl;
|
||||
Window root, win;
|
||||
Visual *vis;
|
||||
GContext gc;
|
||||
XImage *img;
|
||||
XEvent ev, copy;
|
||||
XTextProperty tp;
|
||||
XClassHint ch;
|
||||
XSizeHints hints;
|
||||
XSetWindowAttributes attrs;
|
||||
char *data;
|
||||
char **list;
|
||||
int count, i;
|
||||
|
||||
dpy = XOpenDisplay(argc > 1 ? argv[1] : "0:0");
|
||||
if (!dpy)
|
||||
return 1;
|
||||
|
||||
scr = DefaultScreen(dpy);
|
||||
root = RootWindow(dpy, scr);
|
||||
vis = DefaultVisual(dpy, scr);
|
||||
gc = DefaultGC(dpy, scr);
|
||||
|
||||
attrs.background_pixel = WhitePixel(dpy, scr);
|
||||
attrs.border_pixel = BlackPixel(dpy, scr);
|
||||
attrs.colormap = 0;
|
||||
win = XCreateWindow(dpy, root, 20, 20, (unsigned)w, (unsigned)h, 0,
|
||||
DefaultDepth(dpy, scr), 1, vis, 0L, &attrs);
|
||||
win = XCreateSimpleWindow(dpy, root, 20, 20, (unsigned)w, (unsigned)h, 0,
|
||||
BlackPixel(dpy, scr), WhitePixel(dpy, scr));
|
||||
XStoreName(dpy, win, "bit-address spike");
|
||||
XSelectInput(dpy, win, ExposureMask | KeyPressMask | StructureNotifyMask);
|
||||
XMapWindow(dpy, win);
|
||||
|
||||
bpl = ((w + 31) / 32) * 4;
|
||||
data = malloc((unsigned long)bpl * h);
|
||||
if (!data)
|
||||
return 1;
|
||||
pattern(data, bpl, w, h);
|
||||
|
||||
img = XCreateImage(dpy, vis, (unsigned)DefaultDepth(dpy, scr), ZPixmap, 0,
|
||||
data, (unsigned)w, (unsigned)h, 32, bpl);
|
||||
if (!img)
|
||||
return 1;
|
||||
img->byte_order = LSBFirst;
|
||||
img->bitmap_bit_order = LSBFirst;
|
||||
img->f.put_pixel(img, 3, 4, 1);
|
||||
XPutImage(dpy, win, gc, img, 0, 0, 0, 0, (unsigned)w, (unsigned)h);
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
XNextEvent(dpy, &ev);
|
||||
copy = ev;
|
||||
count = handle(dpy, ©);
|
||||
if (count < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (XGetWMName(dpy, win, &tp) == 0) {
|
||||
list = 0;
|
||||
count = 0;
|
||||
if (XmbTextPropertyToTextList(dpy, &tp, &list, &count) >= 0) {
|
||||
for (i = 0; i < count; i++)
|
||||
list[i][0] = 'x';
|
||||
XFreeStringList(list);
|
||||
}
|
||||
XFree(tp.value);
|
||||
}
|
||||
|
||||
ch.res_name = argv[0];
|
||||
ch.res_class = "Spike";
|
||||
hints.flags = 0;
|
||||
hints.x = 20;
|
||||
hints.y = 20;
|
||||
hints.width = w;
|
||||
hints.height = h;
|
||||
XSetClassHint(dpy, win, &ch);
|
||||
XSetWMProperties(dpy, win, &tp, &tp, argv, argc, &hints, 0, &ch);
|
||||
|
||||
XSetForeground(dpy, gc, BlackPixel(dpy, scr));
|
||||
XSetBackground(dpy, gc, WhitePixel(dpy, scr));
|
||||
XSync(dpy, 0);
|
||||
XCloseDisplay(dpy);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* Minimal Xlib declarations for the bit-address spike.
|
||||
*
|
||||
* Only the parts a small X client touches. The layouts follow X11R4/Xlib.h;
|
||||
* the point is to exercise pointer-bearing structs and the standard
|
||||
* ScreenOfDisplay()/RootWindow()/DefaultVisual() macros, not to be a complete
|
||||
* Xlib.
|
||||
*/
|
||||
#ifndef XLIB_MIN_H
|
||||
#define XLIB_MIN_H
|
||||
|
||||
typedef unsigned long XID;
|
||||
typedef XID Window;
|
||||
typedef XID Drawable;
|
||||
typedef XID Pixmap;
|
||||
typedef XID Atom;
|
||||
typedef XID Colormap;
|
||||
typedef XID Cursor;
|
||||
typedef XID Font;
|
||||
typedef XID GContext;
|
||||
typedef XID KeySym;
|
||||
typedef int Bool;
|
||||
typedef unsigned char KeyCode;
|
||||
typedef unsigned long Mask;
|
||||
|
||||
typedef struct _Display Display;
|
||||
typedef struct _Screen Screen;
|
||||
typedef struct _Visual Visual;
|
||||
typedef struct _XImage XImage;
|
||||
typedef struct _XScreenFormat ScreenFormat;
|
||||
typedef struct _XExtData XExtData;
|
||||
|
||||
struct _XScreenFormat {
|
||||
int depth;
|
||||
int bits_per_pixel;
|
||||
int scanline_pad;
|
||||
};
|
||||
|
||||
struct _XExtData {
|
||||
int number;
|
||||
XExtData *next;
|
||||
int (*free_private)(XExtData *);
|
||||
char *private_data;
|
||||
};
|
||||
|
||||
struct _Visual {
|
||||
XExtData *ext_data;
|
||||
Visual *visualid; /* really VisualID, but size/alignment is what matters */
|
||||
int class;
|
||||
unsigned long red_mask;
|
||||
unsigned long green_mask;
|
||||
unsigned long blue_mask;
|
||||
int bits_per_rgb;
|
||||
int map_entries;
|
||||
};
|
||||
|
||||
struct _Screen {
|
||||
XExtData *ext_data;
|
||||
Display *display;
|
||||
Window root;
|
||||
int width, height;
|
||||
int mwidth, mheight;
|
||||
int ndepths;
|
||||
void *depths;
|
||||
int root_depth;
|
||||
Visual *root_visual;
|
||||
GContext default_gc;
|
||||
Colormap cmap;
|
||||
unsigned long white_pixel;
|
||||
unsigned long black_pixel;
|
||||
int max_maps, min_maps;
|
||||
int backing_store;
|
||||
Bool save_unders;
|
||||
long root_input_mask;
|
||||
};
|
||||
|
||||
struct _Display {
|
||||
XExtData *ext_data;
|
||||
void *private1;
|
||||
int fd;
|
||||
int private2;
|
||||
int proto_major_version;
|
||||
int proto_minor_version;
|
||||
char *vendor;
|
||||
XID private3;
|
||||
XID private4;
|
||||
XID private5;
|
||||
int private6;
|
||||
XID (*resource_alloc)(Display *);
|
||||
int byte_order;
|
||||
int bitmap_unit;
|
||||
int bitmap_pad;
|
||||
int bitmap_bit_order;
|
||||
int nformats;
|
||||
ScreenFormat *pixmap_format;
|
||||
int private8;
|
||||
int release;
|
||||
void *private9;
|
||||
void *private10;
|
||||
int qlen;
|
||||
unsigned long last_request_read;
|
||||
unsigned long request;
|
||||
void *private11;
|
||||
void *private12;
|
||||
void *private13;
|
||||
void *private14;
|
||||
unsigned max_request_size;
|
||||
void *db;
|
||||
int default_screen;
|
||||
int nscreens;
|
||||
Screen *screens;
|
||||
unsigned long motion_buffer;
|
||||
};
|
||||
|
||||
struct _XImage {
|
||||
int width, height;
|
||||
int xoffset;
|
||||
int format;
|
||||
char *data;
|
||||
int byte_order;
|
||||
int bitmap_unit;
|
||||
int bitmap_bit_order;
|
||||
int bitmap_pad;
|
||||
int depth;
|
||||
int bytes_per_line;
|
||||
int bits_per_pixel;
|
||||
unsigned long red_mask;
|
||||
unsigned long green_mask;
|
||||
unsigned long blue_mask;
|
||||
void *obdata;
|
||||
struct funcs {
|
||||
XImage *(*create_image)(Display *, Visual *, unsigned int, int, int, char *,
|
||||
unsigned int, unsigned int, int, int);
|
||||
int (*destroy_image)(XImage *);
|
||||
unsigned long (*get_pixel)(XImage *, int, int);
|
||||
int (*put_pixel)(XImage *, int, int, unsigned long);
|
||||
XImage *(*sub_image)(XImage *, int, int, unsigned int, unsigned int);
|
||||
int (*add_pixel)(XImage *, long);
|
||||
} f;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
unsigned long serial;
|
||||
Bool send_event;
|
||||
Display *display;
|
||||
Window window;
|
||||
} XAnyEvent;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
unsigned long serial;
|
||||
Bool send_event;
|
||||
Display *display;
|
||||
Window window;
|
||||
Window root;
|
||||
Window subwindow;
|
||||
unsigned long time;
|
||||
int x, y;
|
||||
int x_root, y_root;
|
||||
unsigned int state;
|
||||
unsigned int keycode;
|
||||
Bool same_screen;
|
||||
} XKeyEvent;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
unsigned long serial;
|
||||
Bool send_event;
|
||||
Display *display;
|
||||
Window window;
|
||||
Window root;
|
||||
Window subwindow;
|
||||
unsigned long time;
|
||||
int x, y;
|
||||
int x_root, y_root;
|
||||
unsigned int state;
|
||||
unsigned int button;
|
||||
Bool same_screen;
|
||||
} XButtonEvent;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
unsigned long serial;
|
||||
Bool send_event;
|
||||
Display *display;
|
||||
Window window;
|
||||
int x, y;
|
||||
int width, height;
|
||||
int count;
|
||||
} XExposeEvent;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
unsigned long serial;
|
||||
Bool send_event;
|
||||
Display *display;
|
||||
Window event;
|
||||
Window window;
|
||||
int x, y;
|
||||
int width, height;
|
||||
int border_width;
|
||||
Window above;
|
||||
Bool override_redirect;
|
||||
} XConfigureEvent;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
unsigned long serial;
|
||||
Bool send_event;
|
||||
Display *display;
|
||||
Window window;
|
||||
Atom message_type;
|
||||
int format;
|
||||
union {
|
||||
char b[20];
|
||||
short s[10];
|
||||
long l[5];
|
||||
} data;
|
||||
} XClientMessageEvent;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
Display *display;
|
||||
XID resourceid;
|
||||
unsigned long serial;
|
||||
unsigned char error_code;
|
||||
unsigned char request_code;
|
||||
unsigned char minor_code;
|
||||
} XErrorEvent;
|
||||
|
||||
typedef union _XEvent {
|
||||
int type;
|
||||
XAnyEvent xany;
|
||||
XKeyEvent xkey;
|
||||
XButtonEvent xbutton;
|
||||
XExposeEvent xexpose;
|
||||
XConfigureEvent xconfigure;
|
||||
XClientMessageEvent xclient;
|
||||
XErrorEvent xerror;
|
||||
long pad[24];
|
||||
} XEvent;
|
||||
|
||||
typedef struct {
|
||||
unsigned char *value;
|
||||
Atom encoding;
|
||||
int format;
|
||||
unsigned long nitems;
|
||||
} XTextProperty;
|
||||
|
||||
typedef struct {
|
||||
char *res_name;
|
||||
char *res_class;
|
||||
} XClassHint;
|
||||
|
||||
typedef struct {
|
||||
long flags;
|
||||
int x, y;
|
||||
int width, height;
|
||||
int min_width, min_height;
|
||||
int max_width, max_height;
|
||||
int width_inc, height_inc;
|
||||
struct {
|
||||
int x;
|
||||
int y;
|
||||
} min_aspect, max_aspect;
|
||||
int base_width, base_height;
|
||||
int win_gravity;
|
||||
} XSizeHints;
|
||||
|
||||
typedef struct {
|
||||
Pixmap background_pixmap;
|
||||
unsigned long background_pixel;
|
||||
Pixmap border_pixmap;
|
||||
unsigned long border_pixel;
|
||||
int bit_gravity;
|
||||
int win_gravity;
|
||||
int backing_store;
|
||||
unsigned long backing_planes;
|
||||
unsigned long backing_pixel;
|
||||
Bool save_under;
|
||||
long event_mask;
|
||||
long do_not_propagate_mask;
|
||||
Bool override_redirect;
|
||||
Colormap colormap;
|
||||
Cursor cursor;
|
||||
} XSetWindowAttributes;
|
||||
|
||||
#define DefaultScreen(dpy) ((dpy)->default_screen)
|
||||
#define ScreenOfDisplay(dpy, scr) ((dpy)->screens + (scr))
|
||||
#define RootWindow(dpy, scr) (ScreenOfDisplay((dpy), (scr))->root)
|
||||
#define DefaultVisual(dpy, scr) (ScreenOfDisplay((dpy), (scr))->root_visual)
|
||||
#define DefaultDepth(dpy, scr) (ScreenOfDisplay((dpy), (scr))->root_depth)
|
||||
#define DefaultGC(dpy, scr) (ScreenOfDisplay((dpy), (scr))->default_gc)
|
||||
#define BlackPixel(dpy, scr) (ScreenOfDisplay((dpy), (scr))->black_pixel)
|
||||
#define WhitePixel(dpy, scr) (ScreenOfDisplay((dpy), (scr))->white_pixel)
|
||||
#define DisplayWidth(dpy, scr) (ScreenOfDisplay((dpy), (scr))->width)
|
||||
#define DisplayHeight(dpy, scr) (ScreenOfDisplay((dpy), (scr))->height)
|
||||
|
||||
#define LSBFirst 0
|
||||
#define MSBFirst 1
|
||||
#define XYBitmap 0
|
||||
#define XYPixmap 1
|
||||
#define ZPixmap 2
|
||||
|
||||
#define KeyPress 2
|
||||
#define Expose 12
|
||||
#define ConfigureNotify 22
|
||||
|
||||
#define ExposureMask (1L << 15)
|
||||
#define KeyPressMask (1L << 0)
|
||||
#define ButtonPressMask (1L << 2)
|
||||
#define StructureNotifyMask (1L << 17)
|
||||
#define NoEventMask 0L
|
||||
|
||||
extern Display *XOpenDisplay(const char *);
|
||||
extern int XCloseDisplay(Display *);
|
||||
extern Window XCreateSimpleWindow(Display *, Window, int, int, unsigned int,
|
||||
unsigned int, unsigned int, unsigned long,
|
||||
unsigned long);
|
||||
extern Window XCreateWindow(Display *, Window, int, int, unsigned int,
|
||||
unsigned int, unsigned int, int, unsigned int,
|
||||
Visual *, unsigned long, XSetWindowAttributes *);
|
||||
extern int XChangeWindowAttributes(Display *, Window, unsigned long,
|
||||
XSetWindowAttributes *);
|
||||
extern int XStoreName(Display *, Window, const char *);
|
||||
extern int XSelectInput(Display *, Window, long);
|
||||
extern int XMapWindow(Display *, Window);
|
||||
extern GContext XCreateGC(Display *, Drawable, unsigned long, void *);
|
||||
extern int XSetForeground(Display *, GContext, unsigned long);
|
||||
extern int XSetBackground(Display *, GContext, unsigned long);
|
||||
extern XImage *XCreateImage(Display *, Visual *, unsigned int, int, int, char *,
|
||||
unsigned int, unsigned int, int, int);
|
||||
extern int XPutImage(Display *, Drawable, GContext, XImage *, int, int, int, int,
|
||||
unsigned int, unsigned int);
|
||||
extern int XSync(Display *, Bool);
|
||||
extern int XNextEvent(Display *, XEvent *);
|
||||
extern int XLookupString(XKeyEvent *, char *, int, KeySym *, void *);
|
||||
extern int XGetWMName(Display *, Window, XTextProperty *);
|
||||
extern int XmbTextPropertyToTextList(Display *, const XTextProperty *, char ***, int *);
|
||||
extern void XFreeStringList(char **);
|
||||
extern int XFree(void *);
|
||||
extern int XSetClassHint(Display *, Window, XClassHint *);
|
||||
extern int XSetWMProperties(Display *, Window, XTextProperty *, XTextProperty *,
|
||||
char **, int, XSizeHints *, void *, XClassHint *);
|
||||
extern void *malloc(unsigned long);
|
||||
extern void *memset(void *, int, unsigned long);
|
||||
extern void *memcpy(void *, const void *, unsigned long);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
# ROM import tables
|
||||
|
||||
One file per ROM revision. Addresses are **bit** addresses and are not assumed
|
||||
stable across firmware revisions; v2.2 is the first target.
|
||||
|
||||
`vt1200-v22.syms` contains only the entries already proven by the hand-written
|
||||
payloads in `vendor/vt1200/dwt/gui/xhello.asm` and the README. The compiler
|
||||
milestone that first calls Xlib must extend it from the Ghidra project /
|
||||
`ptr_constants.py` and validate each entry on MAME, then on the real unit.
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,14 @@
|
||||
# VT1200 v2.2 ROM entry points, bit addresses.
|
||||
# Source: dwt/gui/xhello.asm comments and README.md (v2.2 image md5
|
||||
# b46a3e442ccb86134d2356bd70d22fd2). These are the known-good entries used
|
||||
# by the existing hand-written payloads; the rest must be extracted from the
|
||||
# Ghidra project / ROM before the Xlib client milestone.
|
||||
#
|
||||
# C name bit address
|
||||
XOpenDisplay 0x3F9C2570
|
||||
XCreateSimpleWindow 0x3FD41B90
|
||||
XMapWindow 0x3FD645B0
|
||||
XSync 0x3FD9F6B0
|
||||
strlen 0x3FB458D0
|
||||
malloc 0x3F701230
|
||||
free 0x3F702440
|
||||
Reference in new issue
Block a user