9 Commits
m3 .. m4
Author SHA1 Message Date
arti de5a365596 tms34010: clarify M4 carry-forwards and hash wording
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
unprivileged-download-artifact/action.yml (push) Invalid workflow file
2026-11-12 12:23:01 +00:00
arti 61ee9bbc99 tms34010: M4 function pointers, XImage/events and termtris support
unprivileged-download-artifact/action.yml (push) Invalid workflow file
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
2026-11-12 11:49:30 +00:00
arti b08e57424a tms34010: reject narrow last-fixed varargs anchors
The contiguous-stack varargs walk depends on SelectionDAGBuilder's
argument-copy elision making &last_fixed the incoming stack slot.  The
elision requires the fixed object and the parameter alloca to have equal
size, so an i8/i16 anchor silently stays a private local and the walk
reads the wrong slot.  Reject a variadic definition unless its last fixed
argument is i32 or a pointer, and pin the i32 anchor's FP-64 slot and the
first variadic at one slot below in m4-varargs-anchor.ll.
2026-11-12 11:20:21 +00:00
arti 6261ac442a tms34010: M4 varargs, narrow arguments, and termtris scalar fixes
Support the contiguous-stack varargs form termtris' compat.c sprintf
needs, promote i8/i16 fixed arguments to one i32 slot, and select i1 and
any-extending narrow loads.  Recursively emit nested array initializers,
keep divide-by-constant on DIVS/DIVU (MULHS/SMUL_LOHI are unsupported),
and disable jump tables.  Add a vector setcc result type so O2's <3 x i32>
struct copy legalizes.  The termtris gate exposed all of these.
2026-11-12 10:51:48 +00:00
arti 3c810ec0be tms34010: fix high-bit target constants in compares 2026-11-12 07:19:39 +00:00
arti a49c934380 tms34010: add CALL Rs and function-pointer calls
Add the one-word indirect call CALL Rs (0000 1001 001R DDDD, 0x0920) and
extend LowerCall to accept a function-pointer callee alongside the existing
direct CALLA path.  The indirect callee is a 32-bit bit address: a constant or
non-function global becomes MOVI and any other value is copied into an
allocatable ARegs virtual register after the argument pushes, so the pushes
cannot clobber the target.  The old-STK save, argument order, FS0/FS1
re-establishment and A8 return are unchanged.

GSPA 2.00 accepts CALL A0 (not CALL $A0); dis34 prints it as call aN.  The new
m4-fnptr lit test covers a callback argument, a memory-loaded pointer, an
absolute ROM address and a no-argument callback.  The obsolete
m2-reject-call-indirect test is removed.
2026-11-12 06:40:09 +00:00
arti 4ff6e2df9d tms34010: select MPYS/MPYU/DIVS/DIVU for 32-bit arithmetic
Add the register-file multiply and divide encodings and restrict their tied
destination to the odd allocatable A registers, so the instruction uses the
single-register 32-bit form instead of a Rd:Rd+1 pair.  MPYS covers the low
32 bits of a 32x32 product, MPYU the masked 16x16 unsigned product, and
DIVS/DIVU the 32/32 quotients.  SREM/UREM expand to dividend - quotient *
divisor because the odd-destination divide does not return a remainder.

Sign-extension in register (which DAGCombine forms from ordinary short
arithmetic) selects SEXT8/SEXT16 and expands to the verified SLL/SRA pair.
2026-11-12 06:04:53 +00:00
arti 4b300a75e9 tms34010: pin the full signed/unsigned branch predicate map 2026-11-12 05:46:40 +00:00
arti df6a6128d2 tms34010: add M4 zero-extending loads and module-unique private labels 2026-11-12 05:38:20 +00:00
36 changed files with 1341 additions and 170 deletions

No files matched your search

+316 -53
View File
@@ -1,12 +1,14 @@
# LLVM 23.1.5 fork for TMS34010
Branch `m3` is `llvmorg-23.1.5` plus the TMS34010 target and clang
Branch `m4` is `llvmorg-23.1.5` plus the TMS34010 target and clang
bit-address changes (M0), a minimal TMS34010 backend (M1), the TI ABI, calls
and `.data` support (M2), and integer control flow plus the first compiled C
Xlib window (M3). This branch is the canonical source for the target;
the project's `patches/llvm` mirror is frozen at M0. Branch `m2` (the fork's
default) is the M2 tip, and branch `tms34010` keeps the M1 backend and its
`m1-*` suite.
and `.data` support (M2), integer control flow plus the first compiled C Xlib
window (M3), and M4's function pointers, multiply/divide, zero-extending
narrow loads, the XImage/event client and the pinned termtris integration.
This branch is the canonical source for the target; the project's
`patches/llvm` mirror is frozen at M0. Branch `m3` is the previous milestone
snapshot and the fork's default branch; branch `m2` is the M2 tip and branch
`tms34010` keeps the M1 backend and its `m1-*` suite.
## M2 state
@@ -303,25 +305,268 @@ just nonzero return markers. The differential CI check
compiler output for K&R sources and needs no DOSBox; the stored assembly uses
TI's `JRLE`/`JRUC` spellings, which the check accepts alongside `JALE`/`JAUC`.
### M4 prerequisites (carry-forwards)
### M4 state
- **Add zero-extending narrow loads.** M3 rejects `unsigned char`,
`unsigned short` and `_Bool` loads (`m3-reject-zext.ll`); M4 must add the
`ZEXTLOAD i8`/`i16` patterns and replace the reject test.
- **Namespace every private label.** The M3 harness's per-translation-unit
`LBB…` prefixing rewrites block labels only. Deterministic private string
labels are still per-TU names, so two TUs with literals collide in GSPA's
flat symbol table. M4 must namespace all private labels or make compiler
private names module-unique.
- **Add unsigned/`<=` runtime coverage.** `JALE`, `JALO`, `JAHI` and `JALS`,
`JAHS`, `JANE`, `JAGE` are exercised by lit tests only and do not appear in
the compiled M3 image. M4 should add an unsigned/`<=` runtime marker.
- **Replace fragile test plumbing.** The `always_inline` root helper and the
register-hardcoded nine-argument dis34 check are test-strength aids that will
not survive real M4 code; make them operand- and register-independent.
- **Tighten test strength.** `m3-cmpi`'s `CHECK-DAG` for 0/1 does not pin their
order, and `layout.sh` pairs the root probe with fixed constants; neither
weakens the current pass, but both should be tightened.
M4 closes the remaining Xlib path to a real image/event client and a
full-game integration. It is still **assembly-only**: GSPA 2.00 and GSPLNK
2.00 under DOSBox produce the COFF. There is no `MCCodeEmitter`, object
writer, `llc -filetype=obj` or `clang -c`.
#### Function pointers and `CALL Rs`
A function pointer is an ordinary 32-bit value holding a code bit address.
Taking a function's address materialises `MOVI _inc, A?`; an initialized
global function pointer is a `.data` word (`.long _dec`). `LowerCall` accepts
a callee that is not a `GlobalAddress` and emits the one-word register call
`CALL Rs` (GSPA encodes it as `0x0920`; `dis34` prints `call aN`). The call
still saves the old STK (`MOVE A14, -*SP, 1`), pushes its arguments
last-to-first on the program stack, and re-establishes `SETF 16, 0, 0` /
`SETF 32, 0, 1`; the callee's `RETS 2` restores A14. Direct calls remain
`CALLA _sym`. `m4-fnptr.ll` pins the selection, `scripts/tests/m4/fnptr.sh`
runs the indirect path in MAME and checks `dis34` at the linked address, and
`scripts/tests/m4/diff/ti-fnptr.s` is the 1987-compiler ABI golden.
#### 32-bit multiply and divide
`MPYS` (`0x5C00`), `MPYU` (`0x5E00`), `DIVS` (`0x5800`) and `DIVU`
(`0x5A00`) are selected directly; there is no integer multiply/divide helper
in `RTS.LIB` (`_div` is only the C `div()` wrapper; `FD$MUL`/`FD$DIV` are
floating point). The destination must be an **odd** A register (`A1`, `A3`,
…, `A11`): an even `Rd` denotes the `Rd:Rd+1` pair and would clobber the next
register (A15/SP in particular), so `ARegsOdd` ties the single-register form.
- 32×32 low product: one `MPYS` (the low 32 bits are identical for signed
and unsigned).
- 16×16 unsigned product: one `MPYU` (the natural shape after zero-extension
to i32).
- 32/32 quotient: `DIVS` or `DIVU`.
- 32/32 remainder: quotient, `MPYS` by the divisor, then `SUB`.
`SREM`/`UREM` are custom-lowered this way so C truncation semantics hold and
no even-destination pair is needed. `m4-mul.ll` pins `MPYS`/`MPYU`/`DIVS`/
`DIVU` and the remainder sequence.
The `-O2` loop policy is `-replexitval=never` in `scripts/tms-clang`.
`opt` runs without a target machine at that point, so the driver cannot
express this as a TTI policy; IndVarSimplify's exit-value replacement turns
`sumto`'s loop into a closed-form **i33** multiply, which this i64-less
target cannot select. With the flag, `sumto` keeps a real back-edge and
`squares` uses `MPYS`. `m4-divconst.ll` also pins that the magic-number
divide combine does not fire: `MULHS`/`MULHU`/`SMUL_LOHI`/`UMUL_LOHI` are
`Expand`, so divide-by-constant stays `DIVS`/`DIVU`.
#### Zero-extending / extending narrow loads
`ZEXTLOAD i8` and `ZEXTLOAD i16` are custom-lowered to the existing
sign-extending load (`MOVB` or the FS0 `MOVE`) plus `ANDI 255` / `ANDI 65535`,
reusing the original chain and MMO so volatile and ordering behaviour is
preserved. `EXTLOAD` (the `anyext` form) lowers the same way, and an `i1`
load is a byte load masked to one bit. `m4-zext.ll`, `m4-anyext.ll` and
`m4-i1load.ll` pin the sequences. The old M3 `m3-reject-zext.ll` was removed.
#### Module-unique private labels
Every private/local symbol (`GV->hasLocalLinkage()`) gets a deterministic
suffix `_<8 uppercase hex digits>` derived from a custom 64-bit string hash
(FNV-style, not canonical FNV-1a) of `Module::getModuleIdentifier()`. Two
translation units with string literals therefore no longer collide in GSPA's
flat symbol table; public names are byte-for-byte unchanged. `llc file.ll`
uses the file name (or IR `source_filename`) as the identifier; `llc <
file.ll` uses the stable `<stdin>` fallback. `m4-private.ll` and the two-TU
`scripts/tests/m4/private.sh` pin it.
GSPA 2.00 truncates identifiers at 31 characters. A private name long enough
to lose the `_<8 hex>` token at that limit will collide loudly (a duplicate
definition from GSPA), not silently; the naming scheme is unchanged in M4 and
a long-name two-TU link test is an M5 follow-up.
#### `i8`/`i16` fixed arguments
`i8` and `i16` fixed parameters are promoted to one 4-byte program-stack
slot. The caller extends the actual according to the parameter's
`signext`/`zeroext` attribute (`SLL 16`/`SRA 16` for a signed short,
`ANDI 255` for an unsigned char), and the callee truncates on load.
`m4-arg8.ll` pins both sides. `i8`, `i16`, `i64`, float and aggregate
**returns** remain rejected, as do `i64`/float/aggregate arguments.
#### Varargs: the contiguous-stack `&last_fixed` pattern
M4 implements only the form termtris's `compat.c` uses. Fixed arguments live
at `FP-32`, `FP-64`, … and the variadic actuals occupy the following
contiguous slots (`FP-96`, `FP-128`, …). The callee walks from
`(char *)&last_fixed` by decreasing the pointer by `sizeof(long)` (4 bytes),
which reads the first variadic slot. There is **no** `va_list`, `va_start` or
`va_arg`.
A variadic definition with **no** fixed argument is rejected
(`TMS34010 varargs requires at least one fixed argument`): the walk has no
anchor and the epilogue could not tell whether the caller saved STK. An
`i8`/`i16` last fixed argument is also rejected
(`TMS34010 varargs requires an i32 or pointer last fixed argument`): the fixed
object is always 4 bytes, but LLVM's argument-copy elision requires the fixed
object and the parameter alloca to have equal size, so a narrow anchor would
silently read a private local instead of the caller's slot. The supported
anchor is `i32` or a pointer. `m4-varargs.ll`, `m4-varargs-anchor.ll`,
`m4-reject-vararg0.ll` and `m4-reject-vararg-narrow{,16}.ll` pin the rules;
`scripts/tests/m4/varargs.sh` runs three contiguous arguments in MAME.
A **static** variadic definition can lose `...` under `-O2` dead-argument
elimination; the external termtris `sprintf` is unaffected (it is not a
static definition in a TU where the optimizer can see all callers).
#### Driver policies
`scripts/tms-clang` invokes `opt` with two explicit policies:
- `-replexitval=never` — the i33 exit-value multiply described above.
- `-switch-to-lookup=false` — the optimizer's switch-to-lookup transform
creates a *new* byte-offset GEP after `tms-bitaddr --scale`, and
`--normalize` can only treat surviving GEP offsets as bit offsets. Such a
GEP would index the table one bit per slot (an 8× address bug). The backend
also has no jump-table lowering, so compare/branch is correct. termtris's
five-case X-event switch is the motivating case; `m4-switch.ll` pins the
target-level behaviour and `scripts/m4/switch.c` is the driver probe.
#### XImage, XEvent and the ROM layouts
`scripts/m4/x11m4.h` models the 32-bit X11R4 ABI. The TMS34010 C ABI packs
aggregates, so the offsets are the packed object offsets:
| object | byte offset | bit displacement |
|---|---:|---:|
| `Display.screens` | `0x90` | `0x480` |
| `Screen.root` | `0x8` | `0x40` |
| `XImage.data` | 16 | `0x80` |
| `XImage.byte_order` | 20 | `0xA0` |
| `XImage.bytes_per_line` | 40 | `0x140` |
| `XImage.bits_per_pixel` | 44 | `0x160` |
| `XImage.f` | 64 | `0x200` |
| `sizeof(XImage)` | 88 | — |
| `XErrorEvent.error_code` | 16 | `0x80` |
| `sizeof(XErrorEvent)` | 20 | — |
| `XEvent.xkey.keycode` | 52 | `0x1A0` |
| `sizeof(XEvent)` | 96 | — |
`XEvent` is 96 bytes (its largest member is the protocol `pad[24]` of 32-bit
longs), and the termtris event buffer is `vt_u32 evbuf[24]`. `XErrorEvent` is
20 bytes on the 32-bit ABI but would be 19 packed, so `x11m4.h` and the TI
golden declare one explicit trailing `abi_pad` byte. `layout.sh` asserts every
offset with `_Static_assert` and checks the same constants in the 1987 TI
golden; `scripts/tests/m4/diff/ti-image.s` additionally checks the XImage
field displacements in a real image-call function.
#### The XG block and the corrected symbol table
The VT1200 Xlib keeps per-process state in an XG block reached through the
process user area. `scripts/m4/xgfix.c` installs a 1 KiB zeroed block before
`XOpenDisplay`; `XSetErrorHandler`/`XSetIOErrorHandler` store into that block
at `+0x60`/`+0x40`. The client's `int (Display *, XErrorEvent *)` handler
therefore receives the ROM's real error events.
The v2.2 table has **30** entries. M4 corrected the M3 name: `XMapWindow` is
`0x3FD64F60` (MapWindow request opcode 8), while `0x3FD645B0` is
`XRaiseWindow` (ConfigureWindow opcode 12, `CWStackMode = Above`). The old M3
pass with `0x3FD645B0` was the server's configure-side effect, not a mapping
call. A client that wants map-and-raise calls both.
`scripts/m4/validate-syms.sh` checks the ROM range, 16-bit alignment and a
plausible prologue for all 30 entries, and the opcode semantics of the
correction-sensitive ones (`XMapWindow`, `XRaiseWindow`, `XPutImage`,
`XGetImage`, `XCreateImage`, the two error-handler setters).
#### The image/event client
`scripts/m4/xshow.c` is a compiled C client: install the XG block, open
`"0::0"` with a retry, create a 640×480 window, select
key/button/expose/focus masks, create a GC, build a 1-bit LSBFirst XYBitmap
checker, call `XCreateImage`, set `byte_order`/`bitmap_bit_order`,
`XSetErrorHandler`, map and raise, set input focus, draw, then drain events
and quit on the real LK401 `q`.
Two server/Xlib constraints shape the drawing path:
- A single 38 KiB `XPutImage` exceeds the ROM's 16 KiB request buffer, so the
image is emitted in 15 bands of 32 rows; the bitmap path ignores `src_y`, so
each band selects its rows by moving `XImage.data`.
- The server's 1-bit drawing path needs a 32-bit-aligned destination, so each
band is drawn into an aligned depth-1 pixmap (`XCreatePixmap`) and
`XCopyArea`'d to window x=12 (screen x=32). The 12-pixel prefix is filled
with the preceding cell's colour. The checker cells remain 32 pixels; the
phase inside the (20,20) window is 12 pixels. `XCopyArea` copies
`IMG_W - 12` = 628 columns, so the rightmost 12 source columns (the final
12 px of the last cell) are not displayed; the last visible cell is 20 px,
not 32.
The 38,400-byte image and the 1 KiB XG block live in `.bss` at `0x01A00000`
(the termtris-proven region); `prog` stays at `0x018A0000`. The client primes
the ROM blitter (`blit_copy`, `0x3FB44400`) with one 4-byte self-copy before
the first `XPutImage`. `scripts/tests/m4/run.sh` requires the image pixels,
keycode 193 / keysym `0x71`, `quit=1`, zero X errors, focus = the client
window `0x400001`, the last event being KeyPress, and a loader status of 0.
#### Pinned termtris
The full-game integration is `scripts/tests/m4/termtris.sh` plus
`scripts/m4/termtris/build.sh`, pinned to the `vt1200-termtris` tree at
commit `be5b323f5d2cc4f7a7d25e7143c67bd9828ccbdf` (branch `termtris`). Six
translation units (`main`, `game`, `scoredb`, `x11`, `compat`, `xgfix`) are
compiled with this compiler, assembled and linked by the 1987 GSPA/GSPLNK
2.00 with the 1987 `rts.lib`, and run in MAME. The regions are the
0x40000-bit `prog` at `0x018A0000` and `ram` at `0x01A00000`. The final map
is `.text` 0x2E210 bits (23,618 bytes), `.data` 0x21A0 bits, `.bss` 0x1DBD0
bits; it fits `prog` and the loader reports `status=0 (OK)` with no
`UNDEFED` symbols. The gate runs board/move/panels/quit modes with real key
injection and enforces `loops >= 100` (board/move/panels/quit), `events >= 6`
for move/panels/quit (board `>= 2`), `quit = 1` only for quit, focus = the
created termtris window (`0x00400001`), loader status 0 and a non-empty
snapshot each time. The pinned tree is **test-only** (GPLv3) and is not part
of the compiler or project distribution.
### M3 carry-forwards in M4
M4 closes three of the five M3 carry-forwards:
- zero-extending `i8`/`i16` loads (`m4-zext.ll`; the old `m3-reject-zext.ll`
was removed);
- module-unique private labels (`m4-private.ll` and the two-TU
`private.sh`), with the GSPA 31-character caveat above;
- unsigned/`<=` runtime coverage (`m4-branches.ll`, and the MAME
`unsigned.sh` markers for `JALE`/`JALS`/`JAGT`/`JAGE`/`JAHS`/`JAHI`/
`JANE`/`JALT`/`JALO`).
The new M4 harness and `scripts/tests/m4/dis-roundtrip.sh` are
operand/register-independent, and `layout.sh` was tightened. The M3 regression
is deliberately unchanged: `m3-cmpi.ll` still uses `CHECK-DAG: MOVI 0` /
`MOVI 1` (the 1/0 order is not pinned), and `scripts/m3/dis-roundtrip.sh`
still hardcodes the nine `XCreateSimpleWindow` push registers
(`A2`/`A0`). Tightening those M3 tests is a test-hardening follow-up; the M3
suite (`m3-*.ll` and `scripts/tests/m3/`) passes as-is, with only the obsolete
zext-reject test removed.
## M4 non-goals and the M5 boundary
M4 deliberately stops before:
- **Native object output:** no `MCCodeEmitter`, object writer,
`llc -filetype=obj`, `clang -c` or native COFF. GSPA/GSPLNK still produce
the COFF. The `JR`/branch-relaxation code-size pass is M5, as are
MMTM/MMFM register-list saves (the current per-register system-stack pushes
have the identical ABI layout).
- **Floats and `long long`:** no float/double or `i64` arguments, returns,
arithmetic or branches.
- **General varargs:** no `va_list`, `va_start` or `va_arg`; only the
contiguous `&last_fixed` pattern above. Non-contiguous/register varargs are
M5.
- **Bitfields and aggregates by value:** no structs/unions by value, `sret`,
`byval`, bitfields, or aggregate returns. Struct *fields* through pointers
are supported.
- **Narrow returns:** `i8`/`i16` returns remain rejected; fixed `i8`/`i16`
arguments are supported.
- **Xlib beyond the tested calls:** the client uses the validated image,
event, GC, window and error-handler entries. It does not implement the
whole Xlib surface, `XGetImage`, or `XDestroyImage` on a non-heap image.
- **`JR`/relaxation and deeper libc/runtime coverage:** M5 owns the
code-size pass and broader `RTS.LIB` integration.
## Build
@@ -335,12 +580,12 @@ cmake -S llvm -B build -G Ninja \
ninja -C build llc clang opt llvm-config FileCheck count not
```
`build/bin/llvm-lit` is generated by CMake configure. Run the M2+M3 backend
tests:
`build/bin/llvm-lit` is generated by CMake configure. Run the full M2+M3+M4
backend suite:
```sh
build/bin/llvm-lit -v build/test/CodeGen/TMS34010 --filter='(m2|m3)-'
# -> 46 passed (37 m2-*, 9 m3-*)
build/bin/llvm-lit -v build/test/CodeGen/TMS34010 --filter='(m2|m3|m4)-'
# -> 58 passed (32 m2-*, 8 m3-*, 18 m4-*)
```
The M2 tests are `llvm/test/CodeGen/TMS34010/m2-*.ll`:
@@ -352,10 +597,8 @@ The M2 tests are `llvm/test/CodeGen/TMS34010/m2-*.ll`:
`m2-spill`, `m2-returned`, `m2-returned-call`.
- Data: `m2-data`, `m2-data-avail`, `m2-data-align`, `m2-data-z64`,
`m2-data-i1`, `m2-string`, `m2-ptrinit`.
- Loud refusals: `m2-reject-i64`, `m2-reject-struct`, `m2-reject-vararg`,
`m2-reject-sret`, `m2-reject-structret`, `m2-reject-arg8`,
`m2-reject-i8ret`, `m2-reject-float`, `m2-reject-call-indirect`,
`m2-reject-call-arg8`, `m2-reject-call-vararg`.
- Loud refusals: `m2-reject-i64`, `m2-reject-struct`, `m2-reject-sret`,
`m2-reject-structret`, `m2-reject-i8ret`, `m2-reject-float`.
`m2-add` uses `RETS 2`; `m2-global` returns in `A8` with `RETS 0`;
`m2-locals`/`m2-args-locals` require a real frame at `-O0`; `m2-data-align`
@@ -376,33 +619,53 @@ The M3 tests are:
and a constant shift.
- `m3-unaligned`: align-1 `i32` load/store, pointer load and `i16` sign-extending
load select direct `MOVE` forms and never `MOVB`.
- `m3-reject-zext`: a zero-extending `i8`/`i16` load still aborts with
`Cannot select`; M4 must replace this test with the real patterns.
The M4 tests are:
- Loads and data: `m4-zext`, `m4-anyext`, `m4-i1load`, `m4-data-nested`.
- Arithmetic and control flow: `m4-mul`, `m4-divconst`, `m4-cmp-highbit`,
`m4-branches`, `m4-switch`, `m4-vector-copy`.
- ABI and labels: `m4-fnptr`, `m4-arg8`, `m4-private`, `m4-varargs`,
`m4-varargs-anchor`.
- Loud refusals: `m4-reject-vararg0`, `m4-reject-vararg-narrow`,
`m4-reject-vararg-narrow16`.
## Integration test
The VT1200 project holds the harness, linker command file, COFF loader and
MAME checks: <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>. From a
project checkout that has this fork at `build/llvm-clean`:
project checkout that has this fork at `build/llvm-clean` (branch `m4`):
```sh
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m3 ./scripts/build-llvm.sh
scripts/tests/m3/run.sh # layout/symbols, GSPA/GSPLNK, then MAME
scripts/tests/m3/dis-roundtrip.sh # linked .text decoded with dis34
scripts/tests/m3/diff/check.sh # branch/Xlib shape vs. the 1987 TI compiler
scripts/tests/m3/diff/negative-control.sh # the check must reject a wrong TI displacement
scripts/tests/m3/layout.sh # Display/Screen bit displacements (also run by run.sh)
tools/tms-bitaddr/tests/run.sh # GEP scaling/normalization
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m4 ./scripts/build-llvm.sh
ninja -C build/llvm-clean-build llc opt clang llvm-config FileCheck count not
build/llvm-clean-build/bin/llvm-lit -v build/llvm-clean/llvm/test/CodeGen/TMS34010 \
--filter='(m2|m3|m4)-'
scripts/tests/m4/run.sh # XImage/event client in MAME
scripts/tests/m4/dis-roundtrip.sh # linked .text decoded with dis34
scripts/tests/m4/layout.sh # XImage/XEvent/error-event offsets
scripts/tests/m4/symbols.sh # 30-entry v2.2 table and the correction
scripts/tests/m4/termtris.sh # pinned full-game integration
scripts/tests/m4/diff/check.sh # TI function-pointer/image ABI golden
scripts/tests/m4/unsigned.sh
scripts/tests/m4/private.sh
scripts/tests/m4/mul.sh
scripts/tests/m4/fnptr.sh
scripts/tests/m4/varargs.sh
tools/tms-bitaddr/tests/run.sh # GEP scaling/normalization
scripts/tests/m3/run.sh # M3 regression
scripts/tests/m3/dis-roundtrip.sh
scripts/tests/m2/run.sh # M2 regression
scripts/tests/m2/dis-roundtrip.sh
```
Expected: `layout.sh: PASS`, `M3 MAME: PASS`, `M3 dis34 round-trip: PASS`,
`M3 diff: PASS`, `M3 diff negative controls: PASS` and `tms-bitaddr: PASS`.
The MAME gate pins
`VT1200_MONITOR=VR319` and requires the white client pixels described above.
The differential check uses stored golden assembly from the 1987 TI compiler
and does not run DOSBox; the MAME/dis34 gates do. The M2 gates
(`scripts/tests/m2/`) still pass on `m3` (verified 2026-11-11: `M2 MAME`,
`M2 dis34 round-trip` and `M2 diff` all `PASS`); the M1 harness
(`scripts/tests/m1/`) lives on the project's `m1` branch and is not an M3 gate.
The project README lists the prerequisites (DOSBox, mtools, the VT1200 SDK
image, MAME).
Expected: `m4 xshow: PASS`, `M4 dis34 round-trip: PASS`, `layout.sh: PASS`,
`symbols.sh: PASS`, `m4 termtris: PASS`, `M4 diff: PASS`, the unsigned /
private / mul / fnptr / varargs PASS lines, `tms-bitaddr: PASS`, and the M3/M2
regression PASS lines. The MAME gates pin `VT1200_MONITOR=VR319`; the image
gate requires the checker pixels, the real LK401 `q` (keycode 193, keysym
`0x71`), focus `0x400001`, zero X errors and a loader status of 0. The
differential check uses stored golden assembly from the 1987 TI compiler and
does not run DOSBox; the MAME/dis34 gates do. The pinned termtris tree is
test-only. The project README lists the prerequisites (DOSBox, mtools, the
VT1200 SDK image, MAME).
@@ -268,6 +268,15 @@ void TMS34010AsmPrinter::emitDataInitializer(const Constant *Init) {
if (const auto *CA = dyn_cast<ConstantArray>(Init)) {
Type *ElemTy = CA->getType()->getElementType();
// A nested array (for example game.c's `uint16_t tiles[][2]`) recurses
// element by element. Each element is a first-class initializer handled
// by one of the arms below, and array elements have no inter-element
// padding, so the flattened width is exact.
if (ElemTy->isArrayTy()) {
for (const Use &U : CA->operands())
emitDataInitializer(cast<Constant>(U.get()));
return;
}
if (ElemTy->isIntegerTy(8)) {
SmallVector<uint64_t, 32> Bytes;
for (const Use &U : CA->operands()) {
+245 -35
View File
@@ -31,8 +31,12 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
: TargetLowering(TM, STI) {
addRegisterClass(MVT::i32, &TMS34010::ARegsRegClass);
// The backend has no SEXT-as-field-width path that suits every width, so
// sign extension in register selects the SEXT8/SEXT16 pseudo and expands to
// a shift pair after instruction selection (see EmitInstrWithCustomInserter).
// DAGCombine forms these nodes from ordinary `short` shift pairs.
// MOVB is an 8-bit sign-extending load and an 8-bit truncating store.
// M1 only needs signed char, so a zero-extending byte load has no pattern.
setLoadExtAction(ISD::SEXTLOAD, MVT::i32, MVT::i8, Legal);
setTruncStoreAction(MVT::i32, MVT::i8, Legal);
@@ -40,6 +44,43 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
setLoadExtAction(ISD::SEXTLOAD, MVT::i32, MVT::i16, Legal);
setTruncStoreAction(MVT::i32, MVT::i16, Legal);
// There is no zero-extending load instruction. Lower a zero-extending
// byte/short load as the sign-extending load followed by an ANDI mask that
// clears the sign-extension bits. i1 loads occupy a byte in memory (the
// same rule as the data layout's i8 minimum) and mask to one bit. An
// anyext load may choose any high bits, so zero-extending it is valid and
// avoids an unselectable load when the DAG shares one narrow load between a
// truncating and a widening use.
setLoadExtAction(ISD::ZEXTLOAD, MVT::i32, MVT::i1, Custom);
setLoadExtAction(ISD::ZEXTLOAD, MVT::i32, MVT::i8, Custom);
setLoadExtAction(ISD::ZEXTLOAD, MVT::i32, MVT::i16, Custom);
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i1, Custom);
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i8, Custom);
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i16, Custom);
// Multiply and divide are single register-register instructions (MPYS/DIVS/
// DIVU) with an odd tied destination; see TMS34010InstrInfo.td. The
// remainder has no direct 32-bit form, so it is expanded from the quotient
// and a multiply in LowerOperation.
setOperationAction(ISD::MUL, MVT::i32, Legal);
setOperationAction(ISD::SDIV, MVT::i32, Legal);
setOperationAction(ISD::UDIV, MVT::i32, Legal);
setOperationAction(ISD::SREM, MVT::i32, Custom);
setOperationAction(ISD::UREM, MVT::i32, Custom);
// There is no high-half multiply. If MULHS/MULHU were legal (or custom),
// the target-independent divide/remainder-by-constant combine would turn
// `x / c` into a MULHS magic-number sequence the target cannot select. Keep
// them expanding, which also makes isOperationLegalOrCustom false so the
// combine leaves the real DIVS/DIVU in place.
setOperationAction(ISD::MULHS, MVT::i32, Expand);
setOperationAction(ISD::MULHU, MVT::i32, Expand);
setOperationAction(ISD::SMUL_LOHI, MVT::i32, Expand);
setOperationAction(ISD::UMUL_LOHI, MVT::i32, Expand);
// The backend has no jump table lowering (no BR_JT). Force switches through
// the ordinary compare/branch path even for clang's five-case event switch.
setMinimumJumpTableEntries(~0u);
// There is no conditional move or dedicated setcc result; let the generic
// legalizer turn setcc into a select_cc over 1/0 and select into a
// select_cc, then lower the compare and branch ourselves.
@@ -78,6 +119,20 @@ bool TMS34010TargetLowering::allowsMisalignedMemoryAccesses(
return true;
}
EVT TMS34010TargetLowering::getSetCCResultType(const DataLayout &DL,
LLVMContext &Context,
EVT VT) const {
(void)DL;
// There is no vector register class; a vector value is split into i32
// scalars by the type legalizer. The default implementation asserts for
// vectors, which turns an otherwise legalizable `<N x i32>` struct copy into
// a crash. Match the element result type so vector legalization can split
// it, and keep the scalar result at i32.
if (VT.isVector())
return EVT::getVectorVT(Context, MVT::i32, VT.getVectorElementCount());
return MVT::i32;
}
bool TMS34010TargetLowering::allowsMisalignedMemoryAccesses(
LLT VT, unsigned AddrSpace, Align Alignment,
MachineMemOperand::Flags Flags, unsigned *Fast) const {
@@ -104,14 +159,43 @@ SDValue TMS34010TargetLowering::LowerFormalArguments(
// Validate the IR signature before legalization can split or promote it.
// Checking only the legalized MVT lets an i64 or an aggregate arrive as
// several i32 pieces and be silently accepted (and miscompiled).
if (F.isVarArg() || IsVarArg)
report_fatal_error("M2 does not support varargs", false);
// several i32 pieces and be silently accepted (and miscompiled). Integers
// narrower than i32 are promoted to one i32 stack slot; the SelectionDAG
// caller truncates the slot back to the declared width (and applies the
// signext/zeroext attribute). Only the contiguous-stack form of varargs is
// supported: there is no va_start/va_arg/va_list, and a callee that needs
// its variadic arguments takes the address of its last fixed argument and
// walks down (see m4-varargs.ll and compat.c's sprintf). That walk needs
// an anchor, so a variadic definition with no fixed argument is rejected:
// its epilogue also could not tell whether the caller saved STK.
//
// The anchor is only the incoming stack slot when SelectionDAGBuilder's
// argument-copy elision can replace the parameter's alloca with the fixed
// object. That elision requires the fixed object and the alloca to have
// the same size (SelectionDAGBuilder.cpp, tryToElideArgumentCopy). A
// narrow (i8/i16) last fixed parameter would have a one-byte alloca, so the
// elision is skipped: `&last_fixed` would be a private local, the walk
// would read the wrong slot, and no diagnostic would be emitted. Restrict
// the supported anchor to one 32-bit slot.
(void)IsVarArg;
if (F.isVarArg()) {
if (F.arg_size() == 0)
report_fatal_error(
"TMS34010 varargs requires at least one fixed argument", false);
Type *LastTy = F.getArg(F.arg_size() - 1)->getType();
if (!LastTy->isPointerTy() && !LastTy->isIntegerTy(32))
report_fatal_error(
"TMS34010 varargs requires an i32 or pointer last fixed argument",
false);
}
for (const Argument &Arg : F.args()) {
Type *Ty = Arg.getType();
if (!Ty->isIntegerTy(32) && !Ty->isPointerTy())
report_fatal_error("M2 supports only i32 and pointer arguments", false);
if (!Ty->isPointerTy() &&
!(Ty->isIntegerTy() && Ty->getIntegerBitWidth() <= 32))
report_fatal_error(
"TMS34010 supports only integer (<=32 bit) and pointer arguments",
false);
}
// Every explicit argument is passed on the program stack. Argument i lives
@@ -131,7 +215,9 @@ SDValue TMS34010TargetLowering::LowerFormalArguments(
"M2 supports only plain arguments (no sret/byval/inreg/nest)",
false);
if (In.VT != MVT::i32)
report_fatal_error("M2 supports only i32 and pointer arguments", false);
report_fatal_error(
"TMS34010 supports only integer (<=32 bit) and pointer arguments",
false);
int FI = MFI.CreateFixedObject(4, -static_cast<int64_t>(i + 1) * 4, true);
SDValue FIN = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
@@ -156,20 +242,29 @@ SDValue TMS34010TargetLowering::LowerCall(
// the marker and lower an ordinary call rather than failing on it.
CLI.IsTailCall = false;
// M2 has no indirect calls: the callee must be a direct global symbol. A
// function pointer, a plain integer, or an external symbol (asm label) is
// outside the M2 ABI.
auto *Callee = dyn_cast<GlobalAddressSDNode>(CLI.Callee);
if (!Callee || !isa<Function>(Callee->getGlobal()))
report_fatal_error("M2 supports only direct calls to global functions",
// A direct call names a global function. Anything else is an indirect call
// through a 32-bit bit address (a function pointer, a plain integer, or the
// inttoptr of an absolute ROM entry). External-symbol (asm label) callees
// stay outside the ABI.
const GlobalAddressSDNode *CalleeGA =
dyn_cast<GlobalAddressSDNode>(CLI.Callee);
bool IsIndirect = false;
if (CalleeGA && isa<Function>(CalleeGA->getGlobal())) {
// Direct call to a named function.
} else if (isa<ExternalSymbolSDNode>(CLI.Callee)) {
report_fatal_error("M2 supports only direct calls to global functions "
"and indirect calls through a function pointer",
false);
const Function *CalleeFn = cast<Function>(Callee->getGlobal());
} else {
IsIndirect = true;
}
// CLI.IsVarArg is not populated for ordinary direct calls in this LLVM: the
// call-site function type records only the actual arguments, so the callee
// declaration's own type is the reliable varargs source.
if (CLI.IsVarArg || CalleeFn->isVarArg())
report_fatal_error("M2 does not support varargs", false);
// Variadic calls need no special lowering: CLI.Outs already holds every
// actual argument, and the push loop below stores them last-first in the
// same contiguous program-stack slots as the fixed arguments. That is the
// only varargs form supported (no va_start/va_arg/va_list); the callee can
// take the address of its last fixed argument and step down one 32-bit slot
// per variadic argument, which is exactly what compat.c's sprintf does.
// Check the original IR types: legalization may promote an i8 or split an
// aggregate before the arguments reach Outs/OutVals.
@@ -185,14 +280,19 @@ SDValue TMS34010TargetLowering::LowerCall(
"(no sret/byval/inreg/nest/inalloca/preallocated)",
false);
Type *Ty = Arg.OrigTy;
if (!Ty->isIntegerTy(32) && !Ty->isPointerTy())
report_fatal_error("M2 supports only i32 and pointer call arguments",
false);
if (!Ty->isPointerTy() &&
!(Ty->isIntegerTy() && Ty->getIntegerBitWidth() <= 32))
report_fatal_error(
"TMS34010 supports only integer (<=32 bit) and pointer call "
"arguments",
false);
}
for (const ISD::OutputArg &Out : CLI.Outs)
if (Out.VT != MVT::i32)
report_fatal_error("M2 supports only i32 and pointer call arguments",
false);
report_fatal_error(
"TMS34010 supports only integer (<=32 bit) and pointer call "
"arguments",
false);
SDValue Chain = CLI.Chain;
@@ -213,21 +313,55 @@ SDValue TMS34010TargetLowering::LowerCall(
DAG.getMachineNode(TMS34010::PUSHARG, DL, MVT::Other, Arg, Chain), 0);
}
// CALLA references the symbol directly. A TargetGlobalAddress operand is
// left for the asm printer as a relocation; a plain GlobalAddress would be
// selected to a MOVI and the call would lose its symbol.
SDValue Target = DAG.getTargetGlobalAddress(
Callee->getGlobal(), DL, getPointerTy(DAG.getDataLayout()),
Callee->getOffset(), Callee->getTargetFlags());
// A direct call references the symbol through CALLA. A TargetGlobalAddress
// operand is left for the asm printer as a relocation; a plain GlobalAddress
// would be selected to a MOVI and the call would lose its symbol.
//
// An indirect call carries the callee as a 32-bit bit address. Materialise
// or copy it in an allocatable A register *after* the argument pushes, so
// the argument setup cannot clobber the target: A13/A14/A15 are outside
// ARegs and PUSHARG only advances A14. A constant or a non-function global
// address becomes MOVI; any other value (a formal-argument load, a memory
// load, a CopyFromReg) is copied into a fresh ARegs virtual register.
SDValue Target;
unsigned CallOpc;
if (!IsIndirect) {
Target = DAG.getTargetGlobalAddress(CalleeGA->getGlobal(), DL,
getPointerTy(DAG.getDataLayout()),
CalleeGA->getOffset(),
CalleeGA->getTargetFlags());
CallOpc = TMS34010::CALLA;
} else {
if (const auto *C = dyn_cast<ConstantSDNode>(CLI.Callee)) {
// getSignedTargetConstant keeps the low 32 bits of a bit-address whose
// top bit is set; getTargetConstant would sign-extend it to 64 bits and
// assert when building the i32 APInt.
SDValue Imm =
DAG.getSignedTargetConstant(C->getSExtValue(), DL, MVT::i32);
Target =
SDValue(DAG.getMachineNode(TMS34010::MOVI, DL, MVT::i32, Imm), 0);
} else if (CalleeGA) {
SDValue Sym = DAG.getTargetGlobalAddress(
CalleeGA->getGlobal(), DL, getPointerTy(DAG.getDataLayout()),
CalleeGA->getOffset(), CalleeGA->getTargetFlags());
Target =
SDValue(DAG.getMachineNode(TMS34010::MOVI, DL, MVT::i32, Sym), 0);
} else {
Register TargetReg =
MF.getRegInfo().createVirtualRegister(&TMS34010::ARegsRegClass);
Chain = DAG.getCopyToReg(Chain, DL, TargetReg, CLI.Callee);
Target = DAG.getRegister(TargetReg, MVT::i32);
}
CallOpc = TMS34010::CALL;
}
const TargetRegisterInfo *TRI = DAG.getSubtarget().getRegisterInfo();
const uint32_t *Mask = TRI->getCallPreservedMask(MF, CLI.CallConv);
assert(Mask && "M2 requires a call-preserved register mask");
SDValue RegMask = DAG.getRegisterMask(Mask);
Chain = SDValue(DAG.getMachineNode(TMS34010::CALLA, DL, MVT::Other, Target,
RegMask, Chain),
0);
Chain = SDValue(
DAG.getMachineNode(CallOpc, DL, MVT::Other, Target, RegMask, Chain), 0);
// ROM/Xlib code clobbers the field-size state, so re-establish both fields
// before any further access: FS0 = 16 for `short` and FS1 = 32 for words.
@@ -353,7 +487,10 @@ static SDValue emitCmpAndBranch(SDValue Chain, SDValue LHS, SDValue RHS,
unsigned BranchOpc = getBranchOpcode(CC);
SDValue Cmp;
if (const auto *C = dyn_cast<ConstantSDNode>(RHS)) {
SDValue Imm = DAG.getTargetConstant(C->getSExtValue(), DL, MVT::i32);
// Signed form: a 32-bit immediate with the top bit set (or a negative
// constant) must truncate to i32, not sign-extend to uint64_t.
SDValue Imm =
DAG.getSignedTargetConstant(C->getSExtValue(), DL, MVT::i32);
Cmp = SDValue(DAG.getMachineNode(TMS34010::CMPI, DL, MVT::Other,
MVT::Glue, Imm, LHS, Chain),
0);
@@ -425,7 +562,8 @@ SDValue TMS34010TargetLowering::LowerSELECT_CC(SDValue Op,
// materialises each value in its own block.
auto AsTargetConstant = [&](SDValue V) -> SDValue {
if (const auto *C = dyn_cast<ConstantSDNode>(V))
return DAG.getTargetConstant(C->getSExtValue(), DL, V.getValueType());
return DAG.getSignedTargetConstant(C->getSExtValue(), DL,
V.getValueType());
return V;
};
TrueVal = AsTargetConstant(TrueVal);
@@ -444,12 +582,59 @@ SDValue TMS34010TargetLowering::LowerSELECT_CC(SDValue Op,
SDValue TMS34010TargetLowering::LowerOperation(SDValue Op,
SelectionDAG &DAG) const {
switch (Op.getOpcode()) {
case ISD::LOAD:
// A zero-extending (or any-extending) narrow load becomes the legal
// sign-extending load plus an ANDI with the width mask. Any extension may
// choose zero high bits, so the same masking is valid for both. Returning
// merged values keeps the load's chain in the result.
if (auto *LD = dyn_cast<LoadSDNode>(Op.getNode())) {
if (LD->getExtensionType() == ISD::ZEXTLOAD ||
LD->getExtensionType() == ISD::EXTLOAD) {
SDLoc DL(Op);
EVT MemVT = LD->getMemoryVT();
unsigned Bits = MemVT.getSizeInBits();
assert((Bits == 1 || Bits == 8 || Bits == 16) &&
"unexpected zero-extending load width");
// An i1 object occupies a byte, so read a byte rather than asking the
// DAG for a non-byte-sized memory load.
SDValue Ext =
Bits == 1
? DAG.getExtLoad(ISD::SEXTLOAD, DL, MVT::i32,
LD->getChain(), LD->getBasePtr(), MVT::i8,
LD->getMemOperand())
: DAG.getExtLoad(ISD::SEXTLOAD, DL, MVT::i32,
LD->getChain(), LD->getBasePtr(), MemVT,
LD->getMemOperand());
SDValue Mask =
DAG.getConstant(Bits == 32 ? 0xFFFFFFFFu : ((1u << Bits) - 1u), DL,
MVT::i32);
SDValue Zeroed = DAG.getNode(ISD::AND, DL, MVT::i32, Ext, Mask);
SDValue Ops[] = {Zeroed, Ext.getValue(1)};
return DAG.getMergeValues(Ops, DL);
}
}
return SDValue();
case ISD::BRCOND:
return LowerBRCOND(Op, DAG);
case ISD::BR_CC:
return LowerBR_CC(Op, DAG);
case ISD::SELECT_CC:
return LowerSELECT_CC(Op, DAG);
case ISD::SREM:
case ISD::UREM: {
// The odd-destination DIVS/DIVU leaves only the quotient. Reconstruct the
// remainder in the C semantics (which truncates the quotient toward zero):
// rem = dividend - quotient * divisor
// signed and unsigned remainder both use the corresponding divide and the
// same low-32-bit product, so one expansion covers both.
SDLoc DL(Op);
SDValue LHS = Op.getOperand(0);
SDValue RHS = Op.getOperand(1);
unsigned DivOpc = Op.getOpcode() == ISD::SREM ? ISD::SDIV : ISD::UDIV;
SDValue Q = DAG.getNode(DivOpc, DL, MVT::i32, LHS, RHS);
SDValue P = DAG.getNode(ISD::MUL, DL, MVT::i32, Q, RHS);
return DAG.getNode(ISD::SUB, DL, MVT::i32, LHS, P);
}
default:
llvm_unreachable("unexpected TMS34010 custom lowering operation");
}
@@ -460,6 +645,31 @@ MachineBasicBlock *TMS34010TargetLowering::EmitInstrWithCustomInserter(
switch (MI.getOpcode()) {
case TMS34010::SELECTCC:
return emitSelectCC(MI, MBB);
case TMS34010::SEXT8:
case TMS34010::SEXT16: {
// dst = (src << K) >> K, arithmetic, with K = 32 - width.
MachineFunction *MF = MBB->getParent();
MachineRegisterInfo &MRI = MF->getRegInfo();
const TargetInstrInfo &TII = *MF->getSubtarget().getInstrInfo();
const TargetRegisterClass *RC = &TMS34010::ARegsRegClass;
const DebugLoc &DL = MI.getDebugLoc();
Register Dst = MI.getOperand(0).getReg();
Register Src = MI.getOperand(1).getReg();
int64_t Shift = MI.getOpcode() == TMS34010::SEXT8 ? 24 : 16;
// A machine virtual register has one definition before SSA is destroyed,
// so each step produces a fresh one.
Register T1 = MRI.createVirtualRegister(RC);
Register T2 = MRI.createVirtualRegister(RC);
BuildMI(*MBB, MI, DL, TII.get(TargetOpcode::COPY), T1).addReg(Src);
BuildMI(*MBB, MI, DL, TII.get(TMS34010::SLLK), T2)
.addReg(T1)
.addImm(Shift);
BuildMI(*MBB, MI, DL, TII.get(TMS34010::SRAK), Dst)
.addReg(T2)
.addImm(Shift);
MI.eraseFromParent();
return MBB;
}
default:
return nullptr;
}
@@ -63,6 +63,9 @@ public:
MachineMemOperand::Flags Flags = MachineMemOperand::MONone,
unsigned *Fast = nullptr) const override;
EVT getSetCCResultType(const DataLayout &DL, LLVMContext &Context,
EVT VT) const override;
private:
SDValue LowerBRCOND(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerBR_CC(SDValue Op, SelectionDAG &DAG) const;
@@ -154,6 +154,82 @@ def XOR : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
let Inst{3-0} = dst{3-0};
}
// MPYS Rs, Rd: 0101 110S SSSR DDDD (0x5C00). Signed multiply: Rd is the
// 32-bit multiplicand and the low 32 bits of the product are stored back in
// Rd when Rd is odd. The ABI keeps FS1 = 32, so Rs is a full 32-bit multiplier
// and the low half of the signed and unsigned products is identical. An even
// Rd would write the high half to Rd and the low half to Rd+1, clobbering the
// neighbouring register, so the tied destination is ARegsOdd.
def MPYS : TMS34010Inst<(outs ARegsOdd:$dst), (ins ARegs:$src, ARegsOdd:$val),
"MPYS $src, $dst",
[(set ARegsOdd:$dst, (mul ARegs:$src,
ARegsOdd:$val))]> {
let Constraints = "$val = $dst";
let Defs = [ST];
bits<5> src;
bits<5> val;
bits<5> dst;
let Inst{15-9} = 0b0101110;
let Inst{8-5} = src{3-0};
let Inst{4} = dst{4};
let Inst{3-0} = dst{3-0};
}
// MPYU Rs, Rd: 0101 111S SSSR DDDD (0x5E00). Unsigned multiply. A 16x16
// unsigned product reaches the DAG as both operands masked to 16 bits, which
// is the natural place to select MPYU; the low 32 bits are identical to a
// signed multiply, so this is an encoding choice, not a different result.
def MPYU : TMS34010Inst<(outs ARegsOdd:$dst), (ins ARegs:$src, ARegsOdd:$val),
"MPYU $src, $dst",
[(set ARegsOdd:$dst,
(mul (and i32:$src, 65535),
(and ARegsOdd:$val, 65535)))]> {
let Constraints = "$val = $dst";
let Defs = [ST];
bits<5> src;
bits<5> val;
bits<5> dst;
let Inst{15-9} = 0b0101111;
let Inst{8-5} = src{3-0};
let Inst{4} = dst{4};
let Inst{3-0} = dst{3-0};
}
// DIVS Rs, Rd: 0101 100S SSSR DDDD (0x5800). Signed divide of the 32-bit
// dividend in Rd by Rs. With an odd Rd the quotient replaces Rd and no
// remainder is returned; the remainder is synthesised as dividend -
// quotient * divisor (the hardware only returns it with an even Rd pair).
def DIVS : TMS34010Inst<(outs ARegsOdd:$dst), (ins ARegs:$src, ARegsOdd:$val),
"DIVS $src, $dst",
[(set ARegsOdd:$dst, (sdiv ARegsOdd:$val,
ARegs:$src))]> {
let Constraints = "$val = $dst";
let Defs = [ST];
bits<5> src;
bits<5> val;
bits<5> dst;
let Inst{15-9} = 0b0101100;
let Inst{8-5} = src{3-0};
let Inst{4} = dst{4};
let Inst{3-0} = dst{3-0};
}
// DIVU Rs, Rd: 0101 101S SSSR DDDD (0x5A00). Unsigned divide, same form.
def DIVU : TMS34010Inst<(outs ARegsOdd:$dst), (ins ARegs:$src, ARegsOdd:$val),
"DIVU $src, $dst",
[(set ARegsOdd:$dst, (udiv ARegsOdd:$val,
ARegs:$src))]> {
let Constraints = "$val = $dst";
let Defs = [ST];
bits<5> src;
bits<5> val;
bits<5> dst;
let Inst{15-9} = 0b0101101;
let Inst{8-5} = src{3-0};
let Inst{4} = dst{4};
let Inst{3-0} = dst{3-0};
}
// NEG Rd: 0000 0011 101R DDDD (0x03A0).
def NEG : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src), "NEG $dst",
[(set ARegs:$dst, (ineg ARegs:$src))]> {
@@ -495,6 +571,22 @@ def CALLA : TMS34010Inst<(outs), (ins i32imm:$target), "CALLA $target", []> {
let Defs = [A8, A14, ST];
}
// Indirect call through a register: CALL Rs is 0000 1001 001R DDDD (0x0920),
// one word, with the 32-bit bit address in an A register. GSPA 2.00 accepts
// the spelling "CALL A0" (it rejects the documented "CALL $A0" form), and
// dis34 prints the same instruction as "call aN". The target is an ARegs
// operand, never A13/A14/A15, so the argument pushes (which only advance A14)
// cannot clobber it. The rest of the sequence is ABI-identical to CALLA.
def CALL : TMS34010Inst<(outs), (ins ARegs:$target), "CALL $target", []> {
let isCall = 1;
let Size = 2;
let Uses = [A14];
let Defs = [A8, A14, ST];
bits<5> target;
let Inst{15-5} = 0b00001001001;
let Inst{4-0} = target;
}
// Branch targets are basic-block symbols; MCInstLower turns the block operand
// into an MC expression that the InstPrinter prints by name.
def brtarget : Operand<OtherVT>;
@@ -570,6 +662,23 @@ def GETFI : TMS34010Inst<(outs ARegs:$dst), (ins i32imm:$off), "# GETFI", []> {
let isPseudo = 1;
}
// Sign extension in register. The ISA's SEXT reads the current field size, and
// the ABI pins FS0 at 16 for `short`, so a width-specific field extension is
// not available for every width. Expand to the verified shift pair
// (SLL K ; SRA K) instead. DAGCombine forms these nodes from shift pairs in
// ordinary `short` arithmetic, so they must select.
def SEXT8 : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src), "# SEXT8",
[(set ARegs:$dst, (sext_inreg ARegs:$src, i8))]> {
let isPseudo = 1;
let usesCustomInserter = 1;
}
def SEXT16 : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src), "# SEXT16",
[(set ARegs:$dst, (sext_inreg ARegs:$src, i16))]> {
let isPseudo = 1;
let usesCustomInserter = 1;
}
def NOP : TMS34010Inst<(outs), (ins), "NOP", []> {
let Inst{15-0} = 0x0300;
}
@@ -63,6 +63,14 @@ def ARegs : RegisterClass<"TMS34010", [i32], 32,
(add A0, A1, A2, A3, A4, A5, A6, A7,
A9, A10, A11, A12)>;
// MPYS/MPYU/DIVS/DIVU treat an even Rd as the first of a register pair: an
// even multiply writes Rd:Rd+1, and an even divide reads a 64-bit Rd:Rd+1
// dividend and writes the quotient to Rd and the remainder to Rd+1. The
// 32-bit forms here always want the odd (single-register) encoding, so the
// tied destination operand is restricted to the odd allocatable A registers.
def ARegsOdd : RegisterClass<"TMS34010", [i32], 32,
(add A1, A3, A5, A7, A9, A11)>;
// ARegsAll names every A register. It is used only as the operand class of
// the frame and system-stack instructions, which need to name the reserved
// FP/STK/SP registers explicitly; it is never allocated.
@@ -19,6 +19,7 @@
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/Mangler.h"
#include "llvm/IR/Module.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/TargetRegistry.h"
@@ -67,6 +68,34 @@ static void sanitizeGSPASymbolName(SmallVectorImpl<char> &Name) {
Name.assign(Sanitized.begin(), Sanitized.end());
}
// Private/local IR symbols are module-scoped, but GSPA links every translation
// unit into one flat symbol table. Two modules that both contain `.str` would
// otherwise emit the same ``__.str`` label and GSPA reports a duplicate
// definition. Append a deterministic token derived from the module identifier
// to every local symbol; public names are left exactly as the IR spells them.
//
// llc gives a file input the file name as the module identifier (or the IR's
// own source_filename) and "<stdin>" for standard input, so the token is
// deterministic for a given input and stable for lit's stdin RUN lines.
static void appendModuleToken(SmallVectorImpl<char> &Name,
const GlobalValue *GV) {
const Module *M = GV->getParent();
StringRef ID = M ? M->getModuleIdentifier() : StringRef();
// Custom 64-bit string hash (FNV-style, not canonical FNV-1a), folded to
// 32 bits. Eight uppercase hex digits stay GSPA-safe and
// are short enough not to stress the 1987 assembler's identifier limits.
uint64_t Hash = 1469598103934665603ULL;
for (unsigned char C : ID) {
Hash ^= C;
Hash *= 1099511628211ULL;
}
unsigned Token = static_cast<unsigned>(Hash ^ (Hash >> 32));
Name.push_back('_');
for (int Shift = 28; Shift >= 0; Shift -= 4)
Name.push_back(hexdigit((Token >> Shift) & 0xF));
}
namespace {
class TMS34010ObjectFile : public TargetLoweringObjectFileELF {
@@ -76,6 +105,8 @@ public:
SmallString<128> Name("_");
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
sanitizeGSPASymbolName(Name);
if (GV->hasLocalLinkage())
appendModuleToken(Name, GV);
return getContext().getOrCreateSymbol(Name);
}
@@ -86,6 +117,8 @@ public:
OutName.push_back('_');
OutName.append(Name.begin(), Name.end());
sanitizeGSPASymbolName(OutName);
if (GV->hasLocalLinkage())
appendModuleToken(OutName, GV);
}
};
+3 -3
View File
@@ -29,11 +29,11 @@ entry:
; CHECK: .long 11
; CHECK: .long 22
; CHECK: .byte 030h, 03Ah, 03Ah, 030h, 000h
; CHECK-LABEL: {{.*}}long:
; CHECK-LABEL: {{.*}}long{{[A-Za-z0-9_]*}}:
; CHECK: .byte 041h, 042h, 043h, 044h, 045h, 046h, 047h, 048h, 049h, 04Ah, 04Bh, 04Ch, 04Dh, 04Eh, 04Fh, 050h
; CHECK: .byte 051h, 052h, 053h, 054h, 000h
; CHECK-LABEL: {{.*}}zeros16:
; CHECK-LABEL: {{.*}}zeros16{{[A-Za-z0-9_]*}}:
; CHECK: .word 0
; CHECK: .word 0
; CHECK-LABEL: {{.*}}zeros8:
; CHECK-LABEL: {{.*}}zeros8{{[A-Za-z0-9_]*}}:
; CHECK: .byte 0, 0, 0
+3 -3
View File
@@ -9,7 +9,7 @@ entry:
ret ptr inttoptr (i32 add (i32 ptrtoint (ptr @arr to i32), i32 32) to ptr)
}
; CHECK-LABEL: _second:
; CHECK: MOVI _arr+32, A{{[0-9]+}}
; CHECK-NOT: _arr+4,
; CHECK: MOVI _arr{{[A-Za-z0-9_]*}}+32, A{{[0-9]+}}
; CHECK-NOT: _arr{{[A-Za-z0-9_]*}}+4,
; CHECK: RETS 0
; CHECK: .bss _arr,128,16
; CHECK: .bss _arr{{[A-Za-z0-9_]*}},128,16
@@ -1,8 +0,0 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
; CHECK: M2 supports only i32 and pointer arguments
define i32 @argc(i8 %c) {
entry:
%r = sext i8 %c to i32
ret i32 %r
}
@@ -1,9 +0,0 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
; CHECK: M2 supports only i32 and pointer call arguments
declare void @g8(i8)
define void @f() {
entry:
call void @g8(i8 1)
ret void
}
@@ -1,8 +0,0 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
; CHECK: M2 supports only direct calls to global functions
define i32 @indirect(ptr %f) {
entry:
%r = call i32 %f(i32 1)
ret i32 %r
}
@@ -1,9 +0,0 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
; CHECK: M2 does not support varargs
declare void @g(i32, ...)
define void @f() {
entry:
call void @g(i32 1, i32 2)
ret void
}
@@ -1,6 +1,6 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
; CHECK: M2 supports only i32 and pointer arguments
; CHECK: TMS34010 supports only integer (<=32 bit) and pointer arguments
define i32 @f(float %x) {
entry:
ret i32 0
+1 -1
View File
@@ -3,7 +3,7 @@
;
; An i64 argument legalizes to two i32 pieces. M2 must reject the IR
; signature before that split can be mistaken for two register arguments.
; CHECK: M2 supports only i32 and pointer arguments
; CHECK: TMS34010 supports only integer (<=32 bit) and pointer arguments
define i32 @i64arg(i64 %v) {
entry:
%t = trunc i64 %v to i32
@@ -3,7 +3,7 @@
;
; Aggregates are legalized into scalar pieces; reject the struct argument
; rather than silently passing its fields in registers.
; CHECK: M2 supports only i32 and pointer arguments
; CHECK: TMS34010 supports only integer (<=32 bit) and pointer arguments
%struct.S = type { i32, i32 }
define i32 @structarg(%struct.S %s) {
@@ -1,10 +0,0 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; M2 has no varargs ABI, so even a definition that never reads its varargs
; must be rejected.
; CHECK: M2 does not support varargs
define i32 @vararg(i32 %n, ...) {
entry:
ret i32 %n
}
@@ -1,29 +0,0 @@
; RUN: not --crash llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=ZEXT8
; RUN: sed -n '/^; ZEXT16-BEGIN$/,$p' %s > %t.i16.ll
; RUN: not --crash llc -march=tms34010 -O0 -filetype=asm < %t.i16.ll 2>&1 | FileCheck %s --check-prefix=ZEXT16
;
; M3 has only sign-extending narrow loads: MOVB for signed char and the FS0
; MOVE for signed short. A zero-extending load (unsigned char, unsigned short,
; _Bool) would need a ZEXTLOAD pattern that M3 does not have, so llc stops with
; a raw "Cannot select" instead of silently producing wrong code. M4 carries
; the zero-extending patterns. This test keeps the limitation visible in the
; suite; it intentionally accepts the unpolished diagnostic (llc aborts, so the
; RUN lines use not --crash).
;
; ZEXT8: Cannot select: {{.*}} zext from i8
; ZEXT16: Cannot select: {{.*}} zext from i16
define i32 @zload8(ptr %p) {
entry:
%v = load i8, ptr %p, align 1
%r = zext i8 %v to i32
ret i32 %r
}
; ZEXT16-BEGIN
define i32 @zload16(ptr %p) {
entry:
%v = load i16, ptr %p, align 2
%r = zext i16 %v to i32
ret i32 %r
}
+16
View File
@@ -0,0 +1,16 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; One i16 load can be shared by a truncating and a widening use; the DAG then
; makes it an anyext load. Any high bits are valid, so lowering it as the
; sign-extending field load plus a width mask is correct and selectable.
define i32 @anyext(ptr %p, i16 %q) {
%v = load i16, ptr %p, align 2
%d = sub i16 %v, %q
%z = zext i16 %d to i32
%w = zext i16 %v to i32
%r = add i32 %z, %w
ret i32 %r
}
; CHECK-LABEL: _anyext:
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK: ANDI 65535, A{{[0-9]+}}
+41
View File
@@ -0,0 +1,41 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; M4 promotes an i8/i16 parameter to one 32-bit program-stack slot. The
; caller extends the value according to the parameter's signext/zeroext
; attribute; the callee loads the slot and the SelectionDAG caller truncates
; it back to the declared width (applying AssertZext/AssertSext).
define i32 @arg8(i8 zeroext %c) {
%z = zext i8 %c to i32
ret i32 %z
}
define i32 @arg16(i16 signext %s) {
%z = sext i16 %s to i32
ret i32 %z
}
declare void @g8(i8 zeroext)
declare void @g16(i16 signext)
define void @call8(i32 %x) {
%c = trunc i32 %x to i8
call void @g8(i8 zeroext %c)
ret void
}
define void @call16(i32 %x) {
%s = trunc i32 %x to i16
call void @g16(i16 signext %s)
ret void
}
; CHECK-LABEL: _arg8:
; CHECK: ADDI -32, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK-LABEL: _arg16:
; CHECK: ADDI -32, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK-LABEL: _call8:
; CHECK: ANDI 255, A{{[0-9]+}}
; CHECK: MOVE A{{[0-9]+}}, *A14+, 1
; CHECK: CALLA _g8
; CHECK-LABEL: _call16:
; CHECK: SLL 16, A{{[0-9]+}}
; CHECK: SRA 16, A{{[0-9]+}}
; CHECK: MOVE A{{[0-9]+}}, *A14+, 1
; CHECK: CALLA _g16
+94
View File
@@ -0,0 +1,94 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; M4 pins the complete signed/unsigned integer predicate map. The compare is
; glued to the conditional branch, so the CMP and Jcc must stay adjacent.
define i32 @slt(i32 %a, i32 %b) {
entry:
%c = icmp slt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @sle(i32 %a, i32 %b) {
entry:
%c = icmp sle i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @sgt(i32 %a, i32 %b) {
entry:
%c = icmp sgt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @sge(i32 %a, i32 %b) {
entry:
%c = icmp sge i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @ult(i32 %a, i32 %b) {
entry:
%c = icmp ult i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @ule(i32 %a, i32 %b) {
entry:
%c = icmp ule i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @ugt(i32 %a, i32 %b) {
entry:
%c = icmp ugt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @uge(i32 %a, i32 %b) {
entry:
%c = icmp uge i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @eq(i32 %a, i32 %b) {
entry:
%c = icmp eq i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @ne(i32 %a, i32 %b) {
entry:
%c = icmp ne i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
; CHECK-LABEL: _slt:
; CHECK: CMP
; CHECK-NEXT: JALT
; CHECK-LABEL: _sle:
; CHECK: CMP
; CHECK-NEXT: JALE
; CHECK-LABEL: _sgt:
; CHECK: CMP
; CHECK-NEXT: JAGT
; CHECK-LABEL: _sge:
; CHECK: CMP
; CHECK-NEXT: JAGE
; CHECK-LABEL: _ult:
; CHECK: CMP
; CHECK-NEXT: JALO
; CHECK-LABEL: _ule:
; CHECK: CMP
; CHECK-NEXT: JALS
; CHECK-LABEL: _ugt:
; CHECK: CMP
; CHECK-NEXT: JAHI
; CHECK-LABEL: _uge:
; CHECK: CMP
; CHECK-NEXT: JAHS
; CHECK-LABEL: _eq:
; CHECK: CMP
; CHECK-NEXT: JAEQ
; CHECK-LABEL: _ne:
; CHECK: CMP
; CHECK-NEXT: JANE
@@ -0,0 +1,42 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; Comparisons against a 32-bit immediate with bit 31 set must materialise the
; low 32 bits, not sign-extend to uint64_t. This was an APInt assertion in
; LowerSELECT_CC/emitCmpAndBranch (getTargetConstant with a negative
; getSExtValue); the xgfix range check is the original live case.
define i32 @ult_high_bit(i32 %a) {
entry:
%c = icmp ult i32 %a, 2147483649
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @eq_all_ones_select(i32 %a) {
entry:
%c = icmp eq i32 %a, -1
%s = select i1 %c, i32 7, i32 9
ret i32 %s
}
define i32 @xg_range(i32 %proc) {
entry:
%t = add i32 %proc, -33554432
%c = icmp ult i32 %t, -33554176
br i1 %c, label %bad, label %good
bad:
ret i32 0
good:
ret i32 1
}
; CHECK-LABEL: _ult_high_bit:
; CHECK: CMPI -2147483647
; CHECK-NEXT: JALO
; CHECK-LABEL: _eq_all_ones_select:
; CHECK: CMPI -1
; CHECK-NEXT: JAEQ
; CHECK-LABEL: _xg_range:
; CHECK: CMPI -33554177
; CHECK-NEXT: JAHI
@@ -0,0 +1,10 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; A nested array initializer (termtris' uint16_t tiles[][2]) recurses element
; by element; the flattened word sequence must keep every element.
@tiles = internal global [2 x [2 x i16]] [[2 x i16] [i16 1, i16 2], [2 x i16] [i16 3, i16 4]], align 2
; CHECK: _tiles_[[HASH:[0-9A-F]+]]:
; CHECK-NEXT: .word 00001h
; CHECK-NEXT: .word 00002h
; CHECK-NEXT: .word 00003h
; CHECK-NEXT: .word 00004h
+30
View File
@@ -0,0 +1,30 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
;
; The target has no high-half multiply. If MULHS/MULHU or SMUL_LOHI/UMUL_LOHI
; were legal, the target-independent divide-by-constant combine would replace
; these with an unselectable magic-number sequence. They must stay DIVS/DIVU.
define i32 @sdiv7(i32 %x) {
%r = sdiv i32 %x, 7
ret i32 %r
}
define i32 @srem7(i32 %x) {
%r = srem i32 %x, 7
ret i32 %r
}
define i32 @udiv10(i32 %x) {
%r = udiv i32 %x, 10
ret i32 %r
}
define i32 @urem10(i32 %x) {
%r = urem i32 %x, 10
ret i32 %r
}
; CHECK-LABEL: _sdiv7:
; CHECK: DIVS
; CHECK-NOT: MPYS A{{[0-9]+}}, A{{[0-9]+}}
; CHECK-LABEL: _srem7:
; CHECK: DIVS
; CHECK-LABEL: _udiv10:
; CHECK: DIVU
; CHECK-LABEL: _urem10:
; CHECK: DIVU
+82
View File
@@ -0,0 +1,82 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; M4 function pointers. The callee is a 32-bit bit address in an A register
; and the call is the one-word register call CALL Rs (opcode 0x0920). The
; indirect sequence is the M3 direct-call sequence with CALL in place of CALLA:
; save the caller's STK, push the arguments last-first, call, re-establish
; FS0/FS1, reload the old STK and return in A8 through RETS 2.
;
; GSPA 2.00 accepts the register-call spelling "CALL A0" (not "CALL $A0"); the
; printer emits uppercase mnemonics/registers, matching this test.
define i32 @inc(i32 %x) {
%r = add i32 %x, 1
ret i32 %r
}
; The callee arrives as a stack argument, the callback ABI. It must be copied
; into an allocatable A register after the argument pushes and called through
; that register, not through CALLA.
define i32 @apply(ptr %f, i32 %x) {
%r = call i32 %f(i32 %x)
ret i32 %r
}
; CHECK-LABEL: _apply:
; CHECK: MOVE A14, -*SP, 1
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
; CHECK: CALL A{{[0-9]+}}
; CHECK-NOT: CALLA
; CHECK: SETF 32, 0, 1
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
; CHECK: RETS 2
; A function pointer loaded from memory: materialise the pointer variable's
; address, load the 32-bit bit address, then call through the register.
@fnptr = internal global ptr @inc
define i32 @call_loaded(i32 %x) {
%f = load ptr, ptr @fnptr
%r = call i32 %f(i32 %x)
ret i32 %r
}
; CHECK-LABEL: _call_loaded:
; CHECK: MOVI _fnptr{{[A-Za-z0-9_]*}}, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK: CALL A{{[0-9]+}}
; CHECK: RETS 2
; The Xlib-header pattern: an absolute ROM entry cast to a function pointer.
; MOVI materialises the 32-bit bit address (0x3FD64F60) and CALL goes through
; the register. This is a lowering test only; the MAME gate never calls an
; arbitrary ROM address.
define i32 @call_abs(i32 %x) {
%r = call i32 inttoptr (i32 1071009632 to ptr)(i32 %x)
ret i32 %r
}
; CHECK-LABEL: _call_abs:
; CHECK: MOVI 1071009632, A{{[0-9]+}}
; CHECK: CALL A{{[0-9]+}}
; CHECK-NOT: CALLA
; CHECK: RETS 2
; A no-argument callback (the termtris term_reset shape): no argument push and
; no old-STK save, but the same register call and RETS path.
define i32 @call_noargs(ptr %f) {
%r = call i32 %f()
ret i32 %r
}
; CHECK-LABEL: _call_noargs:
; CHECK-NOT: MOVE A14, -*SP, 1
; CHECK: CALL A{{[0-9]+}}
; CHECK: RETS 2
; A function pointer passed as a direct argument stays a symbol: taking its
; address is a MOVI of the function, and the call to the helper is direct.
define i32 @pass_inc() {
%r = call i32 @apply(ptr @inc, i32 41)
ret i32 %r
}
; CHECK-LABEL: _pass_inc:
; CHECK: MOVI _inc, A{{[0-9]+}}
; CHECK: CALLA _apply
; CHECK: RETS 0
+22
View File
@@ -0,0 +1,22 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; clang's GlobalOpt can shrink a boolean global to i1. The object still
; occupies a byte (the data layout's i8 minimum), so an i1 load is a byte load
; masked to one bit. A store truncates to the same byte.
@g = internal global i1 false, align 2
define i32 @load_i1() {
%v = load i1, ptr @g, align 2
%z = zext i1 %v to i32
ret i32 %z
}
define void @store_i1(i32 %x) {
%t = trunc i32 %x to i1
store i1 %t, ptr @g, align 2
ret void
}
; CHECK-LABEL: _load_i1:
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: ANDI 1, A{{[0-9]+}}
; CHECK-LABEL: _store_i1:
; CHECK: ANDI 1, A{{[0-9]+}}
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
+80
View File
@@ -0,0 +1,80 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; M4 adds the register-file ALU multiply and divide instructions. MPYS and
; MPYU multiply the 32-bit Rd (the tied destination) by the multiplier field
; Rs; with an odd Rd the low 32 bits are stored back and no register pair is
; touched. The destination must be odd, so the tied operand uses the
; odd-only ARegsOdd class.
;
; DIVS/DIVU with an odd Rd divide the 32-bit Rd by Rs, leaving the quotient in
; Rd. The remainder is derived as dividend - quotient * divisor, because the
; hardware only returns the remainder with an even destination pair.
; A 32x32 low product: one MPYS to an odd destination.
define i32 @mul32(i32 %a, i32 %b) {
%r = mul i32 %a, %b
ret i32 %r
}
; CHECK-LABEL: _mul32:
; CHECK: MPYS A{{[0-9]+}}, {{A([13579]|11)$}}
; CHECK: RETS 2
; A 16x16 signed product: the operands are truncated to 16 bits and
; sign-extended with a shift pair, then multiplied with MPYS.
define i32 @mul16s(i32 %a, i32 %b) {
%as = shl i32 %a, 16
%bs = shl i32 %b, 16
%ae = ashr i32 %as, 16
%be = ashr i32 %bs, 16
%r = mul i32 %ae, %be
ret i32 %r
}
; CHECK-LABEL: _mul16s:
; CHECK: MPYS
; A 16x16 unsigned product, masked to 16 bits: MPYU.
define i32 @mul16u(i32 %a, i32 %b) {
%ae = and i32 %a, 65535
%be = and i32 %b, 65535
%r = mul i32 %ae, %be
ret i32 %r
}
; CHECK-LABEL: _mul16u:
; CHECK: MPYU
; Signed and unsigned 32/32 division with an odd destination.
define i32 @sdiv32(i32 %a, i32 %b) {
%r = sdiv i32 %a, %b
ret i32 %r
}
; CHECK-LABEL: _sdiv32:
; CHECK: DIVS A{{[0-9]+}}, {{A([13579]|11)$}}
; CHECK: RETS 2
define i32 @udiv32(i32 %a, i32 %b) {
%r = udiv i32 %a, %b
ret i32 %r
}
; CHECK-LABEL: _udiv32:
; CHECK: DIVU A{{[0-9]+}}, {{A([13579]|11)$}}
; CHECK: RETS 2
; Remainder = dividend - quotient * divisor, using the same divide.
define i32 @srem32(i32 %a, i32 %b) {
%r = srem i32 %a, %b
ret i32 %r
}
; CHECK-LABEL: _srem32:
; CHECK: DIVS
; CHECK: MPYS
; CHECK: SUB
define i32 @urem32(i32 %a, i32 %b) {
%r = urem i32 %a, %b
ret i32 %r
}
; CHECK-LABEL: _urem32:
; CHECK: DIVU
; CHECK: MPYS
; CHECK: SUB
+30
View File
@@ -0,0 +1,30 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm %s -o %t-a.s
; RUN: cp %s %t-b.ll
; RUN: llc -march=tms34010 -O0 -filetype=asm %t-b.ll -o %t-b.s
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s -o %t-stdin.s
; RUN: FileCheck %s --check-prefix=SYM < %t-a.s
; RUN: FileCheck %s --check-prefix=PUBLIC < %t-a.s
; RUN: FileCheck %s --check-prefix=SYM < %t-b.s
; RUN: FileCheck %s --check-prefix=PUBLIC < %t-b.s
; RUN: FileCheck %s --check-prefix=STDIN < %t-stdin.s
; RUN: grep -oE '__2EL_2Estr_[0-9A-F]+' %t-a.s > %t-a.sym
; RUN: grep -oE '__2EL_2Estr_[0-9A-F]+' %t-b.s > %t-b.sym
; RUN: not diff %t-a.sym %t-b.sym
; GSPA has one flat symbol table per linked program, so a private label such as
; the string literal's ``__.str`` collides when two translation units are
; concatenated. M4 appends a deterministic module token to private/local
; symbols while leaving public names unchanged. Both files below are compiled
; under different module identifiers, so their private names must differ. The
; stdin RUN line pins the stable fallback token used when llc's module
; identifier is "<stdin>".
@.str = private unnamed_addr constant [5 x i8] c"same\00", align 1
define ptr @public_string() {
ret ptr @.str
}
; STDIN: __2EL_2Estr_6750C254:
; SYM: __2EL_2Estr_{{[0-9A-F]+}}:
; PUBLIC: .globl _public_string
; PUBLIC-LABEL: _public_string:
@@ -0,0 +1,14 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; A variadic callee anchors the contiguous-stack walk at the address of its
; last fixed argument. The argument-copy elision that makes `&arg` the
; incoming slot needs the fixed object and the parameter alloca to have the
; same size; an i8/i16 anchor would remain a private local and silently read
; the wrong slot. Reject it rather than miscompile.
; CHECK: TMS34010 varargs requires an i32 or pointer last fixed argument
define i32 @vararg_narrow_i8(i32 %a, i8 %b, ...) {
entry:
%r = zext i8 %b to i32
ret i32 %r
}
@@ -0,0 +1,10 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; The i16 counterpart of m4-reject-vararg-narrow.ll.
; CHECK: TMS34010 varargs requires an i32 or pointer last fixed argument
define i32 @vararg_narrow_i16(i32 %a, i16 %b, ...) {
entry:
%r = zext i16 %b to i32
ret i32 %r
}
@@ -0,0 +1,10 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
;
; M4 supports only the contiguous-stack varargs pattern, which anchors the
; walk at the last fixed argument. A pure `(...)` definition has no anchor
; (and its epilogue cannot know whether the caller saved STK).
; CHECK: TMS34010 varargs requires at least one fixed argument
define i32 @only_varargs(...) {
ret i32 0
}
+26
View File
@@ -0,0 +1,26 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
;
; The backend has no BR_JT lowering. A five-case switch must stay on the
; compare/branch path (setMinimumJumpTableEntries(~0u)), not become a `.long`
; jump table that llc cannot select.
define i32 @sw(i32 %x) {
entry:
switch i32 %x, label %default [
i32 1, label %c1
i32 2, label %c2
i32 3, label %c3
i32 4, label %c4
i32 5, label %c5
]
c1: ret i32 10
c2: ret i32 20
c3: ret i32 30
c4: ret i32 40
c5: ret i32 50
default: ret i32 0
}
; CHECK-LABEL: _sw:
; CHECK: CMPI 2, A{{[0-9]+}}
; CHECK: JAGT
; CHECK-NOT: .long
; CHECK: RETS
@@ -0,0 +1,33 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
;
; Pin the i32 anchor of the contiguous-stack walk. `%b.addr` is the
; parameter's alloca; SelectionDAGBuilder's argument-copy elision must replace
; it with the incoming fixed object (FP-64 bits, the second fixed argument)
; because the fixed object and the i32 alloca have the same size. The walk
; then subtracts one 32-bit slot and reads the first variadic argument. A
; private-local anchor would use an FP+ offset instead of the FP-64 slot and
; this same-register sequence would not appear.
define i32 @anchor32(i32 %a, i32 %b, ...) {
entry:
%b.addr = alloca i32, align 2
%p = alloca ptr, align 2
store i32 %b, ptr %b.addr, align 2
store ptr %b.addr, ptr %p, align 2
%q = load ptr, ptr %p, align 2
%base = ptrtoint ptr %q to i32
%addr = add i32 %base, -32
%v = inttoptr i32 %addr to ptr
store ptr %v, ptr %p, align 2
%r = load ptr, ptr %p, align 2
%x = load i32, ptr %r, align 2
%sum = add i32 %a, %x
ret i32 %sum
}
; CHECK-LABEL: _anchor32:
; The second fixed argument is loaded from FP-64 (bits).
; CHECK: ADDI -64, [[ANCHOR:A[0-9]+]]
; A slot below it is the first variadic argument: the same register is
; decremented by 32 bits and the loaded pointer is dereferenced.
; CHECK: ADDI -32, [[ANCHOR]]
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
+33
View File
@@ -0,0 +1,33 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; M4 supports the TI ABI's contiguous-stack varargs only: every actual
; argument is pushed into one 32-bit slot, so a callee can take the address
; of its last fixed argument and step down one slot per variadic argument.
; There is no va_start/va_arg/va_list lowering.
define i32 @variadic(i32 %n, ...) {
ret i32 %n
}
define i32 @variadic2(i32 %a, i32 %b, ...) {
%r = add i32 %a, %b
ret i32 %r
}
define i32 @callvar(i32 %a, i32 %b, i32 %c) {
%r = call i32 (i32, ...) @variadic(i32 %a, i32 %b, i32 %c)
ret i32 %r
}
; The fixed arguments keep their FP-relative slots: the first is at FP-32,
; the second at FP-64, and the variadic arguments follow below.
; CHECK-LABEL: _variadic:
; CHECK: ADDI -32, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK-LABEL: _variadic2:
; CHECK: ADDI -32, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK: ADDI -64, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; The caller pushes the actual arguments last-first: c, b, a.
; CHECK-LABEL: _callvar:
; CHECK: MOVE A2, *A14+, 1
; CHECK: MOVE A1, *A14+, 1
; CHECK: MOVE A0, *A14+, 1
; CHECK: CALLA _variadic
@@ -0,0 +1,14 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; O2's SROA/SLP can form a small homogeneous struct copy as `<3 x i32>`. The
; target has no vector register class, but the default getSetCCResultType
; asserts for vectors; the target override lets the type legalizer split the
; load/store into i32 scalars instead of crashing.
define void @copy3(ptr %d, ptr %s) {
%v = load <3 x i32>, ptr %s, align 1
store <3 x i32> %v, ptr %d, align 1
ret void
}
; CHECK-LABEL: _copy3:
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
+22
View File
@@ -0,0 +1,22 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; M4 adds zero-extending narrow loads for unsigned char and unsigned short.
; MOVB and the FS0 MOVE are sign-extending, so the zero-extending load is the
; sign-extending load followed by an ANDI with the width mask. The GSPA text
; shows the mask the source wrote; the .td encoding one's-complements it.
define i32 @zext8(ptr %p) {
%v = load i8, ptr %p, align 1
%r = zext i8 %v to i32
ret i32 %r
}
define i32 @zext16(ptr %p) {
%v = load i16, ptr %p, align 1
%r = zext i16 %v to i32
ret i32 %r
}
; CHECK-LABEL: _zext8:
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: ANDI 255
; CHECK-LABEL: _zext16:
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK: ANDI 65535