Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de5a365596 | ||
|
|
61ee9bbc99 | ||
|
|
b08e57424a | ||
|
|
6261ac442a | ||
|
|
3c810ec0be | ||
|
|
a49c934380 | ||
|
|
4ff6e2df9d | ||
|
|
4b300a75e9 | ||
|
|
df6a6128d2 | ||
|
|
73c85350a4 | ||
|
|
1968ccd39b | ||
|
|
8d088c40df | ||
|
|
afb4ad2eef | ||
|
|
05762fceb8 | ||
|
|
bbc591b77c |
No files matched your search
+488
-27
@@ -1,10 +1,14 @@
|
|||||||
# LLVM 23.1.5 fork for TMS34010
|
# LLVM 23.1.5 fork for TMS34010
|
||||||
|
|
||||||
Branch `m2` is `llvmorg-23.1.5` plus the TMS34010 target and clang
|
Branch `m4` is `llvmorg-23.1.5` plus the TMS34010 target and clang
|
||||||
bit-address changes (M0), a minimal TMS34010 backend (M1) and the TI ABI, calls
|
bit-address changes (M0), a minimal TMS34010 backend (M1), the TI ABI, calls
|
||||||
and `.data` support (M2). This branch is the canonical source for the target;
|
and `.data` support (M2), integer control flow plus the first compiled C Xlib
|
||||||
the project's `patches/llvm` mirror is frozen at M0. Branch `tms34010` keeps
|
window (M3), and M4's function pointers, multiply/divide, zero-extending
|
||||||
the M1 backend and its `m1-*` suite.
|
narrow loads, the XImage/event client and the pinned termtris integration.
|
||||||
|
This branch is the canonical source for the target; the project's
|
||||||
|
`patches/llvm` mirror is frozen at M0. Branch `m3` is the previous milestone
|
||||||
|
snapshot and the fork's default branch; branch `m2` is the M2 tip and branch
|
||||||
|
`tms34010` keeps the M1 backend and its `m1-*` suite.
|
||||||
|
|
||||||
## M2 state
|
## M2 state
|
||||||
|
|
||||||
@@ -48,8 +52,12 @@ pointer returns. Narrow (`i8`/`i16`) parameters, `i64`, floats/aggregates,
|
|||||||
varargs, `sret`/`byval`/`inreg`/`nest` and indirect calls fail loudly with an
|
varargs, `sret`/`byval`/`inreg`/`nest` and indirect calls fail loudly with an
|
||||||
`M2 ...` diagnostic. Ordinary `tail` markers are **demoted** to ordinary
|
`M2 ...` diagnostic. Ordinary `tail` markers are **demoted** to ordinary
|
||||||
calls (`LowerCall` clears `IsTailCall`); `musttail` is not implemented and
|
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
|
M2 C does not emit it. Real tail calls remain a later milestone (M4+). Narrow
|
||||||
function stay supported (`MOVB`, FS0 `MOVE`). The optimizer's `returned` parameter attribute
|
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
|
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
|
stack-slot ABI. `m2-returned.ll` pins that; `m2-reject-sret.ll` keeps the
|
||||||
rejection of the ABI-affecting attributes.
|
rejection of the ABI-affecting attributes.
|
||||||
@@ -150,6 +158,416 @@ _strlen:
|
|||||||
and the ROM's `RETS 2` returns directly to our caller. M2 consumes `strlen`
|
and the ROM's `RETS 2` returns directly to our caller. M2 consumes `strlen`
|
||||||
only; later milestones extend and validate the table.
|
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/M5 must add
|
||||||
|
multiply/`i64` lowering, a target-level pass policy, or a target diagnostic
|
||||||
|
instead of the raw abort.
|
||||||
|
|
||||||
|
### 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 closes the remaining Xlib path to a real image/event client and a
|
||||||
|
full-game integration. It is still **assembly-only**: GSPA 2.00 and GSPLNK
|
||||||
|
2.00 under DOSBox produce the COFF. There is no `MCCodeEmitter`, object
|
||||||
|
writer, `llc -filetype=obj` or `clang -c`.
|
||||||
|
|
||||||
|
#### Function pointers and `CALL Rs`
|
||||||
|
|
||||||
|
A function pointer is an ordinary 32-bit value holding a code bit address.
|
||||||
|
Taking a function's address materialises `MOVI _inc, A?`; an initialized
|
||||||
|
global function pointer is a `.data` word (`.long _dec`). `LowerCall` accepts
|
||||||
|
a callee that is not a `GlobalAddress` and emits the one-word register call
|
||||||
|
`CALL Rs` (GSPA encodes it as `0x0920`; `dis34` prints `call aN`). The call
|
||||||
|
still saves the old STK (`MOVE A14, -*SP, 1`), pushes its arguments
|
||||||
|
last-to-first on the program stack, and re-establishes `SETF 16, 0, 0` /
|
||||||
|
`SETF 32, 0, 1`; the callee's `RETS 2` restores A14. Direct calls remain
|
||||||
|
`CALLA _sym`. `m4-fnptr.ll` pins the selection, `scripts/tests/m4/fnptr.sh`
|
||||||
|
runs the indirect path in MAME and checks `dis34` at the linked address, and
|
||||||
|
`scripts/tests/m4/diff/ti-fnptr.s` is the 1987-compiler ABI golden.
|
||||||
|
|
||||||
|
#### 32-bit multiply and divide
|
||||||
|
|
||||||
|
`MPYS` (`0x5C00`), `MPYU` (`0x5E00`), `DIVS` (`0x5800`) and `DIVU`
|
||||||
|
(`0x5A00`) are selected directly; there is no integer multiply/divide helper
|
||||||
|
in `RTS.LIB` (`_div` is only the C `div()` wrapper; `FD$MUL`/`FD$DIV` are
|
||||||
|
floating point). The destination must be an **odd** A register (`A1`, `A3`,
|
||||||
|
…, `A11`): an even `Rd` denotes the `Rd:Rd+1` pair and would clobber the next
|
||||||
|
register (A15/SP in particular), so `ARegsOdd` ties the single-register form.
|
||||||
|
|
||||||
|
- 32×32 low product: one `MPYS` (the low 32 bits are identical for signed
|
||||||
|
and unsigned).
|
||||||
|
- 16×16 unsigned product: one `MPYU` (the natural shape after zero-extension
|
||||||
|
to i32).
|
||||||
|
- 32/32 quotient: `DIVS` or `DIVU`.
|
||||||
|
- 32/32 remainder: quotient, `MPYS` by the divisor, then `SUB`.
|
||||||
|
|
||||||
|
`SREM`/`UREM` are custom-lowered this way so C truncation semantics hold and
|
||||||
|
no even-destination pair is needed. `m4-mul.ll` pins `MPYS`/`MPYU`/`DIVS`/
|
||||||
|
`DIVU` and the remainder sequence.
|
||||||
|
|
||||||
|
The `-O2` loop policy is `-replexitval=never` in `scripts/tms-clang`.
|
||||||
|
`opt` runs without a target machine at that point, so the driver cannot
|
||||||
|
express this as a TTI policy; IndVarSimplify's exit-value replacement turns
|
||||||
|
`sumto`'s loop into a closed-form **i33** multiply, which this i64-less
|
||||||
|
target cannot select. With the flag, `sumto` keeps a real back-edge and
|
||||||
|
`squares` uses `MPYS`. `m4-divconst.ll` also pins that the magic-number
|
||||||
|
divide combine does not fire: `MULHS`/`MULHU`/`SMUL_LOHI`/`UMUL_LOHI` are
|
||||||
|
`Expand`, so divide-by-constant stays `DIVS`/`DIVU`.
|
||||||
|
|
||||||
|
#### Zero-extending / extending narrow loads
|
||||||
|
|
||||||
|
`ZEXTLOAD i8` and `ZEXTLOAD i16` are custom-lowered to the existing
|
||||||
|
sign-extending load (`MOVB` or the FS0 `MOVE`) plus `ANDI 255` / `ANDI 65535`,
|
||||||
|
reusing the original chain and MMO so volatile and ordering behaviour is
|
||||||
|
preserved. `EXTLOAD` (the `anyext` form) lowers the same way, and an `i1`
|
||||||
|
load is a byte load masked to one bit. `m4-zext.ll`, `m4-anyext.ll` and
|
||||||
|
`m4-i1load.ll` pin the sequences. The old M3 `m3-reject-zext.ll` was removed.
|
||||||
|
|
||||||
|
#### Module-unique private labels
|
||||||
|
|
||||||
|
Every private/local symbol (`GV->hasLocalLinkage()`) gets a deterministic
|
||||||
|
suffix `_<8 uppercase hex digits>` derived from a custom 64-bit string hash
|
||||||
|
(FNV-style, not canonical FNV-1a) of `Module::getModuleIdentifier()`. Two
|
||||||
|
translation units with string literals therefore no longer collide in GSPA's
|
||||||
|
flat symbol table; public names are byte-for-byte unchanged. `llc file.ll`
|
||||||
|
uses the file name (or IR `source_filename`) as the identifier; `llc <
|
||||||
|
file.ll` uses the stable `<stdin>` fallback. `m4-private.ll` and the two-TU
|
||||||
|
`scripts/tests/m4/private.sh` pin it.
|
||||||
|
|
||||||
|
GSPA 2.00 truncates identifiers at 31 characters. A private name long enough
|
||||||
|
to lose the `_<8 hex>` token at that limit will collide loudly (a duplicate
|
||||||
|
definition from GSPA), not silently; the naming scheme is unchanged in M4 and
|
||||||
|
a long-name two-TU link test is an M5 follow-up.
|
||||||
|
|
||||||
|
#### `i8`/`i16` fixed arguments
|
||||||
|
|
||||||
|
`i8` and `i16` fixed parameters are promoted to one 4-byte program-stack
|
||||||
|
slot. The caller extends the actual according to the parameter's
|
||||||
|
`signext`/`zeroext` attribute (`SLL 16`/`SRA 16` for a signed short,
|
||||||
|
`ANDI 255` for an unsigned char), and the callee truncates on load.
|
||||||
|
`m4-arg8.ll` pins both sides. `i8`, `i16`, `i64`, float and aggregate
|
||||||
|
**returns** remain rejected, as do `i64`/float/aggregate arguments.
|
||||||
|
|
||||||
|
#### Varargs: the contiguous-stack `&last_fixed` pattern
|
||||||
|
|
||||||
|
M4 implements only the form termtris's `compat.c` uses. Fixed arguments live
|
||||||
|
at `FP-32`, `FP-64`, … and the variadic actuals occupy the following
|
||||||
|
contiguous slots (`FP-96`, `FP-128`, …). The callee walks from
|
||||||
|
`(char *)&last_fixed` by decreasing the pointer by `sizeof(long)` (4 bytes),
|
||||||
|
which reads the first variadic slot. There is **no** `va_list`, `va_start` or
|
||||||
|
`va_arg`.
|
||||||
|
|
||||||
|
A variadic definition with **no** fixed argument is rejected
|
||||||
|
(`TMS34010 varargs requires at least one fixed argument`): the walk has no
|
||||||
|
anchor and the epilogue could not tell whether the caller saved STK. An
|
||||||
|
`i8`/`i16` last fixed argument is also rejected
|
||||||
|
(`TMS34010 varargs requires an i32 or pointer last fixed argument`): the fixed
|
||||||
|
object is always 4 bytes, but LLVM's argument-copy elision requires the fixed
|
||||||
|
object and the parameter alloca to have equal size, so a narrow anchor would
|
||||||
|
silently read a private local instead of the caller's slot. The supported
|
||||||
|
anchor is `i32` or a pointer. `m4-varargs.ll`, `m4-varargs-anchor.ll`,
|
||||||
|
`m4-reject-vararg0.ll` and `m4-reject-vararg-narrow{,16}.ll` pin the rules;
|
||||||
|
`scripts/tests/m4/varargs.sh` runs three contiguous arguments in MAME.
|
||||||
|
|
||||||
|
A **static** variadic definition can lose `...` under `-O2` dead-argument
|
||||||
|
elimination; the external termtris `sprintf` is unaffected (it is not a
|
||||||
|
static definition in a TU where the optimizer can see all callers).
|
||||||
|
|
||||||
|
#### Driver policies
|
||||||
|
|
||||||
|
`scripts/tms-clang` invokes `opt` with two explicit policies:
|
||||||
|
|
||||||
|
- `-replexitval=never` — the i33 exit-value multiply described above.
|
||||||
|
- `-switch-to-lookup=false` — the optimizer's switch-to-lookup transform
|
||||||
|
creates a *new* byte-offset GEP after `tms-bitaddr --scale`, and
|
||||||
|
`--normalize` can only treat surviving GEP offsets as bit offsets. Such a
|
||||||
|
GEP would index the table one bit per slot (an 8× address bug). The backend
|
||||||
|
also has no jump-table lowering, so compare/branch is correct. termtris's
|
||||||
|
five-case X-event switch is the motivating case; `m4-switch.ll` pins the
|
||||||
|
target-level behaviour and `scripts/m4/switch.c` is the driver probe.
|
||||||
|
|
||||||
|
#### XImage, XEvent and the ROM layouts
|
||||||
|
|
||||||
|
`scripts/m4/x11m4.h` models the 32-bit X11R4 ABI. The TMS34010 C ABI packs
|
||||||
|
aggregates, so the offsets are the packed object offsets:
|
||||||
|
|
||||||
|
| object | byte offset | bit displacement |
|
||||||
|
|---|---:|---:|
|
||||||
|
| `Display.screens` | `0x90` | `0x480` |
|
||||||
|
| `Screen.root` | `0x8` | `0x40` |
|
||||||
|
| `XImage.data` | 16 | `0x80` |
|
||||||
|
| `XImage.byte_order` | 20 | `0xA0` |
|
||||||
|
| `XImage.bytes_per_line` | 40 | `0x140` |
|
||||||
|
| `XImage.bits_per_pixel` | 44 | `0x160` |
|
||||||
|
| `XImage.f` | 64 | `0x200` |
|
||||||
|
| `sizeof(XImage)` | 88 | — |
|
||||||
|
| `XErrorEvent.error_code` | 16 | `0x80` |
|
||||||
|
| `sizeof(XErrorEvent)` | 20 | — |
|
||||||
|
| `XEvent.xkey.keycode` | 52 | `0x1A0` |
|
||||||
|
| `sizeof(XEvent)` | 96 | — |
|
||||||
|
|
||||||
|
`XEvent` is 96 bytes (its largest member is the protocol `pad[24]` of 32-bit
|
||||||
|
longs), and the termtris event buffer is `vt_u32 evbuf[24]`. `XErrorEvent` is
|
||||||
|
20 bytes on the 32-bit ABI but would be 19 packed, so `x11m4.h` and the TI
|
||||||
|
golden declare one explicit trailing `abi_pad` byte. `layout.sh` asserts every
|
||||||
|
offset with `_Static_assert` and checks the same constants in the 1987 TI
|
||||||
|
golden; `scripts/tests/m4/diff/ti-image.s` additionally checks the XImage
|
||||||
|
field displacements in a real image-call function.
|
||||||
|
|
||||||
|
#### The XG block and the corrected symbol table
|
||||||
|
|
||||||
|
The VT1200 Xlib keeps per-process state in an XG block reached through the
|
||||||
|
process user area. `scripts/m4/xgfix.c` installs a 1 KiB zeroed block before
|
||||||
|
`XOpenDisplay`; `XSetErrorHandler`/`XSetIOErrorHandler` store into that block
|
||||||
|
at `+0x60`/`+0x40`. The client's `int (Display *, XErrorEvent *)` handler
|
||||||
|
therefore receives the ROM's real error events.
|
||||||
|
|
||||||
|
The v2.2 table has **30** entries. M4 corrected the M3 name: `XMapWindow` is
|
||||||
|
`0x3FD64F60` (MapWindow request opcode 8), while `0x3FD645B0` is
|
||||||
|
`XRaiseWindow` (ConfigureWindow opcode 12, `CWStackMode = Above`). The old M3
|
||||||
|
pass with `0x3FD645B0` was the server's configure-side effect, not a mapping
|
||||||
|
call. A client that wants map-and-raise calls both.
|
||||||
|
`scripts/m4/validate-syms.sh` checks the ROM range, 16-bit alignment and a
|
||||||
|
plausible prologue for all 30 entries, and the opcode semantics of the
|
||||||
|
correction-sensitive ones (`XMapWindow`, `XRaiseWindow`, `XPutImage`,
|
||||||
|
`XGetImage`, `XCreateImage`, the two error-handler setters).
|
||||||
|
|
||||||
|
#### The image/event client
|
||||||
|
|
||||||
|
`scripts/m4/xshow.c` is a compiled C client: install the XG block, open
|
||||||
|
`"0::0"` with a retry, create a 640×480 window, select
|
||||||
|
key/button/expose/focus masks, create a GC, build a 1-bit LSBFirst XYBitmap
|
||||||
|
checker, call `XCreateImage`, set `byte_order`/`bitmap_bit_order`,
|
||||||
|
`XSetErrorHandler`, map and raise, set input focus, draw, then drain events
|
||||||
|
and quit on the real LK401 `q`.
|
||||||
|
|
||||||
|
Two server/Xlib constraints shape the drawing path:
|
||||||
|
|
||||||
|
- A single 38 KiB `XPutImage` exceeds the ROM's 16 KiB request buffer, so the
|
||||||
|
image is emitted in 15 bands of 32 rows; the bitmap path ignores `src_y`, so
|
||||||
|
each band selects its rows by moving `XImage.data`.
|
||||||
|
- The server's 1-bit drawing path needs a 32-bit-aligned destination, so each
|
||||||
|
band is drawn into an aligned depth-1 pixmap (`XCreatePixmap`) and
|
||||||
|
`XCopyArea`'d to window x=12 (screen x=32). The 12-pixel prefix is filled
|
||||||
|
with the preceding cell's colour. The checker cells remain 32 pixels; the
|
||||||
|
phase inside the (20,20) window is 12 pixels. `XCopyArea` copies
|
||||||
|
`IMG_W - 12` = 628 columns, so the rightmost 12 source columns (the final
|
||||||
|
12 px of the last cell) are not displayed; the last visible cell is 20 px,
|
||||||
|
not 32.
|
||||||
|
|
||||||
|
The 38,400-byte image and the 1 KiB XG block live in `.bss` at `0x01A00000`
|
||||||
|
(the termtris-proven region); `prog` stays at `0x018A0000`. The client primes
|
||||||
|
the ROM blitter (`blit_copy`, `0x3FB44400`) with one 4-byte self-copy before
|
||||||
|
the first `XPutImage`. `scripts/tests/m4/run.sh` requires the image pixels,
|
||||||
|
keycode 193 / keysym `0x71`, `quit=1`, zero X errors, focus = the client
|
||||||
|
window `0x400001`, the last event being KeyPress, and a loader status of 0.
|
||||||
|
|
||||||
|
#### Pinned termtris
|
||||||
|
|
||||||
|
The full-game integration is `scripts/tests/m4/termtris.sh` plus
|
||||||
|
`scripts/m4/termtris/build.sh`, pinned to the `vt1200-termtris` tree at
|
||||||
|
commit `be5b323f5d2cc4f7a7d25e7143c67bd9828ccbdf` (branch `termtris`). Six
|
||||||
|
translation units (`main`, `game`, `scoredb`, `x11`, `compat`, `xgfix`) are
|
||||||
|
compiled with this compiler, assembled and linked by the 1987 GSPA/GSPLNK
|
||||||
|
2.00 with the 1987 `rts.lib`, and run in MAME. The regions are the
|
||||||
|
0x40000-bit `prog` at `0x018A0000` and `ram` at `0x01A00000`. The final map
|
||||||
|
is `.text` 0x2E210 bits (23,618 bytes), `.data` 0x21A0 bits, `.bss` 0x1DBD0
|
||||||
|
bits; it fits `prog` and the loader reports `status=0 (OK)` with no
|
||||||
|
`UNDEFED` symbols. The gate runs board/move/panels/quit modes with real key
|
||||||
|
injection and enforces `loops >= 100` (board/move/panels/quit), `events >= 6`
|
||||||
|
for move/panels/quit (board `>= 2`), `quit = 1` only for quit, focus = the
|
||||||
|
created termtris window (`0x00400001`), loader status 0 and a non-empty
|
||||||
|
snapshot each time. The pinned tree is **test-only** (GPLv3) and is not part
|
||||||
|
of the compiler or project distribution.
|
||||||
|
|
||||||
|
|
||||||
|
### M3 carry-forwards in M4
|
||||||
|
|
||||||
|
M4 closes three of the five M3 carry-forwards:
|
||||||
|
|
||||||
|
- zero-extending `i8`/`i16` loads (`m4-zext.ll`; the old `m3-reject-zext.ll`
|
||||||
|
was removed);
|
||||||
|
- module-unique private labels (`m4-private.ll` and the two-TU
|
||||||
|
`private.sh`), with the GSPA 31-character caveat above;
|
||||||
|
- unsigned/`<=` runtime coverage (`m4-branches.ll`, and the MAME
|
||||||
|
`unsigned.sh` markers for `JALE`/`JALS`/`JAGT`/`JAGE`/`JAHS`/`JAHI`/
|
||||||
|
`JANE`/`JALT`/`JALO`).
|
||||||
|
|
||||||
|
The new M4 harness and `scripts/tests/m4/dis-roundtrip.sh` are
|
||||||
|
operand/register-independent, and `layout.sh` was tightened. The M3 regression
|
||||||
|
is deliberately unchanged: `m3-cmpi.ll` still uses `CHECK-DAG: MOVI 0` /
|
||||||
|
`MOVI 1` (the 1/0 order is not pinned), and `scripts/m3/dis-roundtrip.sh`
|
||||||
|
still hardcodes the nine `XCreateSimpleWindow` push registers
|
||||||
|
(`A2`/`A0`). Tightening those M3 tests is a test-hardening follow-up; the M3
|
||||||
|
suite (`m3-*.ll` and `scripts/tests/m3/`) passes as-is, with only the obsolete
|
||||||
|
zext-reject test removed.
|
||||||
|
|
||||||
|
## M4 non-goals and the M5 boundary
|
||||||
|
|
||||||
|
M4 deliberately stops before:
|
||||||
|
|
||||||
|
- **Native object output:** no `MCCodeEmitter`, object writer,
|
||||||
|
`llc -filetype=obj`, `clang -c` or native COFF. GSPA/GSPLNK still produce
|
||||||
|
the COFF. The `JR`/branch-relaxation code-size pass is M5, as are
|
||||||
|
MMTM/MMFM register-list saves (the current per-register system-stack pushes
|
||||||
|
have the identical ABI layout).
|
||||||
|
- **Floats and `long long`:** no float/double or `i64` arguments, returns,
|
||||||
|
arithmetic or branches.
|
||||||
|
- **General varargs:** no `va_list`, `va_start` or `va_arg`; only the
|
||||||
|
contiguous `&last_fixed` pattern above. Non-contiguous/register varargs are
|
||||||
|
M5.
|
||||||
|
- **Bitfields and aggregates by value:** no structs/unions by value, `sret`,
|
||||||
|
`byval`, bitfields, or aggregate returns. Struct *fields* through pointers
|
||||||
|
are supported.
|
||||||
|
- **Narrow returns:** `i8`/`i16` returns remain rejected; fixed `i8`/`i16`
|
||||||
|
arguments are supported.
|
||||||
|
- **Xlib beyond the tested calls:** the client uses the validated image,
|
||||||
|
event, GC, window and error-handler entries. It does not implement the
|
||||||
|
whole Xlib surface, `XGetImage`, or `XDestroyImage` on a non-heap image.
|
||||||
|
- **`JR`/relaxation and deeper libc/runtime coverage:** M5 owns the
|
||||||
|
code-size pass and broader `RTS.LIB` integration.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -162,14 +580,15 @@ cmake -S llvm -B build -G Ninja \
|
|||||||
ninja -C build llc clang opt llvm-config FileCheck count not
|
ninja -C build llc 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+M3+M4
|
||||||
|
backend suite:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
build/bin/llvm-lit -v build/test/CodeGen/TMS34010 --filter='m2-'
|
build/bin/llvm-lit -v build/test/CodeGen/TMS34010 --filter='(m2|m3|m4)-'
|
||||||
# -> 37 passed
|
# -> 58 passed (32 m2-*, 8 m3-*, 18 m4-*)
|
||||||
```
|
```
|
||||||
|
|
||||||
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`,
|
- Migrated/legacy selection: `m2-add`, `m2-load`, `m2-char`, `m2-i16`,
|
||||||
`m2-store`, `m2-global`, `m2-late-gep`.
|
`m2-store`, `m2-global`, `m2-late-gep`.
|
||||||
@@ -178,33 +597,75 @@ The tests are `llvm/test/CodeGen/TMS34010/m2-*.ll`:
|
|||||||
`m2-spill`, `m2-returned`, `m2-returned-call`.
|
`m2-spill`, `m2-returned`, `m2-returned-call`.
|
||||||
- Data: `m2-data`, `m2-data-avail`, `m2-data-align`, `m2-data-z64`,
|
- Data: `m2-data`, `m2-data-avail`, `m2-data-align`, `m2-data-z64`,
|
||||||
`m2-data-i1`, `m2-string`, `m2-ptrinit`.
|
`m2-data-i1`, `m2-string`, `m2-ptrinit`.
|
||||||
- Loud refusals: `m2-reject-i64`, `m2-reject-struct`, `m2-reject-vararg`,
|
- Loud refusals: `m2-reject-i64`, `m2-reject-struct`, `m2-reject-sret`,
|
||||||
`m2-reject-sret`, `m2-reject-structret`, `m2-reject-arg8`,
|
`m2-reject-structret`, `m2-reject-i8ret`, `m2-reject-float`.
|
||||||
`m2-reject-i8ret`, `m2-reject-float`, `m2-reject-call-indirect`,
|
|
||||||
`m2-reject-call-arg8`, `m2-reject-call-vararg`.
|
|
||||||
|
|
||||||
`m2-add` uses `RETS 2`; `m2-global` returns in `A8` with `RETS 0`;
|
`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`
|
`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
|
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 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`.
|
||||||
|
- `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
|
## Integration test
|
||||||
|
|
||||||
The VT1200 project holds the harness, linker command file, COFF loader and
|
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
|
MAME checks: <https://git.wut.ee/opencode.siil.arti.ee/tms-c-compiler>. From a
|
||||||
project checkout that has this fork at `build/llvm-clean`:
|
project checkout that has this fork at `build/llvm-clean` (branch `m4`):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m2 ./scripts/build-llvm.sh
|
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m4 ./scripts/build-llvm.sh
|
||||||
scripts/tests/m2/run.sh # GSPA/GSPLNK under DOSBox, then MAME
|
ninja -C build/llvm-clean-build llc opt clang llvm-config FileCheck count not
|
||||||
scripts/tests/m2/dis-roundtrip.sh # linked .text decoded with dis34
|
build/llvm-clean-build/bin/llvm-lit -v build/llvm-clean/llvm/test/CodeGen/TMS34010 \
|
||||||
scripts/tests/m2/diff/check.sh # ABI shape vs. the 1987 TI compiler
|
--filter='(m2|m3|m4)-'
|
||||||
tools/tms-bitaddr/tests/run.sh # GEP scaling/normalization
|
scripts/tests/m4/run.sh # XImage/event client in MAME
|
||||||
|
scripts/tests/m4/dis-roundtrip.sh # linked .text decoded with dis34
|
||||||
|
scripts/tests/m4/layout.sh # XImage/XEvent/error-event offsets
|
||||||
|
scripts/tests/m4/symbols.sh # 30-entry v2.2 table and the correction
|
||||||
|
scripts/tests/m4/termtris.sh # pinned full-game integration
|
||||||
|
scripts/tests/m4/diff/check.sh # TI function-pointer/image ABI golden
|
||||||
|
scripts/tests/m4/unsigned.sh
|
||||||
|
scripts/tests/m4/private.sh
|
||||||
|
scripts/tests/m4/mul.sh
|
||||||
|
scripts/tests/m4/fnptr.sh
|
||||||
|
scripts/tests/m4/varargs.sh
|
||||||
|
tools/tms-bitaddr/tests/run.sh # GEP scaling/normalization
|
||||||
|
scripts/tests/m3/run.sh # M3 regression
|
||||||
|
scripts/tests/m3/dis-roundtrip.sh
|
||||||
|
scripts/tests/m2/run.sh # M2 regression
|
||||||
|
scripts/tests/m2/dis-roundtrip.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: `M2 MAME: PASS`, `M2 dis34 round-trip: PASS`, `M2 diff: PASS` and
|
Expected: `m4 xshow: PASS`, `M4 dis34 round-trip: PASS`, `layout.sh: PASS`,
|
||||||
`tms-bitaddr: PASS`. The differential check uses stored golden assembly from
|
`symbols.sh: PASS`, `m4 termtris: PASS`, `M4 diff: PASS`, the unsigned /
|
||||||
the 1987 TI compiler and does not run DOSBox; the other two gates do. The M1
|
private / mul / fnptr / varargs PASS lines, `tms-bitaddr: PASS`, and the M3/M2
|
||||||
harness (`scripts/tests/m1/`) lives on the project's `m1` branch and is not an
|
regression PASS lines. The MAME gates pin `VT1200_MONITOR=VR319`; the image
|
||||||
M2 gate. The project README lists the prerequisites (DOSBox, mtools, the VT1200
|
gate requires the checker pixels, the real LK401 `q` (keycode 193, keysym
|
||||||
SDK image, MAME).
|
`0x71`), focus `0x400001`, zero X errors and a loader status of 0. The
|
||||||
|
differential check uses stored golden assembly from the 1987 TI compiler and
|
||||||
|
does not run DOSBox; the MAME/dis34 gates do. The pinned termtris tree is
|
||||||
|
test-only. The project README lists the prerequisites (DOSBox, mtools, the
|
||||||
|
VT1200 SDK image, MAME).
|
||||||
@@ -21,6 +21,14 @@ TMS34010MCAsmInfo::TMS34010MCAsmInfo(const Triple &TT,
|
|||||||
CommentString = ";";
|
CommentString = ";";
|
||||||
SeparatorString = ",";
|
SeparatorString = ",";
|
||||||
|
|
||||||
|
// 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.
|
// GSPA directives express alignment and section sizes in bits.
|
||||||
AlignmentIsInBytes = false;
|
AlignmentIsInBytes = false;
|
||||||
|
|
||||||
|
|||||||
@@ -268,6 +268,15 @@ void TMS34010AsmPrinter::emitDataInitializer(const Constant *Init) {
|
|||||||
|
|
||||||
if (const auto *CA = dyn_cast<ConstantArray>(Init)) {
|
if (const auto *CA = dyn_cast<ConstantArray>(Init)) {
|
||||||
Type *ElemTy = CA->getType()->getElementType();
|
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)) {
|
if (ElemTy->isIntegerTy(8)) {
|
||||||
SmallVector<uint64_t, 32> Bytes;
|
SmallVector<uint64_t, 32> Bytes;
|
||||||
for (const Use &U : CA->operands()) {
|
for (const Use &U : CA->operands()) {
|
||||||
|
|||||||
@@ -8,14 +8,21 @@
|
|||||||
|
|
||||||
#include "TMS34010ISelLowering.h"
|
#include "TMS34010ISelLowering.h"
|
||||||
#include "TMS34010.h"
|
#include "TMS34010.h"
|
||||||
|
#include "TMS34010InstrInfo.h"
|
||||||
#include "TMS34010Subtarget.h"
|
#include "TMS34010Subtarget.h"
|
||||||
#include "TMS34010TargetMachine.h"
|
#include "TMS34010TargetMachine.h"
|
||||||
|
#include "llvm/CodeGen/MachineBasicBlock.h"
|
||||||
#include "llvm/CodeGen/MachineFrameInfo.h"
|
#include "llvm/CodeGen/MachineFrameInfo.h"
|
||||||
#include "llvm/CodeGen/MachineFunction.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/CodeGen/TargetRegisterInfo.h"
|
||||||
#include "llvm/IR/Function.h"
|
#include "llvm/IR/Function.h"
|
||||||
#include "llvm/IR/GlobalValue.h"
|
#include "llvm/IR/GlobalValue.h"
|
||||||
#include "llvm/Support/ErrorHandling.h"
|
#include "llvm/Support/ErrorHandling.h"
|
||||||
|
#include <iterator>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
|
|
||||||
@@ -24,8 +31,12 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
|
|||||||
: TargetLowering(TM, STI) {
|
: TargetLowering(TM, STI) {
|
||||||
addRegisterClass(MVT::i32, &TMS34010::ARegsRegClass);
|
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.
|
// 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);
|
setLoadExtAction(ISD::SEXTLOAD, MVT::i32, MVT::i8, Legal);
|
||||||
setTruncStoreAction(MVT::i32, MVT::i8, Legal);
|
setTruncStoreAction(MVT::i32, MVT::i8, Legal);
|
||||||
|
|
||||||
@@ -33,6 +44,52 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
|
|||||||
setLoadExtAction(ISD::SEXTLOAD, MVT::i32, MVT::i16, Legal);
|
setLoadExtAction(ISD::SEXTLOAD, MVT::i32, MVT::i16, Legal);
|
||||||
setTruncStoreAction(MVT::i32, MVT::i16, Legal);
|
setTruncStoreAction(MVT::i32, MVT::i16, Legal);
|
||||||
|
|
||||||
|
// There is no zero-extending load instruction. Lower a zero-extending
|
||||||
|
// byte/short load as the sign-extending load followed by an ANDI mask that
|
||||||
|
// clears the sign-extension bits. i1 loads occupy a byte in memory (the
|
||||||
|
// same rule as the data layout's i8 minimum) and mask to one bit. An
|
||||||
|
// anyext load may choose any high bits, so zero-extending it is valid and
|
||||||
|
// avoids an unselectable load when the DAG shares one narrow load between a
|
||||||
|
// truncating and a widening use.
|
||||||
|
setLoadExtAction(ISD::ZEXTLOAD, MVT::i32, MVT::i1, Custom);
|
||||||
|
setLoadExtAction(ISD::ZEXTLOAD, MVT::i32, MVT::i8, Custom);
|
||||||
|
setLoadExtAction(ISD::ZEXTLOAD, MVT::i32, MVT::i16, Custom);
|
||||||
|
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i1, Custom);
|
||||||
|
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i8, Custom);
|
||||||
|
setLoadExtAction(ISD::EXTLOAD, MVT::i32, MVT::i16, Custom);
|
||||||
|
|
||||||
|
// Multiply and divide are single register-register instructions (MPYS/DIVS/
|
||||||
|
// DIVU) with an odd tied destination; see TMS34010InstrInfo.td. The
|
||||||
|
// remainder has no direct 32-bit form, so it is expanded from the quotient
|
||||||
|
// and a multiply in LowerOperation.
|
||||||
|
setOperationAction(ISD::MUL, MVT::i32, Legal);
|
||||||
|
setOperationAction(ISD::SDIV, MVT::i32, Legal);
|
||||||
|
setOperationAction(ISD::UDIV, MVT::i32, Legal);
|
||||||
|
setOperationAction(ISD::SREM, MVT::i32, Custom);
|
||||||
|
setOperationAction(ISD::UREM, MVT::i32, Custom);
|
||||||
|
// There is no high-half multiply. If MULHS/MULHU were legal (or custom),
|
||||||
|
// the target-independent divide/remainder-by-constant combine would turn
|
||||||
|
// `x / c` into a MULHS magic-number sequence the target cannot select. Keep
|
||||||
|
// them expanding, which also makes isOperationLegalOrCustom false so the
|
||||||
|
// combine leaves the real DIVS/DIVU in place.
|
||||||
|
setOperationAction(ISD::MULHS, MVT::i32, Expand);
|
||||||
|
setOperationAction(ISD::MULHU, MVT::i32, Expand);
|
||||||
|
setOperationAction(ISD::SMUL_LOHI, MVT::i32, Expand);
|
||||||
|
setOperationAction(ISD::UMUL_LOHI, MVT::i32, Expand);
|
||||||
|
|
||||||
|
// The backend has no jump table lowering (no BR_JT). Force switches through
|
||||||
|
// the ordinary compare/branch path even for clang's five-case event switch.
|
||||||
|
setMinimumJumpTableEntries(~0u);
|
||||||
|
|
||||||
|
// There is no conditional move or dedicated setcc result; let the generic
|
||||||
|
// legalizer turn setcc into a select_cc over 1/0 and select into a
|
||||||
|
// select_cc, then lower the compare and branch ourselves.
|
||||||
|
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());
|
computeRegisterProperties(STI.getRegisterInfo());
|
||||||
|
|
||||||
setBooleanContents(ZeroOrOneBooleanContent);
|
setBooleanContents(ZeroOrOneBooleanContent);
|
||||||
@@ -42,6 +99,54 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
|
|||||||
setMaxAtomicSizeInBitsSupported(0);
|
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::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 TMS34010TargetLowering::LowerFormalArguments(
|
||||||
SDValue Chain, CallingConv::ID CallConv, bool IsVarArg,
|
SDValue Chain, CallingConv::ID CallConv, bool IsVarArg,
|
||||||
const SmallVectorImpl<ISD::InputArg> &Ins, const SDLoc &DL,
|
const SmallVectorImpl<ISD::InputArg> &Ins, const SDLoc &DL,
|
||||||
@@ -54,14 +159,43 @@ SDValue TMS34010TargetLowering::LowerFormalArguments(
|
|||||||
|
|
||||||
// Validate the IR signature before legalization can split or promote it.
|
// Validate the IR signature before legalization can split or promote it.
|
||||||
// Checking only the legalized MVT lets an i64 or an aggregate arrive as
|
// Checking only the legalized MVT lets an i64 or an aggregate arrive as
|
||||||
// several i32 pieces and be silently accepted (and miscompiled).
|
// several i32 pieces and be silently accepted (and miscompiled). Integers
|
||||||
if (F.isVarArg() || IsVarArg)
|
// narrower than i32 are promoted to one i32 stack slot; the SelectionDAG
|
||||||
report_fatal_error("M2 does not support varargs", false);
|
// 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()) {
|
for (const Argument &Arg : F.args()) {
|
||||||
Type *Ty = Arg.getType();
|
Type *Ty = Arg.getType();
|
||||||
if (!Ty->isIntegerTy(32) && !Ty->isPointerTy())
|
if (!Ty->isPointerTy() &&
|
||||||
report_fatal_error("M2 supports only i32 and pointer arguments", false);
|
!(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
|
// Every explicit argument is passed on the program stack. Argument i lives
|
||||||
@@ -81,7 +215,9 @@ SDValue TMS34010TargetLowering::LowerFormalArguments(
|
|||||||
"M2 supports only plain arguments (no sret/byval/inreg/nest)",
|
"M2 supports only plain arguments (no sret/byval/inreg/nest)",
|
||||||
false);
|
false);
|
||||||
if (In.VT != MVT::i32)
|
if (In.VT != MVT::i32)
|
||||||
report_fatal_error("M2 supports only i32 and pointer arguments", false);
|
report_fatal_error(
|
||||||
|
"TMS34010 supports only integer (<=32 bit) and pointer arguments",
|
||||||
|
false);
|
||||||
|
|
||||||
int FI = MFI.CreateFixedObject(4, -static_cast<int64_t>(i + 1) * 4, true);
|
int FI = MFI.CreateFixedObject(4, -static_cast<int64_t>(i + 1) * 4, true);
|
||||||
SDValue FIN = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
|
SDValue FIN = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
|
||||||
@@ -106,20 +242,29 @@ SDValue TMS34010TargetLowering::LowerCall(
|
|||||||
// the marker and lower an ordinary call rather than failing on it.
|
// the marker and lower an ordinary call rather than failing on it.
|
||||||
CLI.IsTailCall = false;
|
CLI.IsTailCall = false;
|
||||||
|
|
||||||
// M2 has no indirect calls: the callee must be a direct global symbol. A
|
// A direct call names a global function. Anything else is an indirect call
|
||||||
// function pointer, a plain integer, or an external symbol (asm label) is
|
// through a 32-bit bit address (a function pointer, a plain integer, or the
|
||||||
// outside the M2 ABI.
|
// inttoptr of an absolute ROM entry). External-symbol (asm label) callees
|
||||||
auto *Callee = dyn_cast<GlobalAddressSDNode>(CLI.Callee);
|
// stay outside the ABI.
|
||||||
if (!Callee || !isa<Function>(Callee->getGlobal()))
|
const GlobalAddressSDNode *CalleeGA =
|
||||||
report_fatal_error("M2 supports only direct calls to global functions",
|
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);
|
false);
|
||||||
const Function *CalleeFn = cast<Function>(Callee->getGlobal());
|
} else {
|
||||||
|
IsIndirect = true;
|
||||||
|
}
|
||||||
|
|
||||||
// CLI.IsVarArg is not populated for ordinary direct calls in this LLVM: the
|
// Variadic calls need no special lowering: CLI.Outs already holds every
|
||||||
// call-site function type records only the actual arguments, so the callee
|
// actual argument, and the push loop below stores them last-first in the
|
||||||
// declaration's own type is the reliable varargs source.
|
// same contiguous program-stack slots as the fixed arguments. That is the
|
||||||
if (CLI.IsVarArg || CalleeFn->isVarArg())
|
// only varargs form supported (no va_start/va_arg/va_list); the callee can
|
||||||
report_fatal_error("M2 does not support varargs", false);
|
// 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
|
// Check the original IR types: legalization may promote an i8 or split an
|
||||||
// aggregate before the arguments reach Outs/OutVals.
|
// aggregate before the arguments reach Outs/OutVals.
|
||||||
@@ -135,14 +280,19 @@ SDValue TMS34010TargetLowering::LowerCall(
|
|||||||
"(no sret/byval/inreg/nest/inalloca/preallocated)",
|
"(no sret/byval/inreg/nest/inalloca/preallocated)",
|
||||||
false);
|
false);
|
||||||
Type *Ty = Arg.OrigTy;
|
Type *Ty = Arg.OrigTy;
|
||||||
if (!Ty->isIntegerTy(32) && !Ty->isPointerTy())
|
if (!Ty->isPointerTy() &&
|
||||||
report_fatal_error("M2 supports only i32 and pointer call arguments",
|
!(Ty->isIntegerTy() && Ty->getIntegerBitWidth() <= 32))
|
||||||
false);
|
report_fatal_error(
|
||||||
|
"TMS34010 supports only integer (<=32 bit) and pointer call "
|
||||||
|
"arguments",
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
for (const ISD::OutputArg &Out : CLI.Outs)
|
for (const ISD::OutputArg &Out : CLI.Outs)
|
||||||
if (Out.VT != MVT::i32)
|
if (Out.VT != MVT::i32)
|
||||||
report_fatal_error("M2 supports only i32 and pointer call arguments",
|
report_fatal_error(
|
||||||
false);
|
"TMS34010 supports only integer (<=32 bit) and pointer call "
|
||||||
|
"arguments",
|
||||||
|
false);
|
||||||
|
|
||||||
SDValue Chain = CLI.Chain;
|
SDValue Chain = CLI.Chain;
|
||||||
|
|
||||||
@@ -163,21 +313,55 @@ SDValue TMS34010TargetLowering::LowerCall(
|
|||||||
DAG.getMachineNode(TMS34010::PUSHARG, DL, MVT::Other, Arg, Chain), 0);
|
DAG.getMachineNode(TMS34010::PUSHARG, DL, MVT::Other, Arg, Chain), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CALLA references the symbol directly. A TargetGlobalAddress operand is
|
// A direct call references the symbol through CALLA. A TargetGlobalAddress
|
||||||
// left for the asm printer as a relocation; a plain GlobalAddress would be
|
// operand is left for the asm printer as a relocation; a plain GlobalAddress
|
||||||
// selected to a MOVI and the call would lose its symbol.
|
// would be selected to a MOVI and the call would lose its symbol.
|
||||||
SDValue Target = DAG.getTargetGlobalAddress(
|
//
|
||||||
Callee->getGlobal(), DL, getPointerTy(DAG.getDataLayout()),
|
// An indirect call carries the callee as a 32-bit bit address. Materialise
|
||||||
Callee->getOffset(), Callee->getTargetFlags());
|
// 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 TargetRegisterInfo *TRI = DAG.getSubtarget().getRegisterInfo();
|
||||||
const uint32_t *Mask = TRI->getCallPreservedMask(MF, CLI.CallConv);
|
const uint32_t *Mask = TRI->getCallPreservedMask(MF, CLI.CallConv);
|
||||||
assert(Mask && "M2 requires a call-preserved register mask");
|
assert(Mask && "M2 requires a call-preserved register mask");
|
||||||
SDValue RegMask = DAG.getRegisterMask(Mask);
|
SDValue RegMask = DAG.getRegisterMask(Mask);
|
||||||
|
|
||||||
Chain = SDValue(DAG.getMachineNode(TMS34010::CALLA, DL, MVT::Other, Target,
|
Chain = SDValue(
|
||||||
RegMask, Chain),
|
DAG.getMachineNode(CallOpc, DL, MVT::Other, Target, RegMask, Chain), 0);
|
||||||
0);
|
|
||||||
|
|
||||||
// ROM/Xlib code clobbers the field-size state, so re-establish both fields
|
// 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 for `short` and FS1 = 32 for words.
|
||||||
@@ -234,3 +418,342 @@ SDValue TMS34010TargetLowering::LowerReturn(
|
|||||||
return SDValue(
|
return SDValue(
|
||||||
DAG.getMachineNode(TMS34010::RETS, DL, MVT::Other, NVal, Chain), 0);
|
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 CMP/CMPI for `LHS - RHS` followed by the conditional branch that
|
||||||
|
/// implements `CC`, targeting \p Dest. The flags always represent
|
||||||
|
/// `LHS - RHS`, so CMP takes RHS as its source and LHS as its destination.
|
||||||
|
///
|
||||||
|
/// The compare produces a Glue result that is passed to the branch. Glued
|
||||||
|
/// DAG nodes are scheduled adjacently, so no flag-writing instruction can be
|
||||||
|
/// placed between the compare and the branch (the branch would otherwise test
|
||||||
|
/// the flags of that instruction instead).
|
||||||
|
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 Cmp;
|
||||||
|
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);
|
||||||
|
Cmp = SDValue(DAG.getMachineNode(TMS34010::CMPI, DL, MVT::Other,
|
||||||
|
MVT::Glue, Imm, LHS, Chain),
|
||||||
|
0);
|
||||||
|
} else {
|
||||||
|
Cmp = SDValue(DAG.getMachineNode(TMS34010::CMP, DL, MVT::Other, MVT::Glue,
|
||||||
|
RHS, LHS, Chain),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Results: 0 = chain, 1 = glue. The branch takes the compare's chain and,
|
||||||
|
// as its final operand, the glue that pins it directly after the compare.
|
||||||
|
SDValue Glue = Cmp.getValue(1);
|
||||||
|
return SDValue(DAG.getMachineNode(BranchOpc, DL, MVT::Other, Dest,
|
||||||
|
Cmp.getValue(0), Glue),
|
||||||
|
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::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: CMP; Jcc 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::CMPI))
|
||||||
|
.addImm(RHSMO.getImm())
|
||||||
|
.add(LHSMO);
|
||||||
|
} else {
|
||||||
|
BuildMI(*ThisMBB, InsertPt, DL, TII.get(TMS34010::CMP))
|
||||||
|
.add(RHSMO)
|
||||||
|
.add(LHSMO);
|
||||||
|
}
|
||||||
|
BuildMI(*ThisMBB, InsertPt, DL, TII.get(BranchOpc)).addMBB(TrueMBB);
|
||||||
|
|
||||||
|
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,38 @@ public:
|
|||||||
const SmallVectorImpl<ISD::OutputArg> &Outs,
|
const SmallVectorImpl<ISD::OutputArg> &Outs,
|
||||||
const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
|
const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
|
||||||
SelectionDAG &DAG) const override;
|
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;
|
||||||
|
|
||||||
|
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
|
} // namespace llvm
|
||||||
|
|||||||
@@ -22,6 +22,144 @@ using namespace llvm;
|
|||||||
TMS34010InstrInfo::TMS34010InstrInfo(const TMS34010Subtarget &STI)
|
TMS34010InstrInfo::TMS34010InstrInfo(const TMS34010Subtarget &STI)
|
||||||
: TMS34010GenInstrInfo(STI, RI), RI() {}
|
: TMS34010GenInstrInfo(STI, RI), RI() {}
|
||||||
|
|
||||||
|
/// True for every conditional absolute branch defined in the .td file.
|
||||||
|
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:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (I->getOpcode() == TMS34010::JUncond) {
|
||||||
|
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. The compare is fused into the conditional branch, so Cond just
|
||||||
|
// records its opcode.
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 &&
|
||||||
|
!isConditionalBranch(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cond[0] is the conditional branch opcode; the compare is fused into 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,
|
void TMS34010InstrInfo::copyPhysReg(MachineBasicBlock &MBB,
|
||||||
MachineBasicBlock::iterator I,
|
MachineBasicBlock::iterator I,
|
||||||
const DebugLoc &DL, Register DestReg,
|
const DebugLoc &DL, Register DestReg,
|
||||||
|
|||||||
@@ -45,6 +45,23 @@ public:
|
|||||||
const override;
|
const override;
|
||||||
|
|
||||||
bool expandPostRAPseudo(MachineInstr &MI) 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 {
|
||||||
|
return MI.getDesc().getSize();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace llvm
|
} // namespace llvm
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
// M1 describes encodings for future MC work, but only the assembly printer is
|
// M1 describes encodings for future MC work, but only the assembly printer is
|
||||||
// used. TMS34010 instructions are 1-3 16-bit words.
|
// used. TMS34010 instructions are 1-3 16-bit words.
|
||||||
//
|
//
|
||||||
|
// GSPA text is the M3 encoder: the assembly printer emits mnemonics and the
|
||||||
|
// dormant Inst bits below are not exercised by llc -filetype=asm. An M5 MC
|
||||||
|
// encoder must round-trip every instruction (llvm-mc/as -> dis34) and validate
|
||||||
|
// these bits before object output can rely on them.
|
||||||
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
def TMS34010retglue : SDNode<"TMS34010ISD::RET_GLUE", SDTNone,
|
def TMS34010retglue : SDNode<"TMS34010ISD::RET_GLUE", SDTNone,
|
||||||
@@ -30,10 +35,13 @@ def ADD : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
|
|||||||
"ADD $val, $dst",
|
"ADD $val, $dst",
|
||||||
[(set ARegs:$dst, (add ARegs:$src, ARegs:$val))]> {
|
[(set ARegs:$dst, (add ARegs:$src, ARegs:$val))]> {
|
||||||
let Constraints = "$src = $dst";
|
let Constraints = "$src = $dst";
|
||||||
bits<5> src;
|
let Defs = [ST];
|
||||||
|
// The second ALU operand is encoded in the source field; $src is only the
|
||||||
|
// tied two-address accumulator and must not be encoded.
|
||||||
|
bits<5> val;
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
let Inst{15-10} = 0b010000;
|
let Inst{15-10} = 0b010000;
|
||||||
let Inst{9-5} = src;
|
let Inst{9-5} = val;
|
||||||
let Inst{4-0} = dst;
|
let Inst{4-0} = dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +50,7 @@ def ADDI : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
|||||||
"ADDI $imm, $dst",
|
"ADDI $imm, $dst",
|
||||||
[(set ARegs:$dst, (add ARegs:$src, imm:$imm))]> {
|
[(set ARegs:$dst, (add ARegs:$src, imm:$imm))]> {
|
||||||
let Constraints = "$src = $dst";
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
let Size = 6;
|
let Size = 6;
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
bits<32> imm;
|
bits<32> imm;
|
||||||
@@ -51,10 +60,304 @@ def ADDI : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
|||||||
let Inst{47-32} = imm{31-16};
|
let Inst{47-32} = imm{31-16};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The register-register ALU group uses a 7-bit opcode in bits 15-9, the low
|
||||||
|
// four bits of the source in bits 8-5, the destination register-file bit in
|
||||||
|
// bit 4 and the low four bits of the destination in bits 3-0. Only A
|
||||||
|
// registers (file bit clear) are allocated, and for XOR and the shifts bit 9
|
||||||
|
// is already part of the opcode, so the source is encoded as its low four bits
|
||||||
|
// rather than as the full 5-bit register number used by ADD.
|
||||||
|
|
||||||
|
// SUB Rs, Rd: 0100 010S SSSR DDDD (0x4400).
|
||||||
|
def SUB : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
|
||||||
|
"SUB $val, $dst",
|
||||||
|
[(set ARegs:$dst, (sub ARegs:$src, ARegs:$val))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0100010;
|
||||||
|
let Inst{8-5} = val{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMP Rs, Rd: 0100 100S SSSR DDDD (0x4800). Sets the status register from
|
||||||
|
// Rd - Rs. This is emitted directly from LowerBR_CC/LowerBRCOND and by the
|
||||||
|
// SELECTCC expansion, so it has no pattern.
|
||||||
|
def CMP : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
||||||
|
"CMP $src, $dst", []> {
|
||||||
|
let isCompare = 1;
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> src;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0100100;
|
||||||
|
let Inst{8-5} = src{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMPI imm32, Rd: 0000 1011 011R DDDD followed by the 32-bit immediate
|
||||||
|
// (0x0B60). The status register is set from Rd - imm. Like ANDI, the
|
||||||
|
// immediate is stored one's-complemented.
|
||||||
|
def CMPI : TMS34010Inst<(outs), (ins i32imm:$imm, ARegs:$dst),
|
||||||
|
"CMPI $imm, $dst", []> {
|
||||||
|
let isCompare = 1;
|
||||||
|
let Defs = [ST];
|
||||||
|
let Size = 6;
|
||||||
|
bits<5> dst;
|
||||||
|
bits<32> imm;
|
||||||
|
let Inst{15-5} = 0b00001011011;
|
||||||
|
let Inst{4-0} = dst;
|
||||||
|
let Inst{31-16} = !xor(imm{15-0}, 0xFFFF);
|
||||||
|
let Inst{47-32} = !xor(imm{31-16}, 0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AND Rs, Rd: 0101 000S SSSR DDDD (0x5000).
|
||||||
|
def AND : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
|
||||||
|
"AND $val, $dst",
|
||||||
|
[(set ARegs:$dst, (and ARegs:$src, ARegs:$val))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0101000;
|
||||||
|
let Inst{8-5} = val{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// OR Rs, Rd: 0101 010S SSSR DDDD (0x5400).
|
||||||
|
def OR : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
|
||||||
|
"OR $val, $dst",
|
||||||
|
[(set ARegs:$dst, (or ARegs:$src, ARegs:$val))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0101010;
|
||||||
|
let Inst{8-5} = val{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// XOR Rs, Rd: 0101 011S SSSR DDDD (0x5600). XOR Rd, Rd is CLR.
|
||||||
|
def XOR : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
|
||||||
|
"XOR $val, $dst",
|
||||||
|
[(set ARegs:$dst, (xor ARegs:$src, ARegs:$val))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0101011;
|
||||||
|
let Inst{8-5} = val{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// MPYS Rs, Rd: 0101 110S SSSR DDDD (0x5C00). Signed multiply: Rd is the
|
||||||
|
// 32-bit multiplicand and the low 32 bits of the product are stored back in
|
||||||
|
// Rd when Rd is odd. The ABI keeps FS1 = 32, so Rs is a full 32-bit multiplier
|
||||||
|
// and the low half of the signed and unsigned products is identical. An even
|
||||||
|
// Rd would write the high half to Rd and the low half to Rd+1, clobbering the
|
||||||
|
// neighbouring register, so the tied destination is ARegsOdd.
|
||||||
|
def MPYS : TMS34010Inst<(outs ARegsOdd:$dst), (ins ARegs:$src, ARegsOdd:$val),
|
||||||
|
"MPYS $src, $dst",
|
||||||
|
[(set ARegsOdd:$dst, (mul ARegs:$src,
|
||||||
|
ARegsOdd:$val))]> {
|
||||||
|
let Constraints = "$val = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> src;
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0101110;
|
||||||
|
let Inst{8-5} = src{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// MPYU Rs, Rd: 0101 111S SSSR DDDD (0x5E00). Unsigned multiply. A 16x16
|
||||||
|
// unsigned product reaches the DAG as both operands masked to 16 bits, which
|
||||||
|
// is the natural place to select MPYU; the low 32 bits are identical to a
|
||||||
|
// signed multiply, so this is an encoding choice, not a different result.
|
||||||
|
def MPYU : TMS34010Inst<(outs ARegsOdd:$dst), (ins ARegs:$src, ARegsOdd:$val),
|
||||||
|
"MPYU $src, $dst",
|
||||||
|
[(set ARegsOdd:$dst,
|
||||||
|
(mul (and i32:$src, 65535),
|
||||||
|
(and ARegsOdd:$val, 65535)))]> {
|
||||||
|
let Constraints = "$val = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> src;
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0101111;
|
||||||
|
let Inst{8-5} = src{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// DIVS Rs, Rd: 0101 100S SSSR DDDD (0x5800). Signed divide of the 32-bit
|
||||||
|
// dividend in Rd by Rs. With an odd Rd the quotient replaces Rd and no
|
||||||
|
// remainder is returned; the remainder is synthesised as dividend -
|
||||||
|
// quotient * divisor (the hardware only returns it with an even Rd pair).
|
||||||
|
def DIVS : TMS34010Inst<(outs ARegsOdd:$dst), (ins ARegs:$src, ARegsOdd:$val),
|
||||||
|
"DIVS $src, $dst",
|
||||||
|
[(set ARegsOdd:$dst, (sdiv ARegsOdd:$val,
|
||||||
|
ARegs:$src))]> {
|
||||||
|
let Constraints = "$val = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> src;
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0101100;
|
||||||
|
let Inst{8-5} = src{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// DIVU Rs, Rd: 0101 101S SSSR DDDD (0x5A00). Unsigned divide, same form.
|
||||||
|
def DIVU : TMS34010Inst<(outs ARegsOdd:$dst), (ins ARegs:$src, ARegsOdd:$val),
|
||||||
|
"DIVU $src, $dst",
|
||||||
|
[(set ARegsOdd:$dst, (udiv ARegsOdd:$val,
|
||||||
|
ARegs:$src))]> {
|
||||||
|
let Constraints = "$val = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> src;
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0101101;
|
||||||
|
let Inst{8-5} = src{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEG Rd: 0000 0011 101R DDDD (0x03A0).
|
||||||
|
def NEG : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src), "NEG $dst",
|
||||||
|
[(set ARegs:$dst, (ineg ARegs:$src))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-5} = 0b00000011101;
|
||||||
|
let Inst{4-0} = dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT Rd: 0000 0011 111R DDDD (0x03E0).
|
||||||
|
def NOT : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src), "NOT $dst",
|
||||||
|
[(set ARegs:$dst, (not ARegs:$src))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-5} = 0b00000011111;
|
||||||
|
let Inst{4-0} = dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register-form shifts: the destination is the shifted value and the source is
|
||||||
|
// the shift count.
|
||||||
|
// SLL Rs, Rd: 0110 001S SSSR DDDD (0x6200).
|
||||||
|
def SLL : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
|
||||||
|
"SLL $val, $dst",
|
||||||
|
[(set ARegs:$dst, (shl ARegs:$src, ARegs:$val))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0110001;
|
||||||
|
let Inst{8-5} = val{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// SRA Rs, Rd: 0110 010S SSSR DDDD (0x6400).
|
||||||
|
def SRA : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
|
||||||
|
"SRA $val, $dst",
|
||||||
|
[(set ARegs:$dst, (sra ARegs:$src, ARegs:$val))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0110010;
|
||||||
|
let Inst{8-5} = val{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// SRL Rs, Rd: 0110 011S SSSR DDDD (0x6600).
|
||||||
|
def SRL : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
|
||||||
|
"SRL $val, $dst",
|
||||||
|
[(set ARegs:$dst, (srl ARegs:$src, ARegs:$val))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> val;
|
||||||
|
bits<5> dst;
|
||||||
|
let Inst{15-9} = 0b0110011;
|
||||||
|
let Inst{8-5} = val{3-0};
|
||||||
|
let Inst{4} = dst{4};
|
||||||
|
let Inst{3-0} = dst{3-0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediate-form shifts. The assembler encodes SRA/SRL with the complement of
|
||||||
|
// the count in the instruction word; only the assembly text matters here.
|
||||||
|
// SLL K, Rd: 0010 01KK KKKR DDDD (0x2400).
|
||||||
|
def SLLK : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
||||||
|
"SLL $imm, $dst",
|
||||||
|
[(set ARegs:$dst, (shl ARegs:$src, imm:$imm))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> dst;
|
||||||
|
bits<5> imm;
|
||||||
|
let Inst{15-10} = 0b001001;
|
||||||
|
let Inst{9-5} = imm;
|
||||||
|
let Inst{4-0} = dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SRA K, Rd: 0010 10KK KKKR DDDD (0x2800). The K field stores the two's
|
||||||
|
// complement of the logical count, (-K) & 31, so K=3 is stored as 29.
|
||||||
|
def SRAK : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
||||||
|
"SRA $imm, $dst",
|
||||||
|
[(set ARegs:$dst, (sra ARegs:$src, imm:$imm))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> dst;
|
||||||
|
bits<32> imm;
|
||||||
|
let Inst{15-10} = 0b001010;
|
||||||
|
let Inst{9-5} = !and(!sub(0, imm), 31);
|
||||||
|
let Inst{4-0} = dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SRL K, Rd: 0010 11KK KKKR DDDD (0x2C00). Like SRAK, K is (-K) & 31.
|
||||||
|
def SRLK : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
||||||
|
"SRL $imm, $dst",
|
||||||
|
[(set ARegs:$dst, (srl ARegs:$src, imm:$imm))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
bits<5> dst;
|
||||||
|
bits<32> imm;
|
||||||
|
let Inst{15-10} = 0b001011;
|
||||||
|
let Inst{9-5} = !and(!sub(0, imm), 31);
|
||||||
|
let Inst{4-0} = dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANDI imm32, Rd: 0000 1011 100R DDDD followed by the immediate (0x0B80). The
|
||||||
|
// machine stores the one's complement of the operand and the CPU ANDs with it;
|
||||||
|
// the assembler hides that from the GSPA text.
|
||||||
|
def ANDI : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
||||||
|
"ANDI $imm, $dst",
|
||||||
|
[(set ARegs:$dst, (and ARegs:$src, imm:$imm))]> {
|
||||||
|
let Constraints = "$src = $dst";
|
||||||
|
let Defs = [ST];
|
||||||
|
let Size = 6;
|
||||||
|
bits<5> dst;
|
||||||
|
bits<32> imm;
|
||||||
|
let Inst{15-5} = 0b00001011100;
|
||||||
|
let Inst{4-0} = dst;
|
||||||
|
let Inst{31-16} = !xor(imm{15-0}, 0xFFFF);
|
||||||
|
let Inst{47-32} = !xor(imm{31-16}, 0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
// MOVI imm32, Rd: 0000 1001 111R DDDD followed by the 32-bit immediate.
|
// MOVI imm32, Rd: 0000 1001 111R DDDD followed by the 32-bit immediate.
|
||||||
def MOVI : TMS34010Inst<(outs ARegs:$dst), (ins i32imm:$imm),
|
def MOVI : TMS34010Inst<(outs ARegs:$dst), (ins i32imm:$imm),
|
||||||
"MOVI $imm, $dst",
|
"MOVI $imm, $dst",
|
||||||
[(set ARegs:$dst, imm:$imm)]> {
|
[(set ARegs:$dst, imm:$imm)]> {
|
||||||
|
let Defs = [ST];
|
||||||
let Size = 6;
|
let Size = 6;
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
bits<32> imm;
|
bits<32> imm;
|
||||||
@@ -69,6 +372,7 @@ def MOVI : TMS34010Inst<(outs ARegs:$dst), (ins i32imm:$imm),
|
|||||||
// pattern.
|
// pattern.
|
||||||
def MOVE : TMS34010Inst<(outs ARegsAll:$dst), (ins ARegsAll:$src),
|
def MOVE : TMS34010Inst<(outs ARegsAll:$dst), (ins ARegsAll:$src),
|
||||||
"MOVE $src, $dst", []> {
|
"MOVE $src, $dst", []> {
|
||||||
|
let Defs = [ST];
|
||||||
bits<5> src;
|
bits<5> src;
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
let Inst{15-10} = 0b010011;
|
let Inst{15-10} = 0b010011;
|
||||||
@@ -88,6 +392,7 @@ def MOVE32Load : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src),
|
|||||||
"MOVE *$src, $dst, 1",
|
"MOVE *$src, $dst, 1",
|
||||||
[(set ARegs:$dst, (load ARegs:$src))]> {
|
[(set ARegs:$dst, (load ARegs:$src))]> {
|
||||||
let mayLoad = 1;
|
let mayLoad = 1;
|
||||||
|
let Defs = [ST];
|
||||||
bits<5> src;
|
bits<5> src;
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
let Inst{15-9} = 0b1000011;
|
let Inst{15-9} = 0b1000011;
|
||||||
@@ -101,6 +406,7 @@ def MOVE32Store : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
|||||||
"MOVE $src, *$dst, 1",
|
"MOVE $src, *$dst, 1",
|
||||||
[(store ARegs:$src, ARegs:$dst)]> {
|
[(store ARegs:$src, ARegs:$dst)]> {
|
||||||
let mayStore = 1;
|
let mayStore = 1;
|
||||||
|
let Defs = [ST];
|
||||||
bits<5> src;
|
bits<5> src;
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
let Inst{15-9} = 0b1000001;
|
let Inst{15-9} = 0b1000001;
|
||||||
@@ -114,6 +420,7 @@ def MOVE16Load : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src),
|
|||||||
"MOVE *$src, $dst, 0",
|
"MOVE *$src, $dst, 0",
|
||||||
[(set ARegs:$dst, (sextloadi16 ARegs:$src))]> {
|
[(set ARegs:$dst, (sextloadi16 ARegs:$src))]> {
|
||||||
let mayLoad = 1;
|
let mayLoad = 1;
|
||||||
|
let Defs = [ST];
|
||||||
bits<5> src;
|
bits<5> src;
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
let Inst{15-9} = 0b1000010;
|
let Inst{15-9} = 0b1000010;
|
||||||
@@ -127,6 +434,7 @@ def MOVE16Store : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
|||||||
"MOVE $src, *$dst, 0",
|
"MOVE $src, *$dst, 0",
|
||||||
[(truncstorei16 ARegs:$src, ARegs:$dst)]> {
|
[(truncstorei16 ARegs:$src, ARegs:$dst)]> {
|
||||||
let mayStore = 1;
|
let mayStore = 1;
|
||||||
|
let Defs = [ST];
|
||||||
bits<5> src;
|
bits<5> src;
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
let Inst{15-9} = 0b1000000;
|
let Inst{15-9} = 0b1000000;
|
||||||
@@ -140,6 +448,7 @@ def MOVBLoad : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src),
|
|||||||
"MOVB *$src, $dst",
|
"MOVB *$src, $dst",
|
||||||
[(set ARegs:$dst, (sextloadi8 ARegs:$src))]> {
|
[(set ARegs:$dst, (sextloadi8 ARegs:$src))]> {
|
||||||
let mayLoad = 1;
|
let mayLoad = 1;
|
||||||
|
let Defs = [ST];
|
||||||
bits<5> src;
|
bits<5> src;
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
let Inst{15-9} = 0b1000111;
|
let Inst{15-9} = 0b1000111;
|
||||||
@@ -153,6 +462,7 @@ def MOVBStore : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
|||||||
"MOVB $src, *$dst",
|
"MOVB $src, *$dst",
|
||||||
[(truncstorei8 ARegs:$src, ARegs:$dst)]> {
|
[(truncstorei8 ARegs:$src, ARegs:$dst)]> {
|
||||||
let mayStore = 1;
|
let mayStore = 1;
|
||||||
|
let Defs = [ST];
|
||||||
bits<5> src;
|
bits<5> src;
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
let Inst{15-9} = 0b1000110;
|
let Inst{15-9} = 0b1000110;
|
||||||
@@ -163,6 +473,7 @@ def MOVBStore : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
|||||||
|
|
||||||
// CLR Rd: XOR self, base 0x5600 (0101 011S SSSR DDDD with S == D).
|
// CLR Rd: XOR self, base 0x5600 (0101 011S SSSR DDDD with S == D).
|
||||||
def CLR : TMS34010Inst<(outs ARegs:$dst), (ins), "CLR $dst", []> {
|
def CLR : TMS34010Inst<(outs ARegs:$dst), (ins), "CLR $dst", []> {
|
||||||
|
let Defs = [ST];
|
||||||
bits<5> dst;
|
bits<5> dst;
|
||||||
let Inst{15-9} = 0b0101011;
|
let Inst{15-9} = 0b0101011;
|
||||||
let Inst{8-5} = dst{3-0};
|
let Inst{8-5} = dst{3-0};
|
||||||
@@ -192,14 +503,14 @@ def PUSH32 : TMS34010Inst<(outs), (ins ARegsAll:$src),
|
|||||||
"MOVE $src, -*SP, 1", []> {
|
"MOVE $src, -*SP, 1", []> {
|
||||||
let mayStore = 1;
|
let mayStore = 1;
|
||||||
let Uses = [A15];
|
let Uses = [A15];
|
||||||
let Defs = [A15];
|
let Defs = [A15, ST];
|
||||||
}
|
}
|
||||||
|
|
||||||
def POP32 : TMS34010Inst<(outs ARegsAll:$dst), (ins),
|
def POP32 : TMS34010Inst<(outs ARegsAll:$dst), (ins),
|
||||||
"MOVE *SP+, $dst, 1", []> {
|
"MOVE *SP+, $dst, 1", []> {
|
||||||
let mayLoad = 1;
|
let mayLoad = 1;
|
||||||
let Uses = [A15];
|
let Uses = [A15];
|
||||||
let Defs = [A15];
|
let Defs = [A15, ST];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push one 32-bit argument on the program stack (A14/STK).
|
// Push one 32-bit argument on the program stack (A14/STK).
|
||||||
@@ -207,32 +518,35 @@ def PUSHARG : TMS34010Inst<(outs), (ins ARegs:$src),
|
|||||||
"MOVE $src, *A14+, 1", []> {
|
"MOVE $src, *A14+, 1", []> {
|
||||||
let mayStore = 1;
|
let mayStore = 1;
|
||||||
let Uses = [A14];
|
let Uses = [A14];
|
||||||
let Defs = [A14];
|
let Defs = [A14, ST];
|
||||||
}
|
}
|
||||||
|
|
||||||
// FP = STK, and the program-stack frame adjustment (negative to deallocate).
|
// FP = STK, and the program-stack frame adjustment (negative to deallocate).
|
||||||
def SETFP : TMS34010Inst<(outs), (ins), "MOVE A14, A13", []> {
|
def SETFP : TMS34010Inst<(outs), (ins), "MOVE A14, A13", []> {
|
||||||
let Uses = [A14];
|
let Uses = [A14];
|
||||||
let Defs = [A13];
|
let Defs = [A13, ST];
|
||||||
}
|
}
|
||||||
|
|
||||||
def ADDSTK : TMS34010Inst<(outs), (ins i32imm:$imm), "ADDI $imm, A14", []> {
|
def ADDSTK : TMS34010Inst<(outs), (ins i32imm:$imm), "ADDI $imm, A14", []> {
|
||||||
let Uses = [A14];
|
let Uses = [A14];
|
||||||
let Defs = [A14];
|
let Defs = [A14, ST];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Materialise a frame address; the base is the reserved FP, so this is not
|
// Materialise a frame address; the base is the reserved FP, so this is not
|
||||||
// the allocatable-register MOVE pattern.
|
// the allocatable-register MOVE pattern.
|
||||||
def MOVEFP : TMS34010Inst<(outs ARegs:$dst), (ins), "MOVE A13, $dst", []> {
|
def MOVEFP : TMS34010Inst<(outs ARegs:$dst), (ins), "MOVE A13, $dst", []> {
|
||||||
let Uses = [A13];
|
let Uses = [A13];
|
||||||
|
let Defs = [ST];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the caller's saved old STK from the system stack into A14.
|
// Read the caller's saved old STK from the system stack into A14. The
|
||||||
|
// displacement form is two 16-bit words.
|
||||||
def LOADSTK : TMS34010Inst<(outs), (ins i32imm:$off),
|
def LOADSTK : TMS34010Inst<(outs), (ins i32imm:$off),
|
||||||
"MOVE *SP($off), A14, 1", []> {
|
"MOVE *SP($off), A14, 1", []> {
|
||||||
let mayLoad = 1;
|
let mayLoad = 1;
|
||||||
|
let Size = 4;
|
||||||
let Uses = [A15];
|
let Uses = [A15];
|
||||||
let Defs = [A14];
|
let Defs = [A14, ST];
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ABI's field-size pair. FS0 must be 16 for `short` accesses and FS1
|
// The ABI's field-size pair. FS0 must be 16 for `short` accesses and FS1
|
||||||
@@ -246,13 +560,100 @@ def SETF32 : TMS34010Inst<(outs), (ins), "SETF 32, 0, 1", []> {
|
|||||||
let hasSideEffects = 1;
|
let hasSideEffects = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct ROM/local call; the target is a symbol or bit address.
|
// Direct ROM/local call; the target is a symbol or bit address. CALLA is the
|
||||||
|
// opcode word plus a 32-bit absolute target, three words.
|
||||||
def CALLA : TMS34010Inst<(outs), (ins i32imm:$target), "CALLA $target", []> {
|
def CALLA : TMS34010Inst<(outs), (ins i32imm:$target), "CALLA $target", []> {
|
||||||
let isCall = 1;
|
let isCall = 1;
|
||||||
|
let Size = 6;
|
||||||
// A14 is read to find the arguments and is left pointing at the caller's
|
// A14 is read to find the arguments and is left pointing at the caller's
|
||||||
// saved STK by the callee's epilogue, so the call both uses and defines it.
|
// saved STK by the callee's epilogue, so the call both uses and defines it.
|
||||||
let Uses = [A14];
|
let Uses = [A14];
|
||||||
let Defs = [A8, A14];
|
let Defs = [A8, A14, ST];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indirect call through a register: CALL Rs is 0000 1001 001R DDDD (0x0920),
|
||||||
|
// one word, with the 32-bit bit address in an A register. GSPA 2.00 accepts
|
||||||
|
// the spelling "CALL A0" (it rejects the documented "CALL $A0" form), and
|
||||||
|
// dis34 prints the same instruction as "call aN". The target is an ARegs
|
||||||
|
// operand, never A13/A14/A15, so the argument pushes (which only advance A14)
|
||||||
|
// cannot clobber it. The rest of the sequence is ABI-identical to CALLA.
|
||||||
|
def CALL : TMS34010Inst<(outs), (ins ARegs:$target), "CALL $target", []> {
|
||||||
|
let isCall = 1;
|
||||||
|
let Size = 2;
|
||||||
|
let Uses = [A14];
|
||||||
|
let Defs = [A8, A14, ST];
|
||||||
|
bits<5> target;
|
||||||
|
let Inst{15-5} = 0b00001001001;
|
||||||
|
let Inst{4-0} = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Branch targets are basic-block symbols; MCInstLower turns the block operand
|
||||||
|
// into an MC expression that the InstPrinter prints by name.
|
||||||
|
def brtarget : Operand<OtherVT>;
|
||||||
|
|
||||||
|
// JAUC target: unconditional absolute jump. GSPA 2.00 rejects a bare `ja`,
|
||||||
|
// so the three-word absolute form is the M3 branch; the 32-bit target follows
|
||||||
|
// the opcode word. The branch is an absolute jump, not a fallthrough, hence
|
||||||
|
// both Terminator and Barrier.
|
||||||
|
def JUncond : TMS34010Inst<(outs), (ins brtarget:$target), "JAUC $target",
|
||||||
|
[(br bb:$target)]> {
|
||||||
|
let isBranch = 1;
|
||||||
|
let isTerminator = 1;
|
||||||
|
let isBarrier = 1;
|
||||||
|
let Size = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conditional absolute branches. The JAcc opcode is 1100 cccc 1000 0000
|
||||||
|
// followed by the 32-bit target; the four-bit condition code from dis34 is
|
||||||
|
// uc/p/ls/hi/lt/ge/le/gt/c/nc/eq/ne/v/nv/n/nn = 0..15. A conditional branch
|
||||||
|
// falls through when the condition is false, so it is a terminator and a
|
||||||
|
// branch but not a barrier. The condition flags live in ST.
|
||||||
|
// The lowering glues the compare to the branch: the DAG branch node carries a
|
||||||
|
// trailing glue operand, which forces the CMP/CMPI to be emitted immediately
|
||||||
|
// before the branch. The variadic slot holds that glue; it is not printed.
|
||||||
|
class TMS34010Jcc<bits<4> cc, string asmstr>
|
||||||
|
: TMS34010Inst<(outs), (ins brtarget:$target, variable_ops), asmstr, []> {
|
||||||
|
let isBranch = 1;
|
||||||
|
let isTerminator = 1;
|
||||||
|
let Uses = [ST];
|
||||||
|
let Size = 6;
|
||||||
|
let Inst{15-12} = 0b1100;
|
||||||
|
let Inst{11-8} = cc;
|
||||||
|
let Inst{7-0} = 0b10000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
def JAEQ : TMS34010Jcc<0xA, "JAEQ $target">; // equal (Z)
|
||||||
|
def JANE : TMS34010Jcc<0xB, "JANE $target">; // not equal (~Z)
|
||||||
|
def JALT : TMS34010Jcc<0x4, "JALT $target">; // signed less than
|
||||||
|
def JALE : TMS34010Jcc<0x6, "JALE $target">; // signed less or equal
|
||||||
|
def JAGT : TMS34010Jcc<0x7, "JAGT $target">; // signed greater than
|
||||||
|
def JAGE : TMS34010Jcc<0x5, "JAGE $target">; // signed greater or equal
|
||||||
|
def JALO : TMS34010Jcc<0x8, "JALO $target">; // unsigned lower (JAC, carry)
|
||||||
|
def JALS : TMS34010Jcc<0x2, "JALS $target">; // unsigned lower or same
|
||||||
|
def JAHI : TMS34010Jcc<0x3, "JAHI $target">; // unsigned higher
|
||||||
|
def JAHS : TMS34010Jcc<0x9, "JAHS $target">; // unsigned higher or same (JANC)
|
||||||
|
def JAP : TMS34010Jcc<0x1, "JAP $target">; // positive
|
||||||
|
def JAN : TMS34010Jcc<0xE, "JAN $target">; // negative
|
||||||
|
def JAV : TMS34010Jcc<0xC, "JAV $target">; // overflow
|
||||||
|
def JANV : TMS34010Jcc<0xD, "JANV $target">; // no overflow
|
||||||
|
def JANN : TMS34010Jcc<0xF, "JANN $target">; // nonnegative
|
||||||
|
|
||||||
|
// SELECTCC is expanded after instruction selection into a compare plus a
|
||||||
|
// two-way diamond; it never reaches the assembly printer. The true/false
|
||||||
|
// operands may be registers or immediates, so they use an unconstrained
|
||||||
|
// operand type.
|
||||||
|
def TMS34010SelectOperand : Operand<i32>;
|
||||||
|
|
||||||
|
def SELECTCC : TMS34010Inst<(outs ARegs:$dst),
|
||||||
|
(ins TMS34010SelectOperand:$true,
|
||||||
|
TMS34010SelectOperand:$false,
|
||||||
|
TMS34010SelectOperand:$lhs,
|
||||||
|
TMS34010SelectOperand:$rhs,
|
||||||
|
i32imm:$branchop),
|
||||||
|
"# SELECTCC", []> {
|
||||||
|
let isPseudo = 1;
|
||||||
|
let usesCustomInserter = 1;
|
||||||
|
let Size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frame-index materialisation: the base is implicitly A13; expanded after
|
// Frame-index materialisation: the base is implicitly A13; expanded after
|
||||||
@@ -261,6 +662,23 @@ def GETFI : TMS34010Inst<(outs ARegs:$dst), (ins i32imm:$off), "# GETFI", []> {
|
|||||||
let isPseudo = 1;
|
let isPseudo = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sign extension in register. The ISA's SEXT reads the current field size, and
|
||||||
|
// the ABI pins FS0 at 16 for `short`, so a width-specific field extension is
|
||||||
|
// not available for every width. Expand to the verified shift pair
|
||||||
|
// (SLL K ; SRA K) instead. DAGCombine forms these nodes from shift pairs in
|
||||||
|
// ordinary `short` arithmetic, so they must select.
|
||||||
|
def SEXT8 : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src), "# SEXT8",
|
||||||
|
[(set ARegs:$dst, (sext_inreg ARegs:$src, i8))]> {
|
||||||
|
let isPseudo = 1;
|
||||||
|
let usesCustomInserter = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
def SEXT16 : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src), "# SEXT16",
|
||||||
|
[(set ARegs:$dst, (sext_inreg ARegs:$src, i16))]> {
|
||||||
|
let isPseudo = 1;
|
||||||
|
let usesCustomInserter = 1;
|
||||||
|
}
|
||||||
|
|
||||||
def NOP : TMS34010Inst<(outs), (ins), "NOP", []> {
|
def NOP : TMS34010Inst<(outs), (ins), "NOP", []> {
|
||||||
let Inst{15-0} = 0x0300;
|
let Inst{15-0} = 0x0300;
|
||||||
}
|
}
|
||||||
@@ -48,6 +48,10 @@ TMS34010RegisterInfo::getReservedRegs(const MachineFunction &MF) const {
|
|||||||
Reserved.set(TMS34010::A14);
|
Reserved.set(TMS34010::A14);
|
||||||
Reserved.set(TMS34010::A15);
|
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.
|
// The B file is reserved for a later task.
|
||||||
for (unsigned Reg = TMS34010::B0; Reg <= TMS34010::B15; ++Reg)
|
for (unsigned Reg = TMS34010::B0; Reg <= TMS34010::B15; ++Reg)
|
||||||
Reserved.set(Reg);
|
Reserved.set(Reg);
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ def A13 : TMS34010Reg<13, "A13">;
|
|||||||
def A14 : TMS34010Reg<14, "A14">;
|
def A14 : TMS34010Reg<14, "A14">;
|
||||||
def A15 : TMS34010Reg<15, "A15">;
|
def A15 : TMS34010Reg<15, "A15">;
|
||||||
|
|
||||||
|
// 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.
|
// The B file is reserved for a later task.
|
||||||
def B0 : TMS34010Reg<16, "B0">;
|
def B0 : TMS34010Reg<16, "B0">;
|
||||||
def B1 : TMS34010Reg<17, "B1">;
|
def B1 : TMS34010Reg<17, "B1">;
|
||||||
@@ -56,6 +63,14 @@ def ARegs : RegisterClass<"TMS34010", [i32], 32,
|
|||||||
(add A0, A1, A2, A3, A4, A5, A6, A7,
|
(add A0, A1, A2, A3, A4, A5, A6, A7,
|
||||||
A9, A10, A11, A12)>;
|
A9, A10, A11, A12)>;
|
||||||
|
|
||||||
|
// MPYS/MPYU/DIVS/DIVU treat an even Rd as the first of a register pair: an
|
||||||
|
// even multiply writes Rd:Rd+1, and an even divide reads a 64-bit Rd:Rd+1
|
||||||
|
// dividend and writes the quotient to Rd and the remainder to Rd+1. The
|
||||||
|
// 32-bit forms here always want the odd (single-register) encoding, so the
|
||||||
|
// tied destination operand is restricted to the odd allocatable A registers.
|
||||||
|
def ARegsOdd : RegisterClass<"TMS34010", [i32], 32,
|
||||||
|
(add A1, A3, A5, A7, A9, A11)>;
|
||||||
|
|
||||||
// ARegsAll names every A register. It is used only as the operand class of
|
// 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
|
// the frame and system-stack instructions, which need to name the reserved
|
||||||
// FP/STK/SP registers explicitly; it is never allocated.
|
// FP/STK/SP registers explicitly; it is never allocated.
|
||||||
|
|||||||
@@ -9,17 +9,24 @@
|
|||||||
#include "TMS34010TargetMachine.h"
|
#include "TMS34010TargetMachine.h"
|
||||||
#include "TMS34010.h"
|
#include "TMS34010.h"
|
||||||
#include "TargetInfo/TMS34010TargetInfo.h"
|
#include "TargetInfo/TMS34010TargetInfo.h"
|
||||||
|
#include "llvm/ADT/SmallPtrSet.h"
|
||||||
#include "llvm/ADT/SmallString.h"
|
#include "llvm/ADT/SmallString.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "llvm/ADT/StringExtras.h"
|
#include "llvm/ADT/StringExtras.h"
|
||||||
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
|
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
|
||||||
#include "llvm/CodeGen/TargetPassConfig.h"
|
#include "llvm/CodeGen/TargetPassConfig.h"
|
||||||
|
#include "llvm/IR/CFG.h"
|
||||||
|
#include "llvm/IR/Function.h"
|
||||||
#include "llvm/IR/GlobalValue.h"
|
#include "llvm/IR/GlobalValue.h"
|
||||||
#include "llvm/IR/Mangler.h"
|
#include "llvm/IR/Mangler.h"
|
||||||
|
#include "llvm/IR/Module.h"
|
||||||
#include "llvm/MC/MCContext.h"
|
#include "llvm/MC/MCContext.h"
|
||||||
#include "llvm/MC/MCSymbol.h"
|
#include "llvm/MC/MCSymbol.h"
|
||||||
#include "llvm/MC/TargetRegistry.h"
|
#include "llvm/MC/TargetRegistry.h"
|
||||||
|
#include "llvm/Pass.h"
|
||||||
#include "llvm/Support/Compiler.h"
|
#include "llvm/Support/Compiler.h"
|
||||||
#include "llvm/Target/TargetOptions.h"
|
#include "llvm/Target/TargetOptions.h"
|
||||||
|
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
@@ -61,6 +68,34 @@ static void sanitizeGSPASymbolName(SmallVectorImpl<char> &Name) {
|
|||||||
Name.assign(Sanitized.begin(), Sanitized.end());
|
Name.assign(Sanitized.begin(), Sanitized.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Private/local IR symbols are module-scoped, but GSPA links every translation
|
||||||
|
// unit into one flat symbol table. Two modules that both contain `.str` would
|
||||||
|
// otherwise emit the same ``__.str`` label and GSPA reports a duplicate
|
||||||
|
// definition. Append a deterministic token derived from the module identifier
|
||||||
|
// to every local symbol; public names are left exactly as the IR spells them.
|
||||||
|
//
|
||||||
|
// llc gives a file input the file name as the module identifier (or the IR's
|
||||||
|
// own source_filename) and "<stdin>" for standard input, so the token is
|
||||||
|
// deterministic for a given input and stable for lit's stdin RUN lines.
|
||||||
|
static void appendModuleToken(SmallVectorImpl<char> &Name,
|
||||||
|
const GlobalValue *GV) {
|
||||||
|
const Module *M = GV->getParent();
|
||||||
|
StringRef ID = M ? M->getModuleIdentifier() : StringRef();
|
||||||
|
|
||||||
|
// Custom 64-bit string hash (FNV-style, not canonical FNV-1a), folded to
|
||||||
|
// 32 bits. Eight uppercase hex digits stay GSPA-safe and
|
||||||
|
// are short enough not to stress the 1987 assembler's identifier limits.
|
||||||
|
uint64_t Hash = 1469598103934665603ULL;
|
||||||
|
for (unsigned char C : ID) {
|
||||||
|
Hash ^= C;
|
||||||
|
Hash *= 1099511628211ULL;
|
||||||
|
}
|
||||||
|
unsigned Token = static_cast<unsigned>(Hash ^ (Hash >> 32));
|
||||||
|
Name.push_back('_');
|
||||||
|
for (int Shift = 28; Shift >= 0; Shift -= 4)
|
||||||
|
Name.push_back(hexdigit((Token >> Shift) & 0xF));
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class TMS34010ObjectFile : public TargetLoweringObjectFileELF {
|
class TMS34010ObjectFile : public TargetLoweringObjectFileELF {
|
||||||
@@ -70,6 +105,8 @@ public:
|
|||||||
SmallString<128> Name("_");
|
SmallString<128> Name("_");
|
||||||
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
|
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
|
||||||
sanitizeGSPASymbolName(Name);
|
sanitizeGSPASymbolName(Name);
|
||||||
|
if (GV->hasLocalLinkage())
|
||||||
|
appendModuleToken(Name, GV);
|
||||||
return getContext().getOrCreateSymbol(Name);
|
return getContext().getOrCreateSymbol(Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +117,8 @@ public:
|
|||||||
OutName.push_back('_');
|
OutName.push_back('_');
|
||||||
OutName.append(Name.begin(), Name.end());
|
OutName.append(Name.begin(), Name.end());
|
||||||
sanitizeGSPASymbolName(OutName);
|
sanitizeGSPASymbolName(OutName);
|
||||||
|
if (GV->hasLocalLinkage())
|
||||||
|
appendModuleToken(OutName, GV);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,6 +130,39 @@ static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
|
|||||||
|
|
||||||
namespace {
|
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;
|
||||||
|
|
||||||
class TMS34010PassConfig : public TargetPassConfig {
|
class TMS34010PassConfig : public TargetPassConfig {
|
||||||
public:
|
public:
|
||||||
TMS34010PassConfig(TMS34010TargetMachine &TM, PassManagerBase &PM)
|
TMS34010PassConfig(TMS34010TargetMachine &TM, PassManagerBase &PM)
|
||||||
@@ -102,6 +174,11 @@ public:
|
|||||||
|
|
||||||
bool addInstSelector() override;
|
bool addInstSelector() override;
|
||||||
|
|
||||||
|
void addIRPasses() override {
|
||||||
|
TargetPassConfig::addIRPasses();
|
||||||
|
addPass(new TMS34010SplitCriticalEdges());
|
||||||
|
}
|
||||||
|
|
||||||
// M1 has no stack. The fast register allocator relies on being able to
|
// 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
|
// 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
|
// return register, a `-O0` leaf function can hit its spill path even though
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ entry:
|
|||||||
; CHECK: .long 11
|
; CHECK: .long 11
|
||||||
; CHECK: .long 22
|
; CHECK: .long 22
|
||||||
; CHECK: .byte 030h, 03Ah, 03Ah, 030h, 000h
|
; 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 041h, 042h, 043h, 044h, 045h, 046h, 047h, 048h, 049h, 04Ah, 04Bh, 04Ch, 04Dh, 04Eh, 04Fh, 050h
|
||||||
; CHECK: .byte 051h, 052h, 053h, 054h, 000h
|
; CHECK: .byte 051h, 052h, 053h, 054h, 000h
|
||||||
; CHECK-LABEL: {{.*}}zeros16:
|
; CHECK-LABEL: {{.*}}zeros16{{[A-Za-z0-9_]*}}:
|
||||||
; CHECK: .word 0
|
; CHECK: .word 0
|
||||||
; CHECK: .word 0
|
; CHECK: .word 0
|
||||||
; CHECK-LABEL: {{.*}}zeros8:
|
; CHECK-LABEL: {{.*}}zeros8{{[A-Za-z0-9_]*}}:
|
||||||
; CHECK: .byte 0, 0, 0
|
; CHECK: .byte 0, 0, 0
|
||||||
@@ -9,7 +9,7 @@ entry:
|
|||||||
ret ptr inttoptr (i32 add (i32 ptrtoint (ptr @arr to i32), i32 32) to ptr)
|
ret ptr inttoptr (i32 add (i32 ptrtoint (ptr @arr to i32), i32 32) to ptr)
|
||||||
}
|
}
|
||||||
; CHECK-LABEL: _second:
|
; CHECK-LABEL: _second:
|
||||||
; CHECK: MOVI _arr+32, A{{[0-9]+}}
|
; CHECK: MOVI _arr{{[A-Za-z0-9_]*}}+32, A{{[0-9]+}}
|
||||||
; CHECK-NOT: _arr+4,
|
; CHECK-NOT: _arr{{[A-Za-z0-9_]*}}+4,
|
||||||
; CHECK: RETS 0
|
; CHECK: RETS 0
|
||||||
; CHECK: .bss _arr,128,16
|
; CHECK: .bss _arr{{[A-Za-z0-9_]*}},128,16
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
|
||||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
|
||||||
; CHECK: M2 supports only i32 and pointer arguments
|
|
||||||
define i32 @argc(i8 %c) {
|
|
||||||
entry:
|
|
||||||
%r = sext i8 %c to i32
|
|
||||||
ret i32 %r
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
|
||||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
|
||||||
; CHECK: M2 supports only i32 and pointer call arguments
|
|
||||||
declare void @g8(i8)
|
|
||||||
define void @f() {
|
|
||||||
entry:
|
|
||||||
call void @g8(i8 1)
|
|
||||||
ret void
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
|
||||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
|
||||||
; CHECK: M2 supports only direct calls to global functions
|
|
||||||
define i32 @indirect(ptr %f) {
|
|
||||||
entry:
|
|
||||||
%r = call i32 %f(i32 1)
|
|
||||||
ret i32 %r
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
|
||||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
|
||||||
; CHECK: M2 does not support varargs
|
|
||||||
declare void @g(i32, ...)
|
|
||||||
define void @f() {
|
|
||||||
entry:
|
|
||||||
call void @g(i32 1, i32 2)
|
|
||||||
ret void
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||||
; RUN: not llc -march=tms34010 -O2 -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) {
|
define i32 @f(float %x) {
|
||||||
entry:
|
entry:
|
||||||
ret i32 0
|
ret i32 0
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
;
|
;
|
||||||
; An i64 argument legalizes to two i32 pieces. M2 must reject the IR
|
; An i64 argument legalizes to two i32 pieces. M2 must reject the IR
|
||||||
; signature before that split can be mistaken for two register arguments.
|
; 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) {
|
define i32 @i64arg(i64 %v) {
|
||||||
entry:
|
entry:
|
||||||
%t = trunc i64 %v to i32
|
%t = trunc i64 %v to i32
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
;
|
;
|
||||||
; Aggregates are legalized into scalar pieces; reject the struct argument
|
; Aggregates are legalized into scalar pieces; reject the struct argument
|
||||||
; rather than silently passing its fields in registers.
|
; 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 }
|
%struct.S = type { i32, i32 }
|
||||||
|
|
||||||
define i32 @structarg(%struct.S %s) {
|
define i32 @structarg(%struct.S %s) {
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
|
||||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
|
||||||
;
|
|
||||||
; M2 has no varargs ABI, so even a definition that never reads its varargs
|
|
||||||
; must be rejected.
|
|
||||||
; CHECK: M2 does not support varargs
|
|
||||||
define i32 @vararg(i32 %n, ...) {
|
|
||||||
entry:
|
|
||||||
ret i32 %n
|
|
||||||
}
|
|
||||||
@@ -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: JAUC [[L]]
|
||||||
|
; CHECK-NOT: .LBB
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||||
|
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||||
|
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.
|
||||||
|
; CHECK-LABEL: _lt5:
|
||||||
|
; CHECK: CMPI 5
|
||||||
|
; CHECK-NEXT: JALT
|
||||||
|
; CHECK-DAG: MOVI 0
|
||||||
|
; CHECK-DAG: MOVI 1
|
||||||
|
; CHECK-DAG: RETS 2
|
||||||
@@ -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: JAGT
|
||||||
|
; CHECK: RETS 2
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
; RUN: llc -march=tms34010 -O0 -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: JALE
|
||||||
|
; CHECK: JAUC
|
||||||
|
; CHECK: RETS 2
|
||||||
@@ -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
|
||||||
@@ -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: JALT
|
||||||
|
; 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
|
||||||
@@ -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: JALO
|
||||||
|
; CHECK-LABEL: _ugt:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JAHI
|
||||||
@@ -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]+}}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||||
|
|
||||||
|
; M4 pins the complete signed/unsigned integer predicate map. The compare is
|
||||||
|
; glued to the conditional branch, so the CMP and Jcc must stay adjacent.
|
||||||
|
define i32 @slt(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp slt i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
define i32 @sle(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp sle i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
define i32 @sgt(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp sgt i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
define i32 @sge(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp sge i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
define i32 @ult(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp ult i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
define i32 @ule(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp ule i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
define i32 @ugt(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp ugt i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
define i32 @uge(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp uge i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
define i32 @eq(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp eq i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
define i32 @ne(i32 %a, i32 %b) {
|
||||||
|
entry:
|
||||||
|
%c = icmp ne i32 %a, %b
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
; CHECK-LABEL: _slt:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JALT
|
||||||
|
; CHECK-LABEL: _sle:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JALE
|
||||||
|
; CHECK-LABEL: _sgt:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JAGT
|
||||||
|
; CHECK-LABEL: _sge:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JAGE
|
||||||
|
; CHECK-LABEL: _ult:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JALO
|
||||||
|
; CHECK-LABEL: _ule:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JALS
|
||||||
|
; CHECK-LABEL: _ugt:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JAHI
|
||||||
|
; CHECK-LABEL: _uge:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JAHS
|
||||||
|
; CHECK-LABEL: _eq:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JAEQ
|
||||||
|
; CHECK-LABEL: _ne:
|
||||||
|
; CHECK: CMP
|
||||||
|
; CHECK-NEXT: JANE
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||||
|
|
||||||
|
; Comparisons against a 32-bit immediate with bit 31 set must materialise the
|
||||||
|
; low 32 bits, not sign-extend to uint64_t. This was an APInt assertion in
|
||||||
|
; LowerSELECT_CC/emitCmpAndBranch (getTargetConstant with a negative
|
||||||
|
; getSExtValue); the xgfix range check is the original live case.
|
||||||
|
define i32 @ult_high_bit(i32 %a) {
|
||||||
|
entry:
|
||||||
|
%c = icmp ult i32 %a, 2147483649
|
||||||
|
%r = zext i1 %c to i32
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @eq_all_ones_select(i32 %a) {
|
||||||
|
entry:
|
||||||
|
%c = icmp eq i32 %a, -1
|
||||||
|
%s = select i1 %c, i32 7, i32 9
|
||||||
|
ret i32 %s
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @xg_range(i32 %proc) {
|
||||||
|
entry:
|
||||||
|
%t = add i32 %proc, -33554432
|
||||||
|
%c = icmp ult i32 %t, -33554176
|
||||||
|
br i1 %c, label %bad, label %good
|
||||||
|
bad:
|
||||||
|
ret i32 0
|
||||||
|
good:
|
||||||
|
ret i32 1
|
||||||
|
}
|
||||||
|
|
||||||
|
; CHECK-LABEL: _ult_high_bit:
|
||||||
|
; CHECK: CMPI -2147483647
|
||||||
|
; CHECK-NEXT: JALO
|
||||||
|
|
||||||
|
; CHECK-LABEL: _eq_all_ones_select:
|
||||||
|
; CHECK: CMPI -1
|
||||||
|
; CHECK-NEXT: JAEQ
|
||||||
|
|
||||||
|
; CHECK-LABEL: _xg_range:
|
||||||
|
; CHECK: CMPI -33554177
|
||||||
|
; CHECK-NEXT: JAHI
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||||
|
;
|
||||||
|
; A nested array initializer (termtris' uint16_t tiles[][2]) recurses element
|
||||||
|
; by element; the flattened word sequence must keep every element.
|
||||||
|
@tiles = internal global [2 x [2 x i16]] [[2 x i16] [i16 1, i16 2], [2 x i16] [i16 3, i16 4]], align 2
|
||||||
|
; CHECK: _tiles_[[HASH:[0-9A-F]+]]:
|
||||||
|
; CHECK-NEXT: .word 00001h
|
||||||
|
; CHECK-NEXT: .word 00002h
|
||||||
|
; CHECK-NEXT: .word 00003h
|
||||||
|
; CHECK-NEXT: .word 00004h
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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]+}}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||||
|
;
|
||||||
|
; The backend has no BR_JT lowering. A five-case switch must stay on the
|
||||||
|
; compare/branch path (setMinimumJumpTableEntries(~0u)), not become a `.long`
|
||||||
|
; jump table that llc cannot select.
|
||||||
|
define i32 @sw(i32 %x) {
|
||||||
|
entry:
|
||||||
|
switch i32 %x, label %default [
|
||||||
|
i32 1, label %c1
|
||||||
|
i32 2, label %c2
|
||||||
|
i32 3, label %c3
|
||||||
|
i32 4, label %c4
|
||||||
|
i32 5, label %c5
|
||||||
|
]
|
||||||
|
c1: ret i32 10
|
||||||
|
c2: ret i32 20
|
||||||
|
c3: ret i32 30
|
||||||
|
c4: ret i32 40
|
||||||
|
c5: ret i32 50
|
||||||
|
default: ret i32 0
|
||||||
|
}
|
||||||
|
; CHECK-LABEL: _sw:
|
||||||
|
; CHECK: CMPI 2, A{{[0-9]+}}
|
||||||
|
; CHECK: JAGT
|
||||||
|
; CHECK-NOT: .long
|
||||||
|
; CHECK: RETS
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||||
|
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||||
|
;
|
||||||
|
; Pin the i32 anchor of the contiguous-stack walk. `%b.addr` is the
|
||||||
|
; parameter's alloca; SelectionDAGBuilder's argument-copy elision must replace
|
||||||
|
; it with the incoming fixed object (FP-64 bits, the second fixed argument)
|
||||||
|
; because the fixed object and the i32 alloca have the same size. The walk
|
||||||
|
; then subtracts one 32-bit slot and reads the first variadic argument. A
|
||||||
|
; private-local anchor would use an FP+ offset instead of the FP-64 slot and
|
||||||
|
; this same-register sequence would not appear.
|
||||||
|
define i32 @anchor32(i32 %a, i32 %b, ...) {
|
||||||
|
entry:
|
||||||
|
%b.addr = alloca i32, align 2
|
||||||
|
%p = alloca ptr, align 2
|
||||||
|
store i32 %b, ptr %b.addr, align 2
|
||||||
|
store ptr %b.addr, ptr %p, align 2
|
||||||
|
%q = load ptr, ptr %p, align 2
|
||||||
|
%base = ptrtoint ptr %q to i32
|
||||||
|
%addr = add i32 %base, -32
|
||||||
|
%v = inttoptr i32 %addr to ptr
|
||||||
|
store ptr %v, ptr %p, align 2
|
||||||
|
%r = load ptr, ptr %p, align 2
|
||||||
|
%x = load i32, ptr %r, align 2
|
||||||
|
%sum = add i32 %a, %x
|
||||||
|
ret i32 %sum
|
||||||
|
}
|
||||||
|
; CHECK-LABEL: _anchor32:
|
||||||
|
; The second fixed argument is loaded from FP-64 (bits).
|
||||||
|
; CHECK: ADDI -64, [[ANCHOR:A[0-9]+]]
|
||||||
|
; A slot below it is the first variadic argument: the same register is
|
||||||
|
; decremented by 32 bits and the loaded pointer is dereferenced.
|
||||||
|
; CHECK: ADDI -32, [[ANCHOR]]
|
||||||
|
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in new issue
Block a user