40 Commits
m2 ... m5
Author SHA1 Message Date
arti f9c593ddd5 tms34010: constant memmove and 16-bit byval slots
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
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
2026-11-13 10:14:29 +00:00
arti 466b7ec23b tms34010: M5 native object path, code-size and conformance
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
upload-release-artifact/action.yml (push) Invalid workflow file
2026-11-13 08:37:33 +00:00
arti e0e81b722e tms34010: analyze fused compare-and-branch for machine verifier 2026-11-13 05:54:48 +00:00
arti 04ec6b1419 tms34010: sign-extend short loads and fuse compare+branch 2026-11-13 04:45:16 +00:00
arti af2e1ded7a tms34010: clarify statepoint rejection 2026-11-13 02:00:02 +00:00
arti a5f0c56bf2 tms34010: reject gc.statepoint calls with a diagnostic 2026-11-13 00:54:53 +00:00
arti f1f8ffbb61 tms34010: test whole-word data and bss padding 2026-11-12 23:15:44 +00:00
arti 53293c399a tms34010: pad odd data and bss allocations to words 2026-11-12 22:41:48 +00:00
arti 78cd63b234 tms34010: make the wide-multiply diagnostic width-aware 2026-11-12 21:53:44 +00:00
arti e14fcf5682 tms34010: remove switch-to-lookup; pin replexitval edges 2026-11-12 21:22:35 +00:00
arti b3e6d7389c tms34010: scale uniquified memmove expansion loops 2026-11-12 20:47:27 +00:00
arti b166d892e7 tms34010: do not scale post-memmove continuation GEPs 2026-11-12 20:22:13 +00:00
arti 96391dc024 tms34010: scale static mem intrinsic expansion loops 2026-11-12 19:57:08 +00:00
arti 2604471eb6 tms34010: fix dynamic alloca reservation and memmove scaling 2026-11-12 19:31:03 +00:00
arti cd074251fd tms34010: alloca, byval, narrow returns and mem intrinsics 2026-11-12 19:00:14 +00:00
arti bbc3b21421 tms34010: MMTM/MMFM register-list saves 2026-11-12 17:54:36 +00:00
arti eab7503d76 tms34010: report real short-immediate instruction sizes
ADDI, MOVI, CMPI, SUBI and ADDSTK are emitted in the 16-bit immediate form
when the value fits a signed 16-bit field, but getInstSizeInBytes reported the
.td long form (6 bytes) unconditionally.  The branch-relaxation layout then
over-estimated distances and silently left in-range branches absolute.

Override getInstSizeInBytes to mirror TMS34010MCCodeEmitter: 4 bytes for a
signed-16-bit immediate, 6 otherwise.  Keep the .td Size = 6 for the MC long
form.  m5-jr.ll gains exact_relax, a branch whose exact displacement is 113
words and whose long-form estimate is 137: it is now JREQ, and was JAEQ
before the fix.

The relaxation pass now pre-scans for unsized instructions and keeps every
branch absolute instead of aborting.
2026-11-12 17:11:35 +00:00
arti 40f5520e3d tms34010: JR branches and relaxation 2026-11-12 16:49:49 +00:00
arti f1450ebe7e tms34010: native object path for M4 programs 2026-11-12 16:15:18 +00:00
arti d0d1d6e101 tms34010: fix MC review findings 2026-11-12 15:35:05 +00:00
arti a34b8a3fb7 tms34010: full MC assembler/encoder/disassembler 2026-11-12 15:13:41 +00:00
arti ea154fa696 tms34010: harden TI-COFF relocation writing 2026-11-12 14:30:05 +00:00
arti df6f6fa9d6 tms34010: linkable TI-COFF objects 2026-11-12 14:15:07 +00:00
arti 67bd4c7dda tms34010: cover both MOVI forms and fail loudly on unsupported objects 2026-11-12 13:43:32 +00:00
arti fd41299260 tms34010: add the minimal MC object path 2026-11-12 13:31:20 +00:00
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
arti 73c85350a4 tms34010: correct M3 encodings and narrow-load docs
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
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
2026-11-11 20:30:31 +00:00
arti 1968ccd39b docs: correct M3 milestone wording
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-11 20:06:55 +00:00
arti 8d088c40df docs: M3 branches and Xlib window
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
upload-release-artifact/action.yml (push) Invalid workflow file
2026-11-11 19:56:16 +00:00
arti afb4ad2eef tms34010: allow misaligned loads/stores 2026-11-11 19:45:28 +00:00
arti 05762fceb8 tms34010: add integer comparisons, branches and ALU ops 2026-11-11 18:54:46 +00:00
arti bbc591b77c tms34010: add JAcc branches and GSPA-safe block labels 2026-11-11 18:10:40 +00:00
102 changed files with 9035 additions and 210 deletions

No files matched your search

