Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7749cc810b | ||
|
|
eacaab68f6 | ||
|
|
937a8cc800 | ||
|
|
56d6a71cb7 | ||
|
|
755b3cb7b2 | ||
|
|
b638ab0fad | ||
|
|
6f776e1758 | ||
|
|
e0a721e3e1 | ||
|
|
e081921b72 |
No files matched your search
@@ -3,13 +3,14 @@
|
||||
A modern C toolchain for the DEC VT1200 / TI TMS34010, built as a small fork of
|
||||
upstream LLVM.
|
||||
|
||||
**Status: M0.** Clang understands the `tms34010-unknown-none` target, the TI
|
||||
data model, and bit-addressed pointers. There is **no machine-code backend
|
||||
yet**: M0 stops at LLVM IR. The backend, TI ABI, ROM calls, Xlib and real
|
||||
clients are M1–M5 and are separate plans (see the design spec).
|
||||
**Status: M1.** `llc -march=tms34010` emits GSPA-compatible assembly for a
|
||||
small set of leaf functions, and that assembly assembles, links and runs in
|
||||
MAME. M1 is an **assembly-only, test-only** snapshot: it uses an A0–A3/A0
|
||||
register convention, not the TI ABI, and has no object writer, calls or runtime.
|
||||
|
||||
- Design: [`docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md`](docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md)
|
||||
- M0 plan: [`docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md`](docs/superpowers/plans/2026-11-10-tms34010-m0-fork-and-bit-address-semantics.md)
|
||||
- M1 plan: [`docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md`](docs/superpowers/plans/2026-11-11-tms34010-m1-minimal-backend.md)
|
||||
- Spike: [`spike/REPORT-23.md`](spike/REPORT-23.md) (current, LLVM 23.1.5);
|
||||
[`spike/REPORT.md`](spike/REPORT.md) (historical, 19.1.7)
|
||||
|
||||
@@ -17,42 +18,70 @@ clients are M1–M5 and are separate plans (see the design spec).
|
||||
|
||||
- **Compiler fork:** <https://git.wut.ee/opencode.siil.arti.ee/llvm-tms34010>
|
||||
(branch `tms34010`) — the full buildable LLVM 23.1.5 tree with the TMS34010
|
||||
target and clang bit-address changes.
|
||||
target, clang bit-address changes and the M1 backend.
|
||||
- **This project:** <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>
|
||||
— plans/specs, VT1200 integration tooling, `tms-bitaddr`, `tms-clang`,
|
||||
symbols and the spike.
|
||||
symbols, the M1 harness and the spike.
|
||||
|
||||
The compiler fork is the canonical source for LLVM changes. `patches/llvm`
|
||||
here is a lightweight mirror used by `scripts/build-llvm.sh`, so the project
|
||||
can be built without cloning 3 GB of LLVM; keep it in sync with the fork's
|
||||
`tms34010` branch.
|
||||
The compiler fork is the canonical source for all target code. `patches/llvm`
|
||||
here is a **frozen M0 mirror** used by the default upstream+patches build; M1
|
||||
does not change it.
|
||||
|
||||
## What M0 provides
|
||||
## What M1 provides
|
||||
|
||||
- `clang --target=tms34010-unknown-none`: 32-bit bit-addressed pointers,
|
||||
8-bit signed `char`, 16-bit `short`, 32-bit `int`/`long`, packed structs,
|
||||
`sizeof(struct { char; int; void*; }) == 9`.
|
||||
- Pointer arithmetic scaled by 8; pointer difference divided by 8
|
||||
(`char *` → `/8`, `int *` → `/32`), while `(int)(p+1) - (int)p` stays an
|
||||
integer subtraction.
|
||||
- `tools/tms-bitaddr`: GEP `--scale` and late `--normalize` passes, including
|
||||
constant-expression GEPs.
|
||||
- `scripts/tms-clang`: the M0 pipeline
|
||||
`clang -O0` → `tms-bitaddr --scale` → `opt -O2` → `tms-bitaddr --normalize`.
|
||||
- Lit tests for the triple, data model and pointer difference; an integration
|
||||
test over a real Xlib client IR.
|
||||
M1 fills in the M0 target skeleton and stops at assembly:
|
||||
|
||||
- `llc -march=tms34010 -filetype=asm` for leaf 32-bit functions:
|
||||
integer/pointer arguments in `A0`–`A3`, result in `A0`, return via `RETS 0`.
|
||||
- `ADD` for `i32`, `MOVI` for constants, `MOVE ..., 1` (FS1) for 32-bit
|
||||
load/store, `MOVE ..., 0` (FS0) for `i16`, and sign-extending `MOVB` for
|
||||
`i8` load/store.
|
||||
- Constant global addresses as `MOVI _sym+bits`; a surviving GEP offset is a
|
||||
bit offset (`ADDI 24`, not `ADDI 192`).
|
||||
- Hard errors, not silent fallbacks, for calls (`M1 has no calls; M2 adds the
|
||||
TI ABI`), stack frames (`M1 has no stack frame`), more than four register
|
||||
arguments, unsupported signatures (varargs, `i64`/float/aggregate
|
||||
parameters, `sret`/`byval`/`inreg`/`nest`/`returned`), unsupported return
|
||||
types, and initialized globals or string literals.
|
||||
- Seventeen `llvm-lit`/FileCheck tests under
|
||||
`llvm/test/CodeGen/TMS34010/m1-*.ll` in the compiler fork, including
|
||||
negative tests that pin the loud refusals.
|
||||
- An end-to-end path: `scripts/tms-clang` IR → `llc -O0 -filetype=asm` →
|
||||
**1987 GSPA/GSPLNK 2.00 under DOSBox** → COFF → the VT1200 loader in MAME.
|
||||
The run checks `add(20, 22) == 42`, `load(&word) == 0x12345678`,
|
||||
`second() == &arr[1]` (a `symbol+32` bit relocation), `loadb` of a byte
|
||||
`0x81` returns the sign-extended `0xFFFFFF81`, and `storeb` leaves `0x81`
|
||||
in memory.
|
||||
- A `dis34` round-trip that reads the linked `.text` from the COFF and checks
|
||||
the instructions GSPA encoded at their linked bit addresses.
|
||||
|
||||
## M1 is not the TI ABI
|
||||
|
||||
| | M1 test convention | TI ABI (M2) |
|
||||
|---|---|---|
|
||||
| Integer/pointer arguments | `A0`–`A3`, four maximum | program stack (via `A14` STK) |
|
||||
| Return value | `A0` | `A8` |
|
||||
| Frame | none; leaf functions only | `A13` FP, `A14` STK, `A15` system stack |
|
||||
| Return instruction | `RETS 0` | `RETS 2` |
|
||||
| Calls, callee-saves, stack args | rejected | required |
|
||||
| Field select | harness sets FS1/FS0 before the leaf | `FS1 = 32` preserved across calls |
|
||||
|
||||
M1 assembly is not ABI-compatible with TI's compiler or the ROM Xlib. M2
|
||||
replaces the convention; do not build on the M1 register assignment.
|
||||
|
||||
## Repository layout
|
||||
|
||||
```text
|
||||
build/llvm-src/ local fork (ignored; created by build-llvm.sh or manually)
|
||||
build/llvm-build/ Ninja build of the fork (ignored)
|
||||
build/llvm-clean*/ clean CI clone/build (ignored)
|
||||
patches/llvm/ the complete fork delta against llvmorg-23.1.5
|
||||
build/llvm-clean/ compiler fork checkout used for M1 (ignored)
|
||||
build/llvm-clean-build/ its Ninja build tree (ignored)
|
||||
build/llvm-src/ M0 working fork (ignored)
|
||||
patches/llvm/ frozen M0 patch series against llvmorg-23.1.5
|
||||
tools/tms-bitaddr/ GEP scale/normalize tool
|
||||
scripts/tms-clang M0 pipeline wrapper
|
||||
scripts/tms-clang IR pipeline wrapper (clang + tms-bitaddr + opt)
|
||||
scripts/m1/ M1 harness: C inputs, entry.asm, link.cmd, checks
|
||||
scripts/tests/m1/ M1 gates (run.sh, dis-roundtrip.sh)
|
||||
scripts/tests/m0/ M0 integration tests
|
||||
scripts/build-llvm.sh clean clone + patches + build
|
||||
scripts/build-llvm.sh fork or upstream+patches build
|
||||
scripts/fetch-vt1200.sh fetch the VT1200 RE tree at its pinned commit
|
||||
spike/ measurement corpus and reports
|
||||
symbols/ per-ROM import tables (v2.2 subset)
|
||||
@@ -63,10 +92,13 @@ docs/ design and plans
|
||||
## Prerequisites
|
||||
|
||||
`cmake`, `ninja-build`, `ccache`, a C++17 host compiler (GCC 14 is used here),
|
||||
Python 3, and about 20 GB of disk for the LLVM source and build. On Debian:
|
||||
Python 3, and about 20 GB of disk for the LLVM source and build. The M1 gates
|
||||
additionally need `dosbox`, `mtools` (`mcopy`), the VT1200 SDK image, the
|
||||
`dwt` COFF loader, and a built MAME `vt1200` binary
|
||||
(`build/mame-src/vt1200`). On Debian:
|
||||
|
||||
```sh
|
||||
sudo apt-get install -y cmake ninja-build ccache
|
||||
sudo apt-get install -y cmake ninja-build ccache dosbox mtools
|
||||
```
|
||||
|
||||
## Fetch the VT1200 assets
|
||||
@@ -78,54 +110,130 @@ Xlib call sequence, `as34`, `dis34`, SDKs, manuals). It is not vendored:
|
||||
./scripts/fetch-vt1200.sh # -> vendor/vt1200, pinned 17dfd24...
|
||||
```
|
||||
|
||||
## Build the pinned fork
|
||||
## Build the compiler
|
||||
|
||||
### Compiler fork (M1, the normal path)
|
||||
|
||||
The fork checkout already exists at `build/llvm-clean` on `tms34010`. Build it
|
||||
in place:
|
||||
|
||||
```sh
|
||||
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=tms34010 ./scripts/build-llvm.sh
|
||||
# binaries: build/llvm-clean-build/bin/{llc,clang,opt,llvm-config,FileCheck}
|
||||
# lit driver: build/llvm-clean-build/bin/llvm-lit
|
||||
```
|
||||
|
||||
To clone the fork first instead of using an existing checkout:
|
||||
|
||||
```sh
|
||||
LLVM_REPO=gitea@git.wut.ee:opencode.siil.arti.ee/llvm-tms34010.git \
|
||||
LLVM_BRANCH=tms34010 ./scripts/build-llvm.sh
|
||||
```
|
||||
|
||||
Fork mode **never** checks out, resets, cleans or patches the source tree; it
|
||||
only runs CMake and Ninja. Local commits on the branch are safe. Use
|
||||
`LLVM_BUILD=...` to keep a separate build directory. It builds `llc`, clang,
|
||||
opt, `llvm-config`, FileCheck and the test helpers with X86 (host) and
|
||||
TMS34010; a first build takes roughly 20–40 minutes.
|
||||
|
||||
### Upstream + patches (frozen M0 path)
|
||||
|
||||
```sh
|
||||
./scripts/build-llvm.sh
|
||||
# binaries: build/llvm-clean-build/bin/{clang,opt,llvm-config,FileCheck,llvm-lit}
|
||||
```
|
||||
|
||||
The script clones `llvmorg-23.1.5`, applies `patches/llvm/*.patch`, and builds
|
||||
`clang`/`opt` with X86 (host) and TMS34010. A first build takes roughly
|
||||
20–40 minutes; rebuilds are incremental.
|
||||
This mode clones `llvmorg-23.1.5`, applies `patches/llvm/*.patch`, and builds
|
||||
`build/llvm-clean-build`. It owns and resets only a plain `llvm-project` clone:
|
||||
if `build/llvm-clean` has some other origin (for example the M1 fork), it
|
||||
refuses rather than destroying it. Use fork mode for that checkout.
|
||||
|
||||
For development, `build/llvm-src` is the working fork and `build/llvm-build` is
|
||||
its build tree. `scripts/export-patches.sh` regenerates the committed patch
|
||||
series with `git diff` (including new files via `git add -N`).
|
||||
`scripts/export-patches.sh` regenerates the committed M0 patch series with
|
||||
`git diff` (including new files via `git add -N`); it is not used for M1.
|
||||
|
||||
## Build the M0 tool and run the tests
|
||||
## Build the M0 support tools
|
||||
|
||||
The M1 pipeline still uses `tms-bitaddr` and `tms-clang`:
|
||||
|
||||
```sh
|
||||
LLVM_CONFIG=$PWD/build/llvm-clean-build/bin/llvm-config \
|
||||
tools/tms-bitaddr/build.sh
|
||||
```
|
||||
|
||||
M0 regression test (optional in M1):
|
||||
|
||||
```sh
|
||||
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
|
||||
TMS_OPT=$PWD/build/llvm-clean-build/bin/opt \
|
||||
TMS_BITADDR=$PWD/tools/tms-bitaddr/tms-bitaddr \
|
||||
./scripts/tests/m0/run.sh
|
||||
# -> M0 pipeline: PASS
|
||||
|
||||
build/llvm-clean-build/bin/llvm-lit -v \
|
||||
build/llvm-clean-build/tools/clang/test --filter='TMS34010'
|
||||
# -> 3 passed
|
||||
```
|
||||
|
||||
## Use
|
||||
## Run the M1 gates
|
||||
|
||||
These are the documented commands; all three must pass:
|
||||
|
||||
```sh
|
||||
scripts/tms-clang -O2 -I spike -o /tmp/client.ll spike/xclient.c
|
||||
# 1. LLVM codegen tests: 17 passed
|
||||
build/llvm-clean-build/bin/llvm-lit -v \
|
||||
build/llvm-clean-build/test/CodeGen/TMS34010 --filter='m1-'
|
||||
|
||||
# 2. Compile, assemble, link and run in MAME
|
||||
./scripts/tests/m1/run.sh
|
||||
# -> M1 MAME: PASS
|
||||
|
||||
# 3. Rebuild the COFF and check the GSPA encodings with dis34
|
||||
./scripts/tests/m1/dis-roundtrip.sh
|
||||
# -> M1 dis34 round-trip: PASS
|
||||
```
|
||||
|
||||
The output is LLVM IR in which every address is a bit address. It is not
|
||||
assembly or an object file; `llc` support is M1.
|
||||
`run.sh` compiles five C inputs with `scripts/tms-clang -O2`, runs
|
||||
`llc -march=tms34010 -O0 -filetype=asm` on the normalized IR, concatenates
|
||||
the result with `scripts/m1/entry.asm` and `scripts/m1/char-test.asm`,
|
||||
assembles/links with GSPA/GSPLNK 2.00 under DOSBox, then loads the COFF in
|
||||
MAME. `dis-roundtrip.sh` runs the same build and then disassembles the linked
|
||||
`.text` at the symbol addresses from the COFF symbol table. Paths are
|
||||
overridable with `TMS_CLANG`, `TMS_OPT`, `TMS_BITADDR`, `TMS_LLC`, `MAME`,
|
||||
`DOSBOX`, `SDK_IMG`, `M1_WORK`, `M1_COFF` and `M1_SDK`.
|
||||
|
||||
## What is deliberately missing
|
||||
## Emit assembly by hand
|
||||
|
||||
- TMS34010 instruction selection, registers, frame lowering, asm/MC output.
|
||||
- The TI ABI: A8 return, A13/A14/A15 dual stack, `RETS 2`, `FS1 = 32`.
|
||||
- ROM import symbols beyond the subset in `symbols/`.
|
||||
- Xlib headers/clients on the target, `memcpy`/`byval`/varargs/float/bitfield
|
||||
lowering, native COFF.
|
||||
```sh
|
||||
TMS_CLANG=$PWD/build/llvm-clean-build/bin/clang \
|
||||
TMS_OPT=$PWD/build/llvm-clean-build/bin/opt \
|
||||
TMS_BITADDR=$PWD/tools/tms-bitaddr/tms-bitaddr \
|
||||
scripts/tms-clang -O2 -o /tmp/add.ll scripts/m1/add.c
|
||||
|
||||
These are M1–M5 in the design spec, written as separate plans because their
|
||||
tasks depend on what M0/M1 measure.
|
||||
build/llvm-clean-build/bin/llc -march=tms34010 -O0 -filetype=asm \
|
||||
/tmp/add.ll -o /tmp/add.s
|
||||
```
|
||||
|
||||
Always produce the IR with `scripts/tms-clang` (or run `tms-bitaddr --scale`)
|
||||
before `llc`: raw clang IR carries byte GEP offsets, but `llc` treats every
|
||||
surviving GEP offset as a *bit* offset, so addresses would be 8× too small.
|
||||
Run `llc -O0` on the normalized IR that `tms-clang` already optimized and
|
||||
scaled. `tms-bitaddr --normalize` consumes that scaled IR and now rejects a
|
||||
typed (non-`i8`) or vector GEP instead of emitting its byte offset as bits.
|
||||
The lit tests use `llc -march=tms34010 -O2 -filetype=asm` on hand-written IR
|
||||
to exercise the selector pipeline directly. Output is assembly only: `llc`
|
||||
cannot emit an object file or a usable COFF for this target.
|
||||
|
||||
## Not yet supported (M2 and later)
|
||||
|
||||
- **Data:** only zero-initialized mutable globals (`.bss`) are supported.
|
||||
Initialized globals, string literals and constant/private data (`.data`,
|
||||
`.rodata`) make `llc` fail loudly; the `.data` initializer probe moves to
|
||||
M2.
|
||||
- **Object emission:** no `MCCodeEmitter`, object writer, `llc -filetype=obj`,
|
||||
`clang -c`, or native COFF. GSPA assembles and GSPLNK links; the existing
|
||||
COFF loader loads.
|
||||
- **TI ABI:** `A8` return, `A13`/`A14`/`A15` dual stack, `RETS 2`, `FS1 = 32`
|
||||
across calls, callee-saves, stack arguments, more than four arguments.
|
||||
- **C features:** calls, locals/`alloca`, structures/`byval`, floats, varargs,
|
||||
bitfields, 64-bit arithmetic, `memcpy`/`alloca` lowering.
|
||||
- **Runtime and clients:** ROM/Xlib imports, `strlen`/`malloc`, X11 headers,
|
||||
real VT1200 clients, debug info.
|
||||
- **2ine:** M1 uses the proven 1987 GSPA/GSPLNK under DOSBox, not the external
|
||||
2ine r5.01 tools; 2ine is an M2 speed-up.
|
||||
|
||||
These are M2–M5 in the design spec, each with its own plan.
|
||||
@@ -0,0 +1,386 @@
|
||||
# TMS34010 M1 — Minimal Backend 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:** `llc -march=tms34010` emits GSPA-compatible assembly for a leaf `int add(int, int)`, `int load(int *)` and `char` load/store, and that assembly assembles, links and runs in MAME. No TI ABI yet.
|
||||
|
||||
**Architecture:** Fill in the M0 target skeleton with TableGen register/instruction definitions, a minimal SelectionDAG selector, a provisional register-argument test convention, and an asm printer for GSPA syntax. Use `llc -filetype=asm` and the existing GSPA/gsplnk + COFF loader path. Object emission and the full TI ABI are later milestones.
|
||||
|
||||
**Tech Stack:** LLVM/Clang 23.1.5 fork (`llvm-tms34010`, branch `tms34010`), C++17, CMake/Ninja, `llc`, GSPA/gsplnk under 2ine, MAME, `dis34`, `llvm-lit`/FileCheck.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-11-10-tms34010-llvm-design.md` (§7.4–§7.6, M1)
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Compiler work happens in **`llvm-tms34010`**, branch `tms34010` (clone `build/llvm-m1`). Project plans/tests live in **`tms-c-compiler`**, branch `m1`.
|
||||
- **M1 calling convention is a test convention, not the TI ABI:** integer/pointer arguments in `A0, A1, A2, A3`; return value in `A0`; leaf functions only; no calls, no stack arguments, no callee-saves, no structures/floats/varargs. M2 replaces this with A8/A13/A14/A15, `RETS 2`, and `FS1 = 32`.
|
||||
- M1 compiles at `-O2`; any function needing a stack frame, a call, a structure, float, or an unsupported operation is a hard error, not a silent fallback.
|
||||
- Output is **assembly only**: `llc -march=tms34010 -filetype=asm`. No LLVM object writer, `MCCodeEmitter` or linker in M1; GSPA assembles, gsplnk links, the existing COFF loader loads.
|
||||
- M1 assembles with the **proven 1987 GSPA/GSPLNK 2.00 under DOSBox** (the path exercised by `vendor/vt1200/harness/c-hello/build.sh`), not the external 2ine r5.01 tools. 2ine is an M2 speed-up. The asm printer targets GSPA 2.00 syntax.
|
||||
- GSPA syntax: hex constants `0…h`, lowercase mnemonics accepted, `movi` long form for 32-bit constants, field-select `,1` on 32-bit `MOVE`; the M1 entry sets `SETF 32,0,1` before calling compiled code.
|
||||
- Symbols use the GSPA/TI leading underscore (`_add`); code is linked at a fixed bit address.
|
||||
- No ROM/Xlib imports, no `malloc`, no `strlen`, no TI runtime in M1. M2 owns those.
|
||||
- The M0 `patches/llvm` mirror is frozen; M1 uses the full compiler repo, not the patch mirror.
|
||||
- Tests: LLVM lit/FileCheck for asm; a GSPA/2ine assemble-link step; a MAME run with marker words; a `dis34` round-trip of the emitted asm.
|
||||
|
||||
## Review Focus
|
||||
|
||||
1. **Test convention leakage:** no code path may claim TI ABI compatibility; calls/stack args must fail loudly. Tests in Task 2 and Task 4.
|
||||
2. **`-O2` stack/spill/frame:** leaf functions must not silently use an unimplemented frame; `FrameLowering` must reject frame indices it cannot lower. Tests in Task 2/3.
|
||||
3. **GSPA syntax and field select:** 32-bit `MOVE` must carry `,1`; `MOVB` is the 8-bit access and sign-extends. Tests in Task 3/4.
|
||||
4. **Symbol/link address:** `_add` label, absolute `CALLA`, and bit-address placement must be consistent with the linker. Test in Task 4.
|
||||
5. **Asm/encoding agreement:** GSPA's encoding of the emitted asm must match `dis34` for `ADD`, `MOVE`, `MOVB`, `RETS`; a mismatch means the asm printer is wrong. Test in Task 5.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: M1 compiler checkout, build, and failing `llc` test
|
||||
|
||||
**Files:**
|
||||
- Create: `build/llvm-m1/llvm/test/CodeGen/TMS34010/m1-add.ll` (compiler repo)
|
||||
- Create: `build/llvm-m1/llvm/test/CodeGen/TMS34010/m1-call.ll`, `m1-frame.ll`, `m1-late-gep.ll` (compiler repo)
|
||||
- Modify: `patches/llvm` is **not** changed in M1.
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: M0 `tms34010` branch.
|
||||
- Produces: a built `llc` that lists `tms34010`; the failing test that every later task makes pass.
|
||||
|
||||
- [ ] **Step 1: Clone the compiler repo and build**
|
||||
|
||||
```sh
|
||||
mkdir -p build
|
||||
git clone --depth 1 --branch tms34010 \
|
||||
gitea@git.wut.ee:opencode.siil.arti.ee/llvm-tms34010.git \
|
||||
build/llvm-m1
|
||||
cmake -S build/llvm-m1/llvm -B build/llvm-m1-build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON \
|
||||
-DLLVM_ENABLE_PROJECTS=clang \
|
||||
-DLLVM_TARGETS_TO_BUILD="X86;TMS34010" \
|
||||
-DLLVM_INCLUDE_TESTS=ON -DLLVM_INCLUDE_EXAMPLES=OFF \
|
||||
-DLLVM_INCLUDE_BENCHMARKS=OFF
|
||||
ninja -C build/llvm-m1-build llc opt clang llvm-config FileCheck count not
|
||||
```
|
||||
|
||||
Expected: build succeeds; `build/llvm-m1-build/bin/llc --version` lists `tms34010`.
|
||||
|
||||
- [ ] **Step 2: Write the failing asm test**
|
||||
|
||||
`build/llvm-m1/llvm/test/CodeGen/TMS34010/m1-add.ll`:
|
||||
|
||||
```llvm
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
define i32 @add(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%r = add i32 %a, %b
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _add:
|
||||
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: RETS 0
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run it to verify it fails**
|
||||
|
||||
```sh
|
||||
build/llvm-m1-build/bin/llvm-lit -v \
|
||||
build/llvm-m1-build/test/CodeGen/TMS34010 --filter='m1-add'
|
||||
```
|
||||
|
||||
Expected: FAIL — `llc` reports that the target cannot emit assembly / no instruction selector (the M0 `addPassesToEmitFile` is still a stub).
|
||||
|
||||
- [ ] **Step 4: Commit the failing test in the compiler repo**
|
||||
|
||||
```sh
|
||||
git -C build/llvm-m1 add llvm/test/CodeGen/TMS34010/m1-add.ll
|
||||
git -C build/llvm-m1 commit -m "tms34010: add failing M1 add codegen test"
|
||||
```
|
||||
|
||||
### Task 2: Minimal `add` codegen (registers, ISel, asm printer)
|
||||
|
||||
**Files:**
|
||||
- Create/modify under `build/llvm-m1/llvm/lib/Target/TMS34010/`:
|
||||
- `TMS34010.td`, `TMS34010RegisterInfo.td`, `TMS34010InstrInfo.td`
|
||||
- `TMS34010RegisterInfo.{h,cpp}`, `TMS34010InstrInfo.{h,cpp}`
|
||||
- `TMS34010ISelLowering.{h,cpp}`, `TMS34010ISelDAGToDAG.cpp`
|
||||
- `TMS34010FrameLowering.{h,cpp}`
|
||||
- `TMS34010AsmPrinter.cpp`, `TMS34010MCInstLower.{h,cpp}`
|
||||
- `MCTargetDesc/TMS34010MCAsmInfo.{h,cpp}`, `TMS34010InstPrinter.{h,cpp}`, `TMS34010MCTargetDesc.{h,cpp}`
|
||||
- `TMS34010Subtarget.{h,cpp}`, `TMS34010TargetMachine.{h,cpp}`, CMake files
|
||||
- Test: `build/llvm-m1/llvm/test/CodeGen/TMS34010/m1-add.ll`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `llc`/M0 target skeleton.
|
||||
- Produces: `TMS34010TargetLowering` with a provisional `LowerFormalArguments`/`LowerReturn`; `TMS34010InstrInfo` with `ADD`/`RETS`/`MOVI`; `TMS34010AsmPrinter` emitting GSPA asm; `TMS34010Subtarget` wired into a `CodeGenTargetMachineImpl`.
|
||||
|
||||
- [ ] **Step 1: Define registers and the minimal instructions**
|
||||
|
||||
`TMS34010RegisterInfo.td`: define `A0`–`A15` and `B0`–`B15` (B regs reserved for a later task), an `ARegs` register class (use A0–A7 in M1 patterns). `TMS34010InstrInfo.td`: define `MOVI` (32-bit immediate, 3 words), `ADD` (register), `RETS` (with N immediate), `CLR`, `NOP`; include `AsmString` for GSPA syntax. Keep the encodings in the definitions, but M1 only needs the asm printer.
|
||||
|
||||
- [ ] **Step 2: Wire the target machine and subtarget**
|
||||
|
||||
Switch `TMS34010TargetMachine` to `CodeGenTargetMachineImpl` with the M0 data layout string, `TMS34010Subtarget` and `TargetLoweringObjectFileELF`; implement `createPassConfig` (`addInstSelector`, asm printer), `getSubtargetImpl`, `getObjFileLowering`, and `addInstSelector` returning a `TMS34010ISelDag`. Register `MCAsmInfo`, `MCInstrInfo`, `MCRegisterInfo`, `MCSubtargetInfo`, `MCInstPrinter`; no code emitter.
|
||||
|
||||
- [ ] **Step 3: Implement the provisional convention and patterns**
|
||||
|
||||
`TMS34010TargetLowering`:
|
||||
|
||||
- `addRegisterClass(MVT::i32, &TMS34010::ARegsRegClass)`, `computeRegisterProperties`.
|
||||
- `LowerFormalArguments`: `i32`/pointer args 0..3 → `CCValAssign::getReg` `A0..A3`; more args → `llvm_unreachable`/`report_fatal_error("M1 supports at most four register arguments")`. M1 accepts only 32-bit scalar/pointer arguments; narrow C values are promoted/truncated in IR.
|
||||
- `LowerReturn`: `i32`/pointer result → `A0`; `void` is allowed; anything else → `report_fatal_error`.
|
||||
- `LowerCall`: `report_fatal_error("M1 has no calls; M2 adds the TI ABI")`.
|
||||
- `CanLowerReturn` only for `i32`/pointer.
|
||||
|
||||
TableGen patterns: `add i32` → `ADD`; `ret` with A0 → `RETS 0`; `i32` constants → `MOVI`. `TMS34010FrameLowering` has no prologue/epilogue and `getFrameIndexReference` calls `report_fatal_error` for any frame index (M1 has no stack).
|
||||
|
||||
M1 is asm-only: define the instruction encodings in TableGen for future use, but do not add an `MCCodeEmitter` or object writer. GSPA/dis34 validate the asm in Task 5.
|
||||
|
||||
`m1-call.ll` pins the call refusal:
|
||||
|
||||
```llvm
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; CHECK: M1 has no calls
|
||||
declare i32 @callee(i32)
|
||||
define i32 @caller(i32 %x) {
|
||||
%r = call i32 @callee(i32 %x)
|
||||
ret i32 %r
|
||||
}
|
||||
```
|
||||
|
||||
`m1-late-gep.ll` pins the §7.3.1 contract at `llc`: a surviving GEP offset is in bits, not bytes.
|
||||
|
||||
```llvm
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
define i32 @late(ptr %p) {
|
||||
%g = getelementptr i8, ptr %p, i32 24
|
||||
%v = ptrtoint ptr %g to i32
|
||||
ret i32 %v
|
||||
}
|
||||
; CHECK: ADDI 24
|
||||
; CHECK-NOT: ADDI 192
|
||||
```
|
||||
|
||||
`m1-frame.ll` pins the no-stack refusal:
|
||||
|
||||
```llvm
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; CHECK: M1 has no stack frame
|
||||
define i32 @frame(i32 %n) {
|
||||
%a = alloca i32
|
||||
store i32 %n, ptr %a
|
||||
%v = load i32, ptr %a
|
||||
ret i32 %v
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the lit tests to verify they pass**
|
||||
|
||||
```sh
|
||||
build/llvm-m1-build/bin/llvm-lit -v \
|
||||
build/llvm-m1-build/test/CodeGen/TMS34010 --filter='m1-(add|call|frame|late-gep)'
|
||||
```
|
||||
Expected: PASS — the `add` asm, the call/alloca refusals, and `ADDI 24` (not 192) for the bit-offset GEP.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```sh
|
||||
git -C build/llvm-m1 add llvm/lib/Target/TMS34010 llvm/test/CodeGen/TMS34010/m1-add.ll \
|
||||
llvm/test/CodeGen/TMS34010/m1-call.ll llvm/test/CodeGen/TMS34010/m1-frame.ll \
|
||||
llvm/test/CodeGen/TMS34010/m1-late-gep.ll
|
||||
git -C build/llvm-m1 commit -m "tms34010: minimal add codegen with GSPA asm"
|
||||
```
|
||||
|
||||
### Task 3: Loads, stores and 8-bit accesses
|
||||
|
||||
**Files:**
|
||||
- Modify: `build/llvm-m1/llvm/lib/Target/TMS34010/TMS34010InstrInfo.td`, `TMS34010ISelLowering.cpp`, `TMS34010ISelDAGToDAG.cpp`, `TMS34010FrameLowering.cpp`
|
||||
- Create: `build/llvm-m1/llvm/test/CodeGen/TMS34010/m1-load.ll`, `m1-char.ll`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Task 2 selector/asm printer.
|
||||
- Produces: `MOVE` (32-bit load/store) and `MOVB` (8-bit load/store) patterns with GSPA field select.
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
|
||||
`m1-load.ll`:
|
||||
|
||||
```llvm
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
define i32 @load(ptr %p) {
|
||||
%v = load i32, ptr %p
|
||||
ret i32 %v
|
||||
}
|
||||
; CHECK-LABEL: _load:
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 0
|
||||
```
|
||||
|
||||
`m1-char.ll`:
|
||||
|
||||
```llvm
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
define i32 @loadb(ptr %p) {
|
||||
%v = load i8, ptr %p
|
||||
%r = sext i8 %v to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define void @storeb(ptr %p, i32 %v) {
|
||||
%t = trunc i32 %v to i8
|
||||
store i8 %t, ptr %p
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _loadb:
|
||||
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK-LABEL: _storeb:
|
||||
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run them to verify they fail**
|
||||
|
||||
```sh
|
||||
build/llvm-m1-build/bin/llvm-lit -v \
|
||||
build/llvm-m1-build/test/CodeGen/TMS34010 --filter='m1-(load|char)'
|
||||
```
|
||||
Expected: FAIL — no load/store patterns or `MOVE`/`MOVB` definitions.
|
||||
|
||||
- [ ] **Step 3: Add `MOVE`/`MOVB` and selection**
|
||||
|
||||
Define `MOVE` with a field-select immediate and `MOVB` in the `.td`; add DAG patterns for `load i32`/`store i32` → `MOVE` with `,1`, and `load i8`/`store i8` → `MOVB`. Set `i8`/`i16` load/store legalization to custom where needed. Keep `-O2` leaf-only; any frame index remains a hard error.
|
||||
|
||||
- [ ] **Step 4: Run the tests to verify they pass**
|
||||
|
||||
Run the same lit filter. Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```sh
|
||||
git -C build/llvm-m1 add llvm/lib/Target/TMS34010 llvm/test/CodeGen/TMS34010/m1-load.ll llvm/test/CodeGen/TMS34010/m1-char.ll
|
||||
git -C build/llvm-m1 commit -m "tms34010: add MOVE/MOVB load-store codegen"
|
||||
```
|
||||
|
||||
### Task 4: Assemble, link and run in MAME
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/m1/add.c`, `scripts/m1/load.c`, `scripts/m1/late.c`, `scripts/m1/const_gep.c`, `scripts/m1/entry.asm`, `scripts/m1/link.cmd`, `scripts/m1/run.sh` (project repo)
|
||||
- Create: `scripts/tests/m1/run.sh` (project repo)
|
||||
- Reuse: `vendor/vt1200`, `dwt/loader`, `harness/c-hello` patterns
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `llc` asm from Tasks 2–3; GSPA/GSPLNK 2.00 under DOSBox; existing COFF loader.
|
||||
- Produces: a MAME run whose marker words prove `add(20,22) == 42`, a word load, the late-GEP offset 24, and a `&arr[1]` constant-GEP relocation.
|
||||
|
||||
- [ ] **Step 1: Write the harness**
|
||||
|
||||
`scripts/m1/entry.asm` sets `SETF 32,0,1`, loads `A0 = 20`, `A1 = 22`, `CALLA _add`, stores `A0` at marker `0x018A8000`, repeats a `load` test, then calls `_second` and stores the returned `&arr[1]`, and `RETS`. Link `.text` at bit `0x018A0000` with `gspa`/`gsplnk` 2.00 under DOSBox and a `link.cmd` written with CRLF.
|
||||
|
||||
`add.c` is `int add(int a,int b){return a+b;}`, `load.c` is `int load(int *p){return *p;}`, `late.c` is `int late(char *p, char *q){return (char *)((unsigned)p + 24) == q;}`, and `const_gep.c` is `static int arr[4]; int *second(void){return &arr[1];}`. The harness pipeline is:
|
||||
|
||||
```sh
|
||||
TMS_CLANG=$PWD/build/llvm-m1-build/bin/clang \
|
||||
TMS_OPT=$PWD/build/llvm-m1-build/bin/opt \
|
||||
TMS_BITADDR=$PWD/tools/tms-bitaddr/tms-bitaddr \
|
||||
scripts/tms-clang -O2 -o /tmp/add.ll scripts/m1/add.c
|
||||
build/llvm-m1-build/bin/llc -march=tms34010 -O0 -filetype=asm \
|
||||
/tmp/add.ll -o /tmp/add.s
|
||||
```
|
||||
|
||||
`llc -O0` is deliberate: the IR is already scaled/normalized by `tms-clang`, and M1 does not want a second optimization pipeline recreating GEPs. The target must still treat a surviving GEP offset as bits (`m1-late-gep.ll`). Repeat for `load.c`, `late.c` and `const_gep.c`; check the `late.s` for `ADDI 24` (not 192), and the `const_gep.s` for `@arr+32` (bits, not `+4` bytes). Concatenate with `entry.asm`, assemble/link, and load via `dwt/loader/loader.lua`/MAME.
|
||||
|
||||
The SDK is the 1987 DOSBox path:
|
||||
|
||||
```sh
|
||||
sudo apt-get install -y dosbox mtools
|
||||
IMG="$PWD/vendor/vt1200/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img"
|
||||
mkdir -p build/m1-sdk
|
||||
mcopy -s -n -i "$IMG" "::/A2/*" build/m1-sdk/
|
||||
mcopy -s -n -i "$IMG" "::/C2/*" build/m1-sdk/
|
||||
```
|
||||
|
||||
Invoke `gspa`/`gsplnk` in DOSBox the way `vendor/vt1200/harness/c-hello/build.sh` does; keep the linker command file CRLF.
|
||||
|
||||
- [ ] **Step 2: Run it to verify it fails**
|
||||
|
||||
Run `scripts/tests/m1/run.sh`. Expected: FAIL because `llc` does not yet emit the expected asm or the harness is missing.
|
||||
|
||||
- [ ] **Step 3: Fix the asm/link integration**
|
||||
|
||||
Adjust the asm printer's directives/labels or the linker command as needed; if `late.c`/`const_gep.c` expose missing GEP or global-address lowering, fix it in the compiler repo (target ISel/asm printer), not in the harness. Keep the harness in the project repo. Do not add ROM calls or TI ABI.
|
||||
|
||||
- [ ] **Step 4: Run it to verify it passes**
|
||||
|
||||
Expected: marker `42` (and the loaded word marker) in MAME; `scripts/tests/m1/run.sh` prints `M1 MAME: PASS`.
|
||||
|
||||
- [ ] **Step 5: Commit both repos**
|
||||
|
||||
```sh
|
||||
git -C build/llvm-m1 add llvm/lib/Target/TMS34010
|
||||
git -C build/llvm-m1 commit -m "tms34010: align asm output with GSPA link path"
|
||||
git add scripts/m1 scripts/tests/m1
|
||||
git commit -m "tests: M1 GSPA/MAME smoke test"
|
||||
```
|
||||
|
||||
### Task 5: `dis34` round-trip of the emitted assembly
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/m1/dis-roundtrip.sh` (project repo)
|
||||
- Create: `scripts/tests/m1/dis-roundtrip.sh` (project repo)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: the linked/extracted `.text` bytes and `tools/smart34010/dis34`.
|
||||
- Produces: a byte/mnemonic check that GSPA's encoding of the emitted `ADD`/`MOVE`/`MOVB`/`RETS` matches `dis34`.
|
||||
|
||||
- [ ] **Step 1: Write the failing check**
|
||||
|
||||
The script extracts the linked `.text` bytes, runs `dis34`, and greps for the emitted instructions at the expected addresses. Initially it fails because the harness/asm path is not yet complete.
|
||||
|
||||
- [ ] **Step 2: Run it to verify it fails**
|
||||
|
||||
Run `scripts/tests/m1/dis-roundtrip.sh`. Expected: FAIL.
|
||||
|
||||
- [ ] **Step 3: Reconcile asm and encoding**
|
||||
|
||||
If a mnemonic or operand is wrong, fix the asm printer (compiler repo); if the byte extraction/address is wrong, fix the script. Do not add an LLVM object writer or MCCodeEmitter in M1.
|
||||
|
||||
- [ ] **Step 4: Run it to verify it passes**
|
||||
|
||||
Expected: `M1 dis34 round-trip: PASS`.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```sh
|
||||
git add scripts/m1/dis-roundtrip.sh scripts/tests/m1/dis-roundtrip.sh
|
||||
git commit -m "tests: M1 dis34 round-trip"
|
||||
```
|
||||
|
||||
### Task 6: Document and push M1
|
||||
|
||||
**Files:**
|
||||
- Modify: `README.md` (project repo) — M1 status/commands
|
||||
- Modify: `build/llvm-m1/TMS34010.md` (compiler repo) — M1 backend/test convention
|
||||
- Modify: `scripts/build-llvm.sh` (project repo) — optional `LLVM_FORK`/branch path
|
||||
- Push: compiler repo `tms34010`, project repo `m1`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: all previous tasks.
|
||||
- Produces: a documented M1 snapshot and branches others can clone.
|
||||
|
||||
- [ ] **Step 1: Update the docs**
|
||||
|
||||
State clearly: M1 is a **test-only register convention**, no TI ABI, no calls/ROM; M2 is the ABI. Document `llc -march=tms34010 -O2 -filetype=asm`, the GSPA/2ine path, and the MAME test. Do not claim `clang -c` or object output works.
|
||||
|
||||
- [ ] **Step 2: Run the M1 gates from the documented commands**
|
||||
|
||||
Run the lit filter, `scripts/tests/m1/run.sh`, and `scripts/tests/m1/dis-roundtrip.sh` using the documented build path. Expected: all green.
|
||||
|
||||
- [ ] **Step 3: Commit and push**
|
||||
|
||||
```sh
|
||||
git -C build/llvm-m1 push origin tms34010
|
||||
git add README.md docs scripts
|
||||
git commit -m "docs: M1 minimal backend and test convention"
|
||||
git push origin m1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
This plan is M1 only. The TI ABI (A8/A13/A14/A15, dual stack, `RETS 2`, `FS1`), ROM imports, Xlib and real clients are M2–M5 and get separate plans. The compiler repo is the canonical source for target code; this project repo holds the plans, wrappers, symbols and integration tests.
|
||||
@@ -345,12 +345,15 @@ non-constant pointer expression by 8×.
|
||||
`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.
|
||||
`inttoptr(add(ptrtoint(@.str), 16))`). M1 verifies the `MOVI` immediate
|
||||
form with `&arr[1]`. M1 does not emit `.data`, `.rodata` or string literals
|
||||
at all: any non-null initializer makes `llc` fail loudly. Verifying a
|
||||
`symbol+bit_addend` relocation in a `.data` initializer, a string literal
|
||||
with an offset, and a global pointer initializer is **deferred to M2**
|
||||
along with `.data` support. Fallbacks if GSPA/gsplnk cannot carry the
|
||||
addend then: leave `symbol+addend` entirely to link time, materialize the
|
||||
addend at runtime (`MOVI symbol; ADDI addend`), or pre-resolve in the
|
||||
linker script.
|
||||
- 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.
|
||||
|
||||
@@ -386,8 +389,9 @@ non-constant pointer expression by 8×.
|
||||
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.
|
||||
- **Constant-GEP relocation test**: M1 covers `symbol + bit_addend` in a
|
||||
`MOVI` with `&arr[1]`. The `.data` initializer, string-literal and global
|
||||
pointer initializer parts are deferred to M2 with `.data` support.
|
||||
- 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,
|
||||
|
||||
+80
-16
@@ -1,28 +1,92 @@
|
||||
#!/bin/sh
|
||||
# Build the pinned LLVM 23.1.5 fork from a clean clone and the committed
|
||||
# patch series. Intended for CI and for verifying that patches/llvm is the
|
||||
# complete fork delta.
|
||||
# Build the TMS34010 LLVM compiler.
|
||||
#
|
||||
# Two modes:
|
||||
#
|
||||
# upstream + patches (M0, default)
|
||||
# ./scripts/build-llvm.sh
|
||||
# Clones llvmorg-23.1.5 into build/llvm-clean, applies patches/llvm and
|
||||
# builds it. This mode owns its clone and resets it, but refuses to touch
|
||||
# a checkout whose origin is not llvm-project.
|
||||
#
|
||||
# compiler fork (M1 and later)
|
||||
# LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=tms34010 \
|
||||
# ./scripts/build-llvm.sh
|
||||
# LLVM_REPO=gitea@git.wut.ee:opencode.siil.arti.ee/llvm-tms34010.git \
|
||||
# LLVM_BRANCH=tms34010 ./scripts/build-llvm.sh
|
||||
# Build the fork branch in place. Fork mode never checks out, resets,
|
||||
# cleans or patches the source tree; it only configures and builds it.
|
||||
#
|
||||
# Environment:
|
||||
# LLVM_SRC source tree (default build/llvm-clean)
|
||||
# LLVM_BUILD build tree (default build/llvm-clean-build)
|
||||
# LLVM_REPO git URL to clone when LLVM_SRC does not exist
|
||||
# LLVM_BRANCH branch to clone/build; setting this (or LLVM_REPO) selects
|
||||
# fork mode
|
||||
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}
|
||||
REPO=${LLVM_REPO:-}
|
||||
BRANCH=${LLVM_BRANCH:-}
|
||||
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
|
||||
git -C "$SRC" clean -fdx
|
||||
origin_url() {
|
||||
git -C "$1" config --get remote.origin.url 2>/dev/null || true
|
||||
}
|
||||
|
||||
PATCHROOT=$(pwd)/patches/llvm
|
||||
for p in "$PATCHROOT"/*.patch; do
|
||||
[ -e "$p" ] || continue
|
||||
git -C "$SRC" apply "$p"
|
||||
done
|
||||
if [ -n "$REPO" ] || [ -n "$BRANCH" ]; then
|
||||
echo "== compiler fork mode: $SRC${BRANCH:+ (branch $BRANCH)}"
|
||||
if [ ! -e "$SRC/.git" ]; then
|
||||
[ -n "$REPO" ] || {
|
||||
echo "build-llvm: $SRC is not a git checkout; set LLVM_REPO to clone it" >&2
|
||||
exit 1
|
||||
}
|
||||
if [ -n "$BRANCH" ]; then
|
||||
git clone --branch "$BRANCH" "$REPO" "$SRC"
|
||||
else
|
||||
git clone "$REPO" "$SRC"
|
||||
fi
|
||||
fi
|
||||
if [ -n "$BRANCH" ]; then
|
||||
CURRENT=$(git -C "$SRC" rev-parse --abbrev-ref HEAD)
|
||||
if [ "$CURRENT" != "$BRANCH" ]; then
|
||||
echo "build-llvm: $SRC is on '$CURRENT', not '$BRANCH'" >&2
|
||||
echo "build-llvm: check the branch out first; fork mode never" >&2
|
||||
echo "build-llvm: modifies the source tree" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "== upstream + patches mode: $SRC (tag $TAG)"
|
||||
if [ -e "$SRC/.git" ]; then
|
||||
URL=$(origin_url "$SRC")
|
||||
case "$URL" in
|
||||
*llvm-project*) ;;
|
||||
*)
|
||||
echo "build-llvm: refusing to reset $SRC: origin is" >&2
|
||||
echo "build-llvm: '${URL:-<none>}', not llvm-project." >&2
|
||||
echo "build-llvm: use LLVM_REPO/LLVM_BRANCH for a compiler fork" >&2
|
||||
echo "build-llvm: checkout (default mode must not reset it)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
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
|
||||
git -C "$SRC" clean -fdx
|
||||
|
||||
PATCHROOT=$(pwd)/patches/llvm
|
||||
for p in "$PATCHROOT"/*.patch; do
|
||||
[ -e "$p" ] || continue
|
||||
git -C "$SRC" apply "$p"
|
||||
done
|
||||
fi
|
||||
|
||||
cmake -S "$SRC/llvm" -B "$BUILD" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON \
|
||||
@@ -30,4 +94,4 @@ cmake -S "$SRC/llvm" -B "$BUILD" -G Ninja \
|
||||
-DLLVM_TARGETS_TO_BUILD="X86;TMS34010" \
|
||||
-DLLVM_INCLUDE_TESTS=ON -DLLVM_INCLUDE_EXAMPLES=OFF \
|
||||
-DLLVM_INCLUDE_BENCHMARKS=OFF
|
||||
ninja -C "$BUILD" clang opt llvm-config FileCheck count not
|
||||
ninja -C "$BUILD" clang opt llc llvm-config FileCheck count not
|
||||
@@ -0,0 +1 @@
|
||||
int add(int a, int b) { return a + b; }
|
||||
@@ -0,0 +1,22 @@
|
||||
; char-test.asm -- run the compiled char helpers and publish their results.
|
||||
;
|
||||
; Appended after char.s so _loadb/_storeb (and every earlier function) keep
|
||||
; their linked addresses. _start calls this instead of calling _second
|
||||
; directly, which is the same one-word-size calla; _char_test calls _second
|
||||
; and returns its result in A0 so the existing second() marker still works.
|
||||
;
|
||||
; storeb(018A8080, 0x81) writes the byte 0x81
|
||||
; loadb(018A8080) returns the sign-extended 0xFFFFFF81
|
||||
.text
|
||||
.globl _char_test
|
||||
_char_test:
|
||||
calla _second ; A0 = &arr[1]
|
||||
move a0, a3 ; save it for the second() marker
|
||||
movi 018A8080h, a0 ; p = storeb/loadb marker byte
|
||||
movi 81h, a1 ; v = 0x81 (low byte stored by MOVB)
|
||||
calla _storeb
|
||||
calla _loadb ; A0 is still 018A8080
|
||||
movi 018A8060h, a2
|
||||
move a0, *a2, 1 ; 32-bit loadb marker
|
||||
move a3, a0 ; restore &arr[1]
|
||||
rets
|
||||
@@ -0,0 +1,2 @@
|
||||
int loadb(char *p) { return *p; }
|
||||
void storeb(char *p, int v) { *p = v; }
|
||||
@@ -0,0 +1,3 @@
|
||||
static int arr[4];
|
||||
|
||||
int *second(void) { return &arr[1]; }
|
||||
Executable
+232
@@ -0,0 +1,232 @@
|
||||
#!/bin/sh
|
||||
# M1 dis34 round-trip.
|
||||
#
|
||||
# scripts/m1/dis-roundtrip.sh
|
||||
#
|
||||
# Extracts the linked .text bytes from build/m1/m1.coff (using the COFF
|
||||
# section table, not a hard-coded file offset), disassembles them with the
|
||||
# VT1200 tree's dis34 at the linked bit address, and checks that GSPA encoded
|
||||
# the assembly llc printed:
|
||||
#
|
||||
# _add ADD A0, A1 ; MOVE A1, A0 ; RETS 0
|
||||
# _load MOVE *A0, A0, 1 ; RETS 0
|
||||
# _late ADDI 24, A0 ; RETS 0
|
||||
# _second MOVI _arr+32, A0 ; RETS 0
|
||||
# _loadb MOVB *A0, A0 ; RETS 0
|
||||
# _storeb MOVB A1, *A0 ; RETS 0
|
||||
#
|
||||
# This verifies the 1987 GSPA assembler's encoding, not a dormant LLVM Inst
|
||||
# encoding. Overridable: M1_COFF DIS34
|
||||
set -eu
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
COFF=${M1_COFF:-$ROOT/build/m1/m1.coff}
|
||||
DIS34=${DIS34:-$ROOT/vendor/vt1200/tools/smart34010/dis34}
|
||||
|
||||
fail() {
|
||||
echo "M1 dis34 round-trip: FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ -f "$COFF" ] || fail "linked COFF not found: $COFF (run scripts/m1/run.sh)"
|
||||
[ -x "$DIS34" ] || fail "dis34 not found or not executable: $DIS34"
|
||||
|
||||
TMP=$(mktemp -d "${TMPDIR:-/tmp}/m1-dis34.XXXXXX")
|
||||
FAIL_LOG=${M1_DIS_FAIL_LOG:-${TMPDIR:-/tmp}/m1-dis34-failure.txt}
|
||||
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||
|
||||
# Read little-endian fields out of the COFF. od keeps this dependency-free.
|
||||
read_u8s() {
|
||||
od -An -v -tu1 -j "$1" -N "$2" "$COFF"
|
||||
}
|
||||
u16() {
|
||||
read_u8s "$1" 2 | awk '{ print $1 + 256 * $2 }'
|
||||
}
|
||||
u32() {
|
||||
read_u8s "$1" 4 | awk '{ print $1 + 256 * $2 + 65536 * $3 + 16777216 * $4 }'
|
||||
}
|
||||
|
||||
echo "== parse the COFF section table ($COFF)"
|
||||
nscns=$(u16 2)
|
||||
opthdr=$(u16 16)
|
||||
[ "$nscns" -gt 0 ] 2>/dev/null || fail "COFF has no sections (nscns='$nscns')"
|
||||
[ "$opthdr" -gt 0 ] 2>/dev/null || fail "COFF has no optional header (opthdr='$opthdr')"
|
||||
|
||||
TEXT_ADDR=
|
||||
TEXT_BITS=
|
||||
TEXT_PTR=
|
||||
sh_base=$((20 + opthdr))
|
||||
i=0
|
||||
while [ "$i" -lt "$nscns" ]; do
|
||||
o=$((sh_base + i * 40))
|
||||
name=$(dd if="$COFF" bs=1 skip="$o" count=8 2>/dev/null | tr -d '\000')
|
||||
if [ "$name" = ".text" ]; then
|
||||
TEXT_ADDR=$(u32 $((o + 8)))
|
||||
TEXT_BITS=$(u32 $((o + 16)))
|
||||
TEXT_PTR=$(u32 $((o + 20)))
|
||||
break
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ -n "$TEXT_ADDR" ] || fail "COFF has no .text section"
|
||||
[ "$TEXT_BITS" -gt 0 ] 2>/dev/null || fail ".text is empty"
|
||||
[ $((TEXT_BITS % 8)) -eq 0 ] || fail ".text size $TEXT_BITS bits is not whole bytes"
|
||||
|
||||
# The linked layout this check was written against.
|
||||
TEXT_HEX=$(printf '%08X' "$TEXT_ADDR")
|
||||
ENTRY=$(u32 $((20 + 16)))
|
||||
ENTRY_HEX=$(printf '%08X' "$ENTRY")
|
||||
TEXT_BYTES=$((TEXT_BITS / 8))
|
||||
|
||||
[ "$TEXT_HEX" = "018A0000" ] \
|
||||
|| fail ".text is linked at $TEXT_HEX, want 018A0000"
|
||||
[ "$ENTRY_HEX" = "018A0000" ] \
|
||||
|| fail "COFF entry point is $ENTRY_HEX, want 018A0000"
|
||||
[ "$TEXT_PTR" -gt 0 ] || fail ".text has no raw data (scnptr=0)"
|
||||
|
||||
echo " .text at $TEXT_HEX: $TEXT_BITS bits ($TEXT_BYTES bytes), scnptr=$TEXT_PTR"
|
||||
echo " entry point $ENTRY_HEX"
|
||||
|
||||
dd if="$COFF" of="$TMP/text.bin" bs=1 skip="$TEXT_PTR" count="$TEXT_BYTES" 2>/dev/null
|
||||
[ "$(wc -c < "$TMP/text.bin")" -eq "$TEXT_BYTES" ] \
|
||||
|| fail "extracted $(wc -c < "$TMP/text.bin") bytes, want $TEXT_BYTES"
|
||||
|
||||
echo "== resolve the linked symbols from the COFF symbol table"
|
||||
symptr=$(u32 8)
|
||||
nsyms=$(u32 12)
|
||||
[ "$symptr" -gt 0 ] 2>/dev/null || fail "COFF has no symbol table"
|
||||
[ "$nsyms" -gt 0 ] 2>/dev/null || fail "COFF symbol table is empty"
|
||||
|
||||
sym_addr() {
|
||||
_want=$1
|
||||
_i=0
|
||||
while [ "$_i" -lt "$nsyms" ]; do
|
||||
_o=$((symptr + _i * 18))
|
||||
_n=$(dd if="$COFF" bs=1 skip="$_o" count=8 2>/dev/null | tr -d '\000')
|
||||
if [ "$_n" = "$_want" ]; then
|
||||
u32 $((_o + 8))
|
||||
return 0
|
||||
fi
|
||||
_aux=$(od -An -v -tu1 -j $((_o + 17)) -N 1 "$COFF" | awk '{ print $1 }')
|
||||
_i=$((_i + 1 + _aux))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
get_sym() {
|
||||
_name=$1
|
||||
_addr=$(sym_addr "$_name") || fail "COFF symbol table has no $_name"
|
||||
[ -n "$_addr" ] || fail "COFF symbol table has no $_name"
|
||||
printf '%s' "$_addr"
|
||||
}
|
||||
|
||||
ADD=$(get_sym _add)
|
||||
LOAD=$(get_sym _load)
|
||||
LATE=$(get_sym _late)
|
||||
SECOND=$(get_sym _second)
|
||||
LOADB=$(get_sym _loadb)
|
||||
STOREB=$(get_sym _storeb)
|
||||
ADD_HEX=$(printf '%08X' "$ADD")
|
||||
LOAD_HEX=$(printf '%08X' "$LOAD")
|
||||
LATE_HEX=$(printf '%08X' "$LATE")
|
||||
SECOND_HEX=$(printf '%08X' "$SECOND")
|
||||
LOADB_HEX=$(printf '%08X' "$LOADB")
|
||||
STOREB_HEX=$(printf '%08X' "$STOREB")
|
||||
|
||||
[ "$ADD_HEX" = "018A0240" ] || fail "_add linked at $ADD_HEX, want 018A0240"
|
||||
[ "$LOAD_HEX" = "018A0270" ] || fail "_load linked at $LOAD_HEX, want 018A0270"
|
||||
[ "$LATE_HEX" = "018A0290" ] || fail "_late linked at $LATE_HEX, want 018A0290"
|
||||
[ "$SECOND_HEX" = "018A02C0" ] || fail "_second linked at $SECOND_HEX, want 018A02C0"
|
||||
[ "$LOADB_HEX" = "018A0300" ] || fail "_loadb linked at $LOADB_HEX, want 018A0300"
|
||||
[ "$STOREB_HEX" = "018A0320" ] || fail "_storeb linked at $STOREB_HEX, want 018A0320"
|
||||
echo " _add=$ADD_HEX _load=$LOAD_HEX _late=$LATE_HEX _second=$SECOND_HEX"
|
||||
echo " _loadb=$LOADB_HEX _storeb=$STOREB_HEX (appended after _second)"
|
||||
|
||||
echo "== disassemble the emitted .text with dis34"
|
||||
"$DIS34" -b "$TEXT_HEX" -l -o "$TMP/text.bin" > "$TMP/dis34.txt" 2> "$TMP/dis34.err" \
|
||||
|| { cat "$TMP/dis34.err" >&2; fail "dis34 failed"; }
|
||||
[ -s "$TMP/dis34.txt" ] || fail "dis34 produced no output"
|
||||
|
||||
# match_at FILE DECIMAL_ADDR ERE
|
||||
# dis34 -l prints "018a0240:\tadd a0, a1\t; 4001". The operand must be
|
||||
# followed by optional whitespace and then the comment/end-of-line marker, so
|
||||
# "rets 1" cannot satisfy a "rets" pattern.
|
||||
match_at() {
|
||||
_file=$1
|
||||
_addr=$2
|
||||
_pat=$3
|
||||
_laddr=$(printf '%08x' "$_addr")
|
||||
grep -Eiq "^${_laddr}:[[:space:]]+${_pat}([[:space:]]*(;|$))" "$_file"
|
||||
}
|
||||
|
||||
# expect_at LABEL DECIMAL_ADDR EXPECTED_TEXT ERE
|
||||
expect_at() {
|
||||
_label=$1
|
||||
_addr=$2
|
||||
_want=$3
|
||||
_pat=$4
|
||||
_laddr=$(printf '%08x' "$_addr")
|
||||
if match_at "$TMP/dis34.txt" "$_addr" "$_pat"; then
|
||||
echo " ok $_label @ 0x$_laddr: $_want"
|
||||
return 0
|
||||
fi
|
||||
{
|
||||
echo "M1 dis34 round-trip: FAIL"
|
||||
echo " $_label at bit 0x$_laddr: expected $_want"
|
||||
echo " dis34 output near 0x$_laddr:"
|
||||
awk -v a="$_laddr" '
|
||||
{ low = tolower($0) }
|
||||
low ~ ("^" a ":") { seen = 1 }
|
||||
seen { print " " $0; n++ }
|
||||
seen && n >= 3 { exit }
|
||||
' "$TMP/dis34.txt"
|
||||
if cp "$TMP/dis34.txt" "$FAIL_LOG" 2>/dev/null; then
|
||||
echo " full disassembly preserved at: $FAIL_LOG"
|
||||
else
|
||||
echo " full disassembly: $TMP/dis34.txt (temporary; removed on exit)"
|
||||
fi
|
||||
} >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
expect_label() {
|
||||
_addr=$1
|
||||
# dis34 labels branch targets as L followed by %X (no zero padding).
|
||||
_laddr=$(printf '%x' "$_addr")
|
||||
grep -Eiq "^L${_laddr}:" "$TMP/dis34.txt" \
|
||||
|| fail "dis34 did not label the branch target L$(printf '%X' "$_addr")"
|
||||
echo " ok branch target L$(printf '%X' "$_addr") is labelled"
|
||||
}
|
||||
|
||||
echo "== check the linked instructions"
|
||||
expect_label "$ADD"
|
||||
expect_at "_add" "$ADD" "ADD A0, A1" 'add[[:space:]]+a0,[[:space:]]*a1'
|
||||
expect_at "_add+16" $((ADD + 16)) "MOVE A1, A0" 'move[[:space:]]+a1,[[:space:]]*a0'
|
||||
expect_at "_add+32" $((ADD + 32)) "RETS 0" 'rets([[:space:]]+0)?'
|
||||
|
||||
expect_label "$LOAD"
|
||||
expect_at "_load" "$LOAD" "MOVE *A0, A0, 1" 'move[[:space:]]+\*a0,[[:space:]]*a0,[[:space:]]*1'
|
||||
expect_at "_load+16" $((LOAD + 16)) "RETS 0" 'rets([[:space:]]+0)?'
|
||||
|
||||
expect_at "_late" "$LATE" "ADDI 24, A0" 'addi[[:space:]]+(0x18|24),[[:space:]]*a0'
|
||||
expect_at "_late+32" $((LATE + 32)) "RETS 0" 'rets([[:space:]]+0)?'
|
||||
|
||||
expect_label "$SECOND"
|
||||
expect_at "_second" "$SECOND" "MOVI _arr+32, A0" 'movi[[:space:]]+(0x)?18a9020,[[:space:]]*a0'
|
||||
expect_at "_second+48" $((SECOND + 48)) "RETS 0" 'rets([[:space:]]+0)?'
|
||||
|
||||
# Negative control for the RETS operand boundary: "rets 1" must not match.
|
||||
printf '018a0260:\trets\t1\t; 0960\n' > "$TMP/rets-neg.txt"
|
||||
if match_at "$TMP/rets-neg.txt" $((ADD + 32)) 'rets([[:space:]]+0)?'; then
|
||||
fail "negative control: the RETS pattern accepted 'rets 1'"
|
||||
fi
|
||||
echo " ok negative control @ 0x$(printf '%08x' $((ADD + 32))): 'rets 1' rejected"
|
||||
|
||||
expect_label "$LOADB"
|
||||
expect_at "_loadb" "$LOADB" "MOVB *A0, A0" 'movb[[:space:]]+\*a0,[[:space:]]*a0'
|
||||
expect_at "_loadb+16" $((LOADB + 16)) "RETS 0" 'rets([[:space:]]+0)?'
|
||||
|
||||
expect_at "_storeb" "$STOREB" "MOVB A1, *A0" 'movb[[:space:]]+a1,[[:space:]]*\*a0'
|
||||
expect_at "_storeb+16" $((STOREB + 16)) "RETS 0" 'rets([[:space:]]+0)?'
|
||||
|
||||
echo "M1 dis34 round-trip: PASS"
|
||||
@@ -0,0 +1,44 @@
|
||||
; entry.asm -- M1 smoke-test entry, linked at bit 018A0000.
|
||||
;
|
||||
; M1 has no stack frame, no calls and no TI ABI. The generated functions are
|
||||
; leaf functions: i32/pointer arguments in A0-A3, result in A0.
|
||||
;
|
||||
; Marker words (bit addresses, 0x20 bits apart = 4 bytes):
|
||||
; 018A8000 add(20, 22)
|
||||
; 018A8020 load(018A8100)
|
||||
; 018A8040 second() == &arr[1]
|
||||
; 018A8060 loadb(018A8080) == 0xFFFFFF81 (sign-extended 0x81)
|
||||
; 018A8080 storeb(018A8080, 0x81) leaves the byte 0x81
|
||||
;
|
||||
; _late is linked but not called: it is a compile-time check that a surviving
|
||||
; GEP offset is 24 bits, not 192.
|
||||
;
|
||||
; The char helpers are exercised by _char_test (char-test.asm, appended after
|
||||
; the compiled functions). Calling it here instead of calling _second keeps
|
||||
; this entry's size -- and therefore every function's linked address -- the
|
||||
; same; _char_test calls _second and returns its result in A0.
|
||||
.text
|
||||
.globl _start
|
||||
_start:
|
||||
setf 32, 0, 1 ; FS1 = 32 for 32-bit MOVE
|
||||
setf 16, 0, 0 ; FS0 = 16, for any i16 code
|
||||
movi 20, a0
|
||||
movi 22, a1
|
||||
calla _add
|
||||
movi 018A8000h, a2
|
||||
move a0, *a2, 1
|
||||
|
||||
movi 018A8100h, a2
|
||||
movi 12345678h, a3
|
||||
move a3, *a2, 1
|
||||
move a2, a0
|
||||
calla _load
|
||||
movi 018A8020h, a2
|
||||
move a0, *a2, 1
|
||||
|
||||
calla _char_test
|
||||
|
||||
movi 018A8040h, a2
|
||||
move a0, *a2, 1
|
||||
|
||||
rets
|
||||
@@ -0,0 +1,9 @@
|
||||
/* Late-GEP check: p + 3 bytes must reach llc as ADDI 24 -- the address unit is
|
||||
* a bit and tms-bitaddr has already scaled the offset -- not ADDI 192.
|
||||
*
|
||||
* The plan's source also compared p + 3 with another pointer. That form
|
||||
* lowers to setcc/branches, which the M1 backend deliberately does not have;
|
||||
* this form exercises the same late-GEP contract without it. See the M1
|
||||
* implementation design, sections 7.3 and 7.5.
|
||||
*/
|
||||
int late(char *p) { return (int)(p + 3); }
|
||||
@@ -0,0 +1,16 @@
|
||||
-a
|
||||
-o M1.OUT
|
||||
-m M1.MAP
|
||||
-e _start
|
||||
M1.obj
|
||||
MEMORY
|
||||
{
|
||||
prog: origin = 018A0000h, length = 8000h
|
||||
ram: origin = 018A9000h, length = 1000h
|
||||
}
|
||||
SECTIONS
|
||||
{
|
||||
.text: {} > prog
|
||||
.data: {} > prog
|
||||
.bss: {} > ram
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
int load(int *p) { return *p; }
|
||||
@@ -0,0 +1,44 @@
|
||||
-- m1-check.lua -- load the M1 COFF with dwt/loader/loader.lua and read the
|
||||
-- marker words the generated code wrote.
|
||||
--
|
||||
-- M1_LOADER_LUA path to vendor/vt1200/dwt/loader/loader.lua
|
||||
-- M1_EXPECT_LOAD expected load() result (default 0x12345678)
|
||||
-- M1_EXPECT_SECOND expected &arr[1] in bits
|
||||
-- M1_EXPECT_LOADB expected sign-extended byte load (default 0xFFFFFF81)
|
||||
-- M1_EXPECT_STOREB expected byte store (default 0x81)
|
||||
--
|
||||
-- The marker read runs one frame before loader.lua's inspection/exit frame.
|
||||
|
||||
dofile(os.getenv("M1_LOADER_LUA"))
|
||||
|
||||
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
|
||||
local EXPECT_LOAD = tonumber(os.getenv("M1_EXPECT_LOAD") or "0x12345678")
|
||||
local EXPECT_SECOND = tonumber(os.getenv("M1_EXPECT_SECOND") or "0x018A9020")
|
||||
local EXPECT_LOADB = tonumber(os.getenv("M1_EXPECT_LOADB") or "0xFFFFFF81")
|
||||
local EXPECT_STOREB = tonumber(os.getenv("M1_EXPECT_STOREB") or "0x81")
|
||||
local frames = 0
|
||||
local checked = false
|
||||
|
||||
emu.register_frame_done(function()
|
||||
frames = frames + 1
|
||||
if frames == ENDF - 1 and not checked then
|
||||
checked = true
|
||||
local prog = manager.machine.devices[':maincpu'].spaces['program']
|
||||
local add = prog:read_u32(0x018A8000)
|
||||
local load = prog:read_u32(0x018A8020)
|
||||
local second = prog:read_u32(0x018A8040)
|
||||
local loadb = prog:read_u32(0x018A8060)
|
||||
local storeb = prog:read_u8(0x018A8080)
|
||||
print(string.format(
|
||||
"M1: add=%d load=0x%08X second=0x%08X loadb=0x%08X storeb=0x%02X",
|
||||
add, load, second, loadb, storeb))
|
||||
if add == 42 and load == EXPECT_LOAD and second == EXPECT_SECOND
|
||||
and loadb == EXPECT_LOADB and storeb == EXPECT_STOREB then
|
||||
print("M1 MAME: PASS")
|
||||
else
|
||||
print(string.format(
|
||||
"M1 MAME: FAIL (want add=42 load=0x%08X second=0x%08X loadb=0x%08X storeb=0x%02X)",
|
||||
EXPECT_LOAD, EXPECT_SECOND, EXPECT_LOADB, EXPECT_STOREB))
|
||||
end
|
||||
end
|
||||
end, "m1-check")
|
||||
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#!/bin/sh
|
||||
# M1 end-to-end smoke test.
|
||||
#
|
||||
# scripts/m1/run.sh
|
||||
#
|
||||
# Compiles six C functions with scripts/tms-clang + llc -O0, assembles and
|
||||
# links them with the 1987 GSPA/GSPLNK 2.00 under DOSBox, then loads the COFF
|
||||
# into MAME through dwt/loader/loader.lua and checks the marker words:
|
||||
#
|
||||
# add(20, 22) == 42
|
||||
# load(&word) == 0x12345678
|
||||
# second() == &arr[1] (arr + 32 bits)
|
||||
# loadb(&byte) == 0xFFFFFF81 (sign-extended 0x81)
|
||||
# storeb(&byte, 0x81) leaves the byte 0x81
|
||||
#
|
||||
# Overridable: TMS_CLANG TMS_OPT TMS_BITADDR TMS_LLC MAME DOSBOX DOSBOX_CONF
|
||||
# SDK_IMG M1_WORK M1_COFF M1_SDK LOADER_AT LOADER_END
|
||||
set -eu
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
DIR=$ROOT/scripts/m1
|
||||
|
||||
LLVM_BIN=${LLVM_BIN:-$ROOT/build/llvm-clean-build/bin}
|
||||
TMS_CLANG=${TMS_CLANG:-$LLVM_BIN/clang}
|
||||
TMS_OPT=${TMS_OPT:-$LLVM_BIN/opt}
|
||||
TMS_BITADDR=${TMS_BITADDR:-$ROOT/tools/tms-bitaddr/tms-bitaddr}
|
||||
TMS_LLC=${TMS_LLC:-$LLVM_BIN/llc}
|
||||
|
||||
MAME=${MAME:-$ROOT/build/mame-src/vt1200}
|
||||
DOSBOX=${DOSBOX:-dosbox}
|
||||
DOSBOX_CONF=${DOSBOX_CONF:-$ROOT/vendor/vt1200/harness/sdk-scratch/dosbox-bench.conf}
|
||||
SDK_IMG=${SDK_IMG:-$ROOT/vendor/vt1200/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img}
|
||||
|
||||
WORK=${M1_WORK:-$ROOT/build/m1}
|
||||
COFF=${M1_COFF:-$WORK/m1.coff}
|
||||
SDK=${M1_SDK:-$ROOT/build/m1-sdk}
|
||||
ASM=$WORK/asm
|
||||
LOADER_LUA=$ROOT/vendor/vt1200/dwt/loader/loader.lua
|
||||
LOADER_BIN=$ROOT/vendor/vt1200/dwt/loader/build/loader.bin
|
||||
LOADER_AT=${LOADER_AT:-2500}
|
||||
LOADER_END=${LOADER_END:-2700}
|
||||
|
||||
fail() {
|
||||
echo "m1: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
for tool in "$TMS_CLANG" "$TMS_OPT" "$TMS_BITADDR" "$TMS_LLC"; do
|
||||
[ -x "$tool" ] || fail "missing tool: $tool"
|
||||
done
|
||||
|
||||
mkdir -p "$ASM" "$WORK/evidence" "$WORK/run/nvram" "$WORK/run/cfg"
|
||||
mkdir -p "$(dirname "$COFF")"
|
||||
|
||||
echo "== compile and select"
|
||||
export TMS_CLANG TMS_OPT TMS_BITADDR
|
||||
for name in add load late const_gep char; do
|
||||
"$ROOT/scripts/tms-clang" -O2 -o "$ASM/$name.ll" "$DIR/$name.c"
|
||||
"$TMS_LLC" -march=tms34010 -O0 -filetype=asm \
|
||||
"$ASM/$name.ll" -o "$ASM/$name.s" \
|
||||
|| fail "llc failed for $name.c"
|
||||
echo " $name.s: $(grep -c . "$ASM/$name.s") lines"
|
||||
done
|
||||
|
||||
echo "== check the bit-address constants"
|
||||
grep -Eq '^[[:space:]]*ADDI[[:space:]]+24,[[:space:]]*A0' "$ASM/late.s" \
|
||||
|| fail "late.s has no ADDI 24 (late GEP got scaled again?)"
|
||||
grep -Eq 'ADDI[[:space:]]+192' "$ASM/late.s" \
|
||||
&& fail "late.s contains ADDI 192: late GEP was scaled twice"
|
||||
grep -Eq '^[[:space:]]*MOVI[[:space:]]+_arr\+32,[[:space:]]*A0' "$ASM/const_gep.s" \
|
||||
|| fail "const_gep.s has no 'MOVI _arr+32': constant GEP addend is not in bits"
|
||||
grep -Eq '_arr\+4[^0-9]' "$ASM/const_gep.s" \
|
||||
&& fail "const_gep.s references _arr+4 (bytes) instead of _arr+32 (bits)"
|
||||
echo " late.s: $(grep -E 'ADDI' "$ASM/late.s" | head -1 | sed 's/^[[:space:]]*//')"
|
||||
echo " const_gep.s: $(grep -E 'MOVI' "$ASM/const_gep.s" | head -1 | sed 's/^[[:space:]]*//')"
|
||||
|
||||
echo "== extract the 1987 SDK"
|
||||
if [ ! -f "$SDK/GSPA.EXE" ]; then
|
||||
command -v mcopy >/dev/null 2>&1 || fail "mtools (mcopy) is required"
|
||||
[ -f "$SDK_IMG" ] || fail "SDK image not found: $SDK_IMG"
|
||||
mkdir -p "$SDK"
|
||||
mcopy -s -n -i "$SDK_IMG" "::/A2/*" "$SDK/"
|
||||
mcopy -s -n -i "$SDK_IMG" "::/C2/*" "$SDK/"
|
||||
fi
|
||||
[ -f "$SDK/GSPA.EXE" ] || fail "GSPA.EXE not extracted into $SDK"
|
||||
|
||||
echo "== assemble and link with GSPA/GSPLNK under DOSBox"
|
||||
cat "$DIR/entry.asm" "$ASM/add.s" "$ASM/load.s" "$ASM/late.s" \
|
||||
"$ASM/const_gep.s" "$ASM/char.s" "$DIR/char-test.asm" > "$SDK/M1.ASM"
|
||||
# The linker command file must keep CRLF line endings.
|
||||
sed 's/\r$//' "$DIR/link.cmd" | sed 's/$/\r/' > "$SDK/M1.CMD"
|
||||
rm -f "$SDK/M1.OBJ" "$SDK/M1.OUT" "$SDK/M1.MAP" "$SDK/M1.LST"
|
||||
|
||||
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||
timeout "${DOSBOX_TIMEOUT:-300}" "$DOSBOX" -conf "$DOSBOX_CONF" \
|
||||
-noprimaryconf -nolocalconf \
|
||||
-c "mount c $SDK" -c "c:" \
|
||||
-c "gspa M1 M1.obj M1.lst -l" \
|
||||
-c "gsplnk M1.cmd" \
|
||||
-c "exit" -exit > "$WORK/dosbox.log" 2>&1 \
|
||||
|| { tail -20 "$WORK/dosbox.log" >&2; fail "DOSBox failed"; }
|
||||
|
||||
[ -f "$SDK/M1.LST" ] || fail "GSPA produced no listing (see $WORK/dosbox.log)"
|
||||
sed 's/\r$//' "$SDK/M1.LST" > "$WORK/m1.lst"
|
||||
grep -q 'No Errors' "$WORK/m1.lst" \
|
||||
|| { grep -i -B2 -A2 'error' "$WORK/m1.lst" >&2 || true; fail "GSPA reported errors"; }
|
||||
grep -q 'No Warnings' "$WORK/m1.lst" \
|
||||
|| { grep -i -B2 -A2 'warning' "$WORK/m1.lst" >&2 || true; fail "GSPA reported warnings"; }
|
||||
[ -f "$SDK/M1.OUT" ] || fail "GSPLNK produced no M1.OUT"
|
||||
sed 's/\r$//' "$SDK/M1.MAP" > "$WORK/m1.map"
|
||||
cp "$SDK/M1.OUT" "$COFF"
|
||||
cp "$SDK/M1.ASM" "$WORK/m1.asm"
|
||||
|
||||
ENTRY=$(awk '$2 == "_start" { print $1; exit }' "$WORK/m1.map")
|
||||
[ "$ENTRY" = "018A0000" ] || fail "linker entry _start is '$ENTRY', want 018A0000"
|
||||
grep -q 'ENTRY POINT SYMBOL: "_start"' "$WORK/m1.map" \
|
||||
|| fail "linker map has no _start entry point"
|
||||
|
||||
BSS_ORIGIN=$(awk '$1 == ".bss" && $2 == "0" { print $3; exit }' "$WORK/m1.map")
|
||||
BSS_LENGTH=$(awk '$1 == ".bss" && $2 == "0" { print $4; exit }' "$WORK/m1.map")
|
||||
[ -n "$BSS_ORIGIN" ] || fail "linker map has no .bss section"
|
||||
[ "$BSS_LENGTH" = "00000080" ] \
|
||||
|| fail ".bss is $BSS_LENGTH bits, expected 00000080 (only arr[4])"
|
||||
# Pin the required layout independently of the map: &arr[1] is arr + 32 bits
|
||||
# and arr is the first object in the .bss section that link.cmd puts at ram.
|
||||
[ "$BSS_ORIGIN" = "018A9000" ] \
|
||||
|| fail "linker .bss origin is $BSS_ORIGIN, want 018A9000 (link.cmd ram origin)"
|
||||
EXPECT_SECOND=0x018A9020
|
||||
DERIVED_SECOND=$(printf '0x%08X' "$((0x$BSS_ORIGIN + 32))")
|
||||
[ "$DERIVED_SECOND" = "$EXPECT_SECOND" ] \
|
||||
|| fail ".bss origin gives &arr[1] = $DERIVED_SECOND, want $EXPECT_SECOND"
|
||||
echo " linked .text at 018A0000, .bss at $BSS_ORIGIN (128 bits)"
|
||||
echo " expected &arr[1] = $EXPECT_SECOND"
|
||||
|
||||
echo "== run the COFF in MAME"
|
||||
[ -x "$MAME" ] || fail "MAME binary not built: $MAME"
|
||||
[ -f "$LOADER_LUA" ] || fail "COFF loader missing: $LOADER_LUA"
|
||||
[ -f "$LOADER_BIN" ] || fail "COFF loader payload missing: $LOADER_BIN"
|
||||
|
||||
VT1200_SKIP_LEVEL1=1 \
|
||||
LOADER_AT="$LOADER_AT" LOADER_END="$LOADER_END" \
|
||||
LOADER_BIN="$LOADER_BIN" LOADER_COFF="$COFF" \
|
||||
LOADER_OUT="$WORK/evidence" \
|
||||
M1_LOADER_LUA="$LOADER_LUA" \
|
||||
M1_EXPECT_LOAD=0x12345678 M1_EXPECT_SECOND="$EXPECT_SECOND" \
|
||||
timeout "${MAME_TIMEOUT:-600}" "$MAME" vt1200 \
|
||||
-rompath "$ROOT/vendor/vt1200/harness/mame/roms" \
|
||||
-video none -sound none -nothrottle -skip_gameinfo \
|
||||
-nvram_directory "$WORK/run/nvram" \
|
||||
-cfg_directory "$WORK/run/cfg" \
|
||||
-debug -debugger none \
|
||||
-autoboot_script "$DIR/m1-check.lua" -autoboot_delay 0 \
|
||||
> "$WORK/mame.log" 2>&1 \
|
||||
|| { tail -30 "$WORK/mame.log" >&2; fail "MAME run failed"; }
|
||||
|
||||
MARKERS=$(grep -E '^M1: add=' "$WORK/mame.log" | tail -1 || true)
|
||||
[ -n "$MARKERS" ] || { tail -30 "$WORK/mame.log" >&2; fail "MAME produced no M1 markers"; }
|
||||
echo " $MARKERS"
|
||||
|
||||
grep -q 'LOADER: result status=0 (OK)' "$WORK/mame.log" \
|
||||
|| { grep 'LOADER: result' "$WORK/mame.log" >&2 || true; fail "COFF loader did not report status 0"; }
|
||||
grep -q 'placed .text .* 0 differing' "$WORK/mame.log" \
|
||||
|| fail "COFF loader could not verify the placed .text bytes"
|
||||
grep -q '^M1 MAME: PASS' "$WORK/mame.log" \
|
||||
|| { tail -30 "$WORK/mame.log" >&2; fail "marker check failed"; }
|
||||
|
||||
echo "M1 MAME: PASS"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# M1 test gate: rebuild the linked COFF through the normal GSPA/GSPLNK path,
|
||||
# then verify dis34 decodes the emitted instructions at the linked addresses.
|
||||
set -eu
|
||||
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||
|
||||
# Always rebuild: a stale build/m1/m1.coff would let an asm-encoding
|
||||
# regression pass without ever touching the compiler or assembler. Keep
|
||||
# dis-roundtrip.sh pointed at the file this run produces, including when
|
||||
# M1_WORK/M1_COFF are overridden.
|
||||
WORK=${M1_WORK:-$ROOT/build/m1}
|
||||
M1_COFF=${M1_COFF:-$WORK/m1.coff}
|
||||
export M1_COFF
|
||||
|
||||
"$ROOT/scripts/m1/run.sh" "$@"
|
||||
exec "$ROOT/scripts/m1/dis-roundtrip.sh" "$@"
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
# M1 test gate: compile six C functions, assemble/link with GSPA/GSPLNK,
|
||||
# load the COFF in MAME and check the marker words.
|
||||
set -eu
|
||||
ROOT=$(cd "$(dirname "$0")/../../.." && pwd)
|
||||
exec "$ROOT/scripts/m1/run.sh" "$@"
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
#!/bin/sh
|
||||
# tms-clang -- M0 pipeline: clang IR, scale GEPs, optimize, normalize GEPs.
|
||||
# tms-clang -- M1 IR pipeline: clang IR, scale GEPs, optimize, normalize GEPs.
|
||||
#
|
||||
# Usage: tms-clang [-O0|-O2] [-I dir] -o OUT.ll IN.c
|
||||
#
|
||||
# -O2 (the default) scales, optimizes, then normalizes; -O0 skips the optimizer.
|
||||
# No machine code is produced in M0.
|
||||
# The output is normalized IR for llc -march=tms34010; this wrapper does not
|
||||
# invoke llc itself.
|
||||
set -eu
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
|
||||
@@ -212,6 +212,78 @@ static void collect_const_geps(Value *V, std::vector<ConstantExpr *> &Out,
|
||||
collect_const_geps(Op.get(), Out, depth + 1);
|
||||
}
|
||||
|
||||
// --normalize lowers a GEP offset directly as a bit offset (shift 0). That is
|
||||
// only correct for an i8 GEP produced by the optimizer after scaling. A
|
||||
// typed GEP still carries a byte offset (DataLayout strides), and a vector GEP
|
||||
// is not handled at all; treating either as bits would be an 8x address bug.
|
||||
static bool check_normalize_gep(Type *ResultTy, Type *SourceTy,
|
||||
std::string &Msg)
|
||||
{
|
||||
if (ResultTy->isVectorTy()) {
|
||||
Msg = "bitscale: --normalize does not support vector GEPs";
|
||||
return false;
|
||||
}
|
||||
if (!SourceTy->isIntegerTy(8)) {
|
||||
std::string TyStr;
|
||||
raw_string_ostream OS(TyStr);
|
||||
SourceTy->print(OS);
|
||||
Msg = "bitscale: --normalize requires i8 GEPs; found a getelementptr "
|
||||
"with source element type " + OS.str();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Collect every constant-expression GEP, including vector GEPs (the normal
|
||||
// const-GEP collector skips those because it cannot rewrite them).
|
||||
static void collect_all_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)
|
||||
Out.push_back(CE);
|
||||
}
|
||||
for (auto &Op : C->operands())
|
||||
collect_all_const_geps(Op.get(), Out, depth + 1);
|
||||
}
|
||||
|
||||
static bool check_normalize_inputs(Module &M, std::string &Msg)
|
||||
{
|
||||
for (Function &F : M)
|
||||
for (BasicBlock &BB : F)
|
||||
for (Instruction &I : BB)
|
||||
if (auto *GEP = dyn_cast<GetElementPtrInst>(&I))
|
||||
if (!check_normalize_gep(GEP->getType(),
|
||||
GEP->getSourceElementType(), Msg))
|
||||
return false;
|
||||
|
||||
std::vector<ConstantExpr *> Work;
|
||||
for (GlobalVariable &G : M.globals())
|
||||
if (G.hasInitializer())
|
||||
collect_all_const_geps(G.getInitializer(), Work);
|
||||
for (Function &F : M)
|
||||
for (BasicBlock &BB : F)
|
||||
for (Instruction &I : BB)
|
||||
for (Value *Op : I.operands())
|
||||
collect_all_const_geps(Op, Work);
|
||||
|
||||
std::set<ConstantExpr *> Seen;
|
||||
for (ConstantExpr *CE : Work) {
|
||||
if (!Seen.insert(CE).second)
|
||||
continue;
|
||||
auto *GEP = cast<GEPOperator>(CE);
|
||||
if (!check_normalize_gep(GEP->getType(), GEP->getSourceElementType(),
|
||||
Msg))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3) {
|
||||
@@ -242,6 +314,14 @@ int main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (GepShift == 0) {
|
||||
std::string Msg;
|
||||
if (!check_normalize_inputs(*M, Msg)) {
|
||||
errs() << Msg << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Stats Before = collect(*M);
|
||||
|
||||
// Constant-expression GEPs (e.g. `"hello" + 2`) cannot be replaced by an
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
; RUN: not %tms-bitaddr --normalize %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; --normalize treats GEP offsets as bits (shift 0). A typed GEP still has a
|
||||
; byte offset from DataLayout, so accepting it would produce an 8x-small
|
||||
; address. The tool must reject it.
|
||||
%struct.S = type { i32, i32 }
|
||||
|
||||
define ptr @typed(ptr %p) {
|
||||
%q = getelementptr %struct.S, ptr %p, i32 0, i32 1
|
||||
ret ptr %q
|
||||
}
|
||||
; CHECK: bitscale: --normalize requires i8 GEPs
|
||||
@@ -0,0 +1,8 @@
|
||||
; RUN: not %tms-bitaddr --normalize %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; Vector GEPs have no scalar lowering and must be rejected by --normalize.
|
||||
define <4 x ptr> @vec(<4 x ptr> %p) {
|
||||
%q = getelementptr i8, <4 x ptr> %p, i32 1
|
||||
ret <4 x ptr> %q
|
||||
}
|
||||
; CHECK: bitscale: --normalize does not support vector GEPs
|
||||
@@ -3,6 +3,35 @@ set -eu
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
TOOL=${TMS_BITADDR:-$ROOT/tms-bitaddr}
|
||||
FILECHECK=${FILECHECK:-$ROOT/../../build/llvm-build/bin/FileCheck}
|
||||
|
||||
TMP=$(mktemp -d "${TMPDIR:-/tmp}/tms-bitaddr.XXXXXX")
|
||||
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||
|
||||
"$TOOL" --scale "$ROOT/tests/scale.ll" | "$FILECHECK" "$ROOT/tests/scale.ll"
|
||||
"$TOOL" --scale "$ROOT/tests/scale-typed.ll" | "$FILECHECK" "$ROOT/tests/scale-typed.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"
|
||||
|
||||
# --normalize must fail loudly for a typed (byte-offset) GEP and a vector GEP
|
||||
# instead of treating their byte offsets as bits.
|
||||
for test in normalize-reject normalize-vector; do
|
||||
if "$TOOL" --normalize "$ROOT/tests/$test.ll" > "$TMP/$test.out" 2> "$TMP/$test.err"; then
|
||||
echo "tms-bitaddr: FAIL: --normalize accepted $test.ll" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$test" = normalize-reject ]; then
|
||||
grep -q 'requires i8 GEPs' "$TMP/$test.err" || {
|
||||
cat "$TMP/$test.err" >&2
|
||||
echo "tms-bitaddr: FAIL: typed-GEP rejection message missing" >&2
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
grep -q 'does not support vector GEPs' "$TMP/$test.err" || {
|
||||
cat "$TMP/$test.err" >&2
|
||||
echo "tms-bitaddr: FAIL: vector-GEP rejection message missing" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
done
|
||||
|
||||
echo "tms-bitaddr: PASS"
|
||||
@@ -0,0 +1,11 @@
|
||||
; RUN: %tms-bitaddr --scale %s | FileCheck %s
|
||||
;
|
||||
; --scale is allowed to see typed GEPs: it converts the DataLayout byte
|
||||
; offset into a bit offset. i32 * 3 is 12 bytes, so the bit addend is 96.
|
||||
define ptr @typed(ptr %p) {
|
||||
%q = getelementptr i32, ptr %p, i32 3
|
||||
ret ptr %q
|
||||
}
|
||||
; CHECK-LABEL: @typed
|
||||
; CHECK: %bit.addr = add i32 %bit.base, 96
|
||||
; CHECK-NOT: getelementptr
|
||||
Reference in new issue
Block a user