+845 -38
View File
@@ -1,10 +1,17 @@
# LLVM 23.1.5 fork for TMS34010
Branch `m2` is `llvmorg-23.1.5` plus the TMS34010 target and clang
bit-address changes (M0), a minimal TMS34010 backend (M1) and the TI ABI, calls
and `.data` support (M2). This branch is the canonical source for the target;
the project's `patches/llvm` mirror is frozen at M0. Branch `tms34010` keeps
the M1 backend and its `m1-*` suite.
Branch `m5` 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), integer control flow plus the first compiled C Xlib
window (M3), M4's function pointers, multiply/divide, zero-extending narrow
loads, the XImage/event client and the pinned termtris integration, and M5's
native TI-COFF object path, full MC layer, `JR`/relaxation, `MMTM`/`MMFM`,
`alloca`/`byval`/narrow returns/mem intrinsics, and the pinned conformance
tier. This branch is the canonical source for the target; the project's
`patches/llvm` mirror is frozen at M0. Branch `m4` is the previous milestone
snapshot and the fork's default branch; branch `m3` is before that, branch
`m2` is the M2 tip and branch `tms34010` keeps the M1 backend and its `m1-*`
suite.
## M2 state
@@ -22,8 +29,8 @@ The calling convention is the **TI ABI**:
- Caller: if it passes arguments, save the old STK on the system stack with
`MOVE A14, -*SP, 1`; push arguments on the program stack **in reverse
order** (`MOVE <arg>, *A14+, 1`), each already a 32-bit slot; `CALLA`; then
re-establish both field sizes with `SETF 16, 0, 0` and `SETF 32, 0, 1`.
- Callee: set `SETF 16, 0, 0` and `SETF 32, 0, 1`; save the old FP and each
re-establish both field sizes with `SETF 16, 1, 0` and `SETF 32, 0, 1`.
- Callee: set `SETF 16, 1, 0` and `SETF 32, 0, 1`; save the old FP and each
used callee-saved register on the system stack; `FP = STK`; allocate the
frame with `ADDI <bits>, STK`. The first explicit argument is at `FP(-32)`, the second at
`FP(-64)`, and so on; locals are at positive FP offsets.
@@ -40,7 +47,7 @@ The calling convention is the **TI ABI**:
all other state except the reserved registers.
- `FS0` must be 16 (for `short` accesses) and `FS1` must be 32 (for 32-bit
words) at function entry and after every call. The prologue emits
`SETF 16, 0, 0` then `SETF 32, 0, 1`; `LowerCall` emits the same pair
`SETF 16, 1, 0` then `SETF 32, 0, 1`; `LowerCall` emits the same pair
immediately after each `CALLA`.
Supported signatures are `i32` and pointer arguments, and `void`, `i32` or
@@ -48,15 +55,19 @@ pointer returns. Narrow (`i8`/`i16`) parameters, `i64`, floats/aggregates,
varargs, `sret`/`byval`/`inreg`/`nest` and indirect calls fail loudly with an
`M2 ...` diagnostic. Ordinary `tail` markers are **demoted** to ordinary
calls (`LowerCall` clears `IsTailCall`); `musttail` is not implemented and
M2 C does not emit it. Real tail calls are M3. Narrow loads/stores inside a
function stay supported (`MOVB`, FS0 `MOVE`). The optimizer's `returned` parameter attribute
M2 C does not emit it. Real tail calls remain a later milestone (M4+). Narrow
loads/stores inside a function are supported only in their sign-extending
forms: `MOVB` for signed `char` and the FS0 `MOVE` for signed `short`.
Zero-extending narrow loads (`unsigned char`, `unsigned short`, `_Bool`) have
no pattern in M2/M3 and are M4 work; `m3-reject-zext.ll` pins the loud failure.
The optimizer's `returned` parameter attribute
is **accepted**: it is only an optimization hint and does not change the
stack-slot ABI. `m2-returned.ll` pins that; `m2-reject-sret.ll` keeps the
rejection of the ABI-affecting attributes.
### Implemented selection and printing
- `ADD` for `i32`; `MOVI` for constants; `SETF 16, 0, 0` and
- `ADD` for `i32`; `MOVI` for constants; `SETF 16, 1, 0` and
`SETF 32, 0, 1`; `RETS N`.
- `MOVE ..., 1` (FS1) for 32-bit load/store, `MOVE ..., 0` (FS0) for `i16`,
sign-extending `MOVB` for `i8`.
@@ -91,12 +102,12 @@ never reports a fatal error for a spill.
The prologue saves `A13` and each callee-saved register used by the function as
individual system-stack pushes (`MOVE <reg>, -*SP, 1`), and the epilogue pops
them in reverse order after restoring STK with
`MOVE *SP((n+1)*32), A14, 1`. This is a deliberate decision: the stack layout
is identical to `MMTM`/`MMFM` (one 32-bit slot per register, old STK at
`SP+(n+1)*32`), so the ABI-visible contract is unchanged, while the asm-only
printer does not need a variable register-list operand. Callee-saved spill slots
are not allocated on the program stack; the prologue/epilogue own the saves and
restores.
`MOVE *SP((n+1)*32), A14, 1`. M4 chose this form because the stack layout is
identical to `MMTM`/`MMFM` (one 32-bit slot per register, old STK at
`SP+(n+1)*32`), so the ABI-visible contract is unchanged; M5 replaces it with
the one-instruction register-list form without moving any slot (see "M5
state"). Callee-saved spill slots are not allocated on the program stack; the
prologue/epilogue own the saves and restores.
### `.data` and string literals
@@ -150,6 +161,735 @@ _strlen:
and the ROM's `RETS 2` returns directly to our caller. M2 consumes `strlen`
only; later milestones extend and validate the table.
## M3 state
M3 adds the control flow and ALU operations that real C needs, and proves them
with the first compiled C Xlib client. It is still assembly-only and still
`i32`/pointer only.
### M3 limitations
- **Narrow loads are sign-extending only.** `MOVB` covers signed `char` and the
FS0 `MOVE` covers signed `short`; there is no zero-extending pattern.
`unsigned char`, `unsigned short` and `_Bool` loads reach `ZEXTLOAD i8`/`i16`
and `llc` stops with a raw `Cannot select`. `m3-reject-zext.ll` pins that
failure so the limitation stays visible; M4 adds the zero-extending patterns.
- **`-O2` loop exit-value rewriting is not lowered.** IndVarSimplify can turn
`sumto`'s loop into a closed-form `i33` multiply, which M3 cannot select, so
the M3 harness builds `branch.c` with `-replexitval=never`. Ordinary
`while`/`for` loops that keep a real back-edge and phi work. M4 adds 32-bit
multiply/divide and moves the `-replexitval=never` policy into
`tms-clang`; i64 remains M6. See "M4 state".
### Branches, CMP and the status register
The TMS34010 `ST` status register holds the condition flags. It is not a
general-purpose register: it is outside every register class, `CMP`/`CMPI` and
the ALU/shift instructions define it, and the conditional branches use it.
`CMP Rs, Rd` sets the flags from `Rd - Rs`. The lowering always wants the flags
to represent `LHS - RHS`, so it emits `CMP RHS, LHS` (the compare's first
operand is the subtrahend). A constant right-hand side becomes `CMPI imm, LHS`;
a constant left-hand side is swapped with the right and the condition code is
swapped with it. The condition map is the dis34 order:
| IR condition | branch | `JAcc` cc | meaning |
|---|---|---|---|
| `SETEQ` | `JAEQ` | `0xA` | equal (`Z`) |
| `SETNE` | `JANE` | `0xB` | not equal (`~Z`) |
| `SETLT` | `JALT` | `0x4` | signed less than |
| `SETGE` | `JAGE` | `0x5` | signed greater or equal |
| `SETLE` | `JALE` | `0x6` | signed less or equal |
| `SETGT` | `JAGT` | `0x7` | signed greater than |
| `SETULT` | `JALO` | `0x8` | unsigned lower (`C`) |
| `SETUGE` | `JAHS` | `0x9` | unsigned higher or same (`~C`) |
| `SETULE` | `JALS` | `0x2` | unsigned lower or same |
| `SETUGT` | `JAHI` | `0x3` | unsigned higher |
`JAEQ`/`JANE`/`JALT`/`JALE`/`JAGT`/`JAGE` are the signed comparisons;
`JALO`/`JALS`/`JAHI`/`JAHS` are the unsigned ones. `JAP`/`JAN`/`JAV`/`JANV`/
`JANN` exist in the instruction set and the `.td` file but no IR condition
selects them yet.
A `JAcc` encodes as `1100 cccc 1000 0000` plus a 32-bit absolute target (three
words, `Size = 6`); a conditional branch falls through when its condition is
false, so it is a terminator and a branch but not a barrier. `JAUC` is the
three-word unconditional absolute jump. GSPA 2.00 rejects a bare `ja`, so the
backend prints the absolute form; the relative `JR`/branch-relaxation code-size
pass is M5.
A compare feeding a branch is chosen in `LowerBR_CC`/`LowerBRCOND` and emitted
with a **glue** result that the branch consumes. The glue forces the
`CMP`/`CMPI` to be scheduled immediately before its `Jcc`, so no other
flag-writing instruction can slip between them. A general boolean branch
(`brcond` on a non-`setcc` value) compares the value with zero and branches
`SETNE`. A constant-true condition becomes `JAUC`; a constant-false condition
fails loudly rather than silently dropping a CFG edge, because the fallthrough
block cannot be named in this lowering.
### `SELECTCC` and `setcc`
There is no conditional move and no dedicated `setcc` result, so
`SETCC`/`SELECT` are expanded by the legalizer to `SELECT_CC` over the values
1 and 0. `LowerSELECT_CC` turns `SELECT_CC` into the `SELECTCC` pseudo, which
`emitSelectCC` expands after instruction selection:
```
ThisMBB: CMP/CMPI; Jcc TrueMBB
IfFalseBB: <false value>; JAUC SinkMBB (fallthrough path)
TrueMBB: <true value>
SinkMBB: PHI [true, TrueMBB], [false, IfFalseBB]
```
Values may be registers or immediates; each is materialised in its own block,
so a constant does not cost a `MOVI` on the other path. This is the `?:`
diamond (and the `setcc` 1/0 diamond); `m3-setcc.ll` and `m3-if.ll` pin it.
### GSPA-safe `LBB` labels
The generic LLVM block label prefix is `.L` (for example `.LBB0_1`), but GSPA
2.00 identifiers accept only alphanumerics and `_`. `TMS34010MCAsmInfo` sets
`InternalSymbolPrefix` and `LinkerPrivateGlobalPrefix` to `L`, so basic blocks
print as `LBB0_1`, `LBB0_2`, … and the MCInstLower path prints the block
symbol by name. The lit tests require the undotted `LBB` prefix, and
`m3-branch` additionally asserts `CHECK-NOT: .LBB`. The linked-image gate
(`scripts/tests/m3/dis-roundtrip.sh`) is what rejects a dotted `.LBB` in the
concatenated assembly.
llc restarts the `LBB<n>_<m>` numbering in every translation unit. GSPA 2.00
has one flat symbol table per assembly, so the M3 harness gives each TU its
own prefix when it concatenates the generated asm (`LBBB…`, `LBBL…`, `LBBX…`);
the labels stay dot-free `LBB…` names.
### Integer ALU and shifts
Selected by pattern: `SUB`, `AND`, `OR`, `XOR` (with `CLR` as the
`XOR Rd, Rd` form), unary `NEG` and `NOT`, and the shifted-register forms
`SLL`, `SRL`, `SRA`. Immediate forms: `ADDI`, `ANDI`, and `SLLK`, `SRAK`,
`SRLK` for the shift-by-constant cases. The shift count is masked by the
hardware exactly as C requires for the 32-bit type.
### Misaligned accesses
`MOVE`, `MOVB` and the FS0/FS1 field-size forms have no alignment requirement:
the TMS34010 performs unaligned field accesses (TMS34010 User's Guide §7.5).
`TMS34010TargetLowering::allowsMisalignedMemoryAccesses` (both the EVT and LLT
overloads) therefore reports integer/pointer accesses of at most 32 bits in
address space 0 as legal and fast. Without it the legalizer split an align-1
load into byte accesses, which cannot select because only a sign-extending
`MOVB` load exists. Packed/bit-addressed struct fields now select a direct
`MOVE ... , 1` (or the FS0 form for `i16`); `m3-unaligned.ll` pins that no
`MOVB` appears.
### GSPA and the first compiled C Xlib window
The M3 evidence is a compiled C client, not hand-written asm. `scripts/m3` in
the project tree builds `branch.c` (`sumto`/`max2`/`lt`), `layout.c` (the
`Display`/`Screen` layout asserts) and `xhello.c` (the four-call client) with
`tms-clang -O2`, `llc -O0`, then GSPA/GSPLNK 2.00. The linked COFF runs in
MAME and produces:
```asm
CALLA _XOpenDisplay ; "0::0"
ADDI 1152, A1 ; 0x480 Display.screens (byte 0x90)
ADDI 64, A1 ; 0x40 Screen.root (byte 0x8)
MOVE A1, *A14+, 1 ; ... nine XCreateSimpleWindow pushes ...
CALLA _XCreateSimpleWindow
CALLA _XMapWindow
CALLA _XSync
```
The ROM's own hand-written `xhello.asm` uses the same displacements
(`*a11(0480h)`, `*a10(040h)`), which confirms that TMS34010 indirect
displacements are bit offsets. The MAME gate pins `VT1200_MONITOR=VR319` and
requires two all-white 32-bit client words at (150,150) and (250,200), not
just nonzero return markers. The differential CI check
(`scripts/tests/m3/diff/check.sh`) compares this shape with stored 1987 TI
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 state
M4 closed the remaining Xlib path to a real image/event client and a
full-game integration. On the `m4` branch 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` on `m4`;
M5 adds them (see "M5 state").
#### 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, 1, 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 would collide loudly (a duplicate
definition from GSPA), not silently. M5 fixes that: it caps the sanitized base
at 22 characters and folds a longer name into a second 8-hex token, leaving the
module token last in a 31-character label. `m5-private-long.ll` and the two-TU
`scripts/tests/m5/private-long.sh` pin it. Public names longer than 31
characters are still truncated by GSPA.
#### `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` invoked `opt` with two explicit policies on `m4`:
- `-replexitval=never` — the i33 exit-value multiply described above. M5 keeps
this as a documented policy and adds an llc diagnostic for the raw pipeline.
- `-switch-to-lookup=false` — the optimizer's switch-to-lookup transform
creates a *new* byte-offset GEP after `tms-bitaddr --scale`, and
`--normalize` could 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. **M5
removes this flag**: `tms-bitaddr --normalize` recognizes the
`switch.table.*` GEP and shifts its byte/element index by 3 (see "M5
state").
#### 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 source columns (0–627) to window x = 12–639, so the
image's rightmost 12 source columns (628–639, the final 12 px of the last
32-px cell) are never displayed; that last cell is visible only as its left
20 px. The window itself stays 640 columns wide.
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 M4 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. (M5's `JR`/`MMTM`/switch changes shrink the current `m5`
native map to `.text` 0x28AD0 bits, `.data` 0x2540 bits and the same
0x1DBD0-bit `.bss`; see "M5 state".) 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. M5 completes the
remaining M3 test-hardening carry-forwards: `m3-cmpi.ll` now pins the
`-O0`/`-O2` MOVI order with `CHECK`/`CHECK-NEXT`, and
`scripts/m3/dis-roundtrip.sh` parses the nine `XCreateSimpleWindow` push
values/registers instead of hardcoding `A2`/`A0`. The M3 MAME values are
unchanged.
## M5 state
M5 keeps the M4 assembly path as the oracle and adds the native object path
beside it. It is still an integer/pointer milestone: floats, `long long`,
general `va_list` varargs, bitfields, `sret`, aggregate returns and debug info
are M6. `byval` arguments are the one aggregate form M5 adds; struct fields
through pointers are supported.
### Native TI-COFF object path
`llc -march=tms34010 -filetype=obj` now emits a relocatable TI-COFF object.
`TMS34010COFFObjectWriter` and `TMS34010MCStreamer` are target code because TI
COFF is not one of LLVM's built-in formats; `createMCStreamer` installs them
for object output, while `-filetype=asm` keeps the GSPA raw-text path
unchanged.
- Sections are `.text`, `.data` and `.bss` in that order. `SizeOfRawData` is
in **bits**, `PointerToRawData`/`PointerToRelocations` are byte offsets, and
`.bss` is virtual (`scnptr = 0`). Empty `.text`/`.data` still get a header
and a raw pointer, matching GSPA.
- The symbol table starts with three `C_STAT` section symbols with 18-byte aux
records (size in bits, relocation count, line count 0), so their record
indices are 0, 2 and 4. Defined globals are `C_EXT` with a section-relative
**bit** value; undefined externals are `C_EXT` with section 0. Names over 8
bytes use the COFF string table.
- Relocations are 10-byte `R-RELLONG` (`0x11`) records at a bit
`VirtualAddress`. Same-section references use symbol index `0xFFFF` and a
section-relative bit field; cross-section references use the target section
symbol and the target symbol's bit offset; undefined externals use the
`C_EXT` index and the expression addend. Symbol differences and non-32-bit
symbol relocations fail loudly.
- `.data` and `.bss` allocations are at least 2-byte aligned and padded to a
16-bit word: the loader rejects a section whose size is not whole words.
Object-mode `.data` is emitted as real MC bytes/values with fixups; the asm
path still emits raw GSPA `.data` text.
- The writer accepts only `.text`/`.data`/`.bss`. A C-side `.cinit` section is
rejected loudly; termtris's `.cinit` is the hand-written GSPA `cinit.asm`,
assembled into the extra entry object (the asm path concatenates it with the
generated assembly).
GSPLNK 2.00 links the object with a GSPA-assembled entry and the existing dwt
loader runs the image. Native xshow and the six-TU termtris match the asm
markers; the native and asm linked `.text` decode to the same instruction
streams (753 xshow and 6,356 termtris instructions), and the native termtris
links `.text` 166,608 bits (20,826 bytes), `.data` 9,536 bits (1,192 bytes)
and `.bss` 121,808 bits with loader status 0. `scripts/m4/run.sh` gains
`M4_NATIVE=1` (the default is unchanged), `scripts/m5/native.sh` is the
compile/link driver, and
`scripts/tests/m5/native.sh` is the gate. `scripts/tests/m5/object-smoke.sh`
proves the native and GSPA `.text` are byte-identical for the frame/short- and
long-`MOVI` cases; `scripts/tests/m5/native-link.sh` proves the `.data`/`.bss`
relocations, branch loop, same-section calls and a section-0 external call
(`value=42 combo=152 bump1=1 bump2=2 loop=10 ext=77`), then `dis34`s the linked
address.
Limits: `llvm-mc -filetype=obj` uses LLVM's generic ELF writer, not TI COFF;
only `llc -filetype=obj` produces the native object. Public symbols longer than
GSPA's 31 characters are still truncated.
### MC encoder, assembler and disassembler
`TMS34010MCCodeEmitter` covers the supported selection table with the GSPA
short/long immediate choice, one's-complement `CMPI`/`SUBI`/`ANDI` forms, the
SRA/SRL `(-K) & 31` encodings, the cross-file `MOVE` bit and 32-bit absolute
fields. `TMS34010AsmParser` parses the GSPA grammar (A/B registers, `SP`/`b15`,
`*reg`, `*reg+`, `-*reg`, `*reg(disp)`, `@addr`, immediates, symbol
expressions, MMTM/MMFM lists, `JRUC` and the `JAC`/`JANC`/`JRC`/`JRNC`
aliases) and rejects mixed register-file operands for encodings that share bit
4. `TMS34010Disassembler` decodes the inverse table, including the shared
`CLR`/`XOR` encoding, short immediates, register-file repair and register
lists. `TMS34010InstPrinter` prints `A15` as `SP`.
The expected bytes in `llvm/test/MC/TMS34010/` come from GSPA 2.00 listings
and the 1987 `dis34`, not from the new encoder: `encodings.s` pins 126
`-show-encoding` instructions, `disassembler.s` 77 inverse checks, and
`roundtrip.s` 88 instructions round-trip through `dis34`. The MC table is
separate from the `isCodeGenOnly` selection definitions, and moving this layer
to object output also fixed the pre-existing selection `CMPI`/`ANDI` long
forms, which had dropped their `!xor` field assignment in the object path. The
10-byte `MOVE @a,@b`/`MOVB @a,@b` forms are deliberately unmodeled (the shared
`bits<48> Inst` cannot hold 80 bits); they fail loudly rather than decode
wrongly.
### `JR` and branch relaxation
Selection still emits the three-word absolute `JAUC`/`JA<cc>` forms. The
pre-emit `TMS34010BranchRelaxation` pass then switches every local branch to
the one-word `JR`/`JR<cc>` form, lays the function out with real instruction
sizes, and grows a branch back to `JA*` when its signed 8-bit word displacement
is out of range, zero, odd, or its target is unknown. Growing only pushes a
branch further out, so the loop is monotone.
`JR` is `1100 cccc dddddddd`: `target = pc + 16 + disp*16`, with `disp` in
`[-127,-1] ∪ [1,127]` (zero selects the 16-bit form and `0x80` marks `JA`).
The MC backend independently handles a symbolic `JR` through the `FK_JR8`
fixup: `mayNeedRelaxation`/`fixupNeedsRelaxationAdvanced` widen an unencodable
displacement to the `JA*` counterpart. Relative to the M4 938-byte M3 image, 14
relaxed branches save 56 bytes and the MMTM/MMFM change saves another 8,
giving 874 (the current all-`JA` baseline is 930).
`m5-jr.ll` pins a short loop (`JRLT`/`JRUC`, no `JALT`) and a 217-word far
branch that stays `JAEQ`; `scripts/tests/m5/jr.sh` checks the M3 loop, the
`llvm-mc`/object byte equality and the MC relaxation.
### `MMTM`/`MMFM` register-list saves
The prologue emits `PUSH32 A13` then one `MMTM SP, <list>` for the callee-saved
set; the epilogue emits one `MMFM SP, <list>` then `POP32 A13`. `MMTM` numbers
A0 at bit 15 (mask `0xd400` for `A0,A1,A3,A5`), `MMFM` numbers A0 at bit 0
(mask `0x002b`). `MMTM` predecrements SP per selected register walking
A0→A15, so the low→high slot order is `A5,A3,A1,A0`, exactly the old ascending
per-register push order; `MMFM` loads back from the same address upward. The
old-STK load stays `MOVE *SP((1 + CSI + 1)*32), A14, 1` and `RETS 2` is
unchanged.
For n saved registers the old sequence cost 4n bytes (two-byte push/pop pairs)
and the new pair costs 8: n=1 grows by 4, n=2 breaks even, n=3 saves 4 and n=4
saves 8. `m5-mmtm.ll` (4+3 registers) went 184→172 bytes and the unchanged M3
image 882→874. `scripts/tests/m5/mmtm.sh` checks the list, the mask, the
absent per-register saves, the old-STK offset, `RETS 2`, native/asm byte
equality and the M2/M3 MAME markers.
### `alloca`, `byval`, narrow returns and memory intrinsics
- `ISD::DYNAMIC_STACKALLOC` is custom on `i32`; the program stack grows upward
and `A14` holds a **bit** address, so the requested size is shifted by 3 and
the alignment is rounded up in bits. `StackPointerRegisterToSaveRestore` is
A14, `STACKSAVE`/`STACKRESTORE` are expanded, and a no-argument function with
a VLA rebuilds A14 from FP (`MOVE A13, A14` + `ADDI frame_bits, A14`) in the
epilogue; an argument function keeps the caller's old-STK restore.
- A `byval` argument is copied to the current A14 with an always-inline
memcpy, A14 advances by `alignTo(size,2)` **bytes** (×8 bits), and the callee
receives the copy's address. Fixed objects are laid out by the caller's push
order: argument 0 is directly below FP, argument i starts at the sum of the
preceding slot sizes (4 bytes for a scalar, `alignTo(size,2)` for a byval).
- Returns accept void, pointer and any integer ≤32 bits. `zeroext i8` uses
`MOVB`/`ANDI 255`, `signext i16` uses the FS0 `MOVE ...,0` (sign-extending),
and the caller applies the IR's truncation/extension. `i64`, float/double
and aggregate returns are still rejected.
- `findOptimalMemOpLowering` returns false so LLVM's byte-offset memory-op
expansion is not used on bit-address pointers.
`TMS34010SelectionDAGInfo` lowers a constant `memcpy`/`memset` to
word/byte copies with bit-scaled offsets. A target IR pass expands every
remaining `memmove` (including constant sizes <= 64 bytes, which PreISel
leaves alone) into the overlap-safe forward/backward loops, and the scaling
pass shifts the i8 GEPs in `dynamic-/static-memcpy-expansion-`,
`dynamic-/static-memset-expansion-` and `memmove_*` loops by 3. No
`CALLA _memcpy`/`_memset`/`_memmove` is emitted. This fixed a silent 8×
miscompile in constant copies.
`scripts/tests/m5/robust.sh` runs `robust.c` at `-O0` in both paths and
requires `alloca=408 byval=64 narrow=1056 mem=438 alloca_use=1496 byval6=64 memmove=2148`; the four
M5 lit tests are `m5-alloca`, `m5-byval`, `m5-narrow-ret` and `m5-memintrin`.
### Driver policy and the unsupported-IR boundary
`-switch-to-lookup=false` is removed. `tms-bitaddr --normalize` now recognizes
SimplifyCFG's `switch.table.<function>` base (after stripping pointer casts)
and uses shift 3 for its byte/element index; `scale_constant_gep` uses the same
per-base shift and the normalize check accepts the typed `[N x i32]` table.
Post-fix code emits `SLL 3` before the index add, and the M4 switch/termtris
lookups are correct under a plain `tms-clang -O2`.
`-replexitval=never` remains a documented `tms-clang` TMS34010 policy until
i64/M6. `TMS34010RejectUnsupportedIR` (a pre-ISel function pass) turns the raw
`opt`-without-policy shapes into clear `LLVM ERROR` diagnostics instead of
`Cannot select`/assertions:
- an exactly-33-bit `mul` (IndVarSimplify's closed-form exit value) names
`-replexitval=never`;
- a `mul` wider than 33 bits is the i64/M6 path and says so;
- a local (`internal`/`private`) variadic definition at a non-`-O0` llc level
is rejected; `tms-clang -O2` rejects it before opt too. External definitions
such as `sprintf` are unaffected, and `-O0` keeps the M4 varargs gate.
`m5-policy.ll`, `m5-policy-i64.ll` and `m5-reject-static-vararg.ll` pin the
diagnostics; `scripts/tests/m5/policy.sh` checks the default `-O2` switch
table, the driver trace, the raw i33 failure, the static-vararg reject and the
external `sprintf` acceptance.
### Private-label length cap
GSPA keeps only 31 identifier characters. `appendModuleToken()` reserves the
9-character `_<8 hex>` module token, caps the sanitized base at 22 characters,
and if the base is longer keeps its first 13 characters and folds the full
name into another 8-hex token (`13 + '_' + 8 = 22`), then appends the module
token last for exactly 31. Two TUs with the same long static name now link.
`m5-private-long.ll` and the two-TU `scripts/tests/m5/private-long.sh` pin the
capped labels and distinct module tokens. Public long names are still
truncated by GSPA.
### Correctness fixes exposed by the tier
- **FS0 sign extension.** `SETF16` is now `SETF 16, 1, 0` (`0x0570`): the
second SETF operand is the field-extension bit (`fe`), and `fe=0`
zero-extends. Several c-torture tests (`20030128-1`, `920721-1`,
`20000412-1`, `20020911-1`) failed until the short load sign-extended.
`m5-sext-i16.ll` pins it; the zero-extending lowering still masks with
`ANDI 255/65535`, so it remains correct.
- **Compare/branch adjacency.** A register-to-register `MOVE` writes the
status flags; a PHI copy on a loop back edge could land between `CMP` and
`Jcc` (`20000422-1` timed out at `-O2`). Selection now emits the fused
`CMPBR`/`CMPIBR` terminator, and `TMS34010ExpandCmpBr` (post-RA, before PEI
and block placement) splits it into adjacent `CMP`/`CMPI` + `JA<cc>`.
`isConditionalBranch`/`analyzeBranch`/remove/insert recognize the fused
forms, so `-verify-machineinstrs` is clean. `m5-cmpbr-adjacent.ll` forces
PHI copies and requires every compare to be immediately followed by its
branch.
- **`gc.statepoint`.** `StatepointLowering` asserted because this target does
not build `CALLSEQ_START`/`CALLSEQ_END`. A GC strategy containing
`llvm.experimental.gc.statepoint` and any call with operand bundles now get
target diagnostics (`m5-reject-statepoint.ll`). Stackmaps/statepoints remain
out of scope; the shadow-stack tests still pass.
- **Object data and shift encoding.** Object-mode `.bss` is word-granular
(the loader rejected termtris's 121,800-bit section; the native `.bss` is
now 121,808 bits, identical to asm), odd `.data` is padded to a word
(`m5-data-pad.ll`), and the object `SRAK`/`SRLK` forms encode `(-K) & 31`
(`0x2f65`, not `0x2c05`) even though TableGen drops the negated field for
the CodeGen definitions.
### Conformance tier
`scripts/tests/m5/conformance/run.sh` is the single compile/link/MAME/compare
runner:
```
SOURCE.c + shim.c + entry.asm
-> tms-clang -> llc -filetype=asm or -filetype=obj
-> GSPA/GSPLNK (native TI-COFF first; asm fallback only in exploratory mode;
c-testsuite forces native and fails on fallback)
-> pinned dwt loader in MAME
-> status/output/magic block at 01B00000/01B00020/01B00040/01B10000
-> compare with --expect/--expect-status/--expect-output-file
```
Exit codes are 0 pass, 1 failure, 2 skip. `entry.asm` calls `main(1, argv)`,
stores the A8 status, the output length and the `0x434F4E46` ("CONF") magic,
and returns. `shim.c` implements `printf`/`putchar`/`puts` with the M4
contiguous `&fmt` walk (no `va_list`), `exit`/`_exit`/`abort`, a 16 KiB output
buffer and a small freestanding string/integer surface. `abort()` publishes
status 134. `classify.sh` scans for floats, `long long`/`__int128`, bitfields,
`va_list`/`va_start`/`va_arg`, `setjmp`/`longjmp`, signals, wide
characters, float/long-long format specifiers and file I/O, and prints
`feature: reason`; `manifest.tsv` documents the
`test_id<TAB>suite<TAB>expected<TAB>features<TAB>skip_reason` row. A skip is
reported, never counted as a pass. A missing completion magic is a failure.
Pins (`scripts/fetch-conformance.sh`, `vendor/conformance/PINS`):
- GCC 16.2.0 `gcc.c-torture/execute`,
`https://ftp.gnu.org/gnu/gcc/gcc-16.2.0/gcc-16.2.0.tar.xz`, SHA-256
`e6738e29597f733270731aa90600f37ffdc045079dfc27ec7e8192cc81085c3e`
(1,692 `execute` tests);
- c-testsuite `https://github.com/c-testsuite/c-testsuite` commit
`5c7275656d751de0e68b2d340a95b5681858ed07` (220 `single-exec` tests).
Both corpora are materialized under the ignored `vendor/conformance` and are
test-only; nothing is distributed (GCC is GPL and c-testsuite keeps its own
per-test licenses). The GCC version is only the c-torture corpus source; the
compiler under test is `tms-clang`. The three tiers:
- **LLVM `CodeGen/Generic`** (`scripts/tests/m5/generic.sh`) is compile-only:
a 24-file curated allowlist must compile to non-empty asm, and the recursive
sweep covers 197 `.ll` files (107 pass / 90 unsupported / 0 crash). Every
unsupported row has a reason in `generic-expected-fail.txt`; an assertion,
timeout, `SIGKILL` or any non-134 signal/undiagnosed failure is a crash and
fails the gate. The sweep exposed the `gc.statepoint` assertion fixed above.
`2010-11-04-BigByval.ll` needs a 900-second allowance (about 5m43s) and
dominates the ~6–7 minute gate.
- **GCC c-torture** (`scripts/tests/m5/c-torture.sh`) runs the curated 59-test
integer/control/array/pointer list at `-O0` and `-O2`, requires `main` 0 and
the completion magic, and ignores stdout. The gate result is
`pass=59 skip=0 total=59` for each pass and takes about 16–17 minutes.
`--scan` classifies the whole 1,692-test corpus (1,112 supported / 580
skipped:
212 long-long, 180 float, 112 bitfield, 36 varargs, 14 wide-char,
11 file-io, 5 float-format, 3 setjmp, 2 signal, 2 int128, 2
format-unsupported, 1 long-long-format). The MAME injection frames are
300/600; the loader readback and magic remain required.
- **c-testsuite** (`scripts/tests/m5/c-testsuite.sh`) runs 29 curated
`single-exec` tests (17 silent status-0 and 12 formatted-output tests) and
compares stdout byte-for-byte with `.c.expected`. The gate result is
`pass=29 skip=191 total=220` and takes about 4–5 minutes. The skip file
accounts for every other test: 159 `unselected` (outside the curated subset,
not claimed to fail), 10 long-long, 9 float, 4 wide-char, 2 `runtime`
(`00205`/`00216`: the M2 `.data` initializer gap), 2 libc (`calloc`,
`sprintf`), 2 file-io, 2 bitfield, 1 long-long-format. The gate re-runs the
classifier and fails on a stale reason; a listed classifier-rejected test is
a failure, and an all-skip run fails.
The conformance shim captures stdout only; stderr-writing tests are skipped,
not passed. That and the full suites are M6.
### M5 tests and evidence
The lit suite is 76 CodeGen tests (35 `m2-*`, 8 `m3-*`, 18 `m4-*`, 15
`m5-*`) plus 4 MC tests. The M5 CodeGen tests are `m5-object-smoke`,
`m5-jr`, `m5-mmtm`, `m5-alloca`, `m5-byval`, `m5-narrow-ret`, `m5-memintrin`,
`m5-policy`, `m5-policy-i64`, `m5-reject-static-vararg`, `m5-private-long`,
`m5-reject-statepoint`, `m5-sext-i16`, `m5-cmpbr-adjacent` and `m5-data-pad`.
The MC tests are `encodings.s`, `disassembler.s`, `roundtrip.s` and
`jr-range.s`.
The M5 project gates are `object-smoke.sh`, `native-link.sh`,
`mc-roundtrip.sh`, `native.sh`, `jr.sh`, `mmtm.sh`, `robust.sh`, `policy.sh`,
`private-long.sh`, `conformance/selftest.sh`, `generic.sh`, `c-torture.sh`
and `c-testsuite.sh`, plus the unchanged M4/M3/M2 gates and
`tools/tms-bitaddr/tests/run.sh`.
## M5 non-goals and the M6 boundary
M5 deliberately stops before:
- **Floats, `long long` and i64:** no float/double or `i64` arguments,
returns, arithmetic or branches; the i33/`-replexitval` workaround stays
until M6 removes it.
- **General varargs:** no `va_list`, `va_start` or `va_arg`; only the
contiguous `&last_fixed` pattern, which the conformance shim also uses.
- **Bitfields and aggregates:** bitfields, `sret`, aggregate returns and
general aggregate arguments remain unsupported; `byval` arguments and struct
fields through pointers are supported.
- **Debug info, the wider Xlib surface, stderr and the full suites:** no debug
info; the client uses the validated Xlib entries; the conformance harness
captures stdout only and runs curated subsets, not the full GCC/LLVM suites.
- **Deeper libc/runtime coverage:** the conformance shim is freestanding; no
new `RTS.LIB` entries.
- **Native `.cinit` and public long names:** the object writer accepts only
`.text`/`.data`/`.bss`, and public identifiers over 31 characters are
truncated by GSPA.
The pinned termtris, GCC and c-testsuite trees are test-only and are not part
of the compiler or project distribution (termtris is GPLv3; GCC is GPL;
c-testsuite keeps its own per-test licenses).
## Build
```sh
@@ -159,17 +899,20 @@ cmake -S 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 llc clang opt llvm-config FileCheck count not
ninja -C build llc llvm-mc clang opt llvm-config FileCheck count not
```
`build/bin/llvm-lit` is generated by CMake configure. Run the M2 backend tests:
`build/bin/llvm-lit` is generated by CMake configure. Run the full M2–M5
backend and MC suite:
```sh
build/bin/llvm-lit -v build/test/CodeGen/TMS34010 --filter='m2-'
# -> 37 passed
build/bin/llvm-lit -v build/test/CodeGen/TMS34010 --filter='(m2|m3|m4|m5)-'
# -> 76 passed (35 m2-*, 8 m3-*, 18 m4-*, 15 m5-*)
build/bin/llvm-lit -v build/test/MC/TMS34010
# -> 4 passed (encodings, disassembler, round-trip, JR range)
```
The tests are `llvm/test/CodeGen/TMS34010/m2-*.ll`:
The M2 tests are `llvm/test/CodeGen/TMS34010/m2-*.ll`:
- Migrated/legacy selection: `m2-add`, `m2-load`, `m2-char`, `m2-i16`,
`m2-store`, `m2-global`, `m2-late-gep`.
@@ -178,33 +921,97 @@ The 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`
reproduces the string-then-`int` `.data` alignment case; and the call tests pin
the TI caller/callee sequence, the `FS1` fixup, and a real spill.
The M3 tests are:
- `m3-branch`: a `JAUC` self-loop and an `LBB` label, with `CHECK-NOT: .LBB`.
- `m3-loop`: `CMP` immediately followed by `JALE`, an unconditional back-edge
(`JAUC`) and `RETS 2`.
- `m3-if`: `CMP` + `JAGT` and `RETS 2`.
- `m3-cmpi`: the constant-right compare becomes `CMPI` + `JALT`, at `-O0` and
`-O2`; M5 pins the exact MOVI order with `CHECK`/`CHECK-NEXT`.
- `m3-setcc`: the `SELECT_CC` 1/0 diamond (`CMP`/`JALT`/`MOVI 0`/`MOVI 1`).
- `m3-unsigned`: `JALO`/`JAHI` for the unsigned conditions.
- `m3-ops`: `SUB`/`AND`/`OR`/`XOR`/`SLL`/`SRL`/`SRA`, plus `NEG`/`NOT`/`ANDI`
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`.
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 `m5`):
```sh
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m2 ./scripts/build-llvm.sh
scripts/tests/m2/run.sh # GSPA/GSPLNK under DOSBox, then MAME
scripts/tests/m2/dis-roundtrip.sh # linked .text decoded with dis34
scripts/tests/m2/diff/check.sh # ABI shape vs. the 1987 TI compiler
tools/tms-bitaddr/tests/run.sh # GEP scaling/normalization
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m5 ./scripts/build-llvm.sh
ninja -C build/llvm-clean-build llc llvm-mc 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|m5)-'
build/llvm-clean-build/bin/llvm-lit -v build/llvm-clean/llvm/test/MC/TMS34010
scripts/tests/m5/object-smoke.sh # native .text == GSPA .text
scripts/tests/m5/native-link.sh # GSPLNK link and loader/MAME
scripts/tests/m5/mc-roundtrip.sh # 88 instructions through dis34
scripts/tests/m5/native.sh # native xshow + termtris
scripts/tests/m5/jr.sh # JR/relaxation and M3 MAME
scripts/tests/m5/mmtm.sh # MMTM/MMFM and M2/M3 MAME
scripts/tests/m5/robust.sh # alloca/byval/narrow/mem
scripts/tests/m5/policy.sh
scripts/tests/m5/private-long.sh
scripts/tests/m5/conformance/selftest.sh
scripts/tests/m5/generic.sh
scripts/tests/m5/c-torture.sh
scripts/tests/m5/c-testsuite.sh
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/m3/layout.sh
scripts/tests/m3/diff/check.sh
scripts/tests/m2/run.sh # M2 regression
scripts/tests/m2/dis-roundtrip.sh
scripts/tests/m2/diff/check.sh
```
Expected: `M2 MAME: PASS`, `M2 dis34 round-trip: PASS`, `M2 diff: PASS` and
`tms-bitaddr: PASS`. The differential check uses stored golden assembly from
the 1987 TI compiler and does not run DOSBox; the other two gates do. The M1
harness (`scripts/tests/m1/`) lives on the project's `m1` branch and is not an
M2 gate. The project README lists the prerequisites (DOSBox, mtools, the VT1200
SDK image, MAME).
Expected: `M5 object smoke: PASS`, `M5 native-link: PASS`, `M5 MC round-trip:
PASS`, `M5 native: PASS`, `M5 JR: PASS`, `M5 MMTM: PASS`, `M5 robust: PASS`,
`m5 policy: PASS`, `m5 private-long: PASS`, `conformance selftest: PASS`, the
Generic/c-torture/c-testsuite PASS lines with their skip reasons, `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.
MAME and conformance gates must run serially because `M4_SDK` is shared. 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, GCC and c-testsuite trees are
test-only. The project README lists the prerequisites (DOSBox, mtools, the
VT1200 SDK image, MAME and `vendor/conformance`).
@@ -0,0 +1,17 @@
add_llvm_component_library(LLVMTMS34010AsmParser
../MCTargetDesc/TMS34010AsmParser.cpp
LINK_COMPONENTS
CodeGenTypes
MC
MCParser
TMS34010Desc
TMS34010Info
Support
DEPENDS
TMS34010CommonTableGen
ADD_TO_COMPONENT
TMS34010
)
+5
View File
@@ -2,9 +2,12 @@ add_llvm_component_group(TMS34010)
set(LLVM_TARGET_DEFINITIONS TMS34010.td)
tablegen(LLVM TMS34010GenAsmMatcher.inc -gen-asm-matcher)
tablegen(LLVM TMS34010GenAsmWriter.inc -gen-asm-writer)
tablegen(LLVM TMS34010GenDAGISel.inc -gen-dag-isel)
tablegen(LLVM TMS34010GenDisassemblerTables.inc -gen-disassembler)
tablegen(LLVM TMS34010GenInstrInfo.inc -gen-instr-info)
tablegen(LLVM TMS34010GenMCCodeEmitter.inc -gen-emitter)
tablegen(LLVM TMS34010GenRegisterInfo.inc -gen-register-info)
tablegen(LLVM TMS34010GenSDNodeInfo.inc -gen-sd-node-info)
tablegen(LLVM TMS34010GenSubtargetInfo.inc -gen-subtarget)
@@ -46,3 +49,5 @@ add_llvm_target(TMS34010CodeGen
add_subdirectory(MCTargetDesc)
add_subdirectory(TargetInfo)
add_subdirectory(AsmParser)
add_subdirectory(Disassembler)
@@ -0,0 +1,15 @@
add_llvm_component_library(LLVMTMS34010Disassembler
../MCTargetDesc/TMS34010Disassembler.cpp
LINK_COMPONENTS
MCDisassembler
MC
TMS34010Info
Support
DEPENDS
TMS34010CommonTableGen
ADD_TO_COMPONENT
TMS34010
)
@@ -1,6 +1,18 @@
# The assembler and disassembler sources live here next to the MC descriptions
# but are compiled into the standard AsmParser/Disassembler component libraries
# (see ../AsmParser and ../Disassembler), so they are intentionally not part of
# this library.
set(LLVM_OPTIONAL_SOURCES
TMS34010AsmParser.cpp
TMS34010Disassembler.cpp
)
add_llvm_component_library(LLVMTMS34010Desc
TMS34010InstPrinter.cpp
TMS34010MCAsmBackend.cpp
TMS34010MCAsmInfo.cpp
TMS34010MCCodeEmitter.cpp
TMS34010MCObjectWriter.cpp
TMS34010MCTargetDesc.cpp
LINK_COMPONENTS
@@ -0,0 +1,507 @@
//===-- TMS34010AsmParser.cpp - Parse TMS34010 assembly ---------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// GSPA 2.00 syntax is the source language. The parser intentionally emits a
// flat token/register/immediate stream and lets TableGen's asm matcher match
// the literal *, -, +, @ and parenthesis tokens that appear in the MC asm
// strings. The only instruction with a non-regular grammar is MMTM/MMFM,
// whose GSPA operand is a comma-separated register list that must be packed
// into the 16-bit mask operand.
//
//===----------------------------------------------------------------------===//
#include "TMS34010AsmParser.h"
#include "TMS34010MCTargetDesc.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCParser/AsmLexer.h"
#include "llvm/MC/MCParser/MCParsedAsmOperand.h"
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/SMLoc.h"
using namespace llvm;
namespace {
/// One parsed TMS34010 operand. The parser deliberately keeps punctuation as
/// token operands so that a single Matcher table can describe all of GSPA's
/// indirect forms.
class TMS34010Operand : public MCParsedAsmOperand {
enum KindTy { k_Token, k_Reg, k_Imm } Kind;
StringRef Token;
MCRegister Reg = TMS34010::NoRegister;
const MCExpr *Imm = nullptr;
SMLoc Start, End;
public:
TMS34010Operand(StringRef Tok, SMLoc S)
: Kind(k_Token), Token(Tok), Start(S), End(S) {}
TMS34010Operand(MCRegister R, SMLoc S, SMLoc E)
: Kind(k_Reg), Reg(R), Start(S), End(E) {}
TMS34010Operand(const MCExpr *V, SMLoc S, SMLoc E)
: Kind(k_Imm), Imm(V), Start(S), End(E) {}
bool isToken() const override { return Kind == k_Token; }
bool isReg() const override { return Kind == k_Reg; }
bool isImm() const override { return Kind == k_Imm; }
bool isMem() const override { return false; }
StringRef getToken() const {
assert(Kind == k_Token && "not a token");
return Token;
}
MCRegister getReg() const override {
assert(Kind == k_Reg && "not a register");
return Reg;
}
const MCExpr *getImm() const {
assert(Kind == k_Imm && "not an immediate");
return Imm;
}
SMLoc getStartLoc() const override { return Start; }
SMLoc getEndLoc() const override { return End; }
void addRegOperands(MCInst &Inst, unsigned N) const {
assert(Kind == k_Reg && N == 1 && "invalid register operand");
Inst.addOperand(MCOperand::createReg(Reg));
}
void addImmOperands(MCInst &Inst, unsigned N) const {
assert(Kind == k_Imm && N == 1 && "invalid immediate operand");
int64_t Value;
if (Imm->evaluateAsAbsolute(Value))
Inst.addOperand(MCOperand::createImm(Value));
else
Inst.addOperand(MCOperand::createExpr(Imm));
}
void print(raw_ostream &O, const MCAsmInfo &MAI) const override {
switch (Kind) {
case k_Token:
O << "Token " << Token;
break;
case k_Reg:
O << "Reg " << Reg.id();
break;
case k_Imm:
O << "Imm ";
MAI.printExpr(O, *Imm);
break;
}
}
static std::unique_ptr<TMS34010Operand> CreateToken(StringRef Str, SMLoc S) {
return std::make_unique<TMS34010Operand>(Str, S);
}
static std::unique_ptr<TMS34010Operand> CreateReg(MCRegister R, SMLoc S,
SMLoc E) {
return std::make_unique<TMS34010Operand>(R, S, E);
}
static std::unique_ptr<TMS34010Operand> CreateImm(const MCExpr *V, SMLoc S,
SMLoc E) {
return std::make_unique<TMS34010Operand>(V, S, E);
}
};
class TMS34010AsmParser : public MCTargetAsmParser {
MCAsmParser &Parser;
const MCRegisterInfo *MRI;
MCAsmParser &getParser() const { return Parser; }
AsmLexer &getLexer() const { return Parser.getLexer(); }
bool parseOneOperand(OperandVector &Operands);
bool parseIndirectOperand(OperandVector &Operands);
bool parseRegisterListInstruction(StringRef Name, SMLoc NameLoc,
OperandVector &Operands);
#define GET_ASSEMBLER_HEADER
#include "TMS34010GenAsmMatcher.inc"
public:
TMS34010AsmParser(const MCSubtargetInfo &STI, MCAsmParser &Parser,
const MCInstrInfo &MII)
: MCTargetAsmParser(STI, MII), Parser(Parser) {
MCAsmParserExtension::Initialize(Parser);
MRI = getContext().getRegisterInfo();
setAvailableFeatures(ComputeAvailableFeatures(STI.getFeatureBits()));
}
bool parseRegister(MCRegister &Reg, SMLoc &StartLoc,
SMLoc &EndLoc) override;
ParseStatus tryParseRegister(MCRegister &Reg, SMLoc &StartLoc,
SMLoc &EndLoc) override;
bool parseInstruction(ParseInstructionInfo &Info, StringRef Name,
SMLoc NameLoc, OperandVector &Operands) override;
bool matchAndEmitInstruction(SMLoc Loc, unsigned &Opcode,
OperandVector &Operands, MCStreamer &Out,
uint64_t &ErrorInfo,
bool MatchingInlineAsm) override;
ParseStatus parseDirective(AsmToken DirectiveID) override;
};
} // end anonymous namespace
// Defined by the generated matcher at the bottom of this file.
static MCRegister MatchRegisterName(StringRef Name);
static MCRegister MatchRegisterAltName(StringRef Name);
bool TMS34010AsmParser::parseRegister(MCRegister &Reg, SMLoc &StartLoc,
SMLoc &EndLoc) {
ParseStatus Res = tryParseRegister(Reg, StartLoc, EndLoc);
if (Res.isSuccess())
return false;
if (Res.isNoMatch())
return true;
return Error(StartLoc, "invalid register name");
}
ParseStatus TMS34010AsmParser::tryParseRegister(MCRegister &Reg,
SMLoc &StartLoc,
SMLoc &EndLoc) {
if (getLexer().getKind() != AsmToken::Identifier)
return ParseStatus::NoMatch;
// Register AsmNames are uppercase (A0, B15); GSPA's SP alias is lowercase.
// Try the spelling as written, then both cases, so the assembler is
// case-insensitive like GSPA without changing the printer's canonical names.
StringRef Raw = getLexer().getTok().getIdentifier();
auto Lookup = [&](StringRef Name) {
Reg = MatchRegisterName(Name);
if (Reg == TMS34010::NoRegister)
Reg = MatchRegisterAltName(Name);
};
Lookup(Raw);
if (Reg == TMS34010::NoRegister)
Lookup(Raw.upper());
if (Reg == TMS34010::NoRegister)
Lookup(Raw.lower());
if (Reg == TMS34010::NoRegister)
return ParseStatus::NoMatch;
StartLoc = getLexer().getTok().getLoc();
EndLoc = getLexer().getTok().getEndLoc();
getLexer().Lex();
return ParseStatus::Success;
}
/// Parse a `*reg`, `*reg+`, `*reg(disp)` or `-*reg` operand after the leading
/// `*` has been consumed. Any punctuation is retained in the operand stream
/// because it is part of the TableGen asm string.
bool TMS34010AsmParser::parseIndirectOperand(OperandVector &Operands) {
SMLoc StarLoc = getLexer().getLoc();
Operands.push_back(
TMS34010Operand::CreateToken(getLexer().getTok().getString(), StarLoc));
getLexer().Lex(); // eat '*'
MCRegister Reg;
SMLoc StartLoc, EndLoc;
if (parseRegister(Reg, StartLoc, EndLoc))
return Error(StarLoc, "expected register after '*'");
Operands.push_back(TMS34010Operand::CreateReg(Reg, StartLoc, EndLoc));
if (getLexer().is(AsmToken::Plus)) {
SMLoc S = getLexer().getLoc();
Operands.push_back(
TMS34010Operand::CreateToken(getLexer().getTok().getString(), S));
getLexer().Lex();
return false;
}
if (getLexer().is(AsmToken::LParen)) {
SMLoc LS = getLexer().getLoc();
Operands.push_back(
TMS34010Operand::CreateToken(getLexer().getTok().getString(), LS));
getLexer().Lex();
const MCExpr *Disp;
SMLoc DispLoc = getLexer().getLoc();
if (getParser().parseExpression(Disp))
return Error(DispLoc, "expected displacement expression");
Operands.push_back(
TMS34010Operand::CreateImm(Disp, DispLoc, getLexer().getLoc()));
if (!getLexer().is(AsmToken::RParen))
return Error(getLexer().getLoc(), "expected ')'");
SMLoc RS = getLexer().getLoc();
Operands.push_back(
TMS34010Operand::CreateToken(getLexer().getTok().getString(), RS));
getLexer().Lex();
}
return false;
}
bool TMS34010AsmParser::parseOneOperand(OperandVector &Operands) {
switch (getLexer().getKind()) {
case AsmToken::Star:
return parseIndirectOperand(Operands);
case AsmToken::Minus: {
// `-*reg` is a predecrement memory operand; `-imm` is an expression.
AsmToken Minus = getLexer().getTok();
getLexer().Lex();
if (getLexer().is(AsmToken::Star)) {
Operands.push_back(
TMS34010Operand::CreateToken(Minus.getString(), Minus.getLoc()));
return parseIndirectOperand(Operands);
}
// Put the minus back and let the expression parser apply its normal
// precedence: ADDI -5+2 is (-5)+2, not -(5+2).
getLexer().UnLex(Minus);
const MCExpr *Value;
SMLoc StartLoc = Minus.getLoc();
if (getParser().parseExpression(Value))
return Error(StartLoc, "expected expression after '-'");
Operands.push_back(
TMS34010Operand::CreateImm(Value, StartLoc, getLexer().getLoc()));
return false;
}
case AsmToken::At: {
SMLoc S = getLexer().getLoc();
Operands.push_back(
TMS34010Operand::CreateToken(getLexer().getTok().getString(), S));
getLexer().Lex();
const MCExpr *Addr;
SMLoc AddrLoc = getLexer().getLoc();
if (getParser().parseExpression(Addr))
return Error(AddrLoc, "expected absolute address expression");
Operands.push_back(
TMS34010Operand::CreateImm(Addr, AddrLoc, getLexer().getLoc()));
return false;
}
case AsmToken::Identifier: {
MCRegister Reg;
SMLoc StartLoc, EndLoc;
ParseStatus Res = tryParseRegister(Reg, StartLoc, EndLoc);
if (Res.isSuccess()) {
Operands.push_back(TMS34010Operand::CreateReg(Reg, StartLoc, EndLoc));
return false;
}
if (Res.isFailure())
return Error(getLexer().getLoc(), "invalid register name");
[[fallthrough]];
}
case AsmToken::Integer:
case AsmToken::Plus:
case AsmToken::LParen:
case AsmToken::Hash:
default: {
const MCExpr *Value;
SMLoc StartLoc = getLexer().getLoc();
if (getParser().parseExpression(Value))
return Error(StartLoc, "expected expression operand");
Operands.push_back(
TMS34010Operand::CreateImm(Value, StartLoc, getLexer().getLoc()));
return false;
}
}
}
bool TMS34010AsmParser::parseRegisterListInstruction(
StringRef Name, SMLoc NameLoc, OperandVector &Operands) {
Operands.push_back(TMS34010Operand::CreateToken(Name, NameLoc));
MCRegister Dst;
SMLoc StartLoc, EndLoc;
if (parseRegister(Dst, StartLoc, EndLoc))
return Error(NameLoc, "expected a base register");
Operands.push_back(TMS34010Operand::CreateReg(Dst, StartLoc, EndLoc));
if (!parseOptionalToken(AsmToken::Comma))
return Error(getLexer().getLoc(), "expected a register list");
// The canonical form is a register list; a numeric mask is also accepted so
// that the encoding can be pinned directly in tests.
if (getLexer().is(AsmToken::Integer) || getLexer().is(AsmToken::Minus)) {
const MCExpr *Mask;
SMLoc MaskLoc = getLexer().getLoc();
if (getParser().parseExpression(Mask))
return Error(MaskLoc, "expected mask expression");
Operands.push_back(
TMS34010Operand::CreateImm(Mask, MaskLoc, getLexer().getLoc()));
} else {
uint16_t Mask = 0;
unsigned File = MRI->getEncodingValue(Dst) & 0x10;
bool Any = false;
while (true) {
MCRegister Reg;
SMLoc RS, RE;
if (parseRegister(Reg, RS, RE))
return Error(getLexer().getLoc(), "expected register in list");
unsigned Enc = MRI->getEncodingValue(Reg);
if ((Enc & 0x10) != File)
return Error(RS, "register list must use the same register file");
unsigned Low = Enc & 0xF;
if (Name.equals_insensitive("mmtm"))
Mask |= static_cast<uint16_t>(1u << (15 - Low));
else
Mask |= static_cast<uint16_t>(1u << Low);
Any = true;
if (!parseOptionalToken(AsmToken::Comma))
break;
if (getLexer().is(AsmToken::EndOfStatement))
break;
}
if (!Any)
return Error(NameLoc, "empty register list");
Operands.push_back(TMS34010Operand::CreateImm(
MCConstantExpr::create(Mask, getContext()), NameLoc,
getLexer().getLoc()));
}
if (getLexer().isNot(AsmToken::EndOfStatement)) {
SMLoc Loc = getLexer().getLoc();
getParser().eatToEndOfStatement();
return Error(Loc, "unexpected token after register list");
}
getParser().Lex();
return false;
}
bool TMS34010AsmParser::parseInstruction(ParseInstructionInfo &Info,
StringRef Name, SMLoc NameLoc,
OperandVector &Operands) {
if (Name.equals_insensitive("mmtm") || Name.equals_insensitive("mmfm"))
return parseRegisterListInstruction(Name, NameLoc, Operands);
Operands.push_back(TMS34010Operand::CreateToken(Name, NameLoc));
if (getLexer().is(AsmToken::EndOfStatement))
return false;
while (true) {
if (parseOneOperand(Operands))
return true;
if (!parseOptionalToken(AsmToken::Comma))
break;
}
if (getLexer().isNot(AsmToken::EndOfStatement)) {
SMLoc Loc = getLexer().getLoc();
getParser().eatToEndOfStatement();
return Error(Loc, "unexpected token");
}
getParser().Lex();
return false;
}
ParseStatus TMS34010AsmParser::parseDirective(AsmToken DirectiveID) {
return ParseStatus::NoMatch;
}
static bool isShortJR(unsigned Opcode) {
switch (Opcode) {
case TMS34010::MC_JR:
case TMS34010::MC_JRP:
case TMS34010::MC_JRLS:
case TMS34010::MC_JRHI:
case TMS34010::MC_JRLT:
case TMS34010::MC_JRGE:
case TMS34010::MC_JRLE:
case TMS34010::MC_JRGT:
case TMS34010::MC_JRLO:
case TMS34010::MC_JRHS:
case TMS34010::MC_JREQ:
case TMS34010::MC_JRNE:
case TMS34010::MC_JRV:
case TMS34010::MC_JRNV:
case TMS34010::MC_JRN:
case TMS34010::MC_JRNN:
return true;
default:
return false;
}
}
bool TMS34010AsmParser::matchAndEmitInstruction(
SMLoc Loc, unsigned &Opcode, OperandVector &Operands, MCStreamer &Out,
uint64_t &ErrorInfo, bool MatchingInlineAsm) {
MCInst Inst;
unsigned MatchResult =
MatchInstructionImpl(Operands, Inst, ErrorInfo, MatchingInlineAsm);
switch (MatchResult) {
case Match_Success:
Inst.setLoc(Loc);
// The MC JR/JRcc encodings take the raw 8-bit word displacement. Zero
// would be the 16-bit displacement form and 0x80 is the absolute-JA
// marker, so neither belongs in the two-byte encoding.
if (isShortJR(Inst.getOpcode()) && Inst.getNumOperands() == 1 &&
Inst.getOperand(0).isImm()) {
int64_t Disp = Inst.getOperand(0).getImm();
if (Disp == 0 || Disp < -127 || Disp > 127)
return Error(Loc, "8-bit JR displacement must be in [-127,-1] or "
"[1,127]");
}
// All same-file instructions share bit 4 of the instruction word. The
// assembler must reject a pair in different files instead of silently
// encoding the source register number in the destination file. The only
// cross-file instruction is MOVE register-to-register.
if (Inst.getOpcode() != TMS34010::MC_MOVE) {
unsigned File = ~0u;
for (const MCOperand &MO : Inst) {
if (!MO.isReg())
continue;
unsigned F = MRI->getEncodingValue(MO.getReg()) & 0x10;
if (File == ~0u)
File = F;
else if (File != F)
return Error(Loc, "source and destination registers must be in the "
"same register file");
}
}
Out.emitInstruction(Inst, *STI);
return false;
case Match_MnemonicFail:
return Error(Loc, "invalid instruction mnemonic");
case Match_InvalidOperand: {
SMLoc ErrorLoc = Loc;
if (ErrorInfo != ~0U) {
if (ErrorInfo >= Operands.size())
return Error(ErrorLoc, "too few operands for instruction");
ErrorLoc = Operands[ErrorInfo]->getStartLoc();
if (ErrorLoc == SMLoc())
ErrorLoc = Loc;
}
return Error(ErrorLoc, "invalid operand for instruction");
}
default:
return true;
}
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010AsmParser() {
RegisterMCAsmParser<TMS34010AsmParser> X(getTheTMS34010Target());
}
#define GET_REGISTER_MATCHER
#define GET_MATCHER_IMPLEMENTATION
#include "TMS34010GenAsmMatcher.inc"
@@ -0,0 +1,18 @@
//===-- TMS34010AsmParser.h - TMS34010 assembly parser ----------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// The parser implementation is private to TMS34010AsmParser.cpp; it is
// registered through LLVMInitializeTMS34010AsmParser in that file. This
// header exists so the component follows the target layout.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010ASMPARSER_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010ASMPARSER_H
#endif
@@ -0,0 +1,386 @@
//===-- TMS34010Disassembler.cpp - TMS34010 disassembler --------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// TMS34010 instructions are one to three 16-bit words. The generated decoder
// tables contain the fixed instruction bits; this file supplies the instruction
// length, the register-file decoders and a few post-decode fixups for encodings
// whose register file bit is shared between two operands.
//
//===----------------------------------------------------------------------===//
#include "TMS34010MCTargetDesc.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCDecoder.h"
#include "llvm/MC/MCDecoderOps.h"
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Endian.h"
using namespace llvm;
using namespace llvm::MCD;
#define DEBUG_TYPE "tms34010-disassembler"
typedef MCDisassembler::DecodeStatus DecodeStatus;
namespace {
class TMS34010Disassembler : public MCDisassembler {
public:
TMS34010Disassembler(const MCSubtargetInfo &STI, MCContext &Ctx)
: MCDisassembler(STI, Ctx) {}
DecodeStatus getInstruction(MCInst &MI, uint64_t &Size,
ArrayRef<uint8_t> Bytes, uint64_t Address,
raw_ostream &CStream) const override;
};
} // end anonymous namespace
static MCDisassembler *createTMS34010Disassembler(const Target &T,
const MCSubtargetInfo &STI,
MCContext &Ctx) {
return new TMS34010Disassembler(STI, Ctx);
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010Disassembler() {
TargetRegistry::RegisterMCDisassembler(getTheTMS34010Target(),
createTMS34010Disassembler);
}
static const MCRegister GPRDecoderTable[] = {
TMS34010::A0, TMS34010::A1, TMS34010::A2, TMS34010::A3,
TMS34010::A4, TMS34010::A5, TMS34010::A6, TMS34010::A7,
TMS34010::A8, TMS34010::A9, TMS34010::A10, TMS34010::A11,
TMS34010::A12, TMS34010::A13, TMS34010::A14, TMS34010::A15,
TMS34010::B0, TMS34010::B1, TMS34010::B2, TMS34010::B3,
TMS34010::B4, TMS34010::B5, TMS34010::B6, TMS34010::B7,
TMS34010::B8, TMS34010::B9, TMS34010::B10, TMS34010::B11,
TMS34010::B12, TMS34010::B13, TMS34010::B14, TMS34010::B15};
static DecodeStatus DecodeGPRRegisterClass(MCInst &MI, uint64_t RegNo,
uint64_t Address,
const MCDisassembler *Decoder) {
if (RegNo >= std::size(GPRDecoderTable))
return MCDisassembler::Fail;
MI.addOperand(MCOperand::createReg(GPRDecoderTable[RegNo]));
return MCDisassembler::Success;
}
static DecodeStatus DecodeSETFFs(MCInst &MI, uint64_t Field, uint64_t Address,
const MCDisassembler *Decoder) {
MI.addOperand(MCOperand::createImm(Field == 0 ? 32 : Field));
return MCDisassembler::Success;
}
static DecodeStatus DecodeInvImm(MCInst &MI, uint64_t Field, uint64_t Address,
const MCDisassembler *Decoder) {
MI.addOperand(
MCOperand::createImm(static_cast<uint32_t>(~Field)));
return MCDisassembler::Success;
}
static DecodeStatus DecodeNegK(MCInst &MI, uint64_t Field, uint64_t Address,
const MCDisassembler *Decoder) {
// SRA/SRL store (-K) & 31; zero represents 32.
unsigned K = Field == 0 ? 32 : 32 - Field;
MI.addOperand(MCOperand::createImm(K));
return MCDisassembler::Success;
}
#include "TMS34010GenDisassemblerTables.inc"
/// Length of the instruction at the start of Bytes, in bytes. This mirrors
/// the operands' fixed widths in the .td file; the generated decoder verifies
/// the actual bits. 0 means the input is too short or the opcode is not a
/// supported TMS34010 encoding.
static unsigned instructionLength(uint16_t Op, ArrayRef<uint8_t> Bytes) {
unsigned Len = 2;
uint16_t Subop = Op & 0x01e0;
switch (Op & 0xfe00) {
case 0x0000:
break;
case 0x0200:
if (Subop == 0x0140)
Len = 10; // MOVB @a1, @a2 (not currently decoded)
break;
case 0x0400:
case 0x0600:
if (Subop == 0x0180 || Subop == 0x01a0 || Subop == 0x01e0)
Len = 6;
else if (Subop == 0x01c0)
Len = 10; // MOVE @a1, @a2 (not currently decoded)
break;
case 0x0800:
if (Subop == 0x0180 || Subop == 0x01a0 || Subop == 0x01c0)
Len = 4;
else if (Subop == 0x01e0)
Len = 6;
break;
case 0x0a00:
if (Subop == 0x0100 || Subop == 0x0140 || Subop == 0x01e0)
Len = 4;
else if (Subop == 0x0120 || Subop == 0x0160 || Subop == 0x0180 ||
Subop == 0x01a0 || Subop == 0x01c0)
Len = 6;
break;
case 0x0c00:
if (Subop == 0x0100 || Subop == 0x0140)
Len = 6;
else if (Subop == 0x0120 || Subop == 0x0180 || Subop == 0x01a0 ||
Subop == 0x01c0)
Len = 4;
break;
case 0x0e00:
break;
case 0x1000: case 0x1200: case 0x1400: case 0x1600:
case 0x1800: case 0x1a00: case 0x1c00: case 0x1e00:
case 0x2000: case 0x2200: case 0x2400: case 0x2600:
case 0x2800: case 0x2a00: case 0x2c00: case 0x2e00:
case 0x3000: case 0x3200: case 0x3800: case 0x3a00:
case 0x3c00: case 0x3e00:
case 0x4000: case 0x4200: case 0x4400: case 0x4600:
case 0x4800: case 0x4a00: case 0x4c00: case 0x4e00:
case 0x5000: case 0x5200: case 0x5400: case 0x5600:
case 0x5800: case 0x5a00: case 0x5c00: case 0x5e00:
case 0x6000: case 0x6200: case 0x6400: case 0x6600:
case 0x6800: case 0x6a00: case 0x6c00: case 0x6e00:
break;
case 0x8000: case 0x8200: case 0x8400: case 0x8600:
case 0x8800: case 0x8a00: case 0x8c00: case 0x8e00:
case 0x9000: case 0x9200: case 0x9400: case 0x9600:
case 0x9800: case 0x9a00: case 0x9c00:
case 0xa000: case 0xa200: case 0xa400: case 0xa600:
case 0xa800: case 0xaa00:
break;
case 0xac00: case 0xae00: case 0xb000: case 0xb200:
case 0xb400: case 0xb600:
Len = 4;
break;
case 0xb800: case 0xba00: case 0xbc00:
Len = 6;
break;
case 0xc000: case 0xc200: case 0xc400: case 0xc600:
case 0xc800: case 0xca00: case 0xcc00: case 0xce00: {
uint8_t Lo8 = Op & 0xff;
if (Lo8 == 0x80)
Len = 6;
else if (Lo8 == 0x00)
Len = 4;
break;
}
case 0xd000: case 0xd200:
Len = 4;
break;
case 0xd400: case 0xd600:
if (Subop == 0x0000)
Len = 6;
break;
case 0xde00:
break;
default:
break;
}
if (Bytes.size() < Len)
return 0;
return Len;
}
static bool isBRegister(MCRegister Reg) {
return Reg >= TMS34010::B0 && Reg <= TMS34010::B15;
}
/// Many TMS34010 encodings share bit 4 between the two register operands.
/// TableGen decodes the four-bit source as an A register; recover the real
/// file from the five-bit destination.
static void fixSameFile(MCInst &MI, unsigned SrcIdx, unsigned DstIdx) {
if (SrcIdx >= MI.getNumOperands() || DstIdx >= MI.getNumOperands())
return;
if (!MI.getOperand(SrcIdx).isReg() || !MI.getOperand(DstIdx).isReg())
return;
MCRegister Dst = MI.getOperand(DstIdx).getReg();
if (!isBRegister(Dst))
return;
MCRegister Src = MI.getOperand(SrcIdx).getReg();
unsigned Low = Src >= TMS34010::A0 ? Src - TMS34010::A0 : 0;
if (Low > 15)
return;
MI.getOperand(SrcIdx).setReg(GPRDecoderTable[16 + Low]);
}
/// MOVE register-to-register stores the source file in bit 4 and the
/// cross-file bit in bit 9. The destination file is their xor.
static void fixMoveRegisterFile(MCInst &MI, uint16_t Op) {
if (MI.getNumOperands() < 2)
return;
unsigned SrcLow = (Op >> 5) & 0xf;
unsigned DstLow = Op & 0xf;
bool SrcB = (Op & 0x10) != 0;
bool DstB = SrcB ^ ((Op & 0x0200) != 0);
// MC_MOVE's operand order is (outs dst, ins src).
MI.getOperand(0).setReg(GPRDecoderTable[DstLow + (DstB ? 16 : 0)]);
MI.getOperand(1).setReg(GPRDecoderTable[SrcLow + (SrcB ? 16 : 0)]);
}
DecodeStatus TMS34010Disassembler::getInstruction(MCInst &MI, uint64_t &Size,
ArrayRef<uint8_t> Bytes,
uint64_t Address,
raw_ostream &CStream) const {
if (Bytes.size() < 2) {
Size = 0;
return MCDisassembler::Fail;
}
uint16_t Word0 = support::endian::read16le(Bytes.data());
unsigned Len = instructionLength(Word0, Bytes);
if (!Len) {
Size = 0;
return MCDisassembler::Fail;
}
uint64_t Insn = Word0;
if (Len >= 4)
Insn |= static_cast<uint64_t>(
support::endian::read16le(Bytes.data() + 2))
<< 16;
if (Len >= 6)
Insn |= static_cast<uint64_t>(
support::endian::read16le(Bytes.data() + 4))
<< 32;
// The short ADDI/MOVI/CMPI/SUBI forms are selected by the emitter and are
// not separate .td encodings (they would be ambiguous in the asm matcher),
// so synthesise their MCInsts here.
if (Len == 4) {
uint16_t Base = Word0 & 0xffe0;
if (Base == 0x0b00 || Base == 0x09c0 || Base == 0x0b40 ||
Base == 0x0be0) {
uint16_t Raw = support::endian::read16le(Bytes.data() + 2);
unsigned Opc;
int64_t Value;
switch (Base) {
case 0x0b00:
Opc = TMS34010::MC_ADDI;
Value = static_cast<int16_t>(Raw);
break;
case 0x09c0:
Opc = TMS34010::MC_MOVI;
Value = static_cast<int16_t>(Raw);
break;
case 0x0b40:
Opc = TMS34010::MC_CMPI;
Value = static_cast<uint16_t>(~Raw);
break;
default:
Opc = TMS34010::MC_SUBI;
Value = static_cast<uint16_t>(~Raw);
break;
}
MI.clear();
MI.setOpcode(Opc);
MI.addOperand(MCOperand::createReg(GPRDecoderTable[Word0 & 0x1f]));
MI.addOperand(MCOperand::createImm(Value));
Size = 4;
return MCDisassembler::Success;
}
}
const uint8_t *Table = nullptr;
switch (Len) {
case 2:
Table = DecoderTable16;
break;
case 4:
Table = DecoderTable32;
break;
case 6:
Table = DecoderTable48;
break;
default:
Size = 0;
return MCDisassembler::Fail;
}
MI.clear();
DecodeStatus Result =
decodeInstruction(Table, MI, Insn, Address, this, STI);
if (Result == MCDisassembler::Fail) {
Size = 0;
return Result;
}
// Repair the register-file bit for the encodings that share it.
switch (MI.getOpcode()) {
case TMS34010::MC_MOVE:
fixMoveRegisterFile(MI, Word0);
break;
case TMS34010::MC_ADD:
case TMS34010::MC_SUB:
case TMS34010::MC_CMP:
case TMS34010::MC_AND:
case TMS34010::MC_OR:
case TMS34010::MC_XOR:
case TMS34010::MC_MPYS:
case TMS34010::MC_MPYU:
case TMS34010::MC_DIVS:
case TMS34010::MC_DIVU:
case TMS34010::MC_SLA:
case TMS34010::MC_SLL:
case TMS34010::MC_SRA:
case TMS34010::MC_SRL:
case TMS34010::MC_RL:
case TMS34010::MC_MOVE_STORE:
case TMS34010::MC_MOVE_STORE_POST:
case TMS34010::MC_MOVE_STORE_PRE:
case TMS34010::MC_MOVE_BOTH:
case TMS34010::MC_MOVB_BOTH:
case TMS34010::MC_MOVE_BOTH_POST:
case TMS34010::MC_MOVE_BOTH_PRE:
case TMS34010::MC_MOVE_STORE_DISP:
case TMS34010::MC_MOVB_STORE:
case TMS34010::MC_MOVB_STORE_DISP:
fixSameFile(MI, 0, 1);
break;
case TMS34010::MC_MOVE_LOAD:
case TMS34010::MC_MOVE_LOAD_POST:
case TMS34010::MC_MOVE_LOAD_PRE:
case TMS34010::MC_MOVE_LOAD_DISP:
case TMS34010::MC_MOVB_LOAD:
case TMS34010::MC_MOVB_LOAD_DISP:
fixSameFile(MI, 1, 0);
break;
case TMS34010::MC_MOVE_BOTH_DISP:
case TMS34010::MC_MOVE_DISP_POST:
case TMS34010::MC_MOVB_BOTH_DISP:
fixSameFile(MI, 0, 2);
break;
default:
break;
}
// CLR and XOR share an encoding: XOR Rd, Rd is CLR. CLR is
// isAsmParserOnly so the decoder table contains only XOR; rewrite it after
// the register-file repair, otherwise B-file CLR cannot compare equal.
if (MI.getOpcode() == TMS34010::MC_XOR && MI.getNumOperands() == 2 &&
MI.getOperand(0).isReg() && MI.getOperand(1).isReg() &&
MI.getOperand(0).getReg() == MI.getOperand(1).getReg()) {
MCRegister Dst = MI.getOperand(1).getReg();
MI.clear();
MI.setOpcode(TMS34010::MC_CLR);
MI.addOperand(MCOperand::createReg(Dst));
}
Size = Len;
return Result;
}
@@ -0,0 +1,18 @@
//===-- TMS34010Disassembler.h - TMS34010 disassembler ----------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// The decoder is private to TMS34010Disassembler.cpp; it is registered through
// LLVMInitializeTMS34010Disassembler in that file. This header exists so the
// component follows the target layout.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010DISASSEMBLER_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010DISASSEMBLER_H
#endif
@@ -7,15 +7,36 @@
//===----------------------------------------------------------------------===//
#include "TMS34010InstPrinter.h"
#include "TMS34010MCTargetDesc.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
#include "TMS34010GenAsmWriter.inc"
namespace {
const MCRegister GPRTable[] = {
TMS34010::A0, TMS34010::A1, TMS34010::A2, TMS34010::A3,
TMS34010::A4, TMS34010::A5, TMS34010::A6, TMS34010::A7,
TMS34010::A8, TMS34010::A9, TMS34010::A10, TMS34010::A11,
TMS34010::A12, TMS34010::A13, TMS34010::A14, TMS34010::A15,
TMS34010::B0, TMS34010::B1, TMS34010::B2, TMS34010::B3,
TMS34010::B4, TMS34010::B5, TMS34010::B6, TMS34010::B7,
TMS34010::B8, TMS34010::B9, TMS34010::B10, TMS34010::B11,
TMS34010::B12, TMS34010::B13, TMS34010::B14, TMS34010::B15};
} // end anonymous namespace
void TMS34010InstPrinter::printRegName(raw_ostream &O, MCRegister Reg) {
// GSPA spells register 15 of the A file "SP" and rejects "A15".
if (Reg == TMS34010::A15) {
O << "SP";
return;
}
O << getRegisterName(Reg);
}
@@ -23,6 +44,29 @@ void TMS34010InstPrinter::printInst(const MCInst *MI, uint64_t Address,
StringRef Annot,
const MCSubtargetInfo &STI,
raw_ostream &O) {
const unsigned Opcode = MI->getOpcode();
if ((Opcode == TMS34010::MC_MMTM || Opcode == TMS34010::MC_MMFM) &&
MI->getNumOperands() == 2 && MI->getOperand(0).isReg() &&
MI->getOperand(1).isImm()) {
O << "\t" << (Opcode == TMS34010::MC_MMTM ? "MMTM " : "MMFM ");
printRegName(O, MI->getOperand(0).getReg());
unsigned Mask = static_cast<unsigned>(MI->getOperand(1).getImm()) & 0xffff;
unsigned File = (MI->getOperand(0).getReg() >= TMS34010::B0 &&
MI->getOperand(0).getReg() <= TMS34010::B15)
? 16
: 0;
for (unsigned I = 0; I < 16; ++I) {
unsigned Bit = Opcode == TMS34010::MC_MMTM ? (Mask >> (15 - I)) & 1
: (Mask >> I) & 1;
if (!Bit)
continue;
O << ", ";
printRegName(O, GPRTable[File + I]);
}
printAnnotation(O, Annot);
return;
}
printInstruction(MI, Address, O);
printAnnotation(O, Annot);
}
@@ -31,7 +75,7 @@ void TMS34010InstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
raw_ostream &O) {
const MCOperand &Op = MI->getOperand(OpNo);
if (Op.isReg()) {
O << getRegisterName(Op.getReg());
printRegName(O, Op.getReg());
} else if (Op.isImm()) {
O << Op.getImm();
} else {
@@ -0,0 +1,188 @@
//===-- TMS34010MCAsmBackend.cpp - TMS34010 assembler backend -------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "TMS34010MCAsmBackend.h"
#include "TMS34010MCObjectWriter.h"
#include "TMS34010MCTargetDesc.h"
#include "llvm/ADT/Twine.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCFixup.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCSection.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/MCELFObjectWriter.h"
#include "llvm/MC/MCValue.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
using namespace llvm;
namespace {
/// `llvm-mc -triple=tms34010` defaults to the ELF object format (the triple
/// has no OS component), while the CodeGen path uses the target's TI-COFF
/// writer directly. This tiny ELF target writer keeps the generic llvm-mc
/// object path self-consistent; the MC round-trip only needs the .text bytes.
class TMS34010ELFObjectTargetWriter : public MCELFObjectTargetWriter {
public:
TMS34010ELFObjectTargetWriter()
: MCELFObjectTargetWriter(/*Is64Bit=*/false, ELF::ELFOSABI_NONE,
ELF::EM_NONE, /*HasRelocationAddend=*/false) {}
unsigned getRelocType(const MCFixup &Fixup, const MCValue &Target,
bool IsPCRel) const override {
// The MC tests use absolute numeric operands, so this path is not
// exercised; TI-COFF relocations are handled by TMS34010COFFObjectWriter.
return 0;
}
};
/// TI-COFF relocations store the addend in the relocated field and use bit
/// addresses. A defined symbol contributes its section-relative bit offset;
/// the expression constant is already a bit offset (tms-bitaddr --scale
/// scaled address expressions, and --normalize keeps late GEP offsets in
/// bits).
uint64_t tms34010BitAddend(const MCAssembler &Asm, const MCValue &Target) {
int64_t Addend = Target.getConstant();
if (const MCSymbol *Add = Target.getAddSym())
if (Add->isDefined() && Add->isInSection())
Addend += static_cast<int64_t>(Asm.getSymbolOffset(*Add)) * 8;
return static_cast<uint64_t>(Addend);
}
} // end anonymous namespace
TMS34010MCAsmBackend::TMS34010MCAsmBackend(const Target &T)
: MCAsmBackend(llvm::endianness::little) {}
MCFixupKindInfo
TMS34010MCAsmBackend::getFixupKindInfo(MCFixupKind Kind) const {
if (Kind == TMS34010::FK_JR8)
return {"FK_TMS34010_JR8", 0, 8, 0};
// The built-in data fixups are the only other kinds the object path produces.
return MCAsmBackend::getFixupKindInfo(Kind);
}
/// Decode the backend's PC-relative byte value into the ISA's signed word
/// displacement. The fixup sits on the instruction word (the displacement is
/// bits 7-0, the low byte in memory), so the ISA's reference point (PC+16) is
/// two bytes past it:
/// disp = (target - (pc + 2)) / 2 == (Value - 2) / 2.
static std::optional<int64_t> getJRDisplacement(uint64_t Value) {
int64_t Delta = static_cast<int64_t>(Value) - 2;
if (Delta % 2)
return std::nullopt;
return Delta / 2;
}
bool TMS34010MCAsmBackend::mayNeedRelaxation(
unsigned Opcode, ArrayRef<MCOperand> Operands,
const MCSubtargetInfo &STI) const {
// Only a symbolic target can be out of range. The assembler's JR/JRcc take a
// numeric displacement and encode exactly what the source asked for.
return TMS34010::isShortBranch(Opcode) && !Operands.empty() &&
Operands[0].isExpr();
}
bool TMS34010MCAsmBackend::fixupNeedsRelaxationAdvanced(
const MCFragment &F, const MCFixup &Fixup, const MCValue &Target,
uint64_t Value, bool Resolved) const {
if (Fixup.getKind() != TMS34010::FK_JR8)
return false;
// A cross-section or undefined target cannot be resolved to a displacement.
if (!Resolved)
return true;
std::optional<int64_t> Disp = getJRDisplacement(Value);
// Zero is the 16-bit relative form and -128 is the absolute-JA marker, so
// neither is encodable in one word. Anything else outside [-127,127] needs
// the three-word absolute form.
return !Disp || *Disp == 0 || *Disp < -127 || *Disp > 127;
}
void TMS34010MCAsmBackend::relaxInstruction(
MCInst &Inst, const MCSubtargetInfo &STI) const {
unsigned Long = TMS34010::getLongBranchOpcode(Inst.getOpcode());
if (!Long)
llvm_unreachable("TMS34010: no wider form for this branch");
Inst.setOpcode(Long);
}
void TMS34010MCAsmBackend::applyFixup(const MCFragment &F,
const MCFixup &Fixup,
const MCValue &Target, uint8_t *Data,
uint64_t Value, bool IsResolved) {
if (Fixup.getKind() == TMS34010::FK_JR8) {
if (!IsResolved)
report_fatal_error(
"TMS34010: an unresolved JR displacement must be relaxed");
std::optional<int64_t> Disp = getJRDisplacement(Value);
if (!Disp || *Disp == 0 || *Disp < -127 || *Disp > 127)
report_fatal_error(
"TMS34010: JR displacement is out of range after relaxation");
Data[0] = static_cast<uint8_t>(static_cast<int8_t>(*Disp));
return;
}
if (Target.getSubSym())
report_fatal_error(
"TMS34010: symbol differences are not supported in TI-COFF objects");
if (!IsResolved && Fixup.getKind() != FK_Data_4)
report_fatal_error(
"TMS34010: only 32-bit symbol relocations are supported");
// An unresolved symbol reference becomes an R-RELLONG relocation. The
// relocated field holds the addend (a bit offset), as GSPA 2.00 does.
if (!IsResolved)
maybeAddReloc(F, Fixup, Target, Value, /*IsResolved=*/false);
uint64_t Field = IsResolved ? Value : tms34010BitAddend(*Asm, Target);
switch (Fixup.getKind()) {
case FK_Data_1:
Data[0] = static_cast<uint8_t>(Field);
break;
case FK_Data_2:
Data[0] = static_cast<uint8_t>(Field);
Data[1] = static_cast<uint8_t>(Field >> 8);
break;
case FK_Data_4:
Data[0] = static_cast<uint8_t>(Field);
Data[1] = static_cast<uint8_t>(Field >> 8);
Data[2] = static_cast<uint8_t>(Field >> 16);
Data[3] = static_cast<uint8_t>(Field >> 24);
break;
default:
report_fatal_error(Twine("TMS34010: unsupported fixup kind ") +
Twine(Fixup.getKind()));
}
}
bool TMS34010MCAsmBackend::writeNopData(raw_ostream &OS, uint64_t Count,
const MCSubtargetInfo *STI) const {
if (Count % 2)
return false;
// NOP is 0x0300, stored little-endian.
for (uint64_t I = 0; I < Count; I += 2)
OS << '\x00' << '\x03';
return true;
}
std::unique_ptr<MCObjectTargetWriter>
TMS34010MCAsmBackend::createObjectTargetWriter() const {
return std::make_unique<TMS34010ELFObjectTargetWriter>();
}
std::unique_ptr<MCObjectWriter>
TMS34010MCAsmBackend::createObjectWriter(
raw_pwrite_stream &OS, std::shared_ptr<TMS34010GlobalSymbols> Globals) const {
return std::make_unique<TMS34010COFFObjectWriter>(OS, std::move(Globals));
}
@@ -0,0 +1,62 @@
//===-- TMS34010MCAsmBackend.h - TMS34010 assembler backend -----*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCASMBACKEND_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCASMBACKEND_H
#include "TMS34010MCObjectWriter.h"
#include "llvm/MC/MCAsmBackend.h"
namespace llvm {
class MCObjectTargetWriter;
class MCObjectWriter;
class MCRegisterInfo;
class MCSubtargetInfo;
class MCTargetOptions;
class MCInst;
class MCOperand;
class Target;
class raw_pwrite_stream;
class TMS34010MCAsmBackend : public MCAsmBackend {
public:
TMS34010MCAsmBackend(const Target &T);
MCFixupKindInfo getFixupKindInfo(MCFixupKind Kind) const override;
void applyFixup(const MCFragment &F, const MCFixup &Fixup,
const MCValue &Target, uint8_t *Data, uint64_t Value,
bool IsResolved) override;
bool mayNeedRelaxation(unsigned Opcode, ArrayRef<MCOperand> Operands,
const MCSubtargetInfo &STI) const override;
bool fixupNeedsRelaxationAdvanced(const MCFragment &F, const MCFixup &Fixup,
const MCValue &Target, uint64_t Value,
bool Resolved) const override;
void relaxInstruction(MCInst &Inst,
const MCSubtargetInfo &STI) const override;
bool writeNopData(raw_ostream &OS, uint64_t Count,
const MCSubtargetInfo *STI) const override;
std::unique_ptr<MCObjectTargetWriter> createObjectTargetWriter() const override;
/// Build the TI-COFF writer used by `llc -filetype=obj`. The generic
/// MCAsmBackend::createObjectWriter only knows LLVM's built-in object
/// formats, so the target machine calls this directly.
std::unique_ptr<MCObjectWriter>
createObjectWriter(raw_pwrite_stream &OS,
std::shared_ptr<TMS34010GlobalSymbols> Globals) const;
};
} // namespace llvm
#endif // LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCASMBACKEND_H
@@ -19,7 +19,17 @@ TMS34010MCAsmInfo::TMS34010MCAsmInfo(const Triple &TT,
CalleeSaveStackSlotSize = 4;
CommentString = ";";
SeparatorString = ",";
// A comma separates operands, not statements. GSPA statements end at the
// newline, so the lexer must not turn a comma into EndOfStatement.
SeparatorString = "\n";
// GSPA 2.00 identifiers accept only alphanumerics and `_`, so block labels
// must be dot-free. Set both prefixes explicitly rather than relying on the
// generic defaults: InternalSymbolPrefix names basic-block labels (LBB0_1),
// and LinkerPrivateGlobalPrefix covers jump tables and linker-private
// symbols, keeping every generated symbol GSPA-valid and stable.
InternalSymbolPrefix = "L";
LinkerPrivateGlobalPrefix = "L";
// GSPA directives express alignment and section sizes in bits.
AlignmentIsInBytes = false;
@@ -0,0 +1,310 @@
//===-- TMS34010MCCodeEmitter.cpp - TMS34010 code emitter -----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// TMS34010 instructions are one to three 16-bit words, stored little-endian.
// TableGen supplies the base opcode and operand placement; this file fixes the
// two immediate forms GSPA 2.00 selects between.
//
//===----------------------------------------------------------------------===//
#include "TMS34010MCCodeEmitter.h"
#include "TMS34010MCTargetDesc.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCFixup.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
using namespace llvm;
namespace {
class TMS34010MCCodeEmitter : public MCCodeEmitter {
MCContext &Ctx;
const MCInstrInfo &MCII;
/// TableGen'erated: the fixed opcode bits with every named operand merged
/// into its field.
uint64_t getBinaryCodeForInstr(const MCInst &MI,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
/// Return the encoding of one operand, recording a fixup when the operand
/// is an unresolved expression.
unsigned getMachineOpValue(const MCInst &MI, const MCOperand &MO,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
/// SETF encodes fs == 32 as zero.
unsigned getSETFFsValue(const MCInst &MI, unsigned OpNo,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
/// CMPI/SUBI/ANDI store the one's complement of the immediate.
unsigned getInvImmValue(const MCInst &MI, unsigned OpNo,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
/// SRA/SRL store (-K) & 31.
unsigned getNegKValue(const MCInst &MI, unsigned OpNo,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const;
public:
TMS34010MCCodeEmitter(MCContext &Ctx, const MCInstrInfo &MCII)
: Ctx(Ctx), MCII(MCII) {}
void encodeInstruction(const MCInst &MI, SmallVectorImpl<char> &CB,
SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const override;
};
} // end anonymous namespace
unsigned TMS34010MCCodeEmitter::getMachineOpValue(
const MCInst &MI, const MCOperand &MO, SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
if (MO.isReg())
return Ctx.getRegisterInfo()->getEncodingValue(MO.getReg());
if (MO.isImm())
return static_cast<unsigned>(MO.getImm());
if (MO.isExpr()) {
// The instruction word carries a placeholder; encodeInstruction records
// an FK_Data_4 fixup over the 32-bit field so the backend can write the
// bit-address addend and the object writer can emit an R-RELLONG
// relocation. The operand itself is encoded below as zero.
return 0;
}
llvm_unreachable("Unsupported TMS34010 operand");
}
unsigned TMS34010MCCodeEmitter::getSETFFsValue(
const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
const MCOperand &MO = MI.getOperand(OpNo);
if (!MO.isImm())
report_fatal_error("TMS34010: SETF field size must be a constant");
int64_t Value = MO.getImm();
// The ISA stores fs == 32 as zero; 0 is not a legal source spelling in
// GSPA 2.00, which warns about it.
if (Value == 32)
return 0;
if (Value < 0 || Value > 31)
report_fatal_error("TMS34010: SETF field size must be 1..32");
return static_cast<unsigned>(Value);
}
unsigned TMS34010MCCodeEmitter::getInvImmValue(
const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
const MCOperand &MO = MI.getOperand(OpNo);
if (!MO.isImm())
return 0;
return static_cast<uint32_t>(~static_cast<uint64_t>(MO.getImm()));
}
unsigned TMS34010MCCodeEmitter::getNegKValue(
const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups,
const MCSubtargetInfo &STI) const {
const MCOperand &MO = MI.getOperand(OpNo);
if (!MO.isImm())
report_fatal_error("TMS34010: shift count must be a constant");
return static_cast<unsigned>((-MO.getImm()) & 0x1F);
}
static void writeWord(SmallVectorImpl<char> &CB, uint16_t Value) {
support::endian::write<uint16_t>(CB, Value, llvm::endianness::little);
}
void TMS34010MCCodeEmitter::encodeInstruction(
const MCInst &MI, SmallVectorImpl<char> &CB,
SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const {
const unsigned Opcode = MI.getOpcode();
// The one-word relative branches take a symbolic target in CodeGen. Their
// operand is not an absolute address field: record an 8-bit PC-relative fixup
// on the displacement byte; the backend subtracts PC+16 and converts the
// result to the ISA's signed word displacement. A numeric operand (the
// assembler's JR/JRcc) needs no fixup and falls through to the generated
// encoder below.
if (TMS34010::isShortBranch(Opcode) && MI.getNumOperands() == 1 &&
MI.getOperand(0).isExpr()) {
uint16_t Bits =
static_cast<uint16_t>(getBinaryCodeForInstr(MI, Fixups, STI));
writeWord(CB, Bits);
Fixups.push_back(MCFixup::create(0, MI.getOperand(0).getExpr(),
TMS34010::FK_JR8, /*PCRel=*/true));
return;
}
// SRAK/SRLK (the CodeGen instructions) store (-K) & 31 in bits 9..5, but
// TableGen drops that negated field expression for these non-MC defs. The
// MC_SRAK/MC_SRLK assembler defs use TMS34010NegK and are emitted by the
// generated table. Encode the CodeGen form explicitly so `llc -filetype=obj`
// matches the `llc -filetype=asm` text (GSPA) encoding.
if (Opcode == TMS34010::SRAK || Opcode == TMS34010::SRLK) {
unsigned Dst =
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(0).getReg());
const MCOperand &MO = MI.getOperand(2);
if (!MO.isImm())
report_fatal_error("TMS34010: shift count must be a constant");
uint16_t Bits =
static_cast<uint16_t>(Opcode == TMS34010::SRAK ? 0x2800 : 0x2C00);
Bits |= static_cast<uint16_t>(((-MO.getImm()) & 0x1F) << 5);
Bits |= static_cast<uint16_t>(Dst & 0x1F);
writeWord(CB, Bits);
return;
}
// MOVE register-to-register has a cross-file bit (bit 9) that is not a
// straight operand field: it is src{4} xor dst{4}. TableGen's fixed-field
// emitter cannot express that, so encode this one instruction manually.
if (Opcode == TMS34010::MC_MOVE) {
unsigned Dst =
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(0).getReg());
unsigned Src =
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(1).getReg());
uint16_t Bits = 0x4C00;
Bits |= static_cast<uint16_t>(((Src >> 4) ^ (Dst >> 4)) << 9);
Bits |= static_cast<uint16_t>((Src & 0xF) << 5);
Bits |= static_cast<uint16_t>(Src & 0x10);
Bits |= static_cast<uint16_t>(Dst & 0xF);
writeWord(CB, Bits);
return;
}
// GSPA 2.00 encodes ADDI, MOVI, CMPI and SUBI with the short 16-bit
// immediate form when the value fits in a signed 16-bit field, and with the
// long 32-bit form otherwise. For ADDI, MOVI and CMPI the long encoding is
// the .td encoding and the short form clears bit 5; SUBI has a separate
// short opcode (0x0BE0) and stores the one's complement. A symbol operand
// is always long and needs a relocation.
bool ShortImm = false;
unsigned ImmOp = 0;
switch (Opcode) {
case TMS34010::ADDI: ImmOp = 2; ShortImm = true; break;
case TMS34010::ADDSTK: ImmOp = 0; ShortImm = true; break;
case TMS34010::MOVI: ImmOp = 1; ShortImm = true; break;
case TMS34010::CMPI: ImmOp = 0; ShortImm = true; break;
case TMS34010::MC_ADDI: ImmOp = 1; ShortImm = true; break;
case TMS34010::MC_SUBI: ImmOp = 1; ShortImm = true; break;
case TMS34010::MC_CMPI: ImmOp = 1; ShortImm = true; break;
case TMS34010::MC_MOVI: ImmOp = 1; ShortImm = true; break;
case TMS34010::MC_ANDI:
case TMS34010::MC_ORI:
case TMS34010::MC_XORI:
break;
default:
break;
}
if (ShortImm) {
const MCOperand &MO = MI.getOperand(ImmOp);
if (MO.isImm() && isInt<16>(MO.getImm())) {
if (Opcode == TMS34010::MC_SUBI) {
// SUBI's short form is a different opcode word, not a bit in the long
// form. Operand 0 is the destination (MC operand order is outs, ins).
uint64_t Dst =
Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(0).getReg());
writeWord(CB, static_cast<uint16_t>(0x0BE0 | (Dst & 0x1F)));
writeWord(CB, static_cast<uint16_t>(~MO.getImm()));
return;
}
uint64_t Bits = getBinaryCodeForInstr(MI, Fixups, STI);
writeWord(CB, static_cast<uint16_t>(Bits) & ~uint16_t{0x0020});
bool Invert = Opcode == TMS34010::MC_CMPI || Opcode == TMS34010::CMPI;
writeWord(CB, static_cast<uint16_t>(Invert ? ~MO.getImm()
: MO.getImm()));
return;
}
}
uint64_t Bits = getBinaryCodeForInstr(MI, Fixups, STI);
unsigned Size = MCII.get(Opcode).getSize();
for (unsigned Word = 0; Word < Size / 2; ++Word) {
writeWord(CB, static_cast<uint16_t>(Bits));
Bits >>= 16;
}
// Every TMS34010 absolute-address operand occupies the 32-bit word(s)
// following the opcode. Record an FK_Data_4 fixup over that field; the
// backend turns an unresolved symbol reference into a bit-addend TI-COFF
// relocation. The operand index differs per instruction because TableGen
// orders MCInst operands as outs then ins.
int AddrOp = -1;
switch (Opcode) {
case TMS34010::MOVI:
case TMS34010::MC_MOVI:
// CMPI, SUBI and ANDI store the one's complement of the operand; a symbol
// addend would have to be complemented too, which GSPA does not express.
case TMS34010::MC_ADDI:
case TMS34010::MC_ORI:
case TMS34010::MC_XORI:
AddrOp = 1;
break;
case TMS34010::MC_MOVE_ABS_STORE:
case TMS34010::MC_MOVE_ABS_LOAD:
case TMS34010::MC_MOVB_ABS_STORE:
case TMS34010::MC_MOVB_ABS_LOAD:
AddrOp = 1;
break;
case TMS34010::MC_MOVE_ABS_POST:
case TMS34010::CALLA:
case TMS34010::MC_CALLA:
case TMS34010::JUncond:
AddrOp = 0;
break;
default:
if (MCII.get(Opcode).isBranch())
AddrOp = 0;
break;
}
bool SawExpr = false;
if (AddrOp >= 0 && AddrOp < static_cast<int>(MI.getNumOperands())) {
const MCOperand &MO = MI.getOperand(AddrOp);
if (MO.isExpr()) {
// The fixup covers the opcode word plus a 32-bit absolute field. A
// future shorter branch must not silently acquire a misplaced
// relocation.
if (Size < 6)
report_fatal_error(
"TMS34010: symbol operand field is not a 32-bit absolute field");
Fixups.push_back(MCFixup::create(2, MO.getExpr(), FK_Data_4));
SawExpr = true;
}
}
// Any other instruction with an unresolved operand would silently encode
// zero; refuse instead of producing a wrong object. A 16-bit displacement
// or a field-size operand must be resolved by the assembler.
if (!SawExpr) {
for (unsigned I = 0, E = MI.getNumOperands(); I != E; ++I)
if (MI.getOperand(I).isExpr())
report_fatal_error(
"TMS34010: symbol operands are only supported for absolute "
"address/immediate fields");
}
}
MCCodeEmitter *llvm::createTMS34010MCCodeEmitter(const MCInstrInfo &MCII,
MCContext &Ctx) {
return new TMS34010MCCodeEmitter(Ctx, MCII);
}
#include "TMS34010GenMCCodeEmitter.inc"
@@ -0,0 +1,25 @@
//===-- TMS34010MCCodeEmitter.h - TMS34010 code emitter ---------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCCODEEMITTER_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCCODEEMITTER_H
#include "llvm/MC/MCCodeEmitter.h"
namespace llvm {
class MCContext;
class MCInstrInfo;
/// Create the TMS34010 machine code emitter.
MCCodeEmitter *createTMS34010MCCodeEmitter(const MCInstrInfo &MII,
MCContext &Ctx);
} // namespace llvm
#endif // LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCCODEEMITTER_H
@@ -0,0 +1,439 @@
//===-- TMS34010MCObjectWriter.cpp - TI-COFF object writer ---------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// The TI-COFF dialect is the one GSPA 2.00 and GSPLNK 2.00 use (see
// build/m3/m3.coff and vendor/vt1200/dwt/loader/coff.py):
//
// file header: machine 0x0090, optional-header size 0, flags 0x0100
// section: name, paddr/vaddr 0, size in bits, raw/relocation file
// offsets in bytes, flags 0x20 (.text) / 0x40 (.data) /
// 0x80 (.bss)
// symbol: section symbols first, class C_STAT (3) with one 18-byte aux
// record; globals class C_EXT (2) with a bit-address value
// relocation: 10 bytes {bit address, symbol index, type}; type 0x11 is
// R-RELLONG and its field holds the bit-address addend
//
// A same-section reference uses symbol index 0xFFFF and the field holds the
// section-relative bit offset, exactly as GSPA emits. A cross-section
// reference uses the target section symbol and the field holds the target
// symbol's section-relative bit offset. An undefined reference uses its
// C_EXT symbol index and the field holds the expression constant.
//
//===----------------------------------------------------------------------===//
#include "TMS34010MCObjectWriter.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCFixup.h"
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCSection.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/MCValue.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <string>
using namespace llvm;
namespace {
constexpr uint16_t MachineTMS34010 = 0x0090;
constexpr uint16_t FileFlagsAR32WR = 0x0100;
constexpr uint16_t SectionFlagText = 0x20;
constexpr uint16_t SectionFlagData = 0x40;
constexpr uint16_t SectionFlagBss = 0x80;
constexpr uint16_t RelocRellong = 0x11;
constexpr uint8_t ClassExternal = 2; // C_EXT
constexpr uint8_t ClassStatic = 3; // C_STAT (section symbol)
void append16(SmallVectorImpl<char> &V, uint16_t Value) {
support::endian::write<uint16_t>(V, Value, llvm::endianness::little);
}
void append32(SmallVectorImpl<char> &V, uint32_t Value) {
support::endian::write<uint32_t>(V, Value, llvm::endianness::little);
}
void write16(raw_pwrite_stream &OS, uint16_t Value) {
support::endian::write<uint16_t>(OS, Value, llvm::endianness::little);
}
void write32(raw_pwrite_stream &OS, uint32_t Value) {
support::endian::write<uint32_t>(OS, Value, llvm::endianness::little);
}
uint16_t flagsForSectionName(StringRef Name) {
if (Name == ".text")
return SectionFlagText;
if (Name == ".data")
return SectionFlagData;
if (Name == ".bss")
return SectionFlagBss;
llvm_unreachable("not a TI-COFF section");
}
} // end anonymous namespace
bool TMS34010MCStreamer::emitSymbolAttribute(MCSymbol *Symbol,
MCSymbolAttr Attribute) {
switch (Attribute) {
case MCSA_Global:
case MCSA_Weak:
case MCSA_WeakDefinition:
Globals->insert(Symbol);
break;
case MCSA_Local:
Globals->erase(Symbol);
break;
default:
// Visibility and type attributes do not change GSPA's flat symbol table.
break;
}
return true;
}
void TMS34010MCStreamer::emitCommonSymbol(MCSymbol *Symbol, uint64_t Size,
Align ByteAlignment) {
// Common symbols are writable zero-initialized storage. GSPA has no .comm
// directive, so place them in the virtual .bss section like an ordinary
// zero-initialized definition. Every .bss allocation is word-granular:
// GSPA and the VT1200 loader require a section size that is a whole number
// of 16-bit words.
unsigned AlignBytes = std::max<unsigned>(ByteAlignment.value(), 2);
Globals->insert(Symbol);
switchSection(getContext().getObjectFileInfo()->getBSSSection());
emitValueToAlignment(Align(AlignBytes));
emitLabel(Symbol);
emitZeros(Size);
emitValueToAlignment(Align(2));
}
void TMS34010COFFObjectWriter::recordRelocation(const MCFragment &F,
const MCFixup &Fixup,
MCValue Target,
uint64_t &FixedValue) {
if (Target.getSubSym())
report_fatal_error(
"TMS34010: symbol differences are not supported in TI-COFF objects");
const MCSymbol *Symbol = Target.getAddSym();
if (!Symbol)
report_fatal_error("TMS34010: relocation without a symbol");
// The backend has already written the bit-address addend into the relocated
// field; the writer only needs the target symbol and its section here.
Relocations.push_back(
{F.getParent(), Asm->getFragmentOffset(F) + Fixup.getOffset(), Symbol});
}
uint64_t TMS34010COFFObjectWriter::writeObject() {
const uint64_t Start = OS.tell();
struct SectionInfo {
StringRef Name;
const MCSection *Sec;
uint16_t Flags;
uint16_t SecNo;
SmallString<256> Data;
uint32_t RawPtr = 0;
uint32_t RelPtr = 0;
uint32_t SizeBits = 0;
uint16_t NReloc = 0;
};
// GSPA always writes .text, .data and .bss, in that order, even when a
// section is empty. Match that so the linker sees the same shape.
static constexpr StringLiteral Names[] = {".text", ".data", ".bss"};
SmallVector<SectionInfo, 3> Sections;
for (StringLiteral Name : Names) {
const MCSection *Found = nullptr;
for (const MCSection &Sec : *Asm)
if (Sec.getName() == Name) {
Found = &Sec;
break;
}
Sections.push_back({Name, Found, flagsForSectionName(Name),
static_cast<uint16_t>(Sections.size() + 1), {}, 0, 0,
0, 0});
}
// A registered non-empty section that is not one of the three TI-COFF
// sections would be silently lost; fail loudly instead.
for (const MCSection &Sec : *Asm) {
bool Known = false;
for (const SectionInfo &S : Sections)
if (S.Name == Sec.getName())
Known = true;
if (!Known && Sec.begin() != Sec.end())
report_fatal_error(Twine("TMS34010: section '") + Sec.getName() +
"' has no TI-COFF representation");
}
DenseMap<const MCSection *, unsigned> SectionIndex;
for (const SectionInfo &S : Sections)
if (S.Sec)
SectionIndex[S.Sec] = S.SecNo - 1;
// Buffer each section's relocated bytes. .bss is virtual and has no file
// bytes, so only its bit size is recorded.
for (SectionInfo &S : Sections) {
if (!S.Sec)
continue;
if (S.Flags == SectionFlagBss) {
S.SizeBits = static_cast<uint32_t>(Asm->getSectionAddressSize(*S.Sec) * 8);
} else {
raw_svector_ostream DataOS(S.Data);
Asm->writeSectionData(DataOS, S.Sec);
S.SizeBits = static_cast<uint32_t>(S.Data.size() * 8);
}
}
const SectionInfo *Text = &Sections[0];
if (!Text->Sec || Text->Data.empty())
report_fatal_error("TMS34010: object has no .text contents");
// Group relocations by section and remember which undefined symbols they
// reference. GSPA emits a C_EXT entry for every undefined symbol that a
// relocation mentions.
for (SectionInfo &S : Sections) {
if (S.Flags == SectionFlagBss)
continue;
for (const PendingReloc &R : Relocations)
if (R.Section == S.Sec)
++S.NReloc;
}
struct SymbolRecord {
std::string Name;
uint32_t Value;
int16_t Section;
uint8_t Class;
uint8_t NumAux;
uint32_t AuxSizeBits;
uint16_t AuxNReloc;
};
SmallVector<SymbolRecord, 16> Symbols;
DenseMap<const MCSymbol *, uint32_t> SymbolIndex;
// Section symbols first: one record plus one aux record each. The aux
// record repeats the section size in bits, as GSPA writes it.
for (const SectionInfo &S : Sections)
Symbols.push_back({S.Name.str(), 0, static_cast<int16_t>(S.SecNo),
ClassStatic, 1, S.SizeBits, S.NReloc});
// Section symbols occupy two records each (record + aux). Globals follow
// them and have no aux records, so record indices advance by one.
uint32_t NextRecordIndex = 2u * static_cast<uint32_t>(Sections.size());
// Defined globals. Their values are section-relative bit addresses.
for (const MCSymbol &Sym : Asm->symbols()) {
if (Sym.isVariable() || Sym.isTemporary())
continue;
if (!Sym.isDefined())
continue;
if (!Globals->contains(&Sym))
continue;
StringRef Name = Sym.getName();
if (Name.empty() || Name.starts_with("."))
continue;
if (!Sym.isInSection())
report_fatal_error(Twine("TMS34010: absolute global '") + Name +
"' has no TI-COFF representation");
const MCSection &Sec = Sym.getSection();
auto It = SectionIndex.find(&Sec);
if (It == SectionIndex.end())
report_fatal_error(Twine("TMS34010: global '") + Name +
"' is in section '" + Sec.getName() +
"' with no TI-COFF representation");
SymbolIndex[&Sym] = NextRecordIndex++;
Symbols.push_back({Name.str(),
static_cast<uint32_t>(Asm->getSymbolOffset(Sym) * 8),
static_cast<int16_t>(It->second + 1), ClassExternal, 0,
0, 0});
}
// Undefined symbols referenced by a relocation, in symbol-table order.
DenseMap<const MCSymbol *, bool> UndefinedReferenced;
for (const PendingReloc &R : Relocations)
if (R.Symbol->isUndefined())
UndefinedReferenced[R.Symbol] = true;
for (const MCSymbol &Sym : Asm->symbols()) {
if (Sym.isVariable() || Sym.isTemporary())
continue;
if (!Sym.isUndefined() || !UndefinedReferenced.contains(&Sym))
continue;
StringRef Name = Sym.getName();
if (Name.empty() || Name.starts_with("."))
continue;
SymbolIndex[&Sym] = NextRecordIndex++;
Symbols.push_back({Name.str(), 0, 0, ClassExternal, 0, 0, 0});
}
// Resolve relocation targets to symbol-table indices. A target section
// symbol is always at record index 2 * (section number - 1) because each
// section symbol is followed by exactly one aux record.
struct FinalReloc {
uint32_t AddrBits;
uint32_t Symbol;
};
DenseMap<const MCSection *, SmallVector<FinalReloc, 4>> RelocsBySection;
for (const PendingReloc &R : Relocations) {
uint32_t SymbolIndexValue;
const MCSymbol *Target = R.Symbol;
if (Target->isDefined() && Target->isInSection()) {
const MCSection &TargetSec = Target->getSection();
auto It = SectionIndex.find(&TargetSec);
if (It == SectionIndex.end())
report_fatal_error(Twine("TMS34010: relocation target '") +
Target->getName() + "' is in section '" +
TargetSec.getName() +
"' with no TI-COFF representation");
if (&TargetSec == R.Section) {
SymbolIndexValue = 0xFFFF; // same section: section-relative addend
} else {
SymbolIndexValue = It->second * 2; // the target section symbol
}
} else if (Target->isUndefined()) {
auto It = SymbolIndex.find(Target);
if (It == SymbolIndex.end())
report_fatal_error(Twine("TMS34010: relocation symbol '") +
Target->getName() + "' has no symbol-table entry");
SymbolIndexValue = It->second;
} else {
report_fatal_error(Twine("TMS34010: absolute relocation target '") +
Target->getName() + "' has no TI-COFF representation");
}
RelocsBySection[R.Section].push_back(
{static_cast<uint32_t>(R.Offset * 8), SymbolIndexValue});
}
// File layout: headers, raw section bytes, relocation blocks, symbols,
// string table.
uint32_t Cursor = 20 + 40 * static_cast<uint32_t>(Sections.size());
for (SectionInfo &S : Sections) {
if (S.Flags == SectionFlagBss) {
S.RawPtr = 0;
continue;
}
S.RawPtr = Cursor;
Cursor += static_cast<uint32_t>(S.Data.size());
}
for (SectionInfo &S : Sections) {
if (S.Flags == SectionFlagBss) {
S.RelPtr = 0;
continue;
}
S.RelPtr = Cursor;
Cursor += 10u * S.NReloc;
}
const uint32_t SymPtr = Cursor;
// Build the 18-byte symbol records (plus aux records) and the string table.
SmallString<256> SymbolTable;
SmallString<256> StringTable;
uint32_t NumSymbolEntries = 0;
auto AddSymbol = [&](const SymbolRecord &S) {
if (S.Name.size() <= 8) {
SmallString<8> ShortName(S.Name);
ShortName.resize(8, '\0');
SymbolTable.append(ShortName.begin(), ShortName.end());
} else {
append32(SymbolTable, 0);
append32(SymbolTable, static_cast<uint32_t>(4 + StringTable.size()));
StringTable.append(S.Name.begin(), S.Name.end());
StringTable.push_back('\0');
}
append32(SymbolTable, S.Value);
append16(SymbolTable, static_cast<uint16_t>(S.Section));
append16(SymbolTable, 0); // type
SymbolTable.push_back(static_cast<char>(S.Class));
SymbolTable.push_back(static_cast<char>(S.NumAux));
++NumSymbolEntries;
if (S.NumAux) {
// Section auxiliary record: bit size, relocation count, line count,
// then zeroed checksum/number/selection fields.
append32(SymbolTable, S.AuxSizeBits);
append16(SymbolTable, S.AuxNReloc);
append16(SymbolTable, 0);
SymbolTable.append(10, '\0');
NumSymbolEntries += S.NumAux;
}
};
for (const SymbolRecord &S : Symbols)
AddSymbol(S);
// File header (20 bytes).
write16(OS, MachineTMS34010);
write16(OS, static_cast<uint16_t>(Sections.size()));
write32(OS, 0); // TimeDateStamp: deterministic
write32(OS, SymPtr);
write32(OS, NumSymbolEntries);
write16(OS, 0); // SizeOfOptionalHeader
write16(OS, FileFlagsAR32WR);
// Section headers (40 bytes each).
for (const SectionInfo &S : Sections) {
SmallString<8> Name(S.Name);
Name.resize(8, '\0');
OS.write(Name.data(), Name.size());
write32(OS, 0); // PhysicalAddress
write32(OS, 0); // VirtualAddress
write32(OS, S.SizeBits); // SizeOfRawData, in bits
write32(OS, S.RawPtr); // PointerToRawData
write32(OS, S.RelPtr); // PointerToRelocations
write32(OS, 0); // PointerToLineNumbers
write16(OS, S.NReloc);
write16(OS, 0); // NumberOfLineNumbers
write32(OS, S.Flags);
}
// Raw data. .bss has no file bytes.
for (const SectionInfo &S : Sections)
if (S.Flags != SectionFlagBss)
OS.write(S.Data.data(), S.Data.size());
// Relocations. The address is a bit address within the section. .bss has
// no file bytes and must never carry a relocation; a future fixup in .bss is
// a writer bug, not something to drop silently.
for (const SectionInfo &S : Sections) {
auto It = RelocsBySection.find(S.Sec);
if (S.Flags == SectionFlagBss) {
if (It != RelocsBySection.end() && !It->second.empty())
report_fatal_error("TMS34010: .bss relocations are not supported");
continue;
}
if (It == RelocsBySection.end())
continue;
for (const FinalReloc &R : It->second) {
write32(OS, R.AddrBits);
write32(OS, R.Symbol);
write16(OS, RelocRellong);
}
}
// Symbol table and string table.
OS.write(SymbolTable.data(), SymbolTable.size());
write32(OS, static_cast<uint32_t>(4 + StringTable.size()));
OS.write(StringTable.data(), StringTable.size());
return OS.tell() - Start;
}
@@ -0,0 +1,92 @@
//===-- TMS34010MCObjectWriter.h - TI-COFF object writer -------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCOBJECTWRITER_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCOBJECTWRITER_H
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCObjectStreamer.h"
#include "llvm/MC/MCObjectWriter.h"
#include <memory>
#include <utility>
namespace llvm {
class MCAsmBackend;
class MCCodeEmitter;
class MCContext;
class MCFixup;
class MCFragment;
class MCSection;
class MCSymbol;
class MCValue;
class raw_pwrite_stream;
/// Symbols that the assembler printer marked `.globl`. GSPA 2.00 only puts a
/// defined symbol in the object symbol table when it is global; local labels
/// are reached through their section symbol plus a bit offset.
using TMS34010GlobalSymbols = DenseSet<const MCSymbol *>;
/// TI-COFF object writer: `.text`/`.data`/`.bss` sections, a symbol table with
/// section symbols and external symbols, and R-RELLONG relocations whose
/// relocated field is a bit-address addend. The conventions match GSPA 2.00:
///
/// file header: machine 0x0090, optional-header size 0, flags 0x0100
/// section: size in bits, flags 0x20/0x40/0x80, paddr/vaddr 0
/// symbol: section symbols C_STAT (3) with one aux record; globals
/// C_EXT (2) with a bit value and section number
/// relocation: 10 bytes, address in bits, type 0x11 (R-RELLONG)
class TMS34010COFFObjectWriter : public MCObjectWriter {
raw_pwrite_stream &OS;
std::shared_ptr<TMS34010GlobalSymbols> Globals;
struct PendingReloc {
const MCSection *Section;
uint64_t Offset; // byte offset within the section
const MCSymbol *Symbol;
};
SmallVector<PendingReloc, 16> Relocations;
public:
TMS34010COFFObjectWriter(raw_pwrite_stream &OS,
std::shared_ptr<TMS34010GlobalSymbols> Globals)
: OS(OS), Globals(std::move(Globals)) {}
void recordRelocation(const MCFragment &F, const MCFixup &Fixup,
MCValue Target, uint64_t &FixedValue) override;
uint64_t writeObject() override;
};
/// The object streamer for the TI-COFF writer. It tracks `.globl` so the
/// writer can distinguish global definitions from local labels, and it backs
/// the `.bss` path with the target's virtual BSS section.
class TMS34010MCStreamer : public MCObjectStreamer {
std::shared_ptr<TMS34010GlobalSymbols> Globals;
public:
TMS34010MCStreamer(MCContext &Ctx, std::unique_ptr<MCAsmBackend> TAB,
std::unique_ptr<MCObjectWriter> OW,
std::unique_ptr<MCCodeEmitter> Emitter,
std::shared_ptr<TMS34010GlobalSymbols> Globals)
: MCObjectStreamer(Ctx, std::move(TAB), std::move(OW), std::move(Emitter)),
Globals(std::move(Globals)) {}
bool emitSymbolAttribute(MCSymbol *Symbol,
MCSymbolAttr Attribute) override;
void emitCommonSymbol(MCSymbol *Symbol, uint64_t Size,
Align ByteAlignment) override;
};
} // namespace llvm
#endif // LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCOBJECTWRITER_H
@@ -8,7 +8,9 @@
#include "TMS34010MCTargetDesc.h"
#include "TMS34010InstPrinter.h"
#include "TMS34010MCAsmBackend.h"
#include "TMS34010MCAsmInfo.h"
#include "TMS34010MCCodeEmitter.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegisterInfo.h"
@@ -27,6 +29,92 @@ using namespace llvm;
#define GET_REGINFO_MC_DESC
#include "TMS34010GenRegisterInfo.inc"
namespace llvm {
namespace TMS34010 {
unsigned getLongBranchOpcode(unsigned Opcode) {
switch (Opcode) {
case JR: return JUncond;
case JREQ: return JAEQ;
case JRNE: return JANE;
case JRLT: return JALT;
case JRLE: return JALE;
case JRGT: return JAGT;
case JRGE: return JAGE;
case JRLO: return JALO;
case JRLS: return JALS;
case JRHI: return JAHI;
case JRHS: return JAHS;
case JRP: return JAP;
case JRN: return JAN;
case JRV: return JAV;
case JRNV: return JANV;
case JRNN: return JANN;
case MC_JR: return MC_JAUC;
case MC_JREQ: return MC_JAEQ;
case MC_JRNE: return MC_JANE;
case MC_JRLT: return MC_JALT;
case MC_JRLE: return MC_JALE;
case MC_JRGT: return MC_JAGT;
case MC_JRGE: return MC_JAGE;
case MC_JRLO: return MC_JALO;
case MC_JRLS: return MC_JALS;
case MC_JRHI: return MC_JAHI;
case MC_JRHS: return MC_JAHS;
case MC_JRP: return MC_JAP;
case MC_JRN: return MC_JAN;
case MC_JRV: return MC_JAV;
case MC_JRNV: return MC_JANV;
case MC_JRNN: return MC_JANN;
default:
return 0;
}
}
unsigned getShortBranchOpcode(unsigned Opcode) {
switch (Opcode) {
case JUncond: return JR;
case JAEQ: return JREQ;
case JANE: return JRNE;
case JALT: return JRLT;
case JALE: return JRLE;
case JAGT: return JRGT;
case JAGE: return JRGE;
case JALO: return JRLO;
case JALS: return JRLS;
case JAHI: return JRHI;
case JAHS: return JRHS;
case JAP: return JRP;
case JAN: return JRN;
case JAV: return JRV;
case JANV: return JRNV;
case JANN: return JRNN;
case MC_JAUC: return MC_JR;
case MC_JAEQ: return MC_JREQ;
case MC_JANE: return MC_JRNE;
case MC_JALT: return MC_JRLT;
case MC_JALE: return MC_JRLE;
case MC_JAGT: return MC_JRGT;
case MC_JAGE: return MC_JRGE;
case MC_JALO: return MC_JRLO;
case MC_JALS: return MC_JRLS;
case MC_JAHI: return MC_JRHI;
case MC_JAHS: return MC_JRHS;
case MC_JAP: return MC_JRP;
case MC_JAN: return MC_JRN;
case MC_JAV: return MC_JRV;
case MC_JANV: return MC_JRNV;
case MC_JANN: return MC_JRNN;
default:
return 0;
}
}
bool isShortBranch(unsigned Opcode) { return getLongBranchOpcode(Opcode) != 0; }
} // namespace TMS34010
} // namespace llvm
static MCInstrInfo *createTMS34010MCInstrInfo() {
MCInstrInfo *X = new MCInstrInfo();
InitTMS34010MCInstrInfo(X);
@@ -60,6 +148,13 @@ static MCInstPrinter *createTMS34010MCInstPrinter(const Triple &T,
return nullptr;
}
static MCAsmBackend *createTMS34010MCAsmBackend(const Target &T,
const MCSubtargetInfo &STI,
const MCRegisterInfo &MRI,
const MCTargetOptions &Options) {
return new TMS34010MCAsmBackend(T);
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010TargetMC() {
Target &T = getTheTMS34010Target();
@@ -69,10 +164,6 @@ LLVMInitializeTMS34010TargetMC() {
TargetRegistry::RegisterMCRegInfo(T, createTMS34010MCRegisterInfo);
TargetRegistry::RegisterMCSubtargetInfo(T, createTMS34010MCSubtargetInfo);
TargetRegistry::RegisterMCInstPrinter(T, createTMS34010MCInstPrinter);
TargetRegistry::RegisterMCCodeEmitter(T, createTMS34010MCCodeEmitter);
TargetRegistry::RegisterMCAsmBackend(T, createTMS34010MCAsmBackend);
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010AsmParser() {}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010Disassembler() {}
@@ -9,8 +9,34 @@
#ifndef LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCTARGETDESC_H
#define LLVM_LIB_TARGET_TMS34010_MCTARGETDESC_TMS34010MCTARGETDESC_H
#include "llvm/MC/MCFixup.h"
namespace llvm {
class Target;
namespace TMS34010 {
/// Target fixup kinds. FK_JR8 covers the displacement byte of the one-word
/// relative branches: a signed word count from PC+16, i.e.
/// `disp = (target_bit - (pc_bit + 16)) / 16`. It is PC-relative in bytes;
/// the backend converts the byte offset to the ISA's word displacement.
enum FixupKind {
FK_JR8 = FirstTargetFixupKind,
LastTargetFixupKind,
};
/// True for the one-word relative JR/JR<cc> opcodes (CodeGen and MC spellings).
bool isShortBranch(unsigned Opcode);
/// The absolute JA* counterpart of a relative branch, or 0 when \p Opcode is
/// not a relative branch.
unsigned getLongBranchOpcode(unsigned Opcode);
/// The relative JR* counterpart of an absolute branch, or 0 when \p Opcode is
/// not an absolute branch.
unsigned getShortBranchOpcode(unsigned Opcode);
} // namespace TMS34010
} // namespace llvm
#define GET_REGINFO_ENUM
+17
View File
@@ -27,7 +27,24 @@ def TMS34010AsmWriter : AsmWriter {
bit isMCAsmWriter = 1;
}
// The operand grammar uses * - + @ and parentheses as literal tokens in the
// MC asm strings. The default tokenizer only isolates []*!, so add the
// punctuation that follows (or precedes) an operand reference.
def TMS34010AsmParserVariant : AsmParserVariant {
int Variant = 0;
string Name = "tms34010";
string TokenizingCharacters = "[]*!()+";
}
// GSPA spells A15 "SP"; emit the alternative-register matcher so the parser
// accepts it. The canonical register name remains A15.
def TMS34010AsmParser : AsmParser {
let ShouldEmitMatchRegisterAltName = 1;
}
def TMS34010 : Target {
let InstructionSet = TMS34010InstrInfo;
let AssemblyWriters = [TMS34010AsmWriter];
let AssemblyParsers = [TMS34010AsmParser];
let AssemblyParserVariants = [TMS34010AsmParserVariant];
}
+215 -4
View File
@@ -20,6 +20,7 @@
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Operator.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/TargetRegistry.h"
@@ -66,6 +67,13 @@ public:
/// instead of `000h`, used for zero-initialized byte arrays.
void emitByteLines(ArrayRef<uint64_t> Bytes, bool DecimalZeros = false);
/// Object-mode counterparts: emit real MC data and fixups so the TI-COFF
/// writer can produce relocations instead of GSPA source text.
void emitDataInitializerObject(const Constant *Init);
void emitPointerInitializerObject(const Constant *Init);
void emitPointerReferenceObject(const GlobalValue *GV, int64_t Addend);
void emitByteLinesObject(ArrayRef<uint64_t> Bytes);
static char ID;
};
@@ -268,6 +276,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()) {
@@ -334,6 +351,157 @@ void TMS34010AsmPrinter::emitDataInitializer(const Constant *Init) {
reportUnsupportedDataInitializer();
}
void TMS34010AsmPrinter::emitPointerReferenceObject(const GlobalValue *GV,
int64_t Addend) {
const MCExpr *Expr = MCSymbolRefExpr::create(getSymbol(GV), OutContext);
if (Addend != 0)
Expr = MCBinaryExpr::createAdd(
Expr, MCConstantExpr::create(Addend, OutContext), OutContext);
OutStreamer->emitValue(Expr, 4);
}
void TMS34010AsmPrinter::emitPointerInitializerObject(const Constant *Init) {
if (Init->isNullValue()) {
OutStreamer->emitIntValue(0, 4);
return;
}
const Value *V = Init->stripPointerCasts();
if (const auto *GV = dyn_cast<GlobalValue>(V)) {
emitPointerReferenceObject(GV, 0);
return;
}
if (const auto *GEP = dyn_cast<GEPOperator>(V)) {
const auto *BaseGV =
dyn_cast<GlobalValue>(GEP->getPointerOperand()->stripPointerCasts());
if (!BaseGV || GEP->getNumOperands() != 2)
reportUnsupportedDataInitializer();
const auto *Idx = dyn_cast<ConstantInt>(GEP->getOperand(1));
if (!Idx)
reportUnsupportedDataInitializer();
emitPointerReferenceObject(BaseGV, Idx->getSExtValue());
return;
}
if (const auto *CE = dyn_cast<ConstantExpr>(V)) {
if (CE->getOpcode() == Instruction::IntToPtr) {
const GlobalValue *BaseGV = nullptr;
int64_t Addend = 0;
if (decomposePointerInt(CE->getOperand(0), BaseGV, Addend)) {
emitPointerReferenceObject(BaseGV, Addend);
return;
}
}
}
reportUnsupportedDataInitializer();
}
void TMS34010AsmPrinter::emitByteLinesObject(ArrayRef<uint64_t> Bytes) {
SmallString<64> Data;
for (uint64_t Byte : Bytes)
Data.push_back(static_cast<char>(Byte & 0xFF));
OutStreamer->emitBytes(Data);
}
void TMS34010AsmPrinter::emitDataInitializerObject(const Constant *Init) {
if (const auto *CI = dyn_cast<ConstantInt>(Init)) {
unsigned Bits = CI->getType()->getIntegerBitWidth();
uint64_t Value = CI->getZExtValue();
if (Bits <= 8)
OutStreamer->emitIntValue(Value & 0xFF, 1);
else if (Bits <= 16)
OutStreamer->emitIntValue(Value & 0xFFFF, 2);
else if (Bits <= 32)
OutStreamer->emitIntValue(Value & 0xFFFFFFFF, 4);
else
reportUnsupportedDataInitializer();
return;
}
if (Init->getType()->isPointerTy()) {
emitPointerInitializerObject(Init);
return;
}
if (const auto *CDA = dyn_cast<ConstantDataArray>(Init)) {
Type *ElemTy = CDA->getElementType();
if (ElemTy->isIntegerTy(8)) {
SmallVector<uint64_t, 32> Bytes;
for (unsigned I = 0, E = CDA->getNumElements(); I != E; ++I)
Bytes.push_back(CDA->getElementAsInteger(I) & 0xFF);
emitByteLinesObject(Bytes);
return;
}
if (ElemTy->isIntegerTy(16) || ElemTy->isIntegerTy(32) ||
ElemTy->isPointerTy()) {
for (unsigned I = 0, E = CDA->getNumElements(); I != E; ++I)
emitDataInitializerObject(CDA->getElementAsConstant(I));
return;
}
reportUnsupportedDataInitializer();
}
if (const auto *CA = dyn_cast<ConstantArray>(Init)) {
Type *ElemTy = CA->getType()->getElementType();
if (ElemTy->isArrayTy()) {
for (const Use &U : CA->operands())
emitDataInitializerObject(cast<Constant>(U.get()));
return;
}
if (ElemTy->isIntegerTy(8)) {
SmallVector<uint64_t, 32> Bytes;
for (const Use &U : CA->operands()) {
const auto *CI = dyn_cast<ConstantInt>(U.get());
if (!CI)
reportUnsupportedDataInitializer();
Bytes.push_back(CI->getZExtValue() & 0xFF);
}
emitByteLinesObject(Bytes);
return;
}
if (ElemTy->isIntegerTy(16) || ElemTy->isIntegerTy(32) ||
ElemTy->isPointerTy()) {
for (const Use &U : CA->operands())
emitDataInitializerObject(cast<Constant>(U.get()));
return;
}
reportUnsupportedDataInitializer();
}
if (isa<ConstantAggregateZero>(Init)) {
Type *Ty = Init->getType();
if (Ty->isIntegerTy()) {
unsigned Bits = Ty->getIntegerBitWidth();
if (Bits <= 8)
OutStreamer->emitIntValue(0, 1);
else if (Bits <= 16)
OutStreamer->emitIntValue(0, 2);
else if (Bits <= 32)
OutStreamer->emitIntValue(0, 4);
else
reportUnsupportedDataInitializer();
return;
}
if (Ty->isPointerTy()) {
OutStreamer->emitIntValue(0, 4);
return;
}
if (Ty->isArrayTy()) {
uint64_t Size = getDataLayout().getTypeAllocSize(Ty).getFixedValue();
OutStreamer->emitZeros(Size);
return;
}
reportUnsupportedDataInitializer();
}
reportUnsupportedDataInitializer();
}
bool TMS34010AsmPrinter::doInitialization(Module &M) {
AsmPrinter::doInitialization(M);
@@ -355,19 +523,43 @@ void TMS34010AsmPrinter::emitDataGlobal(const GlobalVariable *GV) {
reportUnsupportedDataInitializer();
OutStreamer->switchSection(getObjFileLowering().getDataSection());
const DataLayout &DL = GV->getParent()->getDataLayout();
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
if (!OutStreamer->hasRawTextSupport()) {
// Object mode: emit real bytes and fixups. The alignment must precede the
// label so the symbol value and the emitted data agree.
if (Alignment.value() >= 2)
OutStreamer->emitValueToAlignment(Alignment);
if (!GV->hasLocalLinkage())
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
OutStreamer->emitLabel(getSymbol(GV));
emitDataInitializerObject(Init);
// TI-COFF sections are word-granular. An odd-length byte array (for
// example a 7-byte string literal) would leave .data on an odd byte and
// the VT1200 loader rejects the section (status 4). Pad each object to a
// whole word; the padding belongs to the section, not the symbol.
if (DL.getTypeAllocSize(Init->getType()).getFixedValue() % 2)
OutStreamer->emitIntValue(0, 1);
return;
}
// GSPA implicitly aligns `.word`/`.long` to a 16-bit boundary but does not
// move the label that precedes the directive. After an odd-length object
// (for example the 5-byte "0::0" string) the label would point one byte
// before its value. Emit the alignment explicitly so the label and the
// emitted bytes agree.
const DataLayout &DL = GV->getParent()->getDataLayout();
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
if (Alignment.value() >= 2)
OutStreamer->emitRawText("\t.even\n");
if (!GV->hasLocalLinkage())
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
OutStreamer->emitRawText(getSymbol(GV)->getName() + ":\n");
emitDataInitializer(Init);
// Keep the object's .data a whole number of 16-bit words. An odd byte
// array would otherwise leave the section odd; GSPLNK concatenates input
// sections without realigning them, and the loader requires word sizes.
if (DL.getTypeAllocSize(Init->getType()).getFixedValue() % 2)
OutStreamer->emitRawText("\t.byte 0\n");
}
void TMS34010AsmPrinter::emitGlobalVariable(const GlobalVariable *GV) {
@@ -383,8 +575,27 @@ void TMS34010AsmPrinter::emitGlobalVariable(const GlobalVariable *GV) {
// .bss line. Sizes and alignment in GSPA are in bits.
const DataLayout &DL = GV->getParent()->getDataLayout();
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
uint64_t SizeBits =
DL.getTypeAllocSize(GV->getValueType()).getFixedValue() * 8;
uint64_t SizeBytes =
DL.getTypeAllocSize(GV->getValueType()).getFixedValue();
if (!OutStreamer->hasRawTextSupport()) {
OutStreamer->switchSection(getObjFileLowering().getBSSSection());
// A TI-COFF .bss allocation is word-granular. GSPA advances `.bss`
// allocations to a 16-bit boundary (even when the object is a char or i1),
// and the VT1200 loader rejects a section whose bit size is not a whole
// number of words. Use the IR alignment but never less than two bytes,
// then pad the allocation to a word.
Align BSSAlign(std::max<unsigned>(Alignment.value(), 2));
OutStreamer->emitValueToAlignment(BSSAlign);
if (!GV->hasLocalLinkage())
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
OutStreamer->emitLabel(getSymbol(GV));
OutStreamer->emitZeros(SizeBytes);
OutStreamer->emitValueToAlignment(Align(2));
return;
}
uint64_t SizeBits = ((SizeBytes + 1) & ~1ULL) * 8;
if (!GV->hasLocalLinkage())
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
OutStreamer->emitRawText(Twine("\t.bss\t") + getSymbol(GV)->getName() + "," +
@@ -13,22 +13,44 @@
//
// The prologue keeps the caller's FP and the used callee-saved registers on
// the system stack, then points FP (A13) at the incoming program stack and
// allocates the local frame above it. Each register is saved with its own
// MOVE push, which is ABI-identical to one MMTM slot.
// allocates the local frame above it. FP is one push and the callee-save set
// is one MMTM register-list push; the matching epilogue is one MMFM followed
// by the FP pop. MMTM walks the list low-to-high and predecrements SP for each
// selected register, so the resulting slots are the same as one push per
// register in ascending order.
//
//===----------------------------------------------------------------------===//
#include "TMS34010FrameLowering.h"
#include "TMS34010.h"
#include "TMS34010InstrInfo.h"
#include "TMS34010RegisterInfo.h"
#include "TMS34010Subtarget.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
using namespace llvm;
namespace {
/// Encode the callee-save set as an MMTM/MMFM register list. The two
/// directions number the A file from opposite ends: MMTM's mask has A0 in bit
/// 15 and A15 in bit 0, MMFM's mask has A0 in bit 0. The list therefore
/// remains ascending in both cases.
uint16_t getCalleeSaveMask(const MachineFrameInfo &MFI,
const TMS34010RegisterInfo &TRI, bool Restore) {
uint16_t Mask = 0;
for (const CalleeSavedInfo &CSI : MFI.getCalleeSavedInfo()) {
unsigned Enc = TRI.getEncodingValue(CSI.getReg());
assert(Enc < 16 && "the callee-save set is in the A register file");
Mask |= static_cast<uint16_t>(1u << (Restore ? Enc : 15 - Enc));
}
return Mask;
}
} // end anonymous namespace
StackOffset
TMS34010FrameLowering::getFrameIndexReference(const MachineFunction &MF, int FI,
Register &FrameReg) const {
@@ -54,15 +76,22 @@ void TMS34010FrameLowering::emitPrologue(MachineFunction &MF,
DebugLoc DL = MBBI != MBB.end() ? MBBI->getDebugLoc() : DebugLoc();
// The field-size invariant comes first so that every frame access below is
// a 32-bit access. FS0 = 16 keeps a later `short` load/store correct even
// if the caller left it at 32.
// a 32-bit access. FS0 = 16 sign-extending keeps a later `short` load/store
// correct even if the caller left it at 32.
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETF16));
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETF32));
// Save the caller's frame pointer, then the used callee-saved registers.
// Save the caller's frame pointer, then the used callee-saved registers in
// one register-list push. The FP slot stays where it was: MMTM pushes the
// list below it, exactly like one push per register.
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::PUSH32)).addReg(TMS34010::A13);
for (const CalleeSavedInfo &CSI : MFI.getCalleeSavedInfo())
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::PUSH32)).addReg(CSI.getReg());
const std::vector<CalleeSavedInfo> &CSI = MFI.getCalleeSavedInfo();
if (!CSI.empty()) {
const auto *TRI = STI.getRegisterInfo();
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::MMTM))
.addReg(TMS34010::A15)
.addImm(getCalleeSaveMask(MFI, *TRI, /*Restore=*/false));
}
// FP = STK, then allocate the local frame.
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETFP));
@@ -90,14 +119,27 @@ void TMS34010FrameLowering::emitEpilogue(MachineFunction &MF,
// its old STK below the return address: SP + (NumSaved + 1) * 32.
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::LOADSTK))
.addImm((NumSaved + 1) * 32);
} else if (MFI.hasVarSizedObjects()) {
// A no-argument function has no saved old STK to reload, and A14 points
// somewhere above the fixed frame after a dynamic alloca. Rebuild it from
// the frame pointer (FP + fixed frame size), discarding the dynamic area.
int64_t FrameBits = MFI.getStackSize() * 8;
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::MOVE), TMS34010::A14)
.addReg(TMS34010::A13);
if (FrameBits)
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::ADDSTK)).addImm(FrameBits);
} else {
int64_t FrameBits = MFI.getStackSize() * 8;
if (FrameBits)
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::ADDSTK)).addImm(-FrameBits);
}
// Restore the callee-saved registers in reverse order, then the old FP.
for (const CalleeSavedInfo &CI : llvm::reverse(CSI))
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::POP32), CI.getReg());
// Restore the callee-saved register list in one MMFM, then the old FP.
if (!CSI.empty()) {
const auto *TRI = STI.getRegisterInfo();
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::MMFM))
.addReg(TMS34010::A15)
.addImm(getCalleeSaveMask(MFI, *TRI, /*Restore=*/true));
}
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::POP32), TMS34010::A13);
}
+749 -61
View File
@@ -8,14 +8,24 @@
#include "TMS34010ISelLowering.h"
#include "TMS34010.h"
#include "TMS34010InstrInfo.h"
#include "TMS34010Subtarget.h"
#include "TMS34010TargetMachine.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
#include <iterator>
#include <optional>
#include <utility>
using namespace llvm;
@@ -24,8 +34,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);
@@ -33,6 +47,63 @@ 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);
// Dynamic allocas grow the upward-growing program stack (A14). They are
// lowered explicitly (LowerOperation) rather than by the generic expander:
// the generic path derives the direction from the frame lowering, while the
// TMS34010 frame keeps LLVM's "objects below FP" convention and moves A14
// above the fixed frame. The explicit lowering also lets STACKSAVE/
// STACKRESTORE use A14 as their pointer.
setOperationAction(ISD::DYNAMIC_STACKALLOC, MVT::i32, Custom);
setOperationAction(ISD::STACKSAVE, MVT::Other, Expand);
setOperationAction(ISD::STACKRESTORE, MVT::Other, Expand);
setStackPointerRegisterToSaveRestore(TMS34010::A14);
// 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.
setOperationAction(ISD::SETCC, MVT::i32, Expand);
setOperationAction(ISD::SELECT, MVT::i32, Expand);
setOperationAction(ISD::SELECT_CC, MVT::i32, Custom);
setOperationAction(ISD::BRCOND, MVT::Other, Custom);
setOperationAction(ISD::BR_CC, MVT::i32, Custom);
computeRegisterProperties(STI.getRegisterInfo());
setBooleanContents(ZeroOrOneBooleanContent);
@@ -42,6 +113,72 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
setMaxAtomicSizeInBitsSupported(0);
}
// MOVE, MOVB and the FS0/FS1 field-size forms have no alignment requirement:
// the TMS34010 performs unaligned field accesses (TMS34010 User's Guide,
// section 7.5). Packed/bit-addressed struct fields and the bit-address
// normalizer's align-1 loads and stores must therefore select directly rather
// than being split into byte-sized accesses, which only have a sign-extending
// MOVB load here.
bool TMS34010TargetLowering::allowsMisalignedMemoryAccesses(
EVT VT, unsigned AddrSpace, Align Alignment,
MachineMemOperand::Flags Flags, unsigned *Fast) const {
(void)Alignment;
(void)Flags;
if (AddrSpace != 0)
return false;
if (!VT.isInteger() || VT.getSizeInBits() > 32)
return false;
if (Fast)
*Fast = 1;
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::findOptimalMemOpLowering(
LLVMContext &Context, std::vector<EVT> &MemOps, unsigned Limit,
const MemOp &Op, unsigned DstAS, unsigned SrcAS,
const AttributeList &FuncAttributes, EVT *LargestVT) const {
(void)Context;
(void)MemOps;
(void)Limit;
(void)Op;
(void)DstAS;
(void)SrcAS;
(void)FuncAttributes;
(void)LargestVT;
// The generic expansion forms byte-offset loads/stores on the pointer
// values, but TMS34010 pointers are bit addresses. TMS34010SelectionDAGInfo
// emits the bit-scaled sequence instead.
return false;
}
bool TMS34010TargetLowering::allowsMisalignedMemoryAccesses(
LLT VT, unsigned AddrSpace, Align Alignment,
MachineMemOperand::Flags Flags, unsigned *Fast) const {
(void)Alignment;
(void)Flags;
if (AddrSpace != 0)
return false;
if (!VT.isPointer() && !(VT.isScalar() && VT.getSizeInBits() <= 32))
return false;
if (Fast)
*Fast = 1;
return true;
}
SDValue TMS34010TargetLowering::LowerFormalArguments(
SDValue Chain, CallingConv::ID CallConv, bool IsVarArg,
const SmallVectorImpl<ISD::InputArg> &Ins, const SDLoc &DL,
@@ -54,36 +191,93 @@ 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
// one 32-bit slot below FP for each preceding argument, so its fixed object
// is at byte offset -(i + 1) * 4 (bit offset -(i + 1) * 32 after frame
// elimination).
for (unsigned i = 0, e = Ins.size(); i != e; ++i) {
const ISD::InputArg &In = Ins[i];
// Every explicit argument is passed on the program stack. The caller pushes
// the arguments last-first, so argument 0 occupies the topmost slot directly
// below FP and argument i starts at FP minus the total size of arguments
// 0..i. A plain argument occupies one 32-bit slot; a byval argument
// occupies its object size rounded up to 16 bits (the TI rule) and its
// incoming value is the address of the copy, not a load from that address.
SmallVector<int64_t, 8> ArgOffsets(Ins.size(), 0);
int64_t Offset = 0;
for (unsigned I = 0; I != Ins.size(); ++I) {
const ISD::InputArg &In = Ins[I];
// `returned` is an optimization hint, not an ABI feature: the argument is
// still passed in its normal stack slot, and the callee already returns
// through the ABI's A8. The optimizer infers it for ordinary C such as
// `int first(int a, int b) { return a; }`, so rejecting it made valid -O2
// input uncompilable. The ABI-affecting flags stay rejected.
if (In.Flags.isSRet() || In.Flags.isByVal() || In.Flags.isInReg() ||
In.Flags.isNest())
if (In.Flags.isSRet() || In.Flags.isInAlloca() ||
In.Flags.isPreallocated() || In.Flags.isInReg() || In.Flags.isNest())
report_fatal_error(
"M2 supports only plain arguments (no sret/byval/inreg/nest)",
"M2 supports only plain arguments "
"(no sret/inalloca/preallocated/inreg/nest)",
false);
if (In.VT != MVT::i32)
report_fatal_error("M2 supports only i32 and pointer arguments", false);
if (In.Flags.isByVal()) {
uint64_t Size = In.Flags.getByValSize();
if (!Size)
report_fatal_error("TMS34010 byval argument has zero size", false);
Offset += alignTo(Size, 2);
} else {
Offset += 4;
}
ArgOffsets[I] = -Offset;
}
int FI = MFI.CreateFixedObject(4, -static_cast<int64_t>(i + 1) * 4, true);
for (unsigned i = 0, e = Ins.size(); i != e; ++i) {
const ISD::InputArg &In = Ins[i];
if (In.VT != MVT::i32)
report_fatal_error(
"TMS34010 supports only integer (<=32 bit) and pointer arguments",
false);
int64_t ArgOffset = ArgOffsets[i];
if (In.Flags.isByVal()) {
uint64_t Size = In.Flags.getByValSize();
int FI = MFI.CreateFixedObject(Size, ArgOffset, true);
InVals.push_back(DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout())));
continue;
}
int FI = MFI.CreateFixedObject(4, ArgOffset, true);
SDValue FIN = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
SDValue Load =
DAG.getLoad(MVT::i32, DL, Chain, FIN,
@@ -106,43 +300,82 @@ 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",
false);
const Function *CalleeFn = cast<Function>(Callee->getGlobal());
// A statepoint call is lowered without CALLSEQ_START/CALLSEQ_END: this
// target pushes arguments and calls directly. StatepointLowering expects
// the standard call sequence and asserts on it, so reject the feature with
// a diagnostic instead of letting the generic code crash. Stackmap and
// patchpoint calls do not reach LowerCall, so only the statepoint intrinsic
// is scanned for here. The scan is per call, but only functions with a GC
// strategy pay for it; ordinary TMS34010 code has none. A statepoint in a
// function without a GC strategy is invalid IR and fails earlier in
// upstream GC lowering (Function::getGC), before target lowering runs.
if (MF.getFunction().hasGC()) {
for (const BasicBlock &BB : MF.getFunction())
for (const Instruction &I : BB)
if (const auto *CB = dyn_cast<CallBase>(&I))
if (CB->getIntrinsicID() == Intrinsic::experimental_gc_statepoint)
report_fatal_error(
"TMS34010 does not support gc.statepoint calls", false);
}
if (CLI.CB && CLI.CB->hasOperandBundles())
report_fatal_error(
"TMS34010 does not support calls with operand bundles", false);
// 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);
// 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);
} else {
IsIndirect = true;
}
// 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.
if (!CLI.RetTy->isVoidTy() && !CLI.RetTy->isIntegerTy(32) &&
// aggregate before the arguments reach Outs/OutVals. A narrow return is
// promoted to an i32 in A8; the caller's getCopyFromParts applies the
// zeroext/signext assertion and truncation.
if (!CLI.RetTy->isVoidTy() &&
!(CLI.RetTy->isIntegerTy() && CLI.RetTy->getIntegerBitWidth() <= 32) &&
!CLI.RetTy->isPointerTy())
report_fatal_error("M2 supports only void/i32/pointer returns", false);
report_fatal_error("M2 supports only void/integer(<=32)/pointer returns",
false);
for (const TargetLowering::ArgListEntry &Arg : CLI.Args) {
if (Arg.IsSRet || Arg.IsByVal || Arg.IsInReg || Arg.IsNest ||
Arg.IsInAlloca || Arg.IsPreallocated)
if (Arg.IsSRet || Arg.IsInReg || Arg.IsNest || Arg.IsInAlloca ||
Arg.IsPreallocated)
report_fatal_error(
"M2 supports only plain call arguments "
"(no sret/byval/inreg/nest/inalloca/preallocated)",
"M2 supports only plain and byval call arguments "
"(no sret/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;
@@ -156,31 +389,92 @@ SDValue TMS34010TargetLowering::LowerCall(
0);
// Arguments are pushed last-first, so the first argument ends up at FP(-32).
// Each push advances A14; the chain keeps the pushes in order.
// Each push advances A14; the chain keeps the pushes in order. A byval
// argument is copied into the argument area at the current top of the
// program stack and the area is advanced by its slot size. The callee gets
// the address of that copy.
for (unsigned I = CLI.Outs.size(); I != 0; --I) {
SDValue Arg = CLI.OutVals[I - 1];
const ISD::OutputArg &Out = CLI.Outs[I - 1];
if (Out.Flags.isByVal()) {
uint64_t Size = Out.Flags.getByValSize();
if (!Size)
report_fatal_error("TMS34010 byval argument has zero size", false);
uint64_t SlotSize = alignTo(Size, 2);
SDValue SP = DAG.getCopyFromReg(Chain, DL, TMS34010::A14, MVT::i32);
Chain = SP.getValue(1);
Chain = DAG.getMemcpy(
Chain, DL, SP, Arg, DAG.getConstant(Size, DL, MVT::i32), Align(2),
Align(2), /*isVolatile=*/false, /*AlwaysInline=*/true,
/*CI=*/nullptr, std::nullopt,
MachinePointerInfo::getUnknownStack(MF),
MachinePointerInfo::getUnknownStack(MF));
// ADDSTK and the frame adjustments are in bits; SlotSize is in bytes.
SDValue SlotVal =
DAG.getSignedTargetConstant(SlotSize * 8, DL, MVT::i32);
Chain = SDValue(
DAG.getMachineNode(TMS34010::ADDSTK, DL, MVT::Other, SlotVal, Chain),
0);
continue;
}
Chain = SDValue(
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.
// before any further access: FS0 = 16 sign-extending for `short` and FS1 = 32
// for words.
Chain = SDValue(
DAG.getMachineNode(TMS34010::SETF16, DL, MVT::Other, Chain), 0);
Chain = SDValue(
@@ -202,10 +496,14 @@ bool TMS34010TargetLowering::CanLowerReturn(
const Type *RetTy) const {
// Returning false here makes SelectionDAG demote the return value to an
// sret pointer, silently changing the signature to something M2 cannot
// lower either. Reject unsupported returns loudly instead.
if (RetTy->isVoidTy() || RetTy->isIntegerTy(32) || RetTy->isPointerTy())
// lower either. Reject unsupported returns loudly instead. Narrow integer
// returns are promoted to i32 in A8 and the zeroext/signext attribute is
// applied by the caller's getCopyFromParts, so i8/i16 are accepted.
if (RetTy->isVoidTy() || RetTy->isPointerTy() ||
(RetTy->isIntegerTy() && RetTy->getIntegerBitWidth() <= 32))
return true;
report_fatal_error("M2 supports only void/i32/pointer returns", false);
report_fatal_error("M2 supports only void/integer(<=32)/pointer returns",
false);
}
SDValue TMS34010TargetLowering::LowerReturn(
@@ -219,11 +517,22 @@ SDValue TMS34010TargetLowering::LowerReturn(
(void)IsVarArg;
// A scalar result is returned in A8. A8 is an implicit use of RETS, so the
// copy stays live through the terminator.
// copy stays live through the terminator. The type legalizer has promoted a
// narrow integer return to i32 and applied zeroext/signext already; if an
// i8/i16 ever reaches here, extend it explicitly according to the return
// attribute so A8 still holds the ABI-promoted value.
if (!Outs.empty()) {
if (Outs.size() != 1 || Outs[0].VT != MVT::i32)
report_fatal_error("M2 supports only void/i32/pointer returns", false);
Chain = DAG.getCopyToReg(Chain, DL, TMS34010::A8, OutVals[0]);
if (Outs.size() != 1 || !Outs[0].VT.isInteger() ||
Outs[0].VT.getSizeInBits() > 32)
report_fatal_error("M2 supports only void/integer(<=32)/pointer returns",
false);
SDValue Val = OutVals[0];
if (Val.getValueType() != MVT::i32) {
unsigned ExtOpc = Outs[0].Flags.isSExt() ? ISD::SIGN_EXTEND
: ISD::ZERO_EXTEND;
Val = DAG.getNode(ExtOpc, DL, MVT::i32, Val);
}
Chain = DAG.getCopyToReg(Chain, DL, TMS34010::A8, Val);
}
// RETS must pop the caller's argument words: 2 when the function has
@@ -234,3 +543,382 @@ SDValue TMS34010TargetLowering::LowerReturn(
return SDValue(
DAG.getMachineNode(TMS34010::RETS, DL, MVT::Other, NVal, Chain), 0);
}
/// Map an integer condition code onto the JAcc opcode that tests it. The
/// mapping is the dis34 condition order: signed compares use LT/GE/LE/GT,
/// unsigned compares use LO(LT), HS(GE), LS(LE) and HI(GT), and equality uses
/// EQ/NE. SETTRUE/SETFALSE have no flag test and must be lowered before this
/// point (see emitCmpAndBranch and LowerSELECT_CC).
static unsigned getBranchOpcode(ISD::CondCode CC) {
switch (CC) {
case ISD::SETEQ:
case ISD::SETUEQ:
return TMS34010::JAEQ;
case ISD::SETNE:
case ISD::SETUNE:
return TMS34010::JANE;
case ISD::SETLT:
return TMS34010::JALT;
case ISD::SETGE:
return TMS34010::JAGE;
case ISD::SETLE:
return TMS34010::JALE;
case ISD::SETGT:
return TMS34010::JAGT;
case ISD::SETULT:
return TMS34010::JALO;
case ISD::SETUGE:
return TMS34010::JAHS;
case ISD::SETULE:
return TMS34010::JALS;
case ISD::SETUGT:
return TMS34010::JAHI;
case ISD::SETTRUE:
case ISD::SETFALSE:
report_fatal_error("TMS34010: constant branch condition was not lowered",
false);
default:
report_fatal_error("TMS34010: unsupported integer comparison", false);
}
}
/// Emit a CMPBR/CMPIBR pseudo for `LHS - RHS` and the conditional branch that
/// implements `CC`, targeting \p Dest. The flags always represent
/// `LHS - RHS`, so the compare takes RHS as its source and LHS as its
/// destination. The pseudo stays a single terminator until the post-regalloc
/// pass splits it into adjacent CMP/CMPI and Jcc instructions, so no
/// flag-writing instruction can be placed between them.
static SDValue emitCmpAndBranch(SDValue Chain, SDValue LHS, SDValue RHS,
ISD::CondCode CC, SDValue Dest,
SelectionDAG &DAG, const SDLoc &DL) {
// A constant-true branch needs no compare. There is no way to name the
// fallthrough block of a constant-false BR_CC here, so fail loudly rather
// than silently dropping one edge of the CFG.
if (CC == ISD::SETTRUE)
return SDValue(
DAG.getMachineNode(TMS34010::JUncond, DL, MVT::Other, Dest, Chain), 0);
if (CC == ISD::SETFALSE)
report_fatal_error(
"TMS34010: constant-false branch condition is not supported", false);
// Keep a constant on the right so it can become the CMPI immediate.
if (isa<ConstantSDNode>(LHS) && !isa<ConstantSDNode>(RHS)) {
std::swap(LHS, RHS);
CC = ISD::getSetCCSwappedOperands(CC);
}
unsigned BranchOpc = getBranchOpcode(CC);
SDValue Br = DAG.getTargetConstant(BranchOpc, DL, MVT::i32);
// Keep the compare and branch as one terminator until the post-regalloc
// pass. The ST flags live in a physical register and a MOVE between the
// pair would overwrite them; gluing CMP to Jcc is not enough because a PHI
// copy can be inserted between the two during register allocation or
// scheduling.
if (const auto *C = dyn_cast<ConstantSDNode>(RHS)) {
// 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);
return SDValue(DAG.getMachineNode(TMS34010::CMPIBR, DL, MVT::Other,
{Dest, LHS, Imm, Br, Chain}),
0);
}
return SDValue(DAG.getMachineNode(TMS34010::CMPBR, DL, MVT::Other,
{Dest, LHS, RHS, Br, Chain}),
0);
}
SDValue TMS34010TargetLowering::LowerBRCOND(SDValue Op,
SelectionDAG &DAG) const {
SDValue Chain = Op.getOperand(0);
SDValue Cond = Op.getOperand(1);
SDValue Dest = Op.getOperand(2);
SDLoc DL(Op);
// An icmp feeding a branch is fused into CMP + Jcc.
if (Cond.getOpcode() == ISD::SETCC) {
ISD::CondCode CC = cast<CondCodeSDNode>(Cond.getOperand(2))->get();
return emitCmpAndBranch(Chain, Cond.getOperand(0), Cond.getOperand(1), CC,
Dest, DAG, DL);
}
// A general boolean value: compare it with zero and branch when nonzero.
SDValue Zero = DAG.getConstant(0, DL, MVT::i32);
return emitCmpAndBranch(Chain, Cond, Zero, ISD::SETNE, Dest, DAG, DL);
}
SDValue TMS34010TargetLowering::LowerBR_CC(SDValue Op,
SelectionDAG &DAG) const {
SDValue Chain = Op.getOperand(0);
ISD::CondCode CC = cast<CondCodeSDNode>(Op.getOperand(1))->get();
SDValue LHS = Op.getOperand(2);
SDValue RHS = Op.getOperand(3);
SDValue Dest = Op.getOperand(4);
return emitCmpAndBranch(Chain, LHS, RHS, CC, Dest, DAG, SDLoc(Op));
}
SDValue TMS34010TargetLowering::LowerSELECT_CC(SDValue Op,
SelectionDAG &DAG) const {
SDValue LHS = Op.getOperand(0);
SDValue RHS = Op.getOperand(1);
SDValue TrueVal = Op.getOperand(2);
SDValue FalseVal = Op.getOperand(3);
ISD::CondCode CC = cast<CondCodeSDNode>(Op.getOperand(4))->get();
SDLoc DL(Op);
// Constant conditions do not need a compare or a branch at all.
if (CC == ISD::SETTRUE)
return TrueVal;
if (CC == ISD::SETFALSE)
return FalseVal;
if (isa<ConstantSDNode>(LHS) && !isa<ConstantSDNode>(RHS)) {
std::swap(LHS, RHS);
CC = ISD::getSetCCSwappedOperands(CC);
}
// Target constants stay immediate operands of the pseudo instead of becoming
// separate MOVI instructions ahead of the compare. The custom inserter then
// materialises each value in its own block.
auto AsTargetConstant = [&](SDValue V) -> SDValue {
if (const auto *C = dyn_cast<ConstantSDNode>(V))
return DAG.getSignedTargetConstant(C->getSExtValue(), DL,
V.getValueType());
return V;
};
TrueVal = AsTargetConstant(TrueVal);
FalseVal = AsTargetConstant(FalseVal);
RHS = AsTargetConstant(RHS);
// The pseudo carries both values and the operands to compare; the custom
// inserter materialises each value in its own block and merges with a PHI.
SDValue BranchOpc =
DAG.getTargetConstant(getBranchOpcode(CC), DL, MVT::i32);
SDValue Ops[] = {TrueVal, FalseVal, LHS, RHS, BranchOpc};
return SDValue(
DAG.getMachineNode(TMS34010::SELECTCC, DL, Op.getValueType(), Ops), 0);
}
SDValue TMS34010TargetLowering::LowerOperation(SDValue Op,
SelectionDAG &DAG) const {
switch (Op.getOpcode()) {
case ISD::DYNAMIC_STACKALLOC: {
// The TMS34010 program stack grows upward: A14 points one past the fixed
// frame, so the new object starts at A14 and A14 is advanced past it. The
// returned pointer is the old (possibly alignment-adjusted) A14. A call
// saves this A14 on the system stack and restores it, so the dynamic area
// stays live across calls; the epilogue restores A14 from FP, discarding
// the whole dynamic area.
SDLoc DL(Op);
SDValue Chain = Op.getOperand(0);
SDValue Size = Op.getOperand(1);
Align Alignment = cast<ConstantSDNode>(Op.getOperand(2))->getAlignValue();
// SelectionDAGBuilder::visitAlloca gives a byte count, but A14 and every
// other TMS34010 pointer are bit addresses. Keep the alignment test in
// bytes, but convert both the size and the alignment mask to bits.
assert(Size.getValueType() == MVT::i32 &&
"dynamic alloca size is an i32 byte count");
SDValue SizeBits = DAG.getNode(ISD::SHL, DL, MVT::i32, Size,
DAG.getConstant(3, DL, MVT::i32));
assert(SizeBits.getOpcode() == ISD::SHL &&
isa<ConstantSDNode>(SizeBits.getOperand(1)) &&
cast<ConstantSDNode>(SizeBits.getOperand(1))->getZExtValue() == 3 &&
"dynamic alloca size must be scaled by 3 bits per byte");
SDValue SP = DAG.getCopyFromReg(Chain, DL, TMS34010::A14, MVT::i32);
Chain = SP.getValue(1);
SDValue Ptr = SP;
if (Alignment > Align(2)) {
uint64_t AlignBits = Alignment.value() * 8;
SDValue Bias = DAG.getConstant(AlignBits - 1, DL, MVT::i32);
SDValue Mask =
DAG.getSignedConstant(-static_cast<int64_t>(AlignBits), DL,
MVT::i32);
Ptr = DAG.getNode(ISD::ADD, DL, MVT::i32, Ptr, Bias);
Ptr = DAG.getNode(ISD::AND, DL, MVT::i32, Ptr, Mask);
}
SDValue NewSP = DAG.getNode(ISD::ADD, DL, MVT::i32, Ptr, SizeBits);
Chain = DAG.getCopyToReg(Chain, DL, TMS34010::A14, NewSP);
SDValue Ops[] = {Ptr, Chain};
return DAG.getMergeValues(Ops, DL);
}
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");
}
}
MachineBasicBlock *TMS34010TargetLowering::EmitInstrWithCustomInserter(
MachineInstr &MI, MachineBasicBlock *MBB) const {
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;
}
}
MachineBasicBlock *TMS34010TargetLowering::emitSelectCC(
MachineInstr &MI, MachineBasicBlock *ThisMBB) const {
MachineFunction *MF = ThisMBB->getParent();
MachineRegisterInfo &MRI = MF->getRegInfo();
const TargetInstrInfo &TII = *MF->getSubtarget().getInstrInfo();
const TargetRegisterClass *RC = &TMS34010::ARegsRegClass;
const BasicBlock *LLVM_BB = ThisMBB->getBasicBlock();
const DebugLoc &DL = MI.getDebugLoc();
// Operands: 0 = destination, 1 = true value, 2 = false value, 3 = lhs,
// 4 = rhs, 5 = conditional branch opcode.
Register DstReg = MI.getOperand(0).getReg();
MachineOperand TrueMO = MI.getOperand(1);
MachineOperand FalseMO = MI.getOperand(2);
MachineOperand LHSMO = MI.getOperand(3);
MachineOperand RHSMO = MI.getOperand(4);
unsigned BranchOpc = MI.getOperand(5).getImm();
// Build: ThisMBB: CMPBR/CMPIBR TrueMBB
// IfFalseMBB: false value; JAUC SinkMBB
// TrueMBB: true value
// SinkMBB: PHI
// The false block is laid out directly after the branch so its value is
// materialised on the fallthrough path.
MachineFunction::iterator It = std::next(ThisMBB->getIterator());
MachineBasicBlock *IfFalseMBB = MF->CreateMachineBasicBlock(LLVM_BB);
MachineBasicBlock *TrueMBB = MF->CreateMachineBasicBlock(LLVM_BB);
MachineBasicBlock *SinkMBB = MF->CreateMachineBasicBlock(LLVM_BB);
MF->insert(It, IfFalseMBB);
MF->insert(It, TrueMBB);
MF->insert(It, SinkMBB);
SinkMBB->splice(SinkMBB->begin(), ThisMBB,
std::next(MachineBasicBlock::iterator(MI)), ThisMBB->end());
SinkMBB->transferSuccessorsAndUpdatePHIs(ThisMBB);
ThisMBB->addSuccessor(TrueMBB);
ThisMBB->addSuccessor(IfFalseMBB);
IfFalseMBB->addSuccessor(SinkMBB);
TrueMBB->addSuccessor(SinkMBB);
MachineBasicBlock::iterator InsertPt = MI;
if (RHSMO.isImm()) {
BuildMI(*ThisMBB, InsertPt, DL, TII.get(TMS34010::CMPIBR))
.addMBB(TrueMBB)
.add(LHSMO)
.add(RHSMO)
.addImm(BranchOpc);
} else {
BuildMI(*ThisMBB, InsertPt, DL, TII.get(TMS34010::CMPBR))
.addMBB(TrueMBB)
.add(LHSMO)
.add(RHSMO)
.addImm(BranchOpc);
}
Register TrueReg = MRI.createVirtualRegister(RC);
Register FalseReg = MRI.createVirtualRegister(RC);
auto EmitValue = [&](const MachineOperand &MO, MachineBasicBlock *BB,
Register Dst) {
if (MO.isReg()) {
BuildMI(*BB, BB->end(), DL, TII.get(TargetOpcode::COPY), Dst)
.addReg(MO.getReg());
} else if (MO.isImm()) {
BuildMI(*BB, BB->end(), DL, TII.get(TMS34010::MOVI), Dst)
.addImm(MO.getImm());
} else {
llvm_unreachable("unsupported select operand");
}
};
EmitValue(FalseMO, IfFalseMBB, FalseReg);
BuildMI(*IfFalseMBB, IfFalseMBB->end(), DL, TII.get(TMS34010::JUncond))
.addMBB(SinkMBB);
EmitValue(TrueMO, TrueMBB, TrueReg);
BuildMI(*SinkMBB, SinkMBB->begin(), DL, TII.get(TargetOpcode::PHI), DstReg)
.addReg(TrueReg)
.addMBB(TrueMBB)
.addReg(FalseReg)
.addMBB(IfFalseMBB);
MI.eraseFromParent();
return SinkMBB;
}
@@ -40,6 +40,46 @@ public:
const SmallVectorImpl<ISD::OutputArg> &Outs,
const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
SelectionDAG &DAG) const override;
SDValue LowerOperation(SDValue Op, SelectionDAG &DAG) const override;
MachineBasicBlock *
EmitInstrWithCustomInserter(MachineInstr &MI,
MachineBasicBlock *MBB) const override;
bool shouldFoldConstantShiftPairToMask(const SDNode *N) const override {
// The TMS34010 has immediate shift instructions, so keeping a shift pair
// as SLL/SRL/SRA is cheaper than materialising a mask and ANDing.
return false;
}
bool allowsMisalignedMemoryAccesses(
EVT VT, unsigned AddrSpace = 0, Align Alignment = Align(1),
MachineMemOperand::Flags Flags = MachineMemOperand::MONone,
unsigned *Fast = nullptr) const override;
bool allowsMisalignedMemoryAccesses(
LLT VT, unsigned AddrSpace = 0, Align Alignment = Align(1),
MachineMemOperand::Flags Flags = MachineMemOperand::MONone,
unsigned *Fast = nullptr) const override;
EVT getSetCCResultType(const DataLayout &DL, LLVMContext &Context,
EVT VT) const override;
// Pointers are bit addresses; the generic memory-op expansion uses byte
// offsets. Refuse it so TMS34010SelectionDAGInfo emits the bit-scaled copy.
bool findOptimalMemOpLowering(LLVMContext &Context, std::vector<EVT> &MemOps,
unsigned Limit, const MemOp &Op, unsigned DstAS,
unsigned SrcAS,
const AttributeList &FuncAttributes,
EVT *LargestVT = nullptr) const override;
private:
SDValue LowerBRCOND(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerBR_CC(SDValue Op, SelectionDAG &DAG) const;
SDValue LowerSELECT_CC(SDValue Op, SelectionDAG &DAG) const;
MachineBasicBlock *emitSelectCC(MachineInstr &MI,
MachineBasicBlock *MBB) const;
};
} // namespace llvm
@@ -7,12 +7,17 @@
//===----------------------------------------------------------------------===//
#include "TMS34010InstrInfo.h"
#include "MCTargetDesc/TMS34010MCTargetDesc.h"
#include "TMS34010.h"
#include "TMS34010Subtarget.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
using namespace llvm;
@@ -22,6 +27,239 @@ using namespace llvm;
TMS34010InstrInfo::TMS34010InstrInfo(const TMS34010Subtarget &STI)
: TMS34010GenInstrInfo(STI, RI), RI() {}
/// True for every conditional branch defined in the .td file, absolute or
/// relative. Instruction selection emits the absolute form; the pre-emit
/// branch-relaxation pass may rewrite a local one to the relative form.
static bool isConditionalBranch(unsigned Opcode) {
switch (Opcode) {
case TMS34010::JAEQ:
case TMS34010::JANE:
case TMS34010::JALT:
case TMS34010::JALE:
case TMS34010::JAGT:
case TMS34010::JAGE:
case TMS34010::JALO:
case TMS34010::JALS:
case TMS34010::JAHI:
case TMS34010::JAHS:
case TMS34010::JAP:
case TMS34010::JAN:
case TMS34010::JAV:
case TMS34010::JANV:
case TMS34010::JANN:
case TMS34010::JREQ:
case TMS34010::JRNE:
case TMS34010::JRLT:
case TMS34010::JRLE:
case TMS34010::JRGT:
case TMS34010::JRGE:
case TMS34010::JRLO:
case TMS34010::JRLS:
case TMS34010::JRHI:
case TMS34010::JRHS:
case TMS34010::JRP:
case TMS34010::JRN:
case TMS34010::JRV:
case TMS34010::JRNV:
case TMS34010::JRNN:
return true;
default:
return false;
}
}
/// True for the unconditional branch in either form.
static bool isUnconditionalBranch(unsigned Opcode) {
return Opcode == TMS34010::JUncond || Opcode == TMS34010::JR;
}
/// True for the fused compare-and-branch pseudo. This instruction stays a
/// terminator until TMS34010ExpandCmpBr splits it after register allocation.
static bool isFusedConditionalBranch(unsigned Opcode) {
return Opcode == TMS34010::CMPBR || Opcode == TMS34010::CMPIBR;
}
/// Return the real conditional-branch opcode carried by a CMPBR/CMPIBR.
static unsigned getFusedBranchOpcode(const MachineInstr &MI) {
return MI.getOperand(3).getImm();
}
/// Describe a fused compare-and-branch as a conditional branch for
/// analyzeBranch. Cond[0] is the real JAcc opcode (what a normal conditional
/// branch reports); the remaining operands let insertBranch rebuild the fused
/// pseudo if a pass removes and re-inserts the branch before
/// TMS34010ExpandCmpBr runs.
static void appendFusedCond(SmallVectorImpl<MachineOperand> &Cond,
const MachineInstr &MI) {
Cond.push_back(MachineOperand::CreateImm(getFusedBranchOpcode(MI)));
Cond.push_back(MachineOperand::CreateImm(MI.getOpcode()));
Cond.push_back(MI.getOperand(1));
Cond.push_back(MI.getOperand(2));
}
bool TMS34010InstrInfo::analyzeBranch(MachineBasicBlock &MBB,
MachineBasicBlock *&TBB,
MachineBasicBlock *&FBB,
SmallVectorImpl<MachineOperand> &Cond,
bool AllowModify) const {
TBB = nullptr;
FBB = nullptr;
Cond.clear();
(void)AllowModify;
MachineBasicBlock::iterator I = MBB.getLastNonDebugInstr();
if (I == MBB.end())
return false; // Empty block: it just falls through.
// A single unconditional branch.
if (isUnconditionalBranch(I->getOpcode())) {
TBB = I->getOperand(0).getMBB();
// A conditional branch immediately before the unconditional branch is the
// two-way form: the conditional target is TBB and the unconditional target
// is FBB. A fused compare-and-branch contributes the real JAcc opcode to
// Cond plus the operands needed to rebuild the pseudo.
if (I != MBB.begin()) {
MachineBasicBlock::iterator P = std::prev(I);
while (P != MBB.begin() && P->isDebugInstr())
--P;
if (!P->isDebugInstr() && isConditionalBranch(P->getOpcode())) {
FBB = TBB;
TBB = P->getOperand(0).getMBB();
Cond.push_back(MachineOperand::CreateImm(P->getOpcode()));
} else if (!P->isDebugInstr() &&
isFusedConditionalBranch(P->getOpcode())) {
FBB = TBB;
TBB = P->getOperand(0).getMBB();
appendFusedCond(Cond, *P);
}
}
return false;
}
// A single conditional branch with the false edge as the fallthrough.
if (isConditionalBranch(I->getOpcode())) {
TBB = I->getOperand(0).getMBB();
Cond.push_back(MachineOperand::CreateImm(I->getOpcode()));
return false;
}
// A fused compare-and-branch before TMS34010ExpandCmpBr splits it.
if (isFusedConditionalBranch(I->getOpcode())) {
TBB = I->getOperand(0).getMBB();
appendFusedCond(Cond, *I);
return false;
}
// RETS and any other terminator cannot be analyzed.
return true;
}
unsigned TMS34010InstrInfo::removeBranch(MachineBasicBlock &MBB,
int *BytesRemoved) const {
if (BytesRemoved)
*BytesRemoved = 0;
MachineBasicBlock::iterator I = MBB.end();
unsigned Count = 0;
while (I != MBB.begin()) {
--I;
if (I->isDebugInstr())
continue;
if (I->getOpcode() != TMS34010::JUncond &&
I->getOpcode() != TMS34010::JR &&
!isConditionalBranch(I->getOpcode()) &&
!isFusedConditionalBranch(I->getOpcode()))
break;
if (BytesRemoved)
*BytesRemoved += getInstSizeInBytes(*I);
I->eraseFromParent();
I = MBB.end();
++Count;
}
return Count;
}
unsigned TMS34010InstrInfo::insertBranch(MachineBasicBlock &MBB,
MachineBasicBlock *TBB,
MachineBasicBlock *FBB,
ArrayRef<MachineOperand> Cond,
const DebugLoc &DL,
int *BytesAdded) const {
if (BytesAdded)
*BytesAdded = 0;
assert(TBB && "insertBranch must not be told to insert a fallthrough");
// An empty condition is an unconditional branch. A false target has no
// representation in that form, so fail loudly even in release builds instead
// of silently dropping the edge.
if (Cond.empty()) {
if (FBB)
report_fatal_error(
"TMS34010: cannot insert an unconditional branch with a false target",
false);
auto &MI = *BuildMI(&MBB, DL, get(TMS34010::JUncond)).addMBB(TBB);
if (BytesAdded)
*BytesAdded += getInstSizeInBytes(MI);
return 1;
}
// A fused compare-and-branch is rebuilt as the same pseudo. Cond[0] is the
// real JAcc opcode; Cond[1..3] are the pseudo opcode, LHS and RHS/immediate.
if (Cond.size() == 4) {
unsigned BranchOpc = Cond[0].getImm();
unsigned PseudoOpc = Cond[1].getImm();
assert(isConditionalBranch(BranchOpc) &&
"expected a real conditional branch opcode");
assert(isFusedConditionalBranch(PseudoOpc) &&
"expected a fused compare-and-branch pseudo");
MachineOperand LHS = Cond[2];
MachineOperand RHS = Cond[3];
// The operands were captured from an instruction that has been removed;
// do not carry stale kill flags into the rebuilt pseudo.
if (LHS.isReg())
LHS.setIsKill(false);
if (RHS.isReg())
RHS.setIsKill(false);
auto &MI = *BuildMI(&MBB, DL, get(PseudoOpc))
.addMBB(TBB)
.add(LHS)
.add(RHS)
.addImm(BranchOpc);
unsigned Count = 1;
if (BytesAdded)
*BytesAdded += getInstSizeInBytes(MI);
if (FBB) {
auto &Uncond = *BuildMI(&MBB, DL, get(TMS34010::JUncond)).addMBB(FBB);
if (BytesAdded)
*BytesAdded += getInstSizeInBytes(Uncond);
++Count;
}
return Count;
}
// Cond[0] is the conditional branch opcode; the compare is a separate CMP
// emitted by TMS34010ExpandCmpBr immediately before it.
assert(Cond.size() == 1 && "TMS34010 branch conditions have one component");
unsigned Opcode = Cond[0].getImm();
assert(isConditionalBranch(Opcode) && "expected a conditional branch");
auto &CondMI = *BuildMI(&MBB, DL, get(Opcode)).addMBB(TBB);
unsigned Count = 1;
if (BytesAdded)
*BytesAdded += getInstSizeInBytes(CondMI);
// A two-way branch needs an explicit unconditional jump to the false target.
if (FBB) {
auto &MI = *BuildMI(&MBB, DL, get(TMS34010::JUncond)).addMBB(FBB);
if (BytesAdded)
*BytesAdded += getInstSizeInBytes(MI);
++Count;
}
return Count;
}
void TMS34010InstrInfo::copyPhysReg(MachineBasicBlock &MBB,
MachineBasicBlock::iterator I,
const DebugLoc &DL, Register DestReg,
@@ -91,3 +329,211 @@ bool TMS34010InstrInfo::expandPostRAPseudo(MachineInstr &MI) const {
MI.eraseFromParent();
return true;
}
namespace {
/// Split a compare-and-branch pseudo into adjacent CMP/CMPI and JAcc.
///
/// The pseudo exists because ST is a physical register and the register-to-
/// register MOVE (like almost every ALU instruction) writes it. A PHI copy
/// inserted before register allocation can therefore land between a separate
/// CMP and Jcc, and the branch would test the copy's flags. Keeping the pair
/// as one terminator through scheduling and register allocation avoids that;
/// this pass expands it after register allocation, so nothing can be inserted
/// between the two real instructions.
class TMS34010ExpandCmpBr : public MachineFunctionPass {
public:
static char ID;
TMS34010ExpandCmpBr() : MachineFunctionPass(ID) {}
bool runOnMachineFunction(MachineFunction &MF) override;
};
char TMS34010ExpandCmpBr::ID = 0;
} // end anonymous namespace
bool TMS34010ExpandCmpBr::runOnMachineFunction(MachineFunction &MF) {
const TMS34010InstrInfo &TII =
*MF.getSubtarget<TMS34010Subtarget>().getInstrInfo();
bool Changed = false;
for (MachineBasicBlock &MBB : MF) {
for (MachineInstr &MI : llvm::make_early_inc_range(MBB)) {
const unsigned Opc = MI.getOpcode();
if (Opc != TMS34010::CMPBR && Opc != TMS34010::CMPIBR)
continue;
DebugLoc DL = MI.getDebugLoc();
const MachineOperand Dest = MI.getOperand(0);
const MachineOperand LHS = MI.getOperand(1);
const MachineOperand RHS = MI.getOperand(2);
const unsigned BranchOpc = MI.getOperand(3).getImm();
if (Opc == TMS34010::CMPIBR)
BuildMI(MBB, MI, DL, TII.get(TMS34010::CMPI))
.addImm(RHS.getImm())
.add(LHS);
else
BuildMI(MBB, MI, DL, TII.get(TMS34010::CMP)).add(RHS).add(LHS);
BuildMI(MBB, MI, DL, TII.get(BranchOpc)).add(Dest);
MI.eraseFromParent();
Changed = true;
}
}
return Changed;
}
FunctionPass *llvm::createTMS34010ExpandCmpBrPass() {
return new TMS34010ExpandCmpBr();
}
namespace {
/// Rewrite in-range absolute branches to the one-word relative form.
///
/// The pass runs after prologue/epilogue insertion and block placement, when
/// instruction sizes and block order are final. It starts by assuming the
/// smallest possible layout (every local branch in the one-word form) and then
/// grows back to the absolute form every branch whose displacement does not
/// fit. Growing an instruction can only push a branch further out of range,
/// never bring one back in, so the layout converges and the final encoding is
/// self-consistent.
///
/// Zero is not an encodable displacement (the ISA reserves it for the 16-bit
/// relative form), so a branch to the instruction immediately after it keeps
/// the absolute form.
class TMS34010BranchRelaxation : public MachineFunctionPass {
public:
static char ID;
TMS34010BranchRelaxation() : MachineFunctionPass(ID) {}
bool runOnMachineFunction(MachineFunction &MF) override;
};
char TMS34010BranchRelaxation::ID = 0;
} // end anonymous namespace
unsigned TMS34010InstrInfo::getInstSizeInBytes(const MachineInstr &MI) const {
// ADDI, MOVI, CMPI, SUBI and ADDSTK are emitted in the 16-bit immediate form
// when the value fits a signed 16-bit field. The .td describes only the long
// form (the generic encoder uses MCII.get(Opcode).getSize() for it), so the
// machine-level layout must account for the 4-byte form explicitly. The
// operand indices mirror TMS34010MCCodeEmitter::encodeInstruction.
unsigned ImmOp;
switch (MI.getOpcode()) {
case TMS34010::ADDI:
ImmOp = 2;
break;
case TMS34010::ADDSTK:
case TMS34010::CMPI:
ImmOp = 0;
break;
case TMS34010::MOVI:
case TMS34010::MC_ADDI:
case TMS34010::MC_SUBI:
case TMS34010::MC_CMPI:
case TMS34010::MC_MOVI:
ImmOp = 1;
break;
default:
return MI.getDesc().getSize();
}
if (ImmOp >= MI.getNumOperands() || !MI.getOperand(ImmOp).isImm())
return MI.getDesc().getSize();
return isInt<16>(MI.getOperand(ImmOp).getImm()) ? 4 : 6;
}
bool TMS34010BranchRelaxation::runOnMachineFunction(MachineFunction &MF) {
const TMS34010InstrInfo &TII =
*MF.getSubtarget<TMS34010Subtarget>().getInstrInfo();
// A non-meta instruction with no size would make the layout meaningless.
// Keep every branch absolute instead of guessing (and instead of aborting on
// a function this pass cannot measure).
for (const MachineBasicBlock &MBB : MF)
for (const MachineInstr &MI : MBB)
if (!MI.isDebugInstr() && !MI.isMetaInstruction() &&
TII.getInstSizeInBytes(MI) == 0)
return false;
// Tentatively select the one-word form for every branch whose target is a
// block in this function. The layout below then grows every branch that
// does not fit. Starting from the smallest possible layout is what makes the
// iteration monotone: growing an instruction can only push a branch further
// out of range, never bring one back in, so the loop terminates and the
// result is self-consistent.
bool EverChanged = false;
for (MachineBasicBlock &MBB : MF)
for (MachineInstr &MI : MBB) {
if (MI.isDebugInstr() || MI.isMetaInstruction())
continue;
unsigned ShortOpc = TMS34010::getShortBranchOpcode(MI.getOpcode());
if (!ShortOpc || MI.getNumOperands() == 0 || !MI.getOperand(0).isMBB())
continue;
MI.setDesc(TII.get(ShortOpc));
EverChanged = true;
}
bool Changed = true;
while (Changed) {
Changed = false;
// Byte offset of every block from the start of the function.
DenseMap<const MachineBasicBlock *, int64_t> BlockOffset;
int64_t Offset = 0;
for (const MachineBasicBlock &MBB : MF) {
BlockOffset[&MBB] = Offset;
for (const MachineInstr &MI : MBB) {
if (MI.isDebugInstr() || MI.isMetaInstruction())
continue;
unsigned Size = TII.getInstSizeInBytes(MI);
assert(Size && "unsized instruction survived the pre-scan");
Offset += Size;
}
}
for (MachineBasicBlock &MBB : MF) {
int64_t InstrOffset = BlockOffset.lookup(&MBB);
for (MachineInstr &MI : MBB) {
if (MI.isDebugInstr() || MI.isMetaInstruction())
continue;
unsigned Size = TII.getInstSizeInBytes(MI);
assert(Size && "unsized instruction survived the pre-scan");
unsigned LongOpc = TMS34010::getLongBranchOpcode(MI.getOpcode());
if (LongOpc && MI.getNumOperands() != 0 &&
MI.getOperand(0).isMBB()) {
const MachineBasicBlock *Target = MI.getOperand(0).getMBB();
auto It = BlockOffset.find(Target);
bool Fits = false;
if (It != BlockOffset.end()) {
// One-word instruction: PC after it is InstrOffset + 2 bytes, and
// the ISA displacement counts 16-bit words from PC+16.
int64_t Delta = It->second - (InstrOffset + 2);
int64_t Disp = Delta / 2;
// Zero is the 16-bit relative form, so it is not encodable here.
Fits = Delta % 2 == 0 && Disp != 0 && Disp >= -127 && Disp <= 127;
}
if (!Fits) {
MI.setDesc(TII.get(LongOpc));
Changed = true;
EverChanged = true;
Size = TII.getInstSizeInBytes(MI);
}
}
InstrOffset += Size;
}
}
}
return EverChanged;
}
FunctionPass *llvm::createTMS34010BranchRelaxationPass() {
return new TMS34010BranchRelaxation();
}
@@ -45,8 +45,35 @@ public:
const override;
bool expandPostRAPseudo(MachineInstr &MI) const override;
bool analyzeBranch(MachineBasicBlock &MBB, MachineBasicBlock *&TBB,
MachineBasicBlock *&FBB,
SmallVectorImpl<MachineOperand> &Cond,
bool AllowModify = false) const override;
unsigned removeBranch(MachineBasicBlock &MBB,
int *BytesRemoved = nullptr) const override;
unsigned insertBranch(MachineBasicBlock &MBB, MachineBasicBlock *TBB,
MachineBasicBlock *FBB, ArrayRef<MachineOperand> Cond,
const DebugLoc &DL,
int *BytesAdded = nullptr) const override;
unsigned getInstSizeInBytes(const MachineInstr &MI) const override;
};
/// Shrink local absolute branches to the one-word relative form when the final
/// machine layout proves the displacement fits. Runs after prologue/epilogue
/// insertion and block placement; the MC backend repeats the range check for
/// symbolic operands as a safety net.
FunctionPass *createTMS34010BranchRelaxationPass();
/// Split the compare-and-branch pseudo (CMPBR/CMPIBR) into adjacent CMP/CMPI
/// and JAcc instructions. This runs after register allocation (post-RA), when
/// the PHI copies and the pre-RA schedule are final, so no copy can land
/// between the compare and the branch and overwrite the ST flags.
FunctionPass *createTMS34010ExpandCmpBrPass();
} // namespace llvm
#endif
File diff suppressed because it is too large. Load diff
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "TMS34010MCInstLower.h"
#include "TMS34010.h"
#include "llvm/CodeGen/AsmPrinter.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineInstr.h"
@@ -44,7 +45,16 @@ MCOperand TMS34010MCInstLower::LowerSymbolOperand(const MachineOperand &MO,
}
void TMS34010MCInstLower::Lower(const MachineInstr *MI, MCInst &OutMI) const {
OutMI.setOpcode(MI->getOpcode());
unsigned Opcode = MI->getOpcode();
// The frame lowering uses CodeGen MMTM/MMFM instructions; the assembler and
// encoder use the MC descriptions, whose operands and encoding are the same.
// Remap here so the printer emits the register-list spelling.
if (Opcode == TMS34010::MMTM)
Opcode = TMS34010::MC_MMTM;
else if (Opcode == TMS34010::MMFM)
Opcode = TMS34010::MC_MMFM;
OutMI.setOpcode(Opcode);
for (const MachineOperand &MO : MI->operands()) {
MCOperand MCOp;
@@ -48,6 +48,10 @@ TMS34010RegisterInfo::getReservedRegs(const MachineFunction &MF) const {
Reserved.set(TMS34010::A14);
Reserved.set(TMS34010::A15);
// The status register is defined by CMP/CMPI and read by the conditional
// branches; it must never be allocated.
Reserved.set(TMS34010::ST);
// The B file is reserved for a later task.
for (unsigned Reg = TMS34010::B0; Reg <= TMS34010::B15; ++Reg)
Reserved.set(Reg);
@@ -32,7 +32,20 @@ def A11 : TMS34010Reg<11, "A11">;
def A12 : TMS34010Reg<12, "A12">;
def A13 : TMS34010Reg<13, "A13">;
def A14 : TMS34010Reg<14, "A14">;
def A15 : TMS34010Reg<15, "A15">;
// GSPA spells register 15 of the A file "SP"; it rejects "A15". The encoder
// and the fixed SP forms use the same register number, so an AltName is enough
// for the assembler. The printer canonicalises A15 to SP in
// TMS34010InstPrinter.
def A15 : TMS34010Reg<15, "A15"> {
let AltNames = ["sp"];
}
// The status register is not a general-purpose register: CMP/CMPI define it and
// the conditional branches use it. It is deliberately outside every register
// class so it can never be allocated.
def ST : Register<"ST"> {
let Namespace = "TMS34010";
}
// The B file is reserved for a later task.
def B0 : TMS34010Reg<16, "B0">;
@@ -56,6 +69,26 @@ def ARegs : RegisterClass<"TMS34010", [i32], 32,
(add A0, A1, A2, A3, A4, A5, A6, A7,
A9, A10, A11, A12)>;
// Every register in both files. This class is used only by the MC instruction
// descriptions: the assembler must accept the B file even though the M4
// allocator and ABI do not use it. It is never allocatable, so no code path
// can select a B register from a DAG pattern.
def GPR : RegisterClass<"TMS34010", [i32], 32,
(add A0, A1, A2, A3, A4, A5, A6, A7,
A8, A9, A10, A11, A12, A13, A14, A15,
B0, B1, B2, B3, B4, B5, B6, B7,
B8, B9, B10, B11, B12, B13, B14, B15)> {
let isAllocatable = 0;
}
// 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.
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "TMS34010SelectionDAGInfo.h"
#include "llvm/CodeGen/SelectionDAG.h"
#define GET_SDNODE_DESC
#include "TMS34010GenSDNodeInfo.inc"
@@ -17,3 +18,122 @@ TMS34010SelectionDAGInfo::TMS34010SelectionDAGInfo()
: SelectionDAGGenTargetInfo(TMS34010GenSDNodeInfo) {}
TMS34010SelectionDAGInfo::~TMS34010SelectionDAGInfo() = default;
/// The largest known-size IR memory operation that is expanded inline. Larger
/// constants are normally already converted to loops by
/// PreISelIntrinsicLowering, whose default `getMaxMemIntrinsicInlineSizeThreshold`
/// is also 64. Keep this bound in sync with that threshold: if PreISel is
/// configured to inline larger constants, this hook would see them and (for a
/// non-AlwaysInline copy) return SDValue(), which falls into the libcall path
/// that this target cannot lower.
static constexpr uint64_t MaxInlineMemOpBytes = 64;
/// Byte address arithmetic on this target is bit address arithmetic: a
/// one-byte step is eight in the pointer value. The generic memcpy/memset
/// expansion adds byte offsets, so the target expands the known-size copies
/// itself with bit-scaled word and byte accesses.
static SDValue emitMemCopy(SelectionDAG &DAG, const SDLoc &DL, SDValue Chain,
SDValue Dst, SDValue Src, uint64_t Size,
bool IsVolatile, MachinePointerInfo DstPtrInfo,
MachinePointerInfo SrcPtrInfo) {
EVT PtrVT = Dst.getValueType();
MachineMemOperand::Flags MMOFlags =
IsVolatile ? MachineMemOperand::MOVolatile : MachineMemOperand::MONone;
uint64_t I = 0;
// 32-bit accesses are the widest legal scalar memory operation and are
// unaligned-safe on the TMS34010.
for (; I + 4 <= Size; I += 4) {
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
SDValue SrcAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Src, Off);
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
SDValue Load = DAG.getLoad(MVT::i32, DL, Chain, SrcAddr,
SrcPtrInfo.getWithOffset(I), Align(1), MMOFlags);
Chain = DAG.getStore(Load.getValue(1), DL, Load, DstAddr,
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
}
for (; I < Size; ++I) {
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
SDValue SrcAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Src, Off);
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
SDValue Load = DAG.getLoad(MVT::i8, DL, Chain, SrcAddr,
SrcPtrInfo.getWithOffset(I), Align(1), MMOFlags);
Chain = DAG.getStore(Load.getValue(1), DL, Load, DstAddr,
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
}
return Chain;
}
SDValue TMS34010SelectionDAGInfo::EmitTargetCodeForMemcpy(
SelectionDAG &DAG, const SDLoc &DL, SDValue Chain, SDValue Dst, SDValue Src,
SDValue Size, Align DstAlign, Align SrcAlign, bool IsVolatile,
bool AlwaysInline, MachinePointerInfo DstPtrInfo,
MachinePointerInfo SrcPtrInfo) const {
(void)DstAlign;
(void)SrcAlign;
auto *ConstantSize = dyn_cast<ConstantSDNode>(Size);
if (!ConstantSize)
return SDValue();
uint64_t Bytes = ConstantSize->getZExtValue();
if (!Bytes)
return Chain;
// A non-inline constant this large falls back to the libcall path; byval
// copies are AlwaysInline and stay exact (they must not call with A14 as
// both the destination and the argument area). The bound is coupled to
// PreISel's inline threshold; see MaxInlineMemOpBytes.
if (Bytes > MaxInlineMemOpBytes && !AlwaysInline)
return SDValue();
return emitMemCopy(DAG, DL, Chain, Dst, Src, Bytes, IsVolatile, DstPtrInfo,
SrcPtrInfo);
}
SDValue TMS34010SelectionDAGInfo::EmitTargetCodeForMemset(
SelectionDAG &DAG, const SDLoc &DL, SDValue Chain, SDValue Dst, SDValue Src,
SDValue Size, Align Alignment, bool IsVolatile, bool AlwaysInline,
MachinePointerInfo DstPtrInfo) const {
(void)Alignment;
auto *ConstantSize = dyn_cast<ConstantSDNode>(Size);
if (!ConstantSize)
return SDValue();
uint64_t Bytes = ConstantSize->getZExtValue();
if (!Bytes)
return Chain;
if (Bytes > MaxInlineMemOpBytes && !AlwaysInline)
return SDValue();
EVT PtrVT = Dst.getValueType();
MachineMemOperand::Flags MMOFlags =
IsVolatile ? MachineMemOperand::MOVolatile : MachineMemOperand::MONone;
// Replicate the byte value into a 32-bit word. Src may have been promoted
// past i8 by legalization, so mask the low byte first.
SDValue Byte = DAG.getAnyExtOrTrunc(Src, DL, MVT::i32);
Byte = DAG.getNode(ISD::AND, DL, MVT::i32, Byte,
DAG.getConstant(0xFF, DL, MVT::i32));
SDValue Shift8 = DAG.getConstant(8, DL, MVT::i32);
SDValue Shift16 = DAG.getConstant(16, DL, MVT::i32);
SDValue Shift24 = DAG.getConstant(24, DL, MVT::i32);
SDValue Word = Byte;
Word = DAG.getNode(ISD::OR, DL, MVT::i32, Word,
DAG.getNode(ISD::SHL, DL, MVT::i32, Byte, Shift8));
Word = DAG.getNode(ISD::OR, DL, MVT::i32, Word,
DAG.getNode(ISD::SHL, DL, MVT::i32, Byte, Shift16));
Word = DAG.getNode(ISD::OR, DL, MVT::i32, Word,
DAG.getNode(ISD::SHL, DL, MVT::i32, Byte, Shift24));
SDValue Byte8 = DAG.getAnyExtOrTrunc(Src, DL, MVT::i8);
uint64_t I = 0;
for (; I + 4 <= Bytes; I += 4) {
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
Chain = DAG.getStore(Chain, DL, Word, DstAddr,
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
}
for (; I < Bytes; ++I) {
SDValue Off = DAG.getConstant(I * 8, DL, MVT::i32);
SDValue DstAddr = DAG.getNode(ISD::ADD, DL, PtrVT, Dst, Off);
Chain = DAG.getStore(Chain, DL, Byte8, DstAddr,
DstPtrInfo.getWithOffset(I), Align(1), MMOFlags);
}
return Chain;
}
@@ -21,6 +21,22 @@ public:
TMS34010SelectionDAGInfo();
~TMS34010SelectionDAGInfo() override;
// The TMS34010 pointer type is a *bit* address, while LLVM's generic
// memcpy/memset expansion adds byte offsets. Emitting the copies here keeps
// every offset in bits: four bytes advance the address by 32.
SDValue EmitTargetCodeForMemcpy(SelectionDAG &DAG, const SDLoc &DL,
SDValue Chain, SDValue Dst, SDValue Src,
SDValue Size, Align DstAlign, Align SrcAlign,
bool IsVolatile, bool AlwaysInline,
MachinePointerInfo DstPtrInfo,
MachinePointerInfo SrcPtrInfo) const override;
SDValue EmitTargetCodeForMemset(SelectionDAG &DAG, const SDLoc &DL,
SDValue Chain, SDValue Dst, SDValue Src,
SDValue Size, Align Alignment,
bool IsVolatile, bool AlwaysInline,
MachinePointerInfo DstPtrInfo) const override;
};
} // namespace llvm
@@ -7,19 +7,39 @@
//===----------------------------------------------------------------------===//
#include "TMS34010TargetMachine.h"
#include "MCTargetDesc/TMS34010MCAsmBackend.h"
#include "MCTargetDesc/TMS34010MCObjectWriter.h"
#include "TMS34010.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Mangler.h"
#include "llvm/IR/Module.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Pass.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetOptions.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/LowerMemIntrinsics.h"
#include <memory>
#include <optional>
using namespace llvm;
@@ -61,6 +81,66 @@ 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.
//
// GSPA 2.00 keeps only 31 identifier characters and silently truncates the
// rest. The token itself is `_` plus eight hex digits, so a long private base
// name would push the token past the limit and reintroduce the collision. Cap
// the base at 22 characters; if it is longer, fold the full name into another
// eight-hex token (13-char prefix + `_` + 8 hex = 22) so the module token is
// always the last field of a 31-character label.
//
// Only local symbols are capped. A *public* name longer than 31 characters is
// still silently truncated by GSPA; that remains a known limitation outside
// the private-label scope of this task.
static constexpr unsigned MaxGSPASymbolLength = 31;
static constexpr unsigned HexTokenLength = 9; // '_' + 8 hex digits
static unsigned hashName(StringRef S) {
// Custom 64-bit string hash (FNV-style, not canonical FNV-1a), folded to
// 32 bits. Eight uppercase hex digits stay GSPA-safe.
uint64_t Hash = 1469598103934665603ULL;
for (unsigned char C : S) {
Hash ^= C;
Hash *= 1099511628211ULL;
}
return static_cast<unsigned>(Hash ^ (Hash >> 32));
}
static void appendHexToken(SmallVectorImpl<char> &Name, unsigned Token) {
Name.push_back('_');
for (int Shift = 28; Shift >= 0; Shift -= 4)
Name.push_back(hexdigit((Token >> Shift) & 0xF));
}
static void appendModuleToken(SmallVectorImpl<char> &Name,
const GlobalValue *GV) {
const Module *M = GV->getParent();
StringRef ID = M ? M->getModuleIdentifier() : StringRef();
unsigned Token = hashName(ID);
constexpr unsigned MaxBaseLength =
MaxGSPASymbolLength - HexTokenLength;
if (Name.size() > MaxBaseLength) {
unsigned NameToken = hashName(StringRef(Name.data(), Name.size()));
constexpr unsigned Keep =
MaxBaseLength - HexTokenLength;
SmallString<64> Short;
Short.append(Name.begin(), Name.begin() + Keep);
appendHexToken(Short, NameToken);
Name.assign(Short.begin(), Short.end());
}
appendHexToken(Name, Token);
}
namespace {
class TMS34010ObjectFile : public TargetLoweringObjectFileELF {
@@ -70,6 +150,8 @@ public:
SmallString<128> Name("_");
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
sanitizeGSPASymbolName(Name);
if (GV->hasLocalLinkage())
appendModuleToken(Name, GV);
return getContext().getOrCreateSymbol(Name);
}
@@ -80,6 +162,8 @@ public:
OutName.push_back('_');
OutName.append(Name.begin(), Name.end());
sanitizeGSPASymbolName(OutName);
if (GV->hasLocalLinkage())
appendModuleToken(OutName, GV);
}
};
@@ -91,6 +175,204 @@ static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
namespace {
/// Every register-to-register MOVE writes the status flags. PHI elimination
/// inserts a loop backedge's copy immediately before the terminator; if the
/// terminator is a conditional branch, that copy lands between the CMP and the
/// JA<cc> and the branch tests the copy's flags. Splitting critical edges
/// moves the copy onto the edge. The new blocks are appended at the end of
/// the function so the source block's fallthrough is its false successor,
/// which keeps the original branch polarity (and so its condition code).
class TMS34010SplitCriticalEdges : public FunctionPass {
public:
static char ID;
TMS34010SplitCriticalEdges() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
SmallPtrSet<BasicBlock *, 8> Before;
for (BasicBlock &BB : F)
Before.insert(&BB);
if (!SplitAllCriticalEdges(F))
return false;
SmallVector<BasicBlock *, 8> NewBlocks;
for (BasicBlock &BB : F)
if (!Before.count(&BB))
NewBlocks.push_back(&BB);
for (BasicBlock *BB : NewBlocks)
BB->moveAfter(&F.back());
return true;
}
};
char TMS34010SplitCriticalEdges::ID = 0;
/// PreISelIntrinsicLowering expands a runtime-length or larger-than-threshold
/// constant memcpy/memset/memmove into IR loops built from i8 GEPs. Those
/// GEPs carry byte offsets, but TMS34010 pointers are bit addresses (the
/// tms-clang pipeline normalizes earlier GEPs before llc). Scale only the
/// GEPs in the known expansion / memmove loop blocks; every GEP in the input
/// module is already a bit offset by contract.
class TMS34010ScaleMemExpansionGEPs : public FunctionPass {
public:
static char ID;
TMS34010ScaleMemExpansionGEPs() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
bool Changed = false;
for (BasicBlock &BB : F) {
StringRef Name = BB.getName();
// The exact LowerMemIntrinsics block names. Both the runtime ("dynamic")
// and the >threshold constant ("static") expansions must be scaled.
// memmove has its own loop/middle/residual blocks; do NOT match
// memmove_done (the continuation block keeps the user's already-bit-
// offset GEPs) or the copy_backwards/copy_forward selection blocks.
// LLVM uniquifies repeated block names with a numeric suffix, so match
// the prefixes rather than exact names: a function with two memmoves
// has e.g. memmove_bwd_main_loop7 next to memmove_bwd_main_loop.
bool IsMemMoveLoop = Name.starts_with("memmove_") &&
!Name.starts_with("memmove_done") &&
!Name.starts_with("memmove_copy_");
if (!Name.starts_with("dynamic-memcpy-expansion-") &&
!Name.starts_with("static-memcpy-expansion-") &&
!Name.starts_with("dynamic-memset-expansion-") &&
!Name.starts_with("static-memset-expansion-") && !IsMemMoveLoop)
continue;
SmallVector<GetElementPtrInst *, 4> GEPs;
for (Instruction &I : BB)
if (auto *GEP = dyn_cast<GetElementPtrInst>(&I))
GEPs.push_back(GEP);
for (GetElementPtrInst *GEP : GEPs)
Changed |= scaleGEP(GEP);
}
return Changed;
}
private:
static bool scaleGEP(GetElementPtrInst *GEP) {
// The LowerMemIntrinsics form always indexes i8 with one index.
if (GEP->getNumOperands() != 2 ||
!GEP->getSourceElementType()->isIntegerTy(8))
return false;
IRBuilder<> B(GEP);
Value *Idx = B.CreateSExtOrTrunc(GEP->getOperand(1), B.getInt32Ty(),
"bit.idx");
Value *Base =
B.CreatePtrToInt(GEP->getPointerOperand(), B.getInt32Ty(), "bit.base");
Value *Off = B.CreateShl(Idx, 3, "bit.off");
Value *Addr = B.CreateAdd(Base, Off, "bit.addr");
Value *NewPtr = B.CreateIntToPtr(Addr, GEP->getType(), "bit.ptr");
GEP->replaceAllUsesWith(NewPtr);
GEP->eraseFromParent();
return true;
}
};
char TMS34010ScaleMemExpansionGEPs::ID = 0;
/// PreISelIntrinsicLowering expands only memmove intrinsics whose size is not a
/// compile-time constant (or is above its threshold). A small constant
/// memmove is left for SelectionDAG, where this target has no memmove hook and
/// the fallback libcall is rejected by LowerCall. Expand every remaining
/// memmove into the same overlap-safe forward/backward loops used by the
/// dynamic form; the GEP scaling pass below handles their byte offsets.
class TMS34010LowerMemMove : public FunctionPass {
public:
static char ID;
TMS34010LowerMemMove() : FunctionPass(ID) {}
bool runOnFunction(Function &F) override {
const TargetTransformInfo &TTI =
getAnalysis<TargetTransformInfoWrapperPass>().getTTI(F);
SmallVector<MemMoveInst *, 4> Work;
for (BasicBlock &BB : F)
for (Instruction &I : BB)
if (auto *MM = dyn_cast<MemMoveInst>(&I))
Work.push_back(MM);
bool Changed = false;
for (MemMoveInst *MM : Work) {
if (expandMemMoveAsLoop(MM, TTI)) {
MM->eraseFromParent();
Changed = true;
}
}
return Changed;
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<TargetTransformInfoWrapperPass>();
}
};
char TMS34010LowerMemMove::ID = 0;
/// Reject IR shapes that are outside the supported M5 pipeline, so the raw
/// pipeline gets an actionable diagnostic instead of a generic SelectionDAG
/// failure or an accidental reliance on an unsupported wide-multiply path.
///
/// - An i33 multiply (exactly 33 bits) is what IndVarSimplify's closed-form
/// exit-value replacement produces (n*(n+1)/2). tms-clang keeps the loop
/// with -replexitval=never; an opt run that omits it reaches llc as
/// `mul i33` and gets a diagnostic naming that driver policy.
/// - A 64-bit (or wider) multiply is the ordinary i64 path, not the i33
/// closed-form shape. It is reserved for M6, and its diagnostic must not
/// point at -replexitval=never (that policy is already applied by tms-clang
/// and does nothing for i64).
/// - A local (static) variadic definition loses its `...` to
/// DeadArgumentElimination at -O2 because nothing calls llvm.va_start. The
/// M4 contiguous-stack walk still reads those slots, so reject it with a
/// remedy. -O0 keeps the M4 varargs gate working, and external definitions
/// (sprintf) are unaffected.
class TMS34010RejectUnsupportedIR : public FunctionPass {
public:
static char ID;
explicit TMS34010RejectUnsupportedIR(CodeGenOptLevel OL)
: FunctionPass(ID), OptLevel(OL) {}
bool runOnFunction(Function &F) override {
for (BasicBlock &BB : F)
for (Instruction &I : BB)
if (auto *BO = dyn_cast<BinaryOperator>(&I))
if (BO->getOpcode() == Instruction::Mul)
if (auto *IT = dyn_cast<IntegerType>(BO->getType())) {
unsigned Width = IT->getBitWidth();
if (Width == 33)
report_fatal_error(
"TMS34010: i33 multiply (umul_lohi shape) reached code "
"generation from the raw opt pipeline; tms-clang avoids "
"this closed-form loop exit value with -replexitval=never. "
"Add that flag to opt; the closed-form path is reserved "
"for i64/M6.",
false);
if (Width > 33)
report_fatal_error(
"TMS34010: 64-bit multiply reached code generation; the "
"i64 path is reserved for M6. M5 supports 32-bit "
"multiplication only.",
false);
}
if (OptLevel != CodeGenOptLevel::None && F.isVarArg() &&
F.hasLocalLinkage() && !F.isDeclaration())
report_fatal_error(
Twine("TMS34010: local variadic definition '") + F.getName() +
"' is not accepted at a non-O0 llc level; general varargs/"
"va_list are an M6 feature. M5 supports only the contiguous-"
"stack form anchored at an i32/pointer last fixed argument; "
"compile with -O0 for that M4-compatible subset.",
false);
return false;
}
private:
CodeGenOptLevel OptLevel;
};
char TMS34010RejectUnsupportedIR::ID = 0;
class TMS34010PassConfig : public TargetPassConfig {
public:
TMS34010PassConfig(TMS34010TargetMachine &TM, PassManagerBase &PM)
@@ -102,6 +384,19 @@ public:
bool addInstSelector() override;
void addIRPasses() override {
TargetPassConfig::addIRPasses();
addPass(new TMS34010RejectUnsupportedIR(
getTMS34010TargetMachine().getOptLevel()));
// PreISelIntrinsicLowering ran before this hook and may have introduced
// byte-offset GEPs in the runtime-size mem intrinsic loops. Small constant
// memmove intrinsics are not expanded by it; expand them here so they use
// the same overlap-safe loops and the scaling pass below.
addPass(new TMS34010LowerMemMove());
addPass(new TMS34010ScaleMemExpansionGEPs());
addPass(new TMS34010SplitCriticalEdges());
}
// M1 has no stack. The fast register allocator relies on being able to
// spill and reload values; with four argument registers and a fixed A0
// return register, a `-O0` leaf function can hit its spill path even though
@@ -109,6 +404,17 @@ public:
// This is a machine-level choice only: llc still skips the IR pipeline at
// -O0, so late GEPs keep their bit offsets.
void addFastRegAlloc() override { addOptimizedRegAlloc(); }
// Branch relaxation is a pre-emit pass: it runs after prologue/epilogue
// insertion, pseudo expansion and block placement, when the final layout
// decides which local branches fit the one-word relative form.
void addPostRegAlloc() override {
addPass(createTMS34010ExpandCmpBrPass());
}
void addPreEmitPass() override {
addPass(createTMS34010BranchRelaxationPass());
}
};
} // end anonymous namespace
@@ -136,6 +442,45 @@ TMS34010TargetMachine::createPassConfig(PassManagerBase &PM) {
return new TMS34010PassConfig(*this, PM);
}
Expected<std::unique_ptr<MCStreamer>> TMS34010TargetMachine::createMCStreamer(
raw_pwrite_stream &Out, raw_pwrite_stream *DwoOut, CodeGenFileType FileType,
MCContext &Ctx) {
if (FileType != CodeGenFileType::ObjectFile)
return CodeGenTargetMachineImpl::createMCStreamer(Out, DwoOut, FileType,
Ctx);
if (DwoOut)
return make_error<StringError>(
"TMS34010 does not support split DWARF objects",
inconvertibleErrorCode());
std::unique_ptr<MCCodeEmitter> MCE(
getTarget().createMCCodeEmitter(*getMCInstrInfo(), Ctx));
if (!MCE)
return make_error<StringError>("createMCCodeEmitter failed",
inconvertibleErrorCode());
std::unique_ptr<MCAsmBackend> MAB(getTarget().createMCAsmBackend(
getMCSubtargetInfo(), getMCRegisterInfo(), Options.MCOptions));
if (!MAB)
return make_error<StringError>("createMCAsmBackend failed",
inconvertibleErrorCode());
// TI COFF is not one of LLVM's built-in object formats, so build the writer
// and the generic object streamer directly instead of going through
// MCAsmBackend::createObjectWriter's format switch. The streamer records
// `.globl` symbols in a set shared with the writer, which uses it to tell
// global definitions from local labels.
auto Globals = std::make_shared<TMS34010GlobalSymbols>();
auto *TMSMAB = static_cast<TMS34010MCAsmBackend *>(MAB.get());
std::unique_ptr<MCObjectWriter> OW =
TMSMAB->createObjectWriter(Out, Globals);
return std::make_unique<TMS34010MCStreamer>(Ctx, std::move(MAB),
std::move(OW), std::move(MCE),
std::move(Globals));
}
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeTMS34010Target() {
RegisterTargetMachine<TMS34010TargetMachine> X(getTheTMS34010Target());
@@ -32,6 +32,10 @@ public:
TargetPassConfig *createPassConfig(PassManagerBase &PM) override;
Expected<std::unique_ptr<MCStreamer>>
createMCStreamer(raw_pwrite_stream &Out, raw_pwrite_stream *DwoOut,
CodeGenFileType FileType, MCContext &Ctx) override;
TargetLoweringObjectFile *getObjFileLowering() const override {
return TLOF.get();
}
+1 -1
View File
@@ -11,6 +11,6 @@ entry:
; CHECK-LABEL: _call0:
; CHECK-NOT: MOVE A14, -*SP, 1
; CHECK: CALLA _callee
; CHECK: SETF 16, 0, 0
; CHECK: SETF 16, 1, 0
; CHECK-NEXT: SETF 32, 0, 1
; CHECK: RETS 0
+5 -5
View File
@@ -9,10 +9,10 @@ entry:
}
; CHECK-LABEL: _keep:
; CHECK: MOVE A13, -*SP, 1
; CHECK: MOVE A{{[0-9]+}}, -*SP, 1
; CHECK-NEXT: MMTM SP, A0, A1
; CHECK: CALLA _callee
; CHECK: SETF 32, 0, 1
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
; CHECK: MOVE *SP+, A{{[0-9]+}}, 1
; CHECK: MOVE *SP+, A13, 1
; CHECK: RETS 2
; CHECK: MOVE *SP(128), A14, 1
; CHECK-NEXT: MMFM SP, A0, A1
; CHECK-NEXT: MOVE *SP+, A13, 1
; CHECK-NEXT: RETS 2
+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
+2 -2
View File
@@ -10,9 +10,9 @@ entry:
ret i32 %r
}
; CHECK-LABEL: _caller:
; CHECK: SETF 16, 0, 0
; CHECK: SETF 16, 1, 0
; CHECK-NEXT: SETF 32, 0, 1
; CHECK: CALLA _callee
; CHECK-NEXT: SETF 16, 0, 0
; CHECK-NEXT: SETF 16, 1, 0
; CHECK-NEXT: SETF 32, 0, 1
; CHECK: RETS 2
+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 -1
View File
@@ -27,6 +27,6 @@ entry:
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 0
; CHECK-LABEL: _store_after_call:
; CHECK: CALLA _callee
; CHECK-NEXT: SETF 16, 0, 0
; CHECK-NEXT: SETF 16, 1, 0
; CHECK-NEXT: SETF 32, 0, 1
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 0
@@ -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
}
@@ -0,0 +1,9 @@
; 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
;
; Floating-point returns are outside the M5 integer ABI.
; CHECK: M2 supports only void/integer(<=32)/pointer returns
define float @retf() {
entry:
ret float 1.0
}
@@ -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
@@ -0,0 +1,11 @@
; 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
;
; i64 returns stay outside the M5 ABI even though i8/i16 returns are now
; accepted; CanLowerReturn must reject the original IR type before the type
; legalizer can split it into two i32 return pieces.
; CHECK: M2 supports only void/integer(<=32)/pointer returns
define i64 @ret64() {
entry:
ret i64 7
}
@@ -1,7 +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 void/i32/pointer returns
define i8 @retc() {
entry:
ret i8 7
}
@@ -0,0 +1,11 @@
; 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 inalloca convention has no TMS34010 lowering; reject the formal flag
; rather than treating the pointer as an ordinary argument.
; CHECK: M2 supports only plain arguments (no sret/inalloca/preallocated/inreg/nest)
define void @inalloca_arg(ptr inalloca(i32) %p) {
entry:
store i32 0, ptr %p
ret void
}
@@ -0,0 +1,11 @@
; 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 preallocated convention has no TMS34010 lowering; reject the formal flag
; rather than treating the pointer as an ordinary argument.
; CHECK: M2 supports only plain arguments (no sret/inalloca/preallocated/inreg/nest)
define void @preallocated_arg(ptr preallocated(i32) %p) {
entry:
store i32 0, ptr %p
ret void
}
+1 -1
View File
@@ -3,7 +3,7 @@
;
; M2 has no sret convention. The pointer attribute must be rejected even
; though its legalized type is a plain i32.
; CHECK: M2 supports only plain arguments (no sret/byval/inreg/nest)
; CHECK: M2 supports only plain arguments (no sret/inalloca/preallocated/inreg/nest)
define void @sret_arg(ptr sret(i32) %out) {
entry:
store i32 0, ptr %out
@@ -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) {
@@ -4,7 +4,7 @@
; CanLowerReturn must reject an unsupported return type itself. Returning
; false would make SelectionDAG demote the aggregate to an sret pointer and
; silently change the function signature.
; CHECK: M2 supports only void/i32/pointer returns
; CHECK: M2 supports only void/integer(<=32)/pointer returns
%struct.S = type { i32, i32 }
define %struct.S @structret(i32 %a) {
@@ -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
}
+16
View File
@@ -0,0 +1,16 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; The self-loop keeps the unconditional branch through branch folding. A
; branch to its immediate fallthrough can be removed by branch folding/block
; simplification, so the loop back edge is the compile-only shape check here.
define void @spin() {
entry:
br label %loop
loop:
br label %loop
}
; CHECK-LABEL: _spin:
; CHECK-NOT: .LBB
; CHECK: [[L:LBB[0-9_]+]]:
; CHECK: J{{A|R}}UC [[L]]
; CHECK-NOT: .LBB
+23
View File
@@ -0,0 +1,23 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s --check-prefix=O0
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s --check-prefix=O2
define i32 @lt5(i32 %a) {
entry:
%c = icmp slt i32 %a, 5
%r = zext i1 %c to i32
ret i32 %r
}
; A small constant on the right becomes CMPI, and the compare must stay
; adjacent to the branch: nothing may write the flags in between. The two
; materialized values are pinned in their emitted order, but the surrounding
; frame/return layout is deliberately not pinned so this test anchors only
; the compare, branch, and MOVI order.
; O0-LABEL: _lt5:
; O0: CMPI 5
; O0-NEXT: J{{A|R}}LT
; O0: MOVI 0
; O0: MOVI 1
; O2-LABEL: _lt5:
; O2: CMPI 5
; O2-NEXT: J{{A|R}}LT
; O2: MOVI 1
; O2: MOVI 0
+11
View File
@@ -0,0 +1,11 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @max(i32 %a, i32 %b) {
entry:
%c = icmp sgt i32 %a, %b
%r = select i1 %c, i32 %a, i32 %b
ret i32 %r
}
; CHECK-LABEL: _max:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}GT
; CHECK: RETS 2
+19
View File
@@ -0,0 +1,19 @@
; RUN: llc -march=tms34010 -O0 -verify-machineinstrs -filetype=asm < %s | FileCheck %s
define i32 @sumto(i32 %n) {
entry:
br label %loop
loop:
%i = phi i32 [ 1, %entry ], [ %i.next, %loop ]
%s = phi i32 [ 0, %entry ], [ %s.next, %loop ]
%s.next = add i32 %s, %i
%i.next = add i32 %i, 1
%c = icmp sle i32 %i, %n
br i1 %c, label %loop, label %exit
exit:
ret i32 %s
}
; CHECK-LABEL: _sumto:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LE
; CHECK: J{{A|R}}UC
; CHECK: RETS 2
+48
View File
@@ -0,0 +1,48 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @ops(i32 %a, i32 %b) {
entry:
%s = sub i32 %a, %b
%an = and i32 %s, %a
%o = or i32 %an, %b
%x = xor i32 %o, %s
%l = shl i32 %x, 3
%r = lshr i32 %l, 2
%q = ashr i32 %r, 1
ret i32 %q
}
; `ops` folds the lshr/ashr chain to a logical shift (the sign bit is known
; zero), so a separate function keeps an arithmetic shift in the test.
define i32 @sar(i32 %a) {
entry:
%q = ashr i32 %a, 1
ret i32 %q
}
; `more` keeps the unary forms and the register shift alive at -O2: every
; partial result feeds the returned value, so none of NEG/NOT/ANDI/SLL folds
; away.
define i32 @more(i32 %a, i32 %b) {
entry:
%neg = sub i32 0, %a
%not = xor i32 %b, -1
%masked = and i32 %b, 255
%shl = shl i32 %a, %b
%r = or i32 %neg, %not
%r2 = xor i32 %r, %masked
%r3 = add i32 %r2, %shl
ret i32 %r3
}
; CHECK: SUB
; CHECK: AND
; CHECK: OR
; CHECK: XOR
; CHECK: SLL
; CHECK: SRL
; CHECK-LABEL: _sar:
; CHECK: SRA
; CHECK: RETS 2
; CHECK-LABEL: _more:
; CHECK-DAG: NEG
; CHECK-DAG: NOT
; CHECK-DAG: ANDI
; CHECK-DAG: SLL A
; CHECK: RETS 2
+13
View File
@@ -0,0 +1,13 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
define i32 @lt(i32 %a, i32 %b) {
entry:
%c = icmp slt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
; CHECK-LABEL: _lt:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LT
; CHECK: MOVI 0
; CHECK: MOVI 1
; CHECK: RETS 2
@@ -0,0 +1,48 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
;
; The TMS34010 performs unaligned field accesses (Users Guide, section 7.5):
; MOVE, MOVB and the FS0/FS1 forms have no alignment requirement. Packed
; struct fields and the bit-address normalizer's align-1 accesses must select
; the native instructions directly instead of being split into byte loads,
; which only have a sign-extending MOVB form.
define i32 @load32(ptr %p) {
%v = load i32, ptr %p, align 1
ret i32 %v
}
; CHECK-LABEL: _load32:
; CHECK-NOT: MOVB
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK-NOT: MOVB
; CHECK: RETS 2
define void @store32(ptr %p, i32 %v) {
store i32 %v, ptr %p, align 1
ret void
}
; CHECK-LABEL: _store32:
; CHECK-NOT: MOVB
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
; CHECK-NOT: MOVB
; CHECK: RETS 2
define ptr @loadptr(ptr %p) {
%v = load ptr, ptr %p, align 1
ret ptr %v
}
; CHECK-LABEL: _loadptr:
; CHECK-NOT: MOVB
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK-NOT: MOVB
; CHECK: RETS 2
define i32 @load16sext(ptr %p) {
%v = load i16, ptr %p, align 1
%e = sext i16 %v to i32
ret i32 %e
}
; CHECK-LABEL: _load16sext:
; CHECK-NOT: MOVB
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK-NOT: MOVB
; CHECK: RETS 2
+19
View File
@@ -0,0 +1,19 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
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 @ugt(i32 %a, i32 %b) {
entry:
%c = icmp ugt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
; CHECK-LABEL: _ult:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LO
; CHECK-LABEL: _ugt:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}HI
+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: J{{A|R}}LT
; CHECK-LABEL: _sle:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LE
; CHECK-LABEL: _sgt:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}GT
; CHECK-LABEL: _sge:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}GE
; CHECK-LABEL: _ult:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LO
; CHECK-LABEL: _ule:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}LS
; CHECK-LABEL: _ugt:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}HI
; CHECK-LABEL: _uge:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}HS
; CHECK-LABEL: _eq:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}EQ
; CHECK-LABEL: _ne:
; CHECK: CMP
; CHECK-NEXT: J{{A|R}}NE
@@ -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: J{{A|R}}LO
; CHECK-LABEL: _eq_all_ones_select:
; CHECK: CMPI -1
; CHECK-NEXT: J{{A|R}}EQ
; CHECK-LABEL: _xg_range:
; CHECK: CMPI -33554177
; CHECK-NEXT: J{{A|R}}HI
@@ -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: J{{A|R}}GT
; 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
+117
View File
@@ -0,0 +1,117 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
;
; Dynamic alloca. The TMS34010 program stack (A14/STK) grows upward, so a
; runtime-sized object starts at the current A14 (rounded up to the alloca's
; alignment) and A14 is advanced past it. The fixed frame stays between FP
; (A13) and the old A14, so FP-relative locals/arguments are unaffected. The
; request is 32-bit, so the byte count is 4*n, the A14 advance is (4*n)<<3
; bits, and the 4-byte alloca alignment is the bit mask -32.
declare i32 @leaf()
declare ptr @llvm.stacksave()
declare void @llvm.stackrestore(ptr)
define i32 @alloca_sum(i32 %n) {
entry:
%a = alloca i32, i32 %n, align 4
br label %loop
loop:
%i = phi i32 [ 0, %entry ], [ %i1, %loop ]
%acc = phi i32 [ 0, %entry ], [ %acc1, %loop ]
%p = getelementptr i32, ptr %a, i32 %i
store volatile i32 %i, ptr %p
%v = load volatile i32, ptr %p
%acc1 = add i32 %acc, %v
%i1 = add i32 %i, 1
%c = icmp ult i32 %i1, %n
br i1 %c, label %loop, label %done
done:
%q = getelementptr i32, ptr %a, i32 %n
%last = getelementptr i32, ptr %q, i32 -1
%lv = load i32, ptr %last
%r = add i32 %acc1, %lv
%z = call i32 @leaf()
%s = add i32 %r, %z
ret i32 %s
}
; The allocation reads A14, rounds it to the 32-bit (4-byte) boundary and
; advances A14 by the byte size shifted to bits. The call to @leaf must not
; disturb the allocation: the caller path saves the current A14 on the system
; stack and the callee's epilogue restores it.
; CHECK-LABEL: _alloca_sum:
; CHECK: SLL 5, A{{[0-9]+}}
; CHECK: MOVE A14, A{{[0-9]+}}
; CHECK: ADDI 31, A{{[0-9]+}}
; CHECK: ANDI -32, A{{[0-9]+}}
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
; CHECK-NEXT: MOVE A{{[0-9]+}}, A14
; CHECK: CALLA _leaf
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
; CHECK: RETS 2
@count = external global i32
; A dynamic alloca in a no-argument function has no saved old-STK slot to
; reload. The epilogue must rebuild A14 from FP + fixed frame size so the
; dynamic area is discarded; the old ADDI -frame,A14 would instead leave A14
; above the allocation.
define i32 @alloca_noarg() {
entry:
%n = load i32, ptr @count
%a = alloca i32, i32 %n, align 4
%i = add i32 %n, -1
%p = getelementptr i32, ptr %a, i32 %i
store volatile i32 7, ptr %p
%v = load volatile i32, ptr %p
ret i32 %v
}
; CHECK-LABEL: _alloca_noarg:
; CHECK: MOVE A14, A{{[0-9]+}}
; CHECK: ADDI 31, A{{[0-9]+}}
; CHECK: ANDI -32, A{{[0-9]+}}
; CHECK: SLL 5, A{{[0-9]+}}
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
; CHECK-NEXT: MOVE A{{[0-9]+}}, A14
; CHECK: MOVE A13, A14
; CHECK: RETS 0
; A byte-sized alloca still has its size rounded up to the 2-byte stack
; alignment ((n+1) & ~1) before the byte->bit shift; align 1 itself adds no
; pointer alignment mask.
define i32 @alloca_bytes(i32 %n) {
entry:
%a = alloca i8, i32 %n, align 1
store volatile i8 5, ptr %a
%v = load volatile i8, ptr %a
%z = zext i8 %v to i32
ret i32 %z
}
; CHECK-LABEL: _alloca_bytes:
; CHECK: MOVE A14, A{{[0-9]+}}
; CHECK: ADDI 1, A{{[0-9]+}}
; CHECK: ANDI 536870910, A{{[0-9]+}}
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
; CHECK-NEXT: MOVE A{{[0-9]+}}, A14
; CHECK: RETS 2
; stacksave/stackrestore use A14 as the saved stack pointer.
define i32 @alloca_saved(i32 %n) {
entry:
%saved = call ptr @llvm.stacksave()
%a = alloca i32, i32 %n, align 4
store volatile i32 9, ptr %a
%v = load volatile i32, ptr %a
call void @llvm.stackrestore(ptr %saved)
ret i32 %v
}
; CHECK-LABEL: _alloca_saved:
; CHECK: SLL 5, A{{[0-9]+}}
; CHECK: MOVE A14, A{{[0-9]+}}
; CHECK: ADDI 31, A{{[0-9]+}}
; CHECK: ANDI -32, A{{[0-9]+}}
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
; CHECK-NEXT: MOVE A{{[0-9]+}}, A14
; CHECK: RETS 2
+100
View File
@@ -0,0 +1,100 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
;
; byval arguments. The caller copies the object into the program-stack
; argument area (the current A14) and advances A14 by the object size rounded
; up to a 32-bit argument slot. The callee's incoming value is the address of
; that copy; the address is a fixed object at FP - (sum of the sizes of
; arguments 0..i), preserving the M4 argument order and 32-bit slots.
;
; In @byval_head the byval pair (8 bytes) is argument 0 and the scalar is
; argument 1, so the pair copy starts at FP(-64) bits and the scalar slot at
; FP(-96) bits.
%pair = type { i32, i32 }
define i32 @byval_head(ptr byval(%pair) align 4 %p, i32 %k) {
entry:
%a = load i32, ptr %p
%q = getelementptr %pair, ptr %p, i32 0, i32 1
%b = load i32, ptr %q
%m = mul i32 %a, 3
%r = add i32 %m, %b
%s = add i32 %r, %k
ret i32 %s
}
; The callee reads the private copy: the pointer is FP(-64) and the second
; field is four bytes above it. The scalar argument remains at FP(-96).
; CHECK-LABEL: _byval_head:
; CHECK: MOVE A13, A{{[0-9]+}}
; CHECK-NEXT: ADDI -64, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK: ADDI 4, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK: ADDI -96, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK: RETS 2
define i32 @byval_head_caller(i32 %x, i32 %y, i32 %k) {
entry:
%s = alloca %pair, align 4
%f0 = getelementptr %pair, ptr %s, i32 0, i32 0
store i32 %x, ptr %f0
%f1 = getelementptr %pair, ptr %s, i32 0, i32 1
store i32 %y, ptr %f1
%r = call i32 @byval_head(ptr byval(%pair) align 4 %s, i32 %k)
ret i32 %r
}
; The caller first pushes the scalar (argument 1), then copies the two words of
; the byval object to the current A14 and advances A14 by 8.
; CHECK-LABEL: _byval_head_caller:
; CHECK: MOVE A14, -*SP, 1
; CHECK: MOVE A{{[0-9]+}}, *A14+, 1
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
; CHECK: ADDI 64, A14
; CHECK: CALLA _byval_head
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
; CHECK: RETS 2
; A 6-byte byval object distinguishes the TI 16-bit slot rounding from a
; 32-bit extension: the object occupies 6 bytes, so the scalar argument starts
; at FP(-80) and the caller advances A14 by 6*8 = 48 bits.
%six = type { i32, i16 }
define i32 @byval_six(ptr byval(%six) align 2 %p, i32 %k) {
entry:
%a = load i32, ptr %p
%q = getelementptr %six, ptr %p, i32 0, i32 1
%b = load i16, ptr %q
%bz = zext i16 %b to i32
%m = mul i32 %a, 3
%r = add i32 %m, %bz
%s = add i32 %r, %k
ret i32 %s
}
; CHECK-LABEL: _byval_six:
; CHECK: MOVE A13, A{{[0-9]+}}
; CHECK-NEXT: ADDI -48, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK: ADDI -80, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK: RETS 2
define i32 @byval_six_caller(i32 %x, i32 %y, i32 %k) {
entry:
%s = alloca %six, align 2
store i32 %x, ptr %s
%q = getelementptr %six, ptr %s, i32 0, i32 1
%t = trunc i32 %y to i16
store i16 %t, ptr %q
%r = call i32 @byval_six(ptr byval(%six) align 2 %s, i32 %k)
ret i32 %r
}
; CHECK-LABEL: _byval_six_caller:
; CHECK: MOVE A14, -*SP, 1
; CHECK: MOVE A{{[0-9]+}}, *A14+, 1
; CHECK: ADDI 48, A14
; CHECK: CALLA _byval_six
; CHECK: RETS 2
@@ -0,0 +1,51 @@
; RUN: llc -march=tms34010 -O0 -verify-machineinstrs -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -verify-machineinstrs -filetype=asm < %s | FileCheck %s
;
; The TMS34010 ST flags live in a physical register, and a register-to-register
; MOVE (like almost every ALU instruction) writes ST. A PHI copy inserted for
; the loop back edge must therefore never land between CMP and its conditional
; branch. The compare/branch pair stays a single CMPBR/CMPIBR pseudo through
; scheduling and register allocation and is split only after RA; this test
; keeps several live loop values so the old separate CMP+Jcc form inserted a
; copy (c-torture 20000422-1 O2 then branched on the copy's flags and hung).
@ops = global [13 x i32] zeroinitializer
define i32 @sort_step(i32 %n, i32 %i0) {
entry:
br label %outer
outer:
%i = phi i32 [ %i0, %entry ]
%j = phi i32 [ %n, %entry ]
%keep = phi i32 [ 1, %entry ]
%cond = icmp sgt i32 %j, %i
br i1 %cond, label %body, label %exit
body:
%idx = phi i32 [ %j, %outer ], [ %idxnext, %body ]
%slot = getelementptr i32, ptr @ops, i32 %idx
%v = load i32, ptr %slot, align 2
%cmp = icmp slt i32 %v, %keep
%sel = select i1 %cmp, i32 %v, i32 %keep
%idxnext = add nsw i32 %idx, -1
%jnext = add nsw i32 %j, -1
%inext = add nuw nsw i32 %i, 1
%nextkeep = add nuw nsw i32 %keep, %sel
%done = icmp sgt i32 %idxnext, %i
br i1 %done, label %body, label %exit
exit:
%r = phi i32 [ 0, %outer ], [ %nextkeep, %body ]
ret i32 %r
}
; CHECK-LABEL: _sort_step:
; The loop-back-edge compare is followed immediately by its branch; a copy of
; the PHI values has to stay on one side of the pair.
; CHECK: CMP
; CHECK-NEXT: JR{{.*}} LBB
; CHECK: CMP
; CHECK-NEXT: JR{{.*}} LBB
; CHECK: CMP
; CHECK-NEXT: JR{{.*}} LBB
+22
View File
@@ -0,0 +1,22 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
; RUN: python3 %S/m5-data-pad.py %t.o
;
; TI-COFF sections are word-granular and the VT1200 loader rejects a section
; whose bit size is not a whole number of words. An odd-length byte array
; must be padded, and a char .bss object must allocate a whole word.
@odd = private unnamed_addr constant [3 x i8] c"ab\00", align 1
@cb = global i8 7
@zc = global i8 0
define ptr @p() {
entry:
ret ptr @odd
}
; CHECK: .data
; CHECK-LABEL: {{__.*odd.*}}:
; CHECK: .byte 061h, 062h, 000h
; CHECK: .byte 0
; CHECK-LABEL: _cb:
; CHECK: .byte 007h
; CHECK: .byte 0
; CHECK: .bss _zc,16,8
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""Check the TI-COFF section layout emitted for m5-data-pad.ll.
The IR has a 3-byte string (odd), a 1-byte .data char, and a 1-byte .bss
char. The target must pad the initialized data and the .bss allocation to
whole 16-bit words, because the VT1200 loader rejects a section whose size is
not a whole number of words.
"""
import struct
import sys
path = sys.argv[1]
data = open(path, "rb").read()
machine, nscns, _time, _symptr, _nsyms, opthdr, _flags = struct.unpack_from(
"<HHIIIHH", data, 0)
if machine != 0x0090:
sys.exit(f"{path}: machine {machine:#06x}, want 0x0090")
if nscns != 3:
sys.exit(f"{path}: {nscns} sections, want 3 (.text/.data/.bss)")
base = 20 + opthdr
sections = {}
for i in range(nscns):
off = base + i * 40
name = data[off:off + 8].split(b"\0", 1)[0].decode("latin1")
size, ptr = struct.unpack_from("<II", data, off + 16)
sections[name] = (size, ptr)
for name in (".text", ".data", ".bss"):
if name not in sections:
sys.exit(f"{path}: missing {name}")
data_size, data_ptr = sections[".data"]
if data_size != 48: # 3-byte string + pad + 1-byte char + pad
sys.exit(f"{path}: .data size {data_size} bits, want 48")
raw = data[data_ptr:data_ptr + data_size // 8]
if raw != bytes([0x61, 0x62, 0x00, 0x00, 0x07, 0x00]):
sys.exit(f"{path}: .data bytes {raw.hex()}, want 616200000700")
bss_size, _bss_ptr = sections[".bss"]
if bss_size != 16:
sys.exit(f"{path}: .bss size {bss_size} bits, want 16")
print("m5-data-pad: .data=6 bytes (padded), .bss=2 bytes (whole word)")
+348
View File
@@ -0,0 +1,348 @@
; RUN: llc -march=tms34010 -O0 -verify-machineinstrs -filetype=asm < %s -o %t.s
; RUN: FileCheck %s < %t.s
; RUN: llc -march=tms34010 -O0 -filetype=obj < %s -o %t.o
; RUN: test -s %t.o
; M5 branch relaxation: a local branch whose displacement fits the ISA's 8-bit
; word displacement uses the one-word relative JR/JR<cc> form, and a branch
; that cannot reach in one word keeps the three-word absolute JAUC/JA<cc> form.
; Both file types must agree; scripts/tests/m5/jr.sh checks the linked M3 image
; in MAME and the actual .text sizes.
; The loop body is tiny, so both branches are in range: the conditional exit
; is a JRLT and the back edge is a JRUC.
define i32 @short_loop(i32 %n) {
entry:
br label %loop
loop:
%i = phi i32 [ 0, %entry ], [ %inc, %loop ]
%s = phi i32 [ 0, %entry ], [ %s2, %loop ]
%s2 = add i32 %s, %i
%inc = add i32 %i, 1
%c = icmp slt i32 %inc, %n
br i1 %c, label %loop, label %done
done:
ret i32 %s2
}
; CHECK-LABEL: _short_loop:
; CHECK: CMP
; CHECK-NEXT: JRLT
; CHECK: JRUC
; CHECK-NOT: JALT
; The 24 load/add/store triples in %body each cost 8 bytes: the ADDI uses the
; 16-bit immediate form (4 bytes), not the .td long form (6 bytes). The exact
; layout puts %exit 113 words away, so this branch must be in range and use
; JREQ; measuring the long form instead would count 137 words and leave JAEQ.
; This pins getInstSizeInBytes to the size the encoder really produces.
define i32 @exact_relax(i32 %a) {
entry:
%s = alloca i32
store i32 0, ptr %s
%c = icmp eq i32 %a, 0
br i1 %c, label %exit, label %body
body:
%v0 = load i32, ptr %s
%w0 = add i32 %v0, 1
store i32 %w0, ptr %s
%v1 = load i32, ptr %s
%w1 = add i32 %v1, 1
store i32 %w1, ptr %s
%v2 = load i32, ptr %s
%w2 = add i32 %v2, 1
store i32 %w2, ptr %s
%v3 = load i32, ptr %s
%w3 = add i32 %v3, 1
store i32 %w3, ptr %s
%v4 = load i32, ptr %s
%w4 = add i32 %v4, 1
store i32 %w4, ptr %s
%v5 = load i32, ptr %s
%w5 = add i32 %v5, 1
store i32 %w5, ptr %s
%v6 = load i32, ptr %s
%w6 = add i32 %v6, 1
store i32 %w6, ptr %s
%v7 = load i32, ptr %s
%w7 = add i32 %v7, 1
store i32 %w7, ptr %s
%v8 = load i32, ptr %s
%w8 = add i32 %v8, 1
store i32 %w8, ptr %s
%v9 = load i32, ptr %s
%w9 = add i32 %v9, 1
store i32 %w9, ptr %s
%v10 = load i32, ptr %s
%w10 = add i32 %v10, 1
store i32 %w10, ptr %s
%v11 = load i32, ptr %s
%w11 = add i32 %v11, 1
store i32 %w11, ptr %s
%v12 = load i32, ptr %s
%w12 = add i32 %v12, 1
store i32 %w12, ptr %s
%v13 = load i32, ptr %s
%w13 = add i32 %v13, 1
store i32 %w13, ptr %s
%v14 = load i32, ptr %s
%w14 = add i32 %v14, 1
store i32 %w14, ptr %s
%v15 = load i32, ptr %s
%w15 = add i32 %v15, 1
store i32 %w15, ptr %s
%v16 = load i32, ptr %s
%w16 = add i32 %v16, 1
store i32 %w16, ptr %s
%v17 = load i32, ptr %s
%w17 = add i32 %v17, 1
store i32 %w17, ptr %s
%v18 = load i32, ptr %s
%w18 = add i32 %v18, 1
store i32 %w18, ptr %s
%v19 = load i32, ptr %s
%w19 = add i32 %v19, 1
store i32 %w19, ptr %s
%v20 = load i32, ptr %s
%w20 = add i32 %v20, 1
store i32 %w20, ptr %s
%v21 = load i32, ptr %s
%w21 = add i32 %v21, 1
store i32 %w21, ptr %s
%v22 = load i32, ptr %s
%w22 = add i32 %v22, 1
store i32 %w22, ptr %s
%v23 = load i32, ptr %s
%w23 = add i32 %v23, 1
store i32 %w23, ptr %s
br label %exit
exit:
%r = load i32, ptr %s
ret i32 %r
}
; CHECK-LABEL: _exact_relax:
; CHECK: CMPI 0
; CHECK-NEXT: JREQ
; CHECK-NOT: JAEQ
; A forward conditional branch over 200 volatile loads is far out of the
; signed 8-bit word range (the body is well over 127 words), so it keeps the
; absolute JAEQ form. The unconditional branch to the fallthrough block is
; zero displacement, which the one-word form cannot encode, so it is JAUC too.
@g = global i32 0
define i32 @far_branch(i32 %a) {
entry:
%c = icmp eq i32 %a, 0
br i1 %c, label %far, label %near
near:
%v0 = load volatile i32, ptr @g
%v1 = load volatile i32, ptr @g
%v2 = load volatile i32, ptr @g
%v3 = load volatile i32, ptr @g
%v4 = load volatile i32, ptr @g
%v5 = load volatile i32, ptr @g
%v6 = load volatile i32, ptr @g
%v7 = load volatile i32, ptr @g
%v8 = load volatile i32, ptr @g
%v9 = load volatile i32, ptr @g
%v10 = load volatile i32, ptr @g
%v11 = load volatile i32, ptr @g
%v12 = load volatile i32, ptr @g
%v13 = load volatile i32, ptr @g
%v14 = load volatile i32, ptr @g
%v15 = load volatile i32, ptr @g
%v16 = load volatile i32, ptr @g
%v17 = load volatile i32, ptr @g
%v18 = load volatile i32, ptr @g
%v19 = load volatile i32, ptr @g
%v20 = load volatile i32, ptr @g
%v21 = load volatile i32, ptr @g
%v22 = load volatile i32, ptr @g
%v23 = load volatile i32, ptr @g
%v24 = load volatile i32, ptr @g
%v25 = load volatile i32, ptr @g
%v26 = load volatile i32, ptr @g
%v27 = load volatile i32, ptr @g
%v28 = load volatile i32, ptr @g
%v29 = load volatile i32, ptr @g
%v30 = load volatile i32, ptr @g
%v31 = load volatile i32, ptr @g
%v32 = load volatile i32, ptr @g
%v33 = load volatile i32, ptr @g
%v34 = load volatile i32, ptr @g
%v35 = load volatile i32, ptr @g
%v36 = load volatile i32, ptr @g
%v37 = load volatile i32, ptr @g
%v38 = load volatile i32, ptr @g
%v39 = load volatile i32, ptr @g
%v40 = load volatile i32, ptr @g
%v41 = load volatile i32, ptr @g
%v42 = load volatile i32, ptr @g
%v43 = load volatile i32, ptr @g
%v44 = load volatile i32, ptr @g
%v45 = load volatile i32, ptr @g
%v46 = load volatile i32, ptr @g
%v47 = load volatile i32, ptr @g
%v48 = load volatile i32, ptr @g
%v49 = load volatile i32, ptr @g
%v50 = load volatile i32, ptr @g
%v51 = load volatile i32, ptr @g
%v52 = load volatile i32, ptr @g
%v53 = load volatile i32, ptr @g
%v54 = load volatile i32, ptr @g
%v55 = load volatile i32, ptr @g
%v56 = load volatile i32, ptr @g
%v57 = load volatile i32, ptr @g
%v58 = load volatile i32, ptr @g
%v59 = load volatile i32, ptr @g
%v60 = load volatile i32, ptr @g
%v61 = load volatile i32, ptr @g
%v62 = load volatile i32, ptr @g
%v63 = load volatile i32, ptr @g
%v64 = load volatile i32, ptr @g
%v65 = load volatile i32, ptr @g
%v66 = load volatile i32, ptr @g
%v67 = load volatile i32, ptr @g
%v68 = load volatile i32, ptr @g
%v69 = load volatile i32, ptr @g
%v70 = load volatile i32, ptr @g
%v71 = load volatile i32, ptr @g
%v72 = load volatile i32, ptr @g
%v73 = load volatile i32, ptr @g
%v74 = load volatile i32, ptr @g
%v75 = load volatile i32, ptr @g
%v76 = load volatile i32, ptr @g
%v77 = load volatile i32, ptr @g
%v78 = load volatile i32, ptr @g
%v79 = load volatile i32, ptr @g
%v80 = load volatile i32, ptr @g
%v81 = load volatile i32, ptr @g
%v82 = load volatile i32, ptr @g
%v83 = load volatile i32, ptr @g
%v84 = load volatile i32, ptr @g
%v85 = load volatile i32, ptr @g
%v86 = load volatile i32, ptr @g
%v87 = load volatile i32, ptr @g
%v88 = load volatile i32, ptr @g
%v89 = load volatile i32, ptr @g
%v90 = load volatile i32, ptr @g
%v91 = load volatile i32, ptr @g
%v92 = load volatile i32, ptr @g
%v93 = load volatile i32, ptr @g
%v94 = load volatile i32, ptr @g
%v95 = load volatile i32, ptr @g
%v96 = load volatile i32, ptr @g
%v97 = load volatile i32, ptr @g
%v98 = load volatile i32, ptr @g
%v99 = load volatile i32, ptr @g
%v100 = load volatile i32, ptr @g
%v101 = load volatile i32, ptr @g
%v102 = load volatile i32, ptr @g
%v103 = load volatile i32, ptr @g
%v104 = load volatile i32, ptr @g
%v105 = load volatile i32, ptr @g
%v106 = load volatile i32, ptr @g
%v107 = load volatile i32, ptr @g
%v108 = load volatile i32, ptr @g
%v109 = load volatile i32, ptr @g
%v110 = load volatile i32, ptr @g
%v111 = load volatile i32, ptr @g
%v112 = load volatile i32, ptr @g
%v113 = load volatile i32, ptr @g
%v114 = load volatile i32, ptr @g
%v115 = load volatile i32, ptr @g
%v116 = load volatile i32, ptr @g
%v117 = load volatile i32, ptr @g
%v118 = load volatile i32, ptr @g
%v119 = load volatile i32, ptr @g
%v120 = load volatile i32, ptr @g
%v121 = load volatile i32, ptr @g
%v122 = load volatile i32, ptr @g
%v123 = load volatile i32, ptr @g
%v124 = load volatile i32, ptr @g
%v125 = load volatile i32, ptr @g
%v126 = load volatile i32, ptr @g
%v127 = load volatile i32, ptr @g
%v128 = load volatile i32, ptr @g
%v129 = load volatile i32, ptr @g
%v130 = load volatile i32, ptr @g
%v131 = load volatile i32, ptr @g
%v132 = load volatile i32, ptr @g
%v133 = load volatile i32, ptr @g
%v134 = load volatile i32, ptr @g
%v135 = load volatile i32, ptr @g
%v136 = load volatile i32, ptr @g
%v137 = load volatile i32, ptr @g
%v138 = load volatile i32, ptr @g
%v139 = load volatile i32, ptr @g
%v140 = load volatile i32, ptr @g
%v141 = load volatile i32, ptr @g
%v142 = load volatile i32, ptr @g
%v143 = load volatile i32, ptr @g
%v144 = load volatile i32, ptr @g
%v145 = load volatile i32, ptr @g
%v146 = load volatile i32, ptr @g
%v147 = load volatile i32, ptr @g
%v148 = load volatile i32, ptr @g
%v149 = load volatile i32, ptr @g
%v150 = load volatile i32, ptr @g
%v151 = load volatile i32, ptr @g
%v152 = load volatile i32, ptr @g
%v153 = load volatile i32, ptr @g
%v154 = load volatile i32, ptr @g
%v155 = load volatile i32, ptr @g
%v156 = load volatile i32, ptr @g
%v157 = load volatile i32, ptr @g
%v158 = load volatile i32, ptr @g
%v159 = load volatile i32, ptr @g
%v160 = load volatile i32, ptr @g
%v161 = load volatile i32, ptr @g
%v162 = load volatile i32, ptr @g
%v163 = load volatile i32, ptr @g
%v164 = load volatile i32, ptr @g
%v165 = load volatile i32, ptr @g
%v166 = load volatile i32, ptr @g
%v167 = load volatile i32, ptr @g
%v168 = load volatile i32, ptr @g
%v169 = load volatile i32, ptr @g
%v170 = load volatile i32, ptr @g
%v171 = load volatile i32, ptr @g
%v172 = load volatile i32, ptr @g
%v173 = load volatile i32, ptr @g
%v174 = load volatile i32, ptr @g
%v175 = load volatile i32, ptr @g
%v176 = load volatile i32, ptr @g
%v177 = load volatile i32, ptr @g
%v178 = load volatile i32, ptr @g
%v179 = load volatile i32, ptr @g
%v180 = load volatile i32, ptr @g
%v181 = load volatile i32, ptr @g
%v182 = load volatile i32, ptr @g
%v183 = load volatile i32, ptr @g
%v184 = load volatile i32, ptr @g
%v185 = load volatile i32, ptr @g
%v186 = load volatile i32, ptr @g
%v187 = load volatile i32, ptr @g
%v188 = load volatile i32, ptr @g
%v189 = load volatile i32, ptr @g
%v190 = load volatile i32, ptr @g
%v191 = load volatile i32, ptr @g
%v192 = load volatile i32, ptr @g
%v193 = load volatile i32, ptr @g
%v194 = load volatile i32, ptr @g
%v195 = load volatile i32, ptr @g
%v196 = load volatile i32, ptr @g
%v197 = load volatile i32, ptr @g
%v198 = load volatile i32, ptr @g
%v199 = load volatile i32, ptr @g
ret i32 1
far:
ret i32 7
}
; CHECK-LABEL: _far_branch:
; CHECK: CMPI 0
; CHECK-NEXT: JAEQ
; CHECK-NEXT: JAUC
; CHECK-NOT: JREQ
+184
View File
@@ -0,0 +1,184 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
;
; llvm.memcpy/llvm.memset are lowered to target code: a known size becomes
; bit-scaled word (FS1=32) copies/stores, and a runtime size becomes an inline
; byte loop whose GEP offsets are scaled from bytes to bits. No libcall to
; _memcpy/_memset is emitted, so the same machine instructions feed both the
; assembly and the native object path. This test pins that behavior for M5;
; the ROM/rts.lib entry points are not required (the inline loop is the
; validated fallback for arbitrary lengths).
declare void @llvm.memcpy.p0.p0.i32(ptr, ptr, i32, i1 immarg)
declare void @llvm.memset.p0.i32(ptr, i8, i32, i1 immarg)
declare void @llvm.memmove.p0.p0.i32(ptr, ptr, i32, i1 immarg)
define void @copy_known(ptr %d, ptr %s) {
call void @llvm.memcpy.p0.p0.i32(ptr %d, ptr %s, i32 16, i1 false)
ret void
}
; Four 32-bit load/store pairs, no call. The word offsets are 4, 8 and 12
; bytes * 8 = 32, 64 and 96 bits: the generic expansion would use the byte
; values directly and address the wrong place.
; CHECK-LABEL: _copy_known:
; CHECK-DAG: ADDI 32, A{{[0-9]+}}
; CHECK-DAG: ADDI 64, A{{[0-9]+}}
; CHECK-DAG: ADDI 96, A{{[0-9]+}}
; CHECK: MOVE {{A[0-9]+}}, *A{{[0-9]+}}, 1
; CHECK-NOT: CALLA
; CHECK: RETS 2
define void @fill_known(ptr %d) {
call void @llvm.memset.p0.i32(ptr %d, i8 90, i32 8, i1 false)
ret void
}
; The byte value is replicated into a word (0x5A5A5A5A) and stored twice; the
; second store is four bytes (32 bits) above the first.
; CHECK-LABEL: _fill_known:
; CHECK: MOVI 1515870810, A{{[0-9]+}}
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
; CHECK: ADDI 32, A{{[0-9]+}}
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
; CHECK-NOT: CALLA
; CHECK: RETS 2
define void @copy_dynamic(ptr %d, ptr %s, i32 %n) {
call void @llvm.memcpy.p0.p0.i32(ptr %d, ptr %s, i32 %n, i1 false)
ret void
}
; Runtime length: a byte load/store loop, skipped when n == 0.
; CHECK-LABEL: _copy_dynamic:
; CHECK: CMPI 0, A{{[0-9]+}}
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
; CHECK: JRLO
; CHECK-NOT: CALLA
; CHECK: RETS 2
define void @fill_dynamic(ptr %d, i32 %n) {
call void @llvm.memset.p0.i32(ptr %d, i8 90, i32 %n, i1 false)
ret void
}
; CHECK-LABEL: _fill_dynamic:
; CHECK: CMPI 0, A{{[0-9]+}}
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
; CHECK: JRLO
; CHECK-NOT: CALLA
; CHECK: RETS 2
; Runtime-size memmove is expanded by LowerMemIntrinsics into its own
; memmove_* forward/backward loops. Those blocks are in the scaling pass's
; prefix set too; every address step must be 8 bits per byte.
define void @move_dynamic(ptr %d, ptr %s, i32 %n) {
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 %n, i1 false)
ret void
}
; CHECK-LABEL: _move_dynamic:
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: MOVB
; CHECK: JR{{[A-Z]*}} LBB{{[0-9]+}}_{{[0-9]+}}
; CHECK-NOT: CALLA
; CHECK: RETS 2
; Constants above PreISel's 64-byte inline threshold are expanded by
; LowerMemIntrinsics into static-* loops, not the target hook. Those blocks
; must be in the scaling pass's prefix set too; without them the loop would
; add 1 bit per byte instead of 8.
define void @copy_100(ptr %d, ptr %s) {
call void @llvm.memcpy.p0.p0.i32(ptr %d, ptr %s, i32 100, i1 false)
ret void
}
; CHECK-LABEL: _copy_100:
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
; CHECK: ADDI 1, A{{[0-9]+}}
; CHECK: CMPI 100, A{{[0-9]+}}
; CHECK: JRLO
; CHECK-NOT: CALLA
; CHECK: RETS 2
define void @fill_100(ptr %d) {
call void @llvm.memset.p0.i32(ptr %d, i8 90, i32 100, i1 false)
ret void
}
; CHECK-LABEL: _fill_100:
; CHECK: MOVI 90, A{{[0-9]+}}
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
; CHECK: ADDI 1, A{{[0-9]+}}
; CHECK: CMPI 100, A{{[0-9]+}}
; CHECK: JRLO
; CHECK-NOT: CALLA
; CHECK: RETS 2
; The memmove_done continuation is not a lowerer loop block: GEPs after the
; memmove are already bit offsets by contract and must not be scaled a second
; time. The 24-bit GEP must stay ADDI 24, not become ADDI 192 (24*8).
define i32 @move_then_gep(ptr %d, ptr %s, i32 %n) {
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 %n, i1 false)
%p = getelementptr i8, ptr %d, i32 24
%v = load i32, ptr %p
ret i32 %v
}
; CHECK-LABEL: _move_then_gep:
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: ADDI 24, A{{[0-9]+}}
; CHECK-NOT: ADDI 192
; CHECK: RETS 2
; Two memmove expansions in one function get uniquified block names
; (memmove_bwd_main_loop7, memmove_fwd_main_loop10, memmove_done6, ...).
; The loop prefixes must still be scaled, while neither continuation block may
; scale its user GEP. The two GEPs are already 24-bit and 48-bit offsets.
define i32 @two_moves(ptr %d, ptr %s, i32 %n) {
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 %n, i1 false)
%p1 = getelementptr i8, ptr %d, i32 24
%v1 = load i32, ptr %p1
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 %n, i1 false)
%p2 = getelementptr i8, ptr %d, i32 48
%v2 = load i32, ptr %p2
%r = add i32 %v1, %v2
ret i32 %r
}
; CHECK-LABEL: _two_moves:
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: ADDI 8, A{{[0-9]+}}
; CHECK: ADDI 24, A{{[0-9]+}}
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: ADDI 8, A{{[0-9]+}}
; CHECK: ADDI 48, A{{[0-9]+}}
; CHECK-NOT: ADDI 192
; CHECK-NOT: ADDI 384
; CHECK: RETS 2
; Constant memmove <= 64 bytes is not expanded by PreISel; the target pass
; expands it as the same overlap-safe forward/backward loops, with bit-scaled
; addresses. These cases used to reach the rejected libcall path.
define void @move_10(ptr %d, ptr %s) {
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 10, i1 false)
ret void
}
; CHECK-LABEL: _move_10:
; CHECK: MOVI 10, A{{[0-9]+}}
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
; CHECK-NOT: CALLA
; CHECK: RETS 2
define void @move_64(ptr %d, ptr %s) {
call void @llvm.memmove.p0.p0.i32(ptr %d, ptr %s, i32 64, i1 false)
ret void
}
; CHECK-LABEL: _move_64:
; CHECK: MOVI 64, A{{[0-9]+}}
; CHECK: SLL 3, A{{[0-9]+}}
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: MOVB A{{[0-9]+}}, *A{{[0-9]+}}
; CHECK-NOT: CALLA
; CHECK: RETS 2
+54
View File
@@ -0,0 +1,54 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
;
; M5 register-list frame saves. The callee keeps four values live across the
; call to @leaf. The odd multiply destinations force A1/A3/A5 and the call
; argument forces A0, so the callee-save set is A0, A1, A3, A5. The prologue
; saves it with one MMTM and the epilogue restores it with one MMFM; the old
; per-register MOVE pushes are gone, but the ABI-visible system-stack slots
; are unchanged: A13 (the caller's FP) below the return PC, the four callee
; registers below A13, and the old STK at SP + (1 + 4 + 1) * 32 = SP(192).
declare i32 @leaf(i32)
define i32 @mmtm_callee(i32 %a, i32 %b, i32 %c, i32 %d) {
entry:
%m1 = mul i32 %a, %b
%m2 = mul i32 %c, %d
%m3 = mul i32 %m2, %a
%z = call i32 @leaf(i32 %m1)
%r = add i32 %m2, %m3
%s = add i32 %r, %z
ret i32 %s
}
; CHECK-LABEL: _mmtm_callee:
; CHECK: SETF 32, 0, 1
; CHECK-NEXT: MOVE A13, -*SP, 1
; CHECK-NEXT: MMTM SP, A0, A1, A3, A5
; CHECK-NEXT: MOVE A14, A13
; CHECK: CALLA _leaf
; CHECK: SETF 32, 0, 1
; CHECK: MOVE *SP(192), A14, 1
; CHECK-NEXT: MMFM SP, A0, A1, A3, A5
; CHECK-NEXT: MOVE *SP+, A13, 1
; CHECK-NEXT: RETS 2
; The caller keeps two loaded values live across the call, so it too needs the
; register-list save and the same old-STK/RETS 2 ABI.
define i32 @mmtm_caller(ptr %p, i32 %a, i32 %b) {
entry:
%x = load i32, ptr %p
%m = mul i32 %a, %b
%z = call i32 @mmtm_callee(i32 %x, i32 %m, i32 %a, i32 %b)
%r = add i32 %x, %z
%s = add i32 %r, %m
ret i32 %s
}
; CHECK-LABEL: _mmtm_caller:
; CHECK: MOVE A13, -*SP, 1
; CHECK-NEXT: MMTM SP,
; CHECK: MOVE A14, -*SP, 1
; CHECK: CALLA _mmtm_callee
; CHECK: MMFM SP,
; CHECK: MOVE *SP+, A13, 1
; CHECK-NEXT: RETS 2
@@ -0,0 +1,82 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
; RUN: llc -march=tms34010 -O2 -filetype=obj < %s -o %t.o
;
; i8/i16 returns travel in A8 as a 32-bit ABI value. The callee applies the
; declared zeroext/signext attribute before the copy to A8; a plain (noext)
; narrow return leaves the promoted value alone and the caller applies the
; truncation/extension requested by the IR. i64, float and aggregate returns
; stay rejected (the aggregate case is pinned by m2-reject-structret.ll; the
; other wide/FP returns use the same CanLowerReturn diagnostic).
define zeroext i8 @ret8(i32 %x) noinline {
%t = trunc i32 %x to i8
ret i8 %t
}
; The 8-bit load sign-extends, so zeroext clears the high bits before A8.
; CHECK-LABEL: _ret8:
; CHECK: MOVB *A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: ANDI 255, A{{[0-9]+}}
; CHECK: MOVE A{{[0-9]+}}, A8
; CHECK: RETS 2
define signext i16 @ret16(i32 %x) noinline {
%t = trunc i32 %x to i16
ret i16 %t
}
; The FS0 16-bit load already sign-extends, which is the signext attribute.
; CHECK-LABEL: _ret16:
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK: MOVE A{{[0-9]+}}, A8
; CHECK: RETS 2
define i32 @narrow_caller(i32 %x) {
%a = call zeroext i8 @ret8(i32 %x)
%b = call signext i16 @ret16(i32 %x)
%az = zext i8 %a to i32
%bs = sext i16 %b to i32
%r = add i32 %az, %bs
ret i32 %r
}
; The attribute already guarantees the extension, so the caller consumes A8
; directly.
; CHECK-LABEL: _narrow_caller:
; CHECK: CALLA _ret8
; CHECK: MOVE A8, A{{[0-9]+}}
; CHECK: CALLA _ret16
; CHECK: MOVE A8, A{{[0-9]+}}
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
; CHECK: RETS 2
define i8 @ret8_plain(i32 %x) noinline {
%t = trunc i32 %x to i8
ret i8 %t
}
; CHECK-LABEL: _ret8_plain:
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
; CHECK: MOVE A{{[0-9]+}}, A8
; CHECK: RETS 2
define i32 @plain_caller(i32 %x) {
%a = call i8 @ret8_plain(i32 %x)
%az = zext i8 %a to i32
ret i32 %az
}
; Without zeroext the caller must mask the returned byte itself.
; CHECK-LABEL: _plain_caller:
; CHECK: CALLA _ret8_plain
; CHECK: ANDI 255, A{{[0-9]+}}
; CHECK: MOVE A{{[0-9]+}}, A8
; CHECK: RETS 2
define i32 @plain_caller_s(i32 %x) {
%a = call i8 @ret8_plain(i32 %x)
%az = sext i8 %a to i32
ret i32 %az
}
; The sign-extending caller uses the verified shift pair.
; CHECK-LABEL: _plain_caller_s:
; CHECK: CALLA _ret8_plain
; CHECK: SLL 24, A{{[0-9]+}}
; CHECK: SRA 24, A{{[0-9]+}}
; CHECK: MOVE A{{[0-9]+}}, A8
; CHECK: RETS 2
@@ -0,0 +1,18 @@
; RUN: llc -march=tms34010 -filetype=obj < %s -o %t.o
; RUN: test -s %t.o
; RUN: llc -march=tms34010 -filetype=asm < %s -o %t.s
; RUN: grep -q 'RETS 2' %t.s
; RUN: grep -q 'MOVI 305419896' %t.s
; RUN: grep -q 'MOVI 42' %t.s
define i32 @f(i32 %a) {
%r = add i32 %a, 1
ret i32 %r
}
; Long (32-bit) MOVI immediate form.
define i32 @movi_long() {
ret i32 305419896
}
; Short (16-bit) MOVI immediate form.
define i32 @movi_short() {
ret i32 42
}
@@ -0,0 +1,20 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=I64
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=I64
;
; A 64-bit multiply is the ordinary i64 path, not IndVarSimplify's i33
; closed-form exit value. Its diagnostic must name the i64/M6 boundary and
; must NOT tell the user to add -replexitval=never: tms-clang already passes
; that policy and it does nothing for i64 (it only disables the i33 exit-value
; rewrite). This pins the distinct remediation the Task 8 review found.
define i32 @wide64(i32 %a, i32 %b) {
entry:
%ae = zext i32 %a to i64
%be = zext i32 %b to i64
%m = mul i64 %ae, %be
%r = trunc i64 %m to i32
ret i32 %r
}
; I64: LLVM ERROR: TMS34010: 64-bit multiply reached code generation
; I64-SAME: reserved for M6
; I64-NOT: -replexitval=never
+25
View File
@@ -0,0 +1,25 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=I33
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=I33
;
; M5 keeps the tms-clang `-replexitval=never` driver policy until i64/M6: this
; target has no i64, and IndVarSimplify's closed-form exit value is an i33
; multiply. A raw `opt -passes='default<O2>'` without the policy reaches that
; shape. llc must reject it with a clear, actionable TMS34010 diagnostic, not
; a SelectionDAG "Cannot select" or an assertion, and the message must name
; the driver policy that avoids it.
;
; The shape below is exactly what IndVarSimplify produces for sumto():
; zext i32 -> i33, add 1, mul, lshr 1, trunc i32.
define i32 @sumto_closed_form(i32 %n) {
entry:
%a = zext i32 %n to i33
%b = add i33 %a, 1
%c = mul i33 %a, %b
%d = lshr i33 %c, 1
%e = trunc i33 %d to i32
ret i32 %e
}
; I33: LLVM ERROR: TMS34010: i33 multiply (umul_lohi shape) reached code generation
; I33-SAME: -replexitval=never
; I33-NOT: Cannot select
@@ -0,0 +1,24 @@
; 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: grep -oE '__2ELthis_is__[0-9A-F]{8}_[0-9A-F]{8}' %t-a.s > %t-a.sym
; RUN: grep -oE '__2ELthis_is__[0-9A-F]{8}_[0-9A-F]{8}' %t-b.s > %t-b.sym
; RUN: test -s %t-a.sym
; RUN: test -s %t-b.sym
; RUN: FileCheck %s --check-prefix=SYM < %t-a.s
; RUN: not diff %t-a.sym %t-b.sym
; RUN: awk '{ if (length($0) != 31) exit 1 }' %t-a.sym
; RUN: awk '{ if (length($0) != 31) exit 1 }' %t-b.sym
;
; GSPA 2.00 keeps only 31 identifier characters. The M4 module token is
; `_` plus eight hex digits; if it is appended to a long private name the
; assembler truncates it away and two translation units collide again. M5
; caps the sanitized base and folds the full name into an eight-hex token, so
; the module token always stays at the end and the whole label is 31 chars.
@this_is_a_very_long_private_name = private constant [5 x i8] c"same\00", align 1
define ptr @get_long_private() {
ret ptr @this_is_a_very_long_private_name
}
; SYM: __2ELthis_is__{{[0-9A-F]+}}_{{[0-9A-F]+}}:
@@ -0,0 +1,21 @@
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
;
; The TMS34010 call lowering pushes arguments and emits CALLA directly; it
; never builds CALLSEQ_START/CALLSEQ_END. StatepointLowering expects that
; sequence and asserts (`CallEnd->getOpcode() == ISD::CALLSEQ_END`) when it is
; missing. Reject the feature at the target boundary with a diagnostic
; instead of letting the generic code crash.
;
; The CodeGen/Generic test overloaded-intrinsic-name.ll exposed this.
; CHECK: TMS34010 does not support gc.statepoint calls
define ptr addrspace(1) @test(ptr addrspace(1) %v) gc "statepoint-example" {
entry:
%tok = call token (i64, i32, ptr, i32, i32, ...) @llvm.experimental.gc.statepoint.p0(i64 0, i32 0, ptr elementtype(i1 ()) @return_i1, i32 0, i32 0, i32 0, i32 0) ["gc-live"(ptr addrspace(1) %v)]
%v-new = call ptr addrspace(1) @llvm.experimental.gc.relocate.p1(token %tok, i32 0, i32 0)
ret ptr addrspace(1) %v-new
}
declare zeroext i1 @return_i1()
declare token @llvm.experimental.gc.statepoint.p0(i64, i32, ptr, i32, i32, ...)
declare ptr addrspace(1) @llvm.experimental.gc.relocate.p1(token, i32, i32)
@@ -0,0 +1,29 @@
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=REJECT
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s --check-prefix=ALLOW
;
; A `static` variadic definition is local, and DeadArgumentElimination removes
; its `...` when no `llvm.va_start` is present because it assumes the extra
; arguments are dead. The M4 contiguous-stack walk in the function body still
; reads those slots, so the optimized result silently miscompiles. The
; authoritative guard is tms-clang's pre-opt scan; this llc guard pins the
; non-O0 pipeline boundary with accurate wording (llc cannot see the post-DAE
; shape). -O0 still supports the M4 varargs pattern, and external variadic
; definitions (sprintf) are unaffected.
define internal i32 @static_vararg(i32 %a, ...) {
entry:
ret i32 %a
}
define i32 @caller(i32 %x) {
entry:
%r = call i32 (i32, ...) @static_vararg(i32 %x, i32 7)
ret i32 %r
}
; REJECT: LLVM ERROR: TMS34010: local variadic definition 'static_vararg'
; REJECT-SAME: general varargs/va_list are an M6 feature
; REJECT-SAME: compile with -O0
; ALLOW-LABEL: _static_vararg_{{[0-9A-F]+}}:
; ALLOW: RETS 2
; ALLOW-LABEL: _caller:
; ALLOW: CALLA _static_vararg_{{[0-9A-F]+}}
+52
View File
@@ -0,0 +1,52 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
;
; A 16-bit load must sign-extend into the 32-bit DAG. FS0's field-extension
; bit (the `fe` operand of SETF) controls that: the ABI used to emit
; `SETF 16, 0, 0`, which the hardware zero-extends, so a negative `short`
; entered arithmetic as a large positive i32. c-torture 20030128-1,
; 920721-1 and 20000412-1 exposed the bug through short division, a short
; argument spill, and a short index respectively.
;
; `SETF 16, 1, 0` is the sign-extending FS0 configuration. The SEXTLOAD and
; the zext-mask lowering (m4-zext.ll) both assume it.
@neg = global i16 -32768
define i32 @load_neg(ptr %p) {
%v = load i16, ptr %p
%r = sext i16 %v to i32
ret i32 %r
}
; CHECK-LABEL: _load_neg:
; CHECK: SETF 16, 1, 0
; CHECK: SETF 32, 0, 1
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK-NOT: SLL 16
; CHECK: RETS 2
define i32 @load_global() {
%v = load i16, ptr @neg
%r = sext i16 %v to i32
ret i32 %r
}
; The same sign-extending FS0 load for a global `short` (the c-torture shape).
; CHECK-LABEL: _load_global:
; CHECK: SETF 16, 1, 0
; CHECK: MOVI _neg, A{{[0-9]+}}
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK-NOT: ANDI 65535
; CHECK: RETS 0
define i32 @divide_neg(ptr %p) {
%v = load i16, ptr %p
%s = sext i16 %v to i32
%r = sdiv i32 50, %s
ret i32 %r
}
; The sign-extending load feeds DIVS directly; no shift pair or mask is needed.
; CHECK-LABEL: _divide_neg:
; CHECK: SETF 16, 1, 0
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
; CHECK-NOT: ANDI 65535
; CHECK: DIVS
; CHECK: RETS 2
+167
View File
@@ -0,0 +1,167 @@
# RUN: llvm-mc -triple=tms34010 -disassemble < %s | FileCheck %s
#
# The inverse of the .td encodings, including the short immediate forms that
# the emitter selects and the CLR/XOR shared encoding. Bytes are the GSPA
# listing bytes in memory order.
# CHECK: ADD A1, A2
0x22 0x40
# CHECK: SUB A1, A2
0x22 0x44
# CHECK: CMP A1, A2
0x22 0x48
# CHECK: AND A1, A2
0x22 0x50
# CHECK: OR A1, A2
0x22 0x54
# CHECK: XOR A1, A2
0x22 0x56
# CHECK: CLR A0
0x00 0x56
# CHECK: CLR B0
0x10 0x56
# CHECK: ADDI 5, A0
0x00 0x0b 0x05 0x00
# CHECK: ADDI -32, A0
0x00 0x0b 0xe0 0xff
# CHECK: ADDI -3, A0
0x00 0x0b 0xfd 0xff
# CHECK: MOVI 5, A0
0xc0 0x09 0x05 0x00
# CHECK: CMPI 5, A0
0x40 0x0b 0xfa 0xff
# CHECK: SUBI 5, A0
0xe0 0x0b 0xfa 0xff
# CHECK: CMPI 100000, A0
0x60 0x0b 0x5f 0x79 0xfe 0xff
# CHECK: SUBI 100000, A0
0x00 0x0d 0x5f 0x79 0xfe 0xff
# CHECK: ADDI 100000, A0
0x20 0x0b 0xa0 0x86 0x01 0x00
# CHECK: MOVI 100000, A0
0xe0 0x09 0xa0 0x86 0x01 0x00
# CHECK: ANDI 5, A0
0x80 0x0b 0xfa 0xff 0xff 0xff
# CHECK: SLL 3, A0
0x60 0x24
# CHECK: SRA 3, A0
0xa0 0x2b
# CHECK: SRL 3, A0
0xa0 0x2f
# CHECK: SLL A1, A2
0x22 0x62
# CHECK: SRA A1, A2
0x22 0x64
# CHECK: SRL A1, A2
0x22 0x66
# CHECK: SLA 3, A0
0x60 0x20
# CHECK: SLA A1, A2
0x22 0x60
# CHECK: RL 3, A0
0x60 0x30
# CHECK: RL A1, A2
0x22 0x68
# CHECK: CLR A0
0x00 0x56
# CHECK: NEG A0
0xa0 0x03
# CHECK: NOT A0
0xe0 0x03
# CHECK: MOVE A0, A1
0x01 0x4c
# CHECK: MOVE B0, B1
0x11 0x4c
# CHECK: MOVE A0, B1
0x01 0x4e
# CHECK: MOVE B0, A1
0x11 0x4e
# CHECK: MOVE B0, *B1, 1
0x11 0x82
# CHECK: MOVE *B1, B0, 1
0x30 0x86
# CHECK: MOVE A0, *A1+, 1
0x01 0x92
# CHECK: MOVE *A1+, A0, 1
0x20 0x96
# CHECK: MOVE A0, -*A1, 1
0x01 0xa2
# CHECK: MOVE -*A1, A0, 1
0x20 0xa6
# CHECK: MOVE A0, *A1(16), 1
0x01 0xb2 0x10 0x00
# CHECK: MOVE *A1(16), A0, 1
0x20 0xb6 0x10 0x00
# CHECK: MOVE *A1(16), *A2(32), 1
0x22 0xba 0x10 0x00 0x20 0x00
# CHECK: MOVE *A1(16), *A2+, 1
0x22 0xd2 0x10 0x00
# CHECK: MOVB A0, *A1
0x01 0x8c
# CHECK: MOVB *A1, A0
0x20 0x8e
# CHECK: MOVB *A1, *A2
0x22 0x9c
# CHECK: MOVB A0, *A1(16)
0x01 0xac 0x10 0x00
# CHECK: MOVB *A1(16), A0
0x20 0xae 0x10 0x00
# CHECK: MOVB *A1(16), *A2(32)
0x22 0xbc 0x10 0x00 0x20 0x00
# CHECK: MOVE A0, @4660, 1
0x80 0x07 0x34 0x12 0x00 0x00
# CHECK: MOVE @4660, A0, 1
0xa0 0x07 0x34 0x12 0x00 0x00
# CHECK: MOVB A0, @4660
0xe0 0x05 0x34 0x12 0x00 0x00
# CHECK: MOVB @4660, A0
0xe0 0x07 0x34 0x12 0x00 0x00
# CHECK: CALL A0
0x20 0x09
# CHECK: CALL B0
0x30 0x09
# CHECK: CALLA 4660
0x5f 0x0d 0x34 0x12 0x00 0x00
# CHECK: JAUC 4660
0x80 0xc0 0x34 0x12 0x00 0x00
# CHECK: JAEQ 4660
0x80 0xca 0x34 0x12 0x00 0x00
# CHECK: JR 3
0x03 0xc0
# CHECK: JREQ 3
0x03 0xca
# CHECK: RETS 0
0x60 0x09
# CHECK: RETS 2
0x62 0x09
# CHECK: SETF 16, 0, 0
0x50 0x05
# CHECK: SETF 32, 0, 1
0x40 0x07
# CHECK: SETF 16, 1, 1
0x70 0x07
# CHECK: MMTM SP, A0, A1
0x8f 0x09 0x00 0xc0
# CHECK: MMFM SP, A0, A1
0xaf 0x09 0x03 0x00
# CHECK: MMTM B15, B0, B1
0x9f 0x09 0x00 0xc0
# CHECK: MMFM B15, B0, B1
0xbf 0x09 0x03 0x00
# CHECK: NOP
0x00 0x03
# CHECK: SEXT A0, 0
0x00 0x05
# CHECK: ZEXT A0, 0
0x20 0x05
# CHECK: SEXT A0, 1
0x00 0x07
# CHECK: ZEXT A0, 1
0x20 0x07
+283
View File
@@ -0,0 +1,283 @@
# RUN: llvm-mc -triple=tms34010 -show-encoding %s | FileCheck %s
#
# GSPA 2.00 / dis34 are the encoding oracle. Every expected byte pair below is
# the little-endian 16-bit instruction word from a GSPA listing, not an LLVM
# round-trip of the encoder. Multi-word instructions list their words in
# memory order (opcode word first, then the immediate/address words).
# CHECK: ADD A1, A2{{.*}}encoding: [0x22,0x40]
ADD A1, A2
# CHECK: ADD B1, B2{{.*}}encoding: [0x32,0x40]
ADD B1, B2
# CHECK: SUB A1, A2{{.*}}encoding: [0x22,0x44]
SUB A1, A2
# CHECK: CMP A1, A2{{.*}}encoding: [0x22,0x48]
CMP A1, A2
# CHECK: AND A1, A2{{.*}}encoding: [0x22,0x50]
AND A1, A2
# CHECK: OR A1, A2{{.*}}encoding: [0x22,0x54]
OR A1, A2
# CHECK: XOR A1, A2{{.*}}encoding: [0x22,0x56]
XOR A1, A2
# CHECK: ADDI 5, A0{{.*}}encoding: [0x00,0x0b,0x05,0x00]
ADDI 5, A0
# CHECK: ADDI 100000, A0{{.*}}encoding: [0x20,0x0b,0xa0,0x86,0x01,0x00]
ADDI 100000, A0
# CHECK: ADDI -32, A0{{.*}}encoding: [0x00,0x0b,0xe0,0xff]
ADDI -32, A0
# Unary minus binds tighter than '+': this is (-5)+2 = -3.
# CHECK: ADDI -3, A0{{.*}}encoding: [0x00,0x0b,0xfd,0xff]
ADDI -5+2, A0
# CHECK: SUBI 5, A0{{.*}}encoding: [0xe0,0x0b,0xfa,0xff]
SUBI 5, A0
# CHECK: SUBI 100000, A0{{.*}}encoding: [0x00,0x0d,0x5f,0x79,0xfe,0xff]
SUBI 100000, A0
# CHECK: CMPI 5, A0{{.*}}encoding: [0x40,0x0b,0xfa,0xff]
CMPI 5, A0
# CHECK: CMPI 100000, A0{{.*}}encoding: [0x60,0x0b,0x5f,0x79,0xfe,0xff]
CMPI 100000, A0
# CHECK: ANDI 5, A0{{.*}}encoding: [0x80,0x0b,0xfa,0xff,0xff,0xff]
ANDI 5, A0
# CHECK: ANDI 100000, A0{{.*}}encoding: [0x80,0x0b,0x5f,0x79,0xfe,0xff]
ANDI 100000, A0
# CHECK: ORI 5, A0{{.*}}encoding: [0xa0,0x0b,0x05,0x00,0x00,0x00]
ORI 5, A0
# CHECK: ORI 100000, A0{{.*}}encoding: [0xa0,0x0b,0xa0,0x86,0x01,0x00]
ORI 100000, A0
# CHECK: XORI 5, A0{{.*}}encoding: [0xc0,0x0b,0x05,0x00,0x00,0x00]
XORI 5, A0
# CHECK: XORI 100000, A0{{.*}}encoding: [0xc0,0x0b,0xa0,0x86,0x01,0x00]
XORI 100000, A0
# CHECK: CLR A0{{.*}}encoding: [0x00,0x56]
CLR A0
# CHECK: NEG A0{{.*}}encoding: [0xa0,0x03]
NEG A0
# CHECK: NOT A0{{.*}}encoding: [0xe0,0x03]
NOT A0
# CHECK: SLL 3, A0{{.*}}encoding: [0x60,0x24]
SLL 3, A0
# CHECK: SLL A1, A2{{.*}}encoding: [0x22,0x62]
SLL A1, A2
# CHECK: SRA 3, A0{{.*}}encoding: [0xa0,0x2b]
SRA 3, A0
# CHECK: SRA A1, A2{{.*}}encoding: [0x22,0x64]
SRA A1, A2
# CHECK: SRL 3, A0{{.*}}encoding: [0xa0,0x2f]
SRL 3, A0
# CHECK: SRL A1, A2{{.*}}encoding: [0x22,0x66]
SRL A1, A2
# CHECK: SLA 3, A0{{.*}}encoding: [0x60,0x20]
SLA 3, A0
# CHECK: SLA A1, A2{{.*}}encoding: [0x22,0x60]
SLA A1, A2
# CHECK: RL 3, A0{{.*}}encoding: [0x60,0x30]
RL 3, A0
# CHECK: RL A1, A2{{.*}}encoding: [0x22,0x68]
RL A1, A2
# CHECK: MPYS A1, A2{{.*}}encoding: [0x22,0x5c]
MPYS A1, A2
# CHECK: MPYU A1, A2{{.*}}encoding: [0x22,0x5e]
MPYU A1, A2
# CHECK: DIVS A1, A2{{.*}}encoding: [0x22,0x58]
DIVS A1, A2
# CHECK: DIVU A1, A2{{.*}}encoding: [0x22,0x5a]
DIVU A1, A2
# CHECK: MOVI 5, A0{{.*}}encoding: [0xc0,0x09,0x05,0x00]
MOVI 5, A0
# CHECK: MOVI 100000, A0{{.*}}encoding: [0xe0,0x09,0xa0,0x86,0x01,0x00]
MOVI 100000, A0
# CHECK: MOVE A0, A1{{.*}}encoding: [0x01,0x4c]
MOVE A0, A1
# CHECK: MOVE B0, B1{{.*}}encoding: [0x11,0x4c]
MOVE B0, B1
# CHECK: MOVE A0, B1{{.*}}encoding: [0x01,0x4e]
MOVE A0, B1
# CHECK: MOVE B0, A1{{.*}}encoding: [0x11,0x4e]
MOVE B0, A1
# CHECK: MOVE A0, *A1, 0{{.*}}encoding: [0x01,0x80]
MOVE A0, *A1, 0
# CHECK: MOVE A0, *A1, 1{{.*}}encoding: [0x01,0x82]
MOVE A0, *A1, 1
# CHECK: MOVE *A1, A0, 0{{.*}}encoding: [0x20,0x84]
MOVE *A1, A0, 0
# CHECK: MOVE *A1, A0, 1{{.*}}encoding: [0x20,0x86]
MOVE *A1, A0, 1
# CHECK: MOVE *A0, *A1, 0{{.*}}encoding: [0x01,0x88]
MOVE *A0, *A1, 0
# CHECK: MOVE *A0, *A1, 1{{.*}}encoding: [0x01,0x8a]
MOVE *A0, *A1, 1
# CHECK: MOVE A0, *A1+, 1{{.*}}encoding: [0x01,0x92]
MOVE A0, *A1+, 1
# CHECK: MOVE *A1+, A0, 1{{.*}}encoding: [0x20,0x96]
MOVE *A1+, A0, 1
# CHECK: MOVE A0, -*A1, 1{{.*}}encoding: [0x01,0xa2]
MOVE A0, -*A1, 1
# CHECK: MOVE -*A1, A0, 1{{.*}}encoding: [0x20,0xa6]
MOVE -*A1, A0, 1
# CHECK: MOVE A0, *A1(16), 1{{.*}}encoding: [0x01,0xb2,0x10,0x00]
MOVE A0, *A1(16), 1
# CHECK: MOVE *A1(16), A0, 1{{.*}}encoding: [0x20,0xb6,0x10,0x00]
MOVE *A1(16), A0, 1
# CHECK: MOVE *A1(16), *A2(32), 1{{.*}}encoding: [0x22,0xba,0x10,0x00,0x20,0x00]
MOVE *A1(16), *A2(32), 1
# CHECK: MOVE *A1(16), *A2+, 1{{.*}}encoding: [0x22,0xd2,0x10,0x00]
MOVE *A1(16), *A2+, 1
# CHECK: MOVE @4660, *A1+, 1{{.*}}encoding: [0x01,0xd6,0x34,0x12,0x00,0x00]
MOVE @0x1234, *A1+, 1
# CHECK: MOVB A0, *A1{{.*}}encoding: [0x01,0x8c]
MOVB A0, *A1
# CHECK: MOVB *A1, A0{{.*}}encoding: [0x20,0x8e]
MOVB *A1, A0
# CHECK: MOVB *A1, *A2{{.*}}encoding: [0x22,0x9c]
MOVB *A1, *A2
# CHECK: MOVB A0, *A1(16){{.*}}encoding: [0x01,0xac,0x10,0x00]
MOVB A0, *A1(16)
# CHECK: MOVB *A1(16), A0{{.*}}encoding: [0x20,0xae,0x10,0x00]
MOVB *A1(16), A0
# CHECK: MOVB *A1(16), *A2(32){{.*}}encoding: [0x22,0xbc,0x10,0x00,0x20,0x00]
MOVB *A1(16), *A2(32)
# CHECK: MOVE A0, @4660, 1{{.*}}encoding: [0x80,0x07,0x34,0x12,0x00,0x00]
MOVE A0, @0x1234, 1
# CHECK: MOVE @4660, A0, 1{{.*}}encoding: [0xa0,0x07,0x34,0x12,0x00,0x00]
MOVE @0x1234, A0, 1
# CHECK: MOVB A0, @4660{{.*}}encoding: [0xe0,0x05,0x34,0x12,0x00,0x00]
MOVB A0, @0x1234
# CHECK: MOVB @4660, A0{{.*}}encoding: [0xe0,0x07,0x34,0x12,0x00,0x00]
MOVB @0x1234, A0
# CHECK: CALL A0{{.*}}encoding: [0x20,0x09]
CALL A0
# CHECK: CALL B0{{.*}}encoding: [0x30,0x09]
CALL B0
# CHECK: CALLA 4660{{.*}}encoding: [0x5f,0x0d,0x34,0x12,0x00,0x00]
CALLA 0x1234
# CHECK: JAUC 4660{{.*}}encoding: [0x80,0xc0,0x34,0x12,0x00,0x00]
JAUC 0x1234
# CHECK: JAEQ 4660{{.*}}encoding: [0x80,0xca,0x34,0x12,0x00,0x00]
JAEQ 0x1234
# CHECK: JANE 4660{{.*}}encoding: [0x80,0xcb,0x34,0x12,0x00,0x00]
JANE 0x1234
# CHECK: JALT 4660{{.*}}encoding: [0x80,0xc4,0x34,0x12,0x00,0x00]
JALT 0x1234
# CHECK: JALE 4660{{.*}}encoding: [0x80,0xc6,0x34,0x12,0x00,0x00]
JALE 0x1234
# CHECK: JAGT 4660{{.*}}encoding: [0x80,0xc7,0x34,0x12,0x00,0x00]
JAGT 0x1234
# CHECK: JAGE 4660{{.*}}encoding: [0x80,0xc5,0x34,0x12,0x00,0x00]
JAGE 0x1234
# CHECK: JALO 4660{{.*}}encoding: [0x80,0xc8,0x34,0x12,0x00,0x00]
JALO 0x1234
# CHECK: JALS 4660{{.*}}encoding: [0x80,0xc2,0x34,0x12,0x00,0x00]
JALS 0x1234
# CHECK: JAHI 4660{{.*}}encoding: [0x80,0xc3,0x34,0x12,0x00,0x00]
JAHI 0x1234
# CHECK: JAHS 4660{{.*}}encoding: [0x80,0xc9,0x34,0x12,0x00,0x00]
JAHS 0x1234
# CHECK: JAP 4660{{.*}}encoding: [0x80,0xc1,0x34,0x12,0x00,0x00]
JAP 0x1234
# CHECK: JAN 4660{{.*}}encoding: [0x80,0xce,0x34,0x12,0x00,0x00]
JAN 0x1234
# CHECK: JAV 4660{{.*}}encoding: [0x80,0xcc,0x34,0x12,0x00,0x00]
JAV 0x1234
# CHECK: JANV 4660{{.*}}encoding: [0x80,0xcd,0x34,0x12,0x00,0x00]
JANV 0x1234
# CHECK: JANN 4660{{.*}}encoding: [0x80,0xcf,0x34,0x12,0x00,0x00]
JANN 0x1234
# CHECK: RETS 0{{.*}}encoding: [0x60,0x09]
RETS 0
# CHECK: RETS 0{{.*}}encoding: [0x60,0x09]
RETS
# CHECK: RETS 2{{.*}}encoding: [0x62,0x09]
RETS 2
# CHECK: SETF 16, 0, 0{{.*}}encoding: [0x50,0x05]
SETF 16, 0, 0
# CHECK: SETF 16, 1, 0{{.*}}encoding: [0x70,0x05]
SETF 16, 1, 0
# CHECK: SETF 32, 0, 1{{.*}}encoding: [0x40,0x07]
SETF 32, 0, 1
# CHECK: SETF 16, 1, 1{{.*}}encoding: [0x70,0x07]
SETF 16, 1, 1
# CHECK: MMTM SP, A0, A1{{.*}}encoding: [0x8f,0x09,0x00,0xc0]
MMTM SP, A0, A1
# CHECK: MMFM SP, A0, A1{{.*}}encoding: [0xaf,0x09,0x03,0x00]
MMFM SP, A0, A1
# CHECK: MMTM B15, B0, B1{{.*}}encoding: [0x9f,0x09,0x00,0xc0]
MMTM B15, B0, B1
# CHECK: MMFM B15, B0, B1{{.*}}encoding: [0xbf,0x09,0x03,0x00]
MMFM B15, B0, B1
# CHECK: NOP{{.*}}encoding: [0x00,0x03]
NOP
# CHECK: SEXT A0, 0{{.*}}encoding: [0x00,0x05]
SEXT A0, 0
# CHECK: ZEXT A0, 0{{.*}}encoding: [0x20,0x05]
ZEXT A0, 0
# CHECK: SEXT A0, 1{{.*}}encoding: [0x00,0x07]
SEXT A0, 1
# CHECK: ZEXT A0, 1{{.*}}encoding: [0x20,0x07]
ZEXT A0, 1
# GSPA's unconditional short branch spelling is JRUC (it rejects a bare JR).
# The operand is the raw 8-bit word displacement from PC+16; all 16 condition
# codes are pinned. Task 5 owns branch targets and relaxation.
# CHECK: JR 3{{.*}}encoding: [0x03,0xc0]
JRUC 3
# CHECK: JRP 3{{.*}}encoding: [0x03,0xc1]
JRP 3
# CHECK: JRLS 3{{.*}}encoding: [0x03,0xc2]
JRLS 3
# CHECK: JRHI 3{{.*}}encoding: [0x03,0xc3]
JRHI 3
# CHECK: JRLT 3{{.*}}encoding: [0x03,0xc4]
JRLT 3
# CHECK: JRGE 3{{.*}}encoding: [0x03,0xc5]
JRGE 3
# CHECK: JRLE 3{{.*}}encoding: [0x03,0xc6]
JRLE 3
# CHECK: JRGT 3{{.*}}encoding: [0x03,0xc7]
JRGT 3
# CHECK: JRLO 3{{.*}}encoding: [0x03,0xc8]
JRLO 3
# CHECK: JRHS 3{{.*}}encoding: [0x03,0xc9]
JRHS 3
# CHECK: JREQ 3{{.*}}encoding: [0x03,0xca]
JREQ 3
# CHECK: JRNE 3{{.*}}encoding: [0x03,0xcb]
JRNE 3
# CHECK: JRV 3{{.*}}encoding: [0x03,0xcc]
JRV 3
# CHECK: JRNV 3{{.*}}encoding: [0x03,0xcd]
JRNV 3
# CHECK: JRN 3{{.*}}encoding: [0x03,0xce]
JRN 3
# CHECK: JRNN 3{{.*}}encoding: [0x03,0xcf]
JRNN 3
# Endpoints of the legal 8-bit displacement range.
# CHECK: JR -1{{.*}}encoding: [0xff,0xc0]
JR -1
# CHECK: JR 127{{.*}}encoding: [0x7f,0xc0]
JR 127
# dis34's condition aliases are accepted by the parser and print canonically.
# CHECK: JALO 4660{{.*}}encoding: [0x80,0xc8,0x34,0x12,0x00,0x00]
JAC 0x1234
# CHECK: JAHS 4660{{.*}}encoding: [0x80,0xc9,0x34,0x12,0x00,0x00]
JANC 0x1234
# CHECK: JRLO 3{{.*}}encoding: [0x03,0xc8]
JRC 3
# CHECK: JRHS 3{{.*}}encoding: [0x03,0xc9]
JRNC 3
+6
View File
@@ -0,0 +1,6 @@
# RUN: not llvm-mc -triple=tms34010 -show-encoding %s 2>&1 | FileCheck %s
#
# The 8-bit JR form has no zero displacement (zero is the 16-bit form) and no
# 0x80 low byte (0x80 is the absolute-JA marker).
# CHECK: 8-bit JR displacement must be in
JR 0
Loaded 100 of 102 files, more files were not shown because too many files have changed in this diff. Show more