Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73c85350a4 | ||
|
|
1968ccd39b | ||
|
|
8d088c40df | ||
|
|
afb4ad2eef | ||
|
|
05762fceb8 | ||
|
|
bbc591b77c | ||
|
|
39512c272d | ||
|
|
d7317305b9 | ||
|
|
696832dd44 | ||
|
|
ce084040aa | ||
|
|
a9c33665ea | ||
|
|
454fb4ac15 | ||
|
|
0512ce9d6b | ||
|
|
a6954059ea | ||
|
|
11bdccf7f1 |
No files matched your search
+369
-38
@@ -1,42 +1,327 @@
|
||||
# LLVM 23.1.5 fork for TMS34010
|
||||
|
||||
Branch `tms34010` is `llvmorg-23.1.5` plus the TMS34010 target and clang
|
||||
bit-address changes (M0) and a minimal TMS34010 backend (M1). This branch is
|
||||
the canonical source for the target; the project's `patches/llvm` mirror is
|
||||
frozen at M0.
|
||||
Branch `m3` is `llvmorg-23.1.5` plus the TMS34010 target and clang
|
||||
bit-address changes (M0), a minimal TMS34010 backend (M1), the TI ABI, calls
|
||||
and `.data` support (M2), and integer control flow plus the first compiled C
|
||||
Xlib window (M3). This branch is the canonical source for the target;
|
||||
the project's `patches/llvm` mirror is frozen at M0. Branch `m2` (the fork's
|
||||
default) is the M2 tip, and branch `tms34010` keeps the M1 backend and its
|
||||
`m1-*` suite.
|
||||
|
||||
## M1 state
|
||||
## M2 state
|
||||
|
||||
M1 is an **assembly-only** milestone. It emits GSPA-compatible assembly with
|
||||
M2 is an **assembly-only** milestone. It emits GSPA-compatible assembly with
|
||||
`llc -march=tms34010 -filetype=asm`; it has no `MCCodeEmitter`, object writer,
|
||||
`llc -filetype=obj` or `clang -c`, and no native COFF. The 1987 GSPA assembler
|
||||
and GSPLNK linker produce the COFF that the VT1200 loader runs in MAME.
|
||||
|
||||
The calling convention is a **test convention, not the TI ABI**:
|
||||
The calling convention is the **TI ABI**:
|
||||
|
||||
- Integer/pointer arguments in `A0`–`A3` (four maximum), result in `A0`,
|
||||
return via `RETS 0`.
|
||||
- Leaf functions only: no calls, stack arguments, locals/`alloca`,
|
||||
callee-saves, structures, floats or varargs. A call reports
|
||||
`M1 has no calls; M2 adds the TI ABI`; a frame index reports
|
||||
`M1 has no stack frame`.
|
||||
- The M1 harness sets `SETF 32,0,1` (FS1) and `SETF 16,0,0` (FS0) before
|
||||
entering compiled code.
|
||||
- Integer/pointer result in `A8`.
|
||||
- `A13` is FP, `A14` is STK (the program stack), `A15` is SP (the system
|
||||
stack). `A8` and `A13`–`A15` are reserved; the allocatable set is
|
||||
`A0`–`A7` and `A9`–`A12`.
|
||||
- Caller: if it passes arguments, save the old STK on the system stack with
|
||||
`MOVE A14, -*SP, 1`; push arguments on the program stack **in reverse
|
||||
order** (`MOVE <arg>, *A14+, 1`), each already a 32-bit slot; `CALLA`; then
|
||||
re-establish both field sizes with `SETF 16, 0, 0` and `SETF 32, 0, 1`.
|
||||
- Callee: set `SETF 16, 0, 0` and `SETF 32, 0, 1`; save the old FP and each
|
||||
used callee-saved register on the system stack; `FP = STK`; allocate the
|
||||
frame with `ADDI <bits>, STK`. The first explicit argument is at `FP(-32)`, the second at
|
||||
`FP(-64)`, and so on; locals are at positive FP offsets.
|
||||
- Epilogue: when the function has explicit arguments, restore STK from the
|
||||
caller's saved slot with `MOVE *SP((n+1)*32), A14, 1`; a no-argument
|
||||
function instead releases its own frame with `ADDI -<frame_bits>, A14`.
|
||||
Then pop the saved registers in reverse order, and `RETS 2` when the
|
||||
function has explicit arguments or `RETS 0` otherwise. After `n` system-stack
|
||||
pushes the caller's saved STK is at `SP + (n+1)*32` (the formula applies
|
||||
only when arguments were passed, i.e. the caller saved STK); `RETS N` pops
|
||||
the return PC and advances SP by `32 + 16N`.
|
||||
- Callee-saved registers are `A0`–`A7` and `A9`–`A12`. A value live across a
|
||||
call must survive in one of them (or a spill slot), because a call clobbers
|
||||
all other state except the reserved registers.
|
||||
- `FS0` must be 16 (for `short` accesses) and `FS1` must be 32 (for 32-bit
|
||||
words) at function entry and after every call. The prologue emits
|
||||
`SETF 16, 0, 0` then `SETF 32, 0, 1`; `LowerCall` emits the same pair
|
||||
immediately after each `CALLA`.
|
||||
|
||||
Implemented selection and asm printing:
|
||||
Supported signatures are `i32` and pointer arguments, and `void`, `i32` or
|
||||
pointer returns. Narrow (`i8`/`i16`) parameters, `i64`, floats/aggregates,
|
||||
varargs, `sret`/`byval`/`inreg`/`nest` and indirect calls fail loudly with an
|
||||
`M2 ...` diagnostic. Ordinary `tail` markers are **demoted** to ordinary
|
||||
calls (`LowerCall` clears `IsTailCall`); `musttail` is not implemented and
|
||||
M2 C does not emit it. Real tail calls remain a later milestone (M4+). Narrow
|
||||
loads/stores inside a function are supported only in their sign-extending
|
||||
forms: `MOVB` for signed `char` and the FS0 `MOVE` for signed `short`.
|
||||
Zero-extending narrow loads (`unsigned char`, `unsigned short`, `_Bool`) have
|
||||
no pattern in M2/M3 and are M4 work; `m3-reject-zext.ll` pins the loud failure.
|
||||
The optimizer's `returned` parameter attribute
|
||||
is **accepted**: it is only an optimization hint and does not change the
|
||||
stack-slot ABI. `m2-returned.ll` pins that; `m2-reject-sret.ll` keeps the
|
||||
rejection of the ABI-affecting attributes.
|
||||
|
||||
- `ADD` for `i32`; `MOVI` for constants; `RETS`.
|
||||
- `MOVE ..., 1` (FS1) for 32-bit load/store, `MOVE ..., 0` (FS0) for `i16`
|
||||
load/store, sign-extending `MOVB` for `i8` load/store.
|
||||
- Constant global addresses as `MOVI _sym+bits`; surviving GEP offsets are
|
||||
bit offsets (`ADDI 24`, not `ADDI 192`).
|
||||
- GSPA syntax: leading-underscore symbols, hex `...h`, field-select operands.
|
||||
Output is asm only; encodings are validated by GSPA and `dis34`, not by an
|
||||
LLVM encoder.
|
||||
### Implemented selection and printing
|
||||
|
||||
M2 replaces the convention with the TI ABI: `A8` return, `A13`/`A14`/`A15`
|
||||
stack handling, `RETS 2`, `FS1 = 32` across calls, plus ROM imports. Do not
|
||||
treat M1 output as ABI-compatible.
|
||||
- `ADD` for `i32`; `MOVI` for constants; `SETF 16, 0, 0` and
|
||||
`SETF 32, 0, 1`; `RETS N`.
|
||||
- `MOVE ..., 1` (FS1) for 32-bit load/store, `MOVE ..., 0` (FS0) for `i16`,
|
||||
sign-extending `MOVB` for `i8`.
|
||||
- Argument/return lowering with `PUSH32`, `POP32`, `PUSHARG`, `SETFP`,
|
||||
`ADDSTK`, `MOVEFP`, `LOADSTK`, `SETF16`, `SETF32`, `CALLA` and the `GETFI`
|
||||
pseudo.
|
||||
- Constant global addresses as `MOVI _sym+bits`; surviving GEP offsets are bit
|
||||
offsets (`ADDI 24`, not `ADDI 192`).
|
||||
- GSPA syntax: leading-underscore symbols, hex `...h` (or decimal), signed
|
||||
decimal immediates (`ADDI -32, A5`, `MOVE *SP(128), A14, 1`), field-select
|
||||
operands. Output is asm only; encodings are validated by GSPA and `dis34`,
|
||||
not by an LLVM encoder.
|
||||
|
||||
### Frame-index materialisation (`GETFI`)
|
||||
|
||||
An `ISD::FrameIndex` is selected to the `GETFI` pseudo with a
|
||||
`TargetFrameIndex` operand. `eliminateFrameIndex` resolves it to the
|
||||
FP-relative **bit** offset (fixed objects use `ObjectOffset*8`; locals use
|
||||
`(ObjectOffset + StackSize)*8`). `expandPostRAPseudo` then emits:
|
||||
|
||||
```asm
|
||||
MOVE A13, <dst>
|
||||
ADDI <bit offset>, <dst>
|
||||
```
|
||||
|
||||
The `ADDI` is omitted for offset 0. Spill-slot addresses use the same
|
||||
`GETFI` materialisation followed by a 32-bit `MOVE` store or load; the backend
|
||||
never reports a fatal error for a spill.
|
||||
|
||||
### Per-register system-stack saves
|
||||
|
||||
The prologue saves `A13` and each callee-saved register used by the function as
|
||||
individual system-stack pushes (`MOVE <reg>, -*SP, 1`), and the epilogue pops
|
||||
them in reverse order after restoring STK with
|
||||
`MOVE *SP((n+1)*32), A14, 1`. This is a deliberate decision: the stack layout
|
||||
is identical to `MMTM`/`MMFM` (one 32-bit slot per register, old STK at
|
||||
`SP+(n+1)*32`), so the ABI-visible contract is unchanged, while the asm-only
|
||||
printer does not need a variable register-list operand. Callee-saved spill slots
|
||||
are not allocated on the program stack; the prologue/epilogue own the saves and
|
||||
restores.
|
||||
|
||||
### `.data` and string literals
|
||||
|
||||
Zero-initialized, non-constant, non-private sectionless globals keep the
|
||||
`.bss` path. Every other constant definition is emitted as raw GSPA `.data`
|
||||
text from `TMS34010AsmPrinter::doInitialization` (so initialized data precedes
|
||||
the code), and `.text` is re-emitted before the first function.
|
||||
|
||||
Width and alignment rules:
|
||||
|
||||
- `i8` scalar or byte/string array: `.byte` with two hex digits prefixed by
|
||||
`0` (`0%02Xh`, for example `068h`).
|
||||
- `i16` scalar: `.word` with a leading `0` (`0%04Xh`) so high-bit values
|
||||
assemble.
|
||||
- `i32` scalar: `.long`.
|
||||
- pointer: `.long _symbol+<bit addend>`; the addend is already in bits and is
|
||||
not scaled again.
|
||||
- `.even` before a `.data` global whose alignment is at least 16 bits, so the
|
||||
label and the following `.word`/`.long` agree. This is required when a short
|
||||
string literal precedes an `int`: GSPA aligns the value, not the label.
|
||||
`.even` is a 16-bit alignment only, so explicit `align 4`/`align 8` on
|
||||
`.data` is not honored in M2; the target's standalone object alignment is
|
||||
2 bytes.
|
||||
|
||||
A scalar's directive must match its width, or the next global's offset is
|
||||
wrong. Arrays accept only `i8`, `i16`, `i32` and pointer elements; an `i1` or
|
||||
`i64` array (including `zeroinitializer`) is rejected rather than emitted at
|
||||
the wrong size. Private/local symbol names are sanitized to GSPA-safe names
|
||||
(every non-alphanumeric/non-`_` character becomes `_HH`, keeping the leading
|
||||
`_`), so
|
||||
`.L.str` becomes a deterministic valid label. ELF directives (`.rodata`,
|
||||
`.asciz`, `.p2align`, `.section`) are never emitted. Unsupported initializer
|
||||
shapes fail with
|
||||
`M2 .data supports only integer, pointer and byte-array initializers`;
|
||||
`available_externally` declarations are skipped rather than emitted.
|
||||
|
||||
### ROM imports
|
||||
|
||||
ROM entry points stay out of the compiler. The project's
|
||||
`scripts/m2/gen-rom-imports.sh` turns `symbols/vt1200-v22.syms` into
|
||||
ABI-preserving absolute-jump stubs:
|
||||
|
||||
```asm
|
||||
.globl _strlen
|
||||
_strlen:
|
||||
jauc 3FB458D0h
|
||||
```
|
||||
|
||||
`JAUC` is the TMS34010 unconditional absolute jump; GSPA 2.00 rejects bare
|
||||
`ja`. `CALLA _strlen` pushes the return PC, the stub jumps to the ROM address,
|
||||
and the ROM's `RETS 2` returns directly to our caller. M2 consumes `strlen`
|
||||
only; later milestones extend and validate the table.
|
||||
|
||||
## M3 state
|
||||
|
||||
M3 adds the control flow and ALU operations that real C needs, and proves them
|
||||
with the first compiled C Xlib client. It is still assembly-only and still
|
||||
`i32`/pointer only.
|
||||
|
||||
### M3 limitations
|
||||
|
||||
- **Narrow loads are sign-extending only.** `MOVB` covers signed `char` and the
|
||||
FS0 `MOVE` covers signed `short`; there is no zero-extending pattern.
|
||||
`unsigned char`, `unsigned short` and `_Bool` loads reach `ZEXTLOAD i8`/`i16`
|
||||
and `llc` stops with a raw `Cannot select`. `m3-reject-zext.ll` pins that
|
||||
failure so the limitation stays visible; M4 adds the zero-extending patterns.
|
||||
- **`-O2` loop exit-value rewriting is not lowered.** IndVarSimplify can turn
|
||||
`sumto`'s loop into a closed-form `i33` multiply, which M3 cannot select, so
|
||||
the M3 harness builds `branch.c` with `-replexitval=never`. Ordinary
|
||||
`while`/`for` loops that keep a real back-edge and phi work. M4/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 prerequisites (carry-forwards)
|
||||
|
||||
- **Add zero-extending narrow loads.** M3 rejects `unsigned char`,
|
||||
`unsigned short` and `_Bool` loads (`m3-reject-zext.ll`); M4 must add the
|
||||
`ZEXTLOAD i8`/`i16` patterns and replace the reject test.
|
||||
- **Namespace every private label.** The M3 harness's per-translation-unit
|
||||
`LBB…` prefixing rewrites block labels only. Deterministic private string
|
||||
labels are still per-TU names, so two TUs with literals collide in GSPA's
|
||||
flat symbol table. M4 must namespace all private labels or make compiler
|
||||
private names module-unique.
|
||||
- **Add unsigned/`<=` runtime coverage.** `JALE`, `JALO`, `JAHI` and `JALS`,
|
||||
`JAHS`, `JANE`, `JAGE` are exercised by lit tests only and do not appear in
|
||||
the compiled M3 image. M4 should add an unsigned/`<=` runtime marker.
|
||||
- **Replace fragile test plumbing.** The `always_inline` root helper and the
|
||||
register-hardcoded nine-argument dis34 check are test-strength aids that will
|
||||
not survive real M4 code; make them operand- and register-independent.
|
||||
- **Tighten test strength.** `m3-cmpi`'s `CHECK-DAG` for 0/1 does not pin their
|
||||
order, and `layout.sh` pairs the root probe with fixed constants; neither
|
||||
weakens the current pass, but both should be tightened.
|
||||
|
||||
## Build
|
||||
|
||||
@@ -50,16 +335,49 @@ cmake -S llvm -B build -G Ninja \
|
||||
ninja -C build llc clang opt llvm-config FileCheck count not
|
||||
```
|
||||
|
||||
`build/bin/llvm-lit` is generated by CMake configure. Run the M1 backend tests:
|
||||
`build/bin/llvm-lit` is generated by CMake configure. Run the M2+M3 backend
|
||||
tests:
|
||||
|
||||
```sh
|
||||
build/bin/llvm-lit -v build/test/CodeGen/TMS34010 --filter='m1-'
|
||||
# -> 9 passed
|
||||
build/bin/llvm-lit -v build/test/CodeGen/TMS34010 --filter='(m2|m3)-'
|
||||
# -> 46 passed (37 m2-*, 9 m3-*)
|
||||
```
|
||||
|
||||
The tests are `llvm/test/CodeGen/TMS34010/m1-*.ll`: `add`, `load`, `char`,
|
||||
`i16`, constant global relocation, late GEP bits, `-O0` register allocation,
|
||||
and the call/frame refusal diagnostics.
|
||||
The M2 tests are `llvm/test/CodeGen/TMS34010/m2-*.ll`:
|
||||
|
||||
- Migrated/legacy selection: `m2-add`, `m2-load`, `m2-char`, `m2-i16`,
|
||||
`m2-store`, `m2-global`, `m2-late-gep`.
|
||||
- ABI: `m2-args`, `m2-noret`, `m2-call-noret`, `m2-locals`,
|
||||
`m2-args-locals`, `m2-call`, `m2-call3`, `m2-callee-save`, `m2-fs1`,
|
||||
`m2-spill`, `m2-returned`, `m2-returned-call`.
|
||||
- Data: `m2-data`, `m2-data-avail`, `m2-data-align`, `m2-data-z64`,
|
||||
`m2-data-i1`, `m2-string`, `m2-ptrinit`.
|
||||
- Loud refusals: `m2-reject-i64`, `m2-reject-struct`, `m2-reject-vararg`,
|
||||
`m2-reject-sret`, `m2-reject-structret`, `m2-reject-arg8`,
|
||||
`m2-reject-i8ret`, `m2-reject-float`, `m2-reject-call-indirect`,
|
||||
`m2-reject-call-arg8`, `m2-reject-call-vararg`.
|
||||
|
||||
`m2-add` uses `RETS 2`; `m2-global` returns in `A8` with `RETS 0`;
|
||||
`m2-locals`/`m2-args-locals` require a real frame at `-O0`; `m2-data-align`
|
||||
reproduces the string-then-`int` `.data` alignment case; and the call tests pin
|
||||
the TI caller/callee sequence, the `FS1` fixup, and a real spill.
|
||||
|
||||
The M3 tests are:
|
||||
|
||||
- `m3-branch`: a `JAUC` self-loop and an `LBB` label, with `CHECK-NOT: .LBB`.
|
||||
- `m3-loop`: `CMP` immediately followed by `JALE`, an unconditional back-edge
|
||||
(`JAUC`) and `RETS 2`.
|
||||
- `m3-if`: `CMP` + `JAGT` and `RETS 2`.
|
||||
- `m3-cmpi`: the constant-right compare becomes `CMPI` + `JALT`, at `-O0` and
|
||||
`-O2`.
|
||||
- `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`.
|
||||
- `m3-reject-zext`: a zero-extending `i8`/`i16` load still aborts with
|
||||
`Cannot select`; M4 must replace this test with the real patterns.
|
||||
|
||||
## Integration test
|
||||
|
||||
@@ -68,10 +386,23 @@ 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`:
|
||||
|
||||
```sh
|
||||
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=tms34010 ./scripts/build-llvm.sh
|
||||
./scripts/tests/m1/run.sh # GSPA/GSPLNK under DOSBox, then MAME
|
||||
./scripts/tests/m1/dis-roundtrip.sh # linked .text decoded with dis34
|
||||
LLVM_SRC=$PWD/build/llvm-clean LLVM_BRANCH=m3 ./scripts/build-llvm.sh
|
||||
scripts/tests/m3/run.sh # layout/symbols, GSPA/GSPLNK, then MAME
|
||||
scripts/tests/m3/dis-roundtrip.sh # linked .text decoded with dis34
|
||||
scripts/tests/m3/diff/check.sh # branch/Xlib shape vs. the 1987 TI compiler
|
||||
scripts/tests/m3/diff/negative-control.sh # the check must reject a wrong TI displacement
|
||||
scripts/tests/m3/layout.sh # Display/Screen bit displacements (also run by run.sh)
|
||||
tools/tms-bitaddr/tests/run.sh # GEP scaling/normalization
|
||||
```
|
||||
|
||||
Expected: `M1 MAME: PASS` and `M1 dis34 round-trip: PASS`. The project README
|
||||
lists the prerequisites (DOSBox, mtools, the VT1200 SDK image, MAME).
|
||||
Expected: `layout.sh: PASS`, `M3 MAME: PASS`, `M3 dis34 round-trip: PASS`,
|
||||
`M3 diff: PASS`, `M3 diff negative controls: PASS` and `tms-bitaddr: PASS`.
|
||||
The MAME gate pins
|
||||
`VT1200_MONITOR=VR319` and requires the white client pixels described above.
|
||||
The differential check uses stored golden assembly from the 1987 TI compiler
|
||||
and does not run DOSBox; the MAME/dis34 gates do. The M2 gates
|
||||
(`scripts/tests/m2/`) still pass on `m3` (verified 2026-11-11: `M2 MAME`,
|
||||
`M2 dis34 round-trip` and `M2 diff` all `PASS`); the M1 harness
|
||||
(`scripts/tests/m1/`) lives on the project's `m1` branch and is not an M3 gate.
|
||||
The project README lists the prerequisites (DOSBox, mtools, the VT1200 SDK
|
||||
image, MAME).
|
||||
@@ -21,6 +21,14 @@ TMS34010MCAsmInfo::TMS34010MCAsmInfo(const Triple &TT,
|
||||
CommentString = ";";
|
||||
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.
|
||||
AlignmentIsInBytes = false;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
include "llvm/Target/Target.td"
|
||||
|
||||
include "TMS34010RegisterInfo.td"
|
||||
include "TMS34010CallingConv.td"
|
||||
include "TMS34010InstrInfo.td"
|
||||
|
||||
defm : RemapAllTargetPseudoPointerOperands<ARegs>;
|
||||
|
||||
@@ -9,17 +9,26 @@
|
||||
#include "TMS34010MCInstLower.h"
|
||||
#include "TMS34010.h"
|
||||
#include "TargetInfo/TMS34010TargetInfo.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/Twine.h"
|
||||
#include "llvm/CodeGen/AsmPrinter.h"
|
||||
#include "llvm/CodeGen/MachineInstr.h"
|
||||
#include "llvm/IR/Constants.h"
|
||||
#include "llvm/IR/DataLayout.h"
|
||||
#include "llvm/IR/GlobalVariable.h"
|
||||
#include "llvm/IR/Instruction.h"
|
||||
#include "llvm/IR/Module.h"
|
||||
#include "llvm/IR/Operator.h"
|
||||
#include "llvm/MC/MCInst.h"
|
||||
#include "llvm/MC/MCStreamer.h"
|
||||
#include "llvm/MC/TargetRegistry.h"
|
||||
#include "llvm/Support/Compiler.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/Format.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include "llvm/Target/TargetLoweringObjectFile.h"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
@@ -32,15 +41,94 @@ public:
|
||||
|
||||
StringRef getPassName() const override { return "TMS34010 Assembly Printer"; }
|
||||
|
||||
bool doInitialization(Module &M) override;
|
||||
|
||||
void emitInstruction(const MachineInstr *MI) override;
|
||||
|
||||
void emitGlobalVariable(const GlobalVariable *GV) override;
|
||||
|
||||
/// Emit one `.data` definition. Data is emitted during doInitialization so
|
||||
/// it precedes the code, which is how GSPA programs lay out a module.
|
||||
void emitDataGlobal(const GlobalVariable *GV);
|
||||
|
||||
/// Emit the GSPA directives for one constant initializer. The initializer
|
||||
/// is already in target (bit-addressed) form; no scaling happens here.
|
||||
void emitDataInitializer(const Constant *Init);
|
||||
|
||||
/// Emit a constant pointer as `.long <symbol>[+<bit addend>]`.
|
||||
void emitPointerInitializer(const Constant *Init);
|
||||
|
||||
/// Emit `.long <symbol>[+<bit addend>]`.
|
||||
void emitPointerReference(const GlobalValue *GV, int64_t Addend);
|
||||
|
||||
/// Emit byte values as wrapped `.byte` lines (16 values per line keeps the
|
||||
/// line well inside GSPA's DOS-era input buffer). DecimalZeros prints `0`
|
||||
/// instead of `000h`, used for zero-initialized byte arrays.
|
||||
void emitByteLines(ArrayRef<uint64_t> Bytes, bool DecimalZeros = false);
|
||||
|
||||
static char ID;
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
[[noreturn]] static void reportUnsupportedDataInitializer() {
|
||||
report_fatal_error(
|
||||
"M2 .data supports only integer, pointer and byte-array initializers",
|
||||
false);
|
||||
}
|
||||
|
||||
/// Match the integer expression produced by `tms-bitaddr --normalize` for a
|
||||
/// constant pointer: `ptrtoint(@g)` optionally plus a constant bit offset,
|
||||
/// with an `add` operand order that may be commuted.
|
||||
static bool decomposePointerInt(const Constant *C, const GlobalValue *&GV,
|
||||
int64_t &Addend) {
|
||||
const auto *CE = dyn_cast<ConstantExpr>(C);
|
||||
if (!CE)
|
||||
return false;
|
||||
|
||||
if (CE->getOpcode() == Instruction::PtrToInt) {
|
||||
GV = dyn_cast<GlobalValue>(CE->getOperand(0)->stripPointerCasts());
|
||||
Addend = 0;
|
||||
return GV != nullptr;
|
||||
}
|
||||
|
||||
if (CE->getOpcode() != Instruction::Add)
|
||||
return false;
|
||||
|
||||
const Constant *L = CE->getOperand(0);
|
||||
const Constant *R = CE->getOperand(1);
|
||||
|
||||
const GlobalValue *LGV = nullptr;
|
||||
int64_t LAddend = 0;
|
||||
if (decomposePointerInt(L, LGV, LAddend)) {
|
||||
if (const auto *CI = dyn_cast<ConstantInt>(R)) {
|
||||
GV = LGV;
|
||||
Addend = LAddend + CI->getSExtValue();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const GlobalValue *RGV = nullptr;
|
||||
int64_t RAddend = 0;
|
||||
if (decomposePointerInt(R, RGV, RAddend)) {
|
||||
if (const auto *CI = dyn_cast<ConstantInt>(L)) {
|
||||
GV = RGV;
|
||||
Addend = RAddend + CI->getSExtValue();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// A zero-initialized, writable, externally-visible object is one `.bss`
|
||||
/// line. Every other definition is emitted as GSPA `.data`.
|
||||
static bool isBSSGlobal(const GlobalVariable *GV) {
|
||||
return GV->hasInitializer() && GV->getInitializer()->isNullValue() &&
|
||||
!GV->isConstant() && !GV->hasPrivateLinkage() &&
|
||||
GV->getSection().empty() && !GV->isThreadLocal();
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
||||
TMS34010MCInstLower MCInstLowering(OutContext, *this);
|
||||
|
||||
@@ -49,34 +137,259 @@ void TMS34010AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
||||
EmitToStreamer(*OutStreamer, TmpInst);
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitGlobalVariable(const GlobalVariable *GV) {
|
||||
// External declarations need no output in GSPA.
|
||||
if (GV->isDeclaration())
|
||||
void TMS34010AsmPrinter::emitPointerReference(const GlobalValue *GV,
|
||||
int64_t Addend) {
|
||||
Twine Sym = getSymbol(GV)->getName();
|
||||
if (Addend == 0) {
|
||||
OutStreamer->emitRawText(Twine("\t.long ") + Sym + "\n");
|
||||
return;
|
||||
}
|
||||
uint64_t Abs = Addend < 0 ? -static_cast<uint64_t>(Addend) : Addend;
|
||||
OutStreamer->emitRawText(Twine("\t.long ") + Sym + (Addend < 0 ? "-" : "+") +
|
||||
Twine(Abs) + "\n");
|
||||
}
|
||||
|
||||
// GSPA has no .comm/.local directives; a zero-initialized object is one
|
||||
// .bss line. Sizes and alignment in GSPA are in bits. Anything else
|
||||
// (initialized data, string literals, constant/private data) would fall
|
||||
// through to the generic ELF .data/.rodata/.p2align/.asciz printer, which
|
||||
// GSPA rejects; fail loudly instead.
|
||||
if (GV->hasInitializer() && GV->getInitializer()->isNullValue() &&
|
||||
!GV->isConstant() && !GV->hasPrivateLinkage() &&
|
||||
GV->getSection().empty() && !GV->isThreadLocal()) {
|
||||
const DataLayout &DL = GV->getParent()->getDataLayout();
|
||||
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
|
||||
uint64_t SizeBits =
|
||||
DL.getTypeAllocSize(GV->getValueType()).getFixedValue() * 8;
|
||||
if (!GV->hasLocalLinkage())
|
||||
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
|
||||
OutStreamer->emitRawText(Twine("\t.bss\t") + getSymbol(GV)->getName() +
|
||||
"," + Twine(SizeBits) + "," +
|
||||
Twine(Alignment.value() * 8));
|
||||
void TMS34010AsmPrinter::emitPointerInitializer(const Constant *Init) {
|
||||
if (Init->isNullValue()) {
|
||||
OutStreamer->emitRawText("\t.long 0\n");
|
||||
return;
|
||||
}
|
||||
|
||||
report_fatal_error(
|
||||
"M1 supports only zero-initialized globals; .data/string literals are M2",
|
||||
false);
|
||||
const Value *V = Init->stripPointerCasts();
|
||||
|
||||
if (const auto *GV = dyn_cast<GlobalValue>(V)) {
|
||||
emitPointerReference(GV, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// A constant GEP is a global address plus a bit offset. tms-bitaddr has
|
||||
// already scaled the byte index by eight, so print the addend as-is.
|
||||
if (const auto *GEP = dyn_cast<GEPOperator>(V)) {
|
||||
const auto *BaseGV =
|
||||
dyn_cast<GlobalValue>(GEP->getPointerOperand()->stripPointerCasts());
|
||||
if (!BaseGV || GEP->getNumOperands() != 2)
|
||||
reportUnsupportedDataInitializer();
|
||||
|
||||
const auto *Idx = dyn_cast<ConstantInt>(GEP->getOperand(1));
|
||||
if (!Idx)
|
||||
reportUnsupportedDataInitializer();
|
||||
|
||||
emitPointerReference(BaseGV, Idx->getSExtValue());
|
||||
return;
|
||||
}
|
||||
|
||||
// tms-bitaddr --normalize rewrites constant GEPs in initializers to
|
||||
// inttoptr (add (ptrtoint @g), <bit offset>); accept that shape too.
|
||||
if (const auto *CE = dyn_cast<ConstantExpr>(V)) {
|
||||
if (CE->getOpcode() == Instruction::IntToPtr) {
|
||||
const GlobalValue *BaseGV = nullptr;
|
||||
int64_t Addend = 0;
|
||||
if (decomposePointerInt(CE->getOperand(0), BaseGV, Addend)) {
|
||||
emitPointerReference(BaseGV, Addend);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitByteLines(ArrayRef<uint64_t> Bytes,
|
||||
bool DecimalZeros) {
|
||||
constexpr unsigned ValuesPerLine = 16;
|
||||
for (size_t I = 0, E = Bytes.size(); I < E; I += ValuesPerLine) {
|
||||
std::string Line;
|
||||
raw_string_ostream OS(Line);
|
||||
OS << "\t.byte ";
|
||||
size_t LineEnd = std::min(E, I + ValuesPerLine);
|
||||
for (size_t J = I; J != LineEnd; ++J) {
|
||||
if (J != I)
|
||||
OS << ", ";
|
||||
if (DecimalZeros)
|
||||
OS << "0";
|
||||
else
|
||||
OS << format("0%02Xh", Bytes[J] & 0xFF);
|
||||
}
|
||||
OS << "\n";
|
||||
OutStreamer->emitRawText(OS.str());
|
||||
}
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitDataInitializer(const Constant *Init) {
|
||||
// Integer scalar: GSPA's directive follows the IR width so that a char or
|
||||
// short does not occupy a full 32-bit word.
|
||||
if (const auto *CI = dyn_cast<ConstantInt>(Init)) {
|
||||
unsigned Bits = CI->getType()->getIntegerBitWidth();
|
||||
uint64_t Value = CI->getZExtValue();
|
||||
std::string Line;
|
||||
raw_string_ostream OS(Line);
|
||||
if (Bits <= 8)
|
||||
OS << "\t.byte " << format("0%02Xh", Value & 0xFF);
|
||||
else if (Bits <= 16)
|
||||
// The leading 0 is required: GSPA rejects a hex literal whose first
|
||||
// character is A-F (e.g. FFFFh), which every negative i16 hits.
|
||||
OS << "\t.word " << format("0%04Xh", Value & 0xFFFF);
|
||||
else if (Bits <= 32)
|
||||
OS << "\t.long " << (Value & 0xFFFFFFFF);
|
||||
else
|
||||
reportUnsupportedDataInitializer();
|
||||
OS << "\n";
|
||||
OutStreamer->emitRawText(OS.str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Constant pointer, including a constant GEP.
|
||||
if (Init->getType()->isPointerTy()) {
|
||||
emitPointerInitializer(Init);
|
||||
return;
|
||||
}
|
||||
|
||||
// String literal / constant byte array.
|
||||
if (const auto *CDA = dyn_cast<ConstantDataArray>(Init)) {
|
||||
Type *ElemTy = CDA->getElementType();
|
||||
if (ElemTy->isIntegerTy(8)) {
|
||||
SmallVector<uint64_t, 32> Bytes;
|
||||
for (unsigned I = 0, E = CDA->getNumElements(); I != E; ++I)
|
||||
Bytes.push_back(CDA->getElementAsInteger(I) & 0xFF);
|
||||
emitByteLines(Bytes);
|
||||
return;
|
||||
}
|
||||
// Only 16/32-bit integer and pointer elements have a GSPA directive that
|
||||
// matches their width. Accepting i1 or i64 here would emit one .byte or
|
||||
// .long per element and silently change the object size.
|
||||
if (ElemTy->isIntegerTy(16) || ElemTy->isIntegerTy(32) ||
|
||||
ElemTy->isPointerTy()) {
|
||||
for (unsigned I = 0, E = CDA->getNumElements(); I != E; ++I)
|
||||
emitDataInitializer(CDA->getElementAsConstant(I));
|
||||
return;
|
||||
}
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
if (const auto *CA = dyn_cast<ConstantArray>(Init)) {
|
||||
Type *ElemTy = CA->getType()->getElementType();
|
||||
if (ElemTy->isIntegerTy(8)) {
|
||||
SmallVector<uint64_t, 32> Bytes;
|
||||
for (const Use &U : CA->operands()) {
|
||||
const auto *CI = dyn_cast<ConstantInt>(U.get());
|
||||
if (!CI)
|
||||
reportUnsupportedDataInitializer();
|
||||
Bytes.push_back(CI->getZExtValue() & 0xFF);
|
||||
}
|
||||
emitByteLines(Bytes);
|
||||
return;
|
||||
}
|
||||
if (ElemTy->isIntegerTy(16) || ElemTy->isIntegerTy(32) ||
|
||||
ElemTy->isPointerTy()) {
|
||||
for (const Use &U : CA->operands())
|
||||
emitDataInitializer(cast<Constant>(U.get()));
|
||||
return;
|
||||
}
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
// zeroinitializer in .data (constant or private objects do not take the
|
||||
// .bss path). The directive must follow the element width, or a
|
||||
// [N x i16] would silently occupy 32 bits per element.
|
||||
if (isa<ConstantAggregateZero>(Init)) {
|
||||
Type *Ty = Init->getType();
|
||||
if (Ty->isIntegerTy()) {
|
||||
unsigned Bits = Ty->getIntegerBitWidth();
|
||||
if (Bits <= 8)
|
||||
OutStreamer->emitRawText("\t.byte 0\n");
|
||||
else if (Bits <= 16)
|
||||
OutStreamer->emitRawText("\t.word 0\n");
|
||||
else if (Bits <= 32)
|
||||
OutStreamer->emitRawText("\t.long 0\n");
|
||||
else
|
||||
reportUnsupportedDataInitializer();
|
||||
return;
|
||||
}
|
||||
if (Ty->isPointerTy()) {
|
||||
OutStreamer->emitRawText("\t.long 0\n");
|
||||
return;
|
||||
}
|
||||
if (const auto *AT = dyn_cast<ArrayType>(Ty)) {
|
||||
Type *ElemTy = AT->getElementType();
|
||||
uint64_t N = AT->getNumElements();
|
||||
if (ElemTy->isIntegerTy(8)) {
|
||||
SmallVector<uint64_t, 32> Zeros(N, 0);
|
||||
emitByteLines(Zeros, /*DecimalZeros=*/true);
|
||||
return;
|
||||
}
|
||||
if (ElemTy->isIntegerTy(16)) {
|
||||
for (uint64_t I = 0; I != N; ++I)
|
||||
OutStreamer->emitRawText("\t.word 0\n");
|
||||
return;
|
||||
}
|
||||
if (ElemTy->isIntegerTy(32) || ElemTy->isPointerTy()) {
|
||||
for (uint64_t I = 0; I != N; ++I)
|
||||
OutStreamer->emitRawText("\t.long 0\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
reportUnsupportedDataInitializer();
|
||||
}
|
||||
|
||||
bool TMS34010AsmPrinter::doInitialization(Module &M) {
|
||||
AsmPrinter::doInitialization(M);
|
||||
|
||||
// Emit initialized data before the code. Section switches go through the
|
||||
// streamer so the printer re-emits `.text` for the first function.
|
||||
// `available_externally` definitions are the linker's job, not ours: emitting
|
||||
// them as `.globl` definitions could cause a duplicate-definition error.
|
||||
for (const GlobalVariable &GV : M.globals())
|
||||
if (!GV.isDeclarationForLinker() && !isBSSGlobal(&GV))
|
||||
emitDataGlobal(&GV);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitDataGlobal(const GlobalVariable *GV) {
|
||||
// Only constant initializers can be written as GSPA .data.
|
||||
const Constant *Init = GV->getInitializer();
|
||||
if (!Init || GV->isThreadLocal() || !GV->getSection().empty())
|
||||
reportUnsupportedDataInitializer();
|
||||
|
||||
OutStreamer->switchSection(getObjFileLowering().getDataSection());
|
||||
// GSPA implicitly aligns `.word`/`.long` to a 16-bit boundary but does not
|
||||
// move the label that precedes the directive. After an odd-length object
|
||||
// (for example the 5-byte "0::0" string) the label would point one byte
|
||||
// before its value. Emit the alignment explicitly so the label and the
|
||||
// emitted bytes agree.
|
||||
const DataLayout &DL = GV->getParent()->getDataLayout();
|
||||
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
|
||||
if (Alignment.value() >= 2)
|
||||
OutStreamer->emitRawText("\t.even\n");
|
||||
if (!GV->hasLocalLinkage())
|
||||
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
|
||||
OutStreamer->emitRawText(getSymbol(GV)->getName() + ":\n");
|
||||
emitDataInitializer(Init);
|
||||
}
|
||||
|
||||
void TMS34010AsmPrinter::emitGlobalVariable(const GlobalVariable *GV) {
|
||||
// External declarations (including available_externally) need no output.
|
||||
if (GV->isDeclarationForLinker())
|
||||
return;
|
||||
|
||||
// Initialized data has already been emitted by doInitialization.
|
||||
if (!isBSSGlobal(GV))
|
||||
return;
|
||||
|
||||
// GSPA has no .comm/.local directives; a zero-initialized object is one
|
||||
// .bss line. Sizes and alignment in GSPA are in bits.
|
||||
const DataLayout &DL = GV->getParent()->getDataLayout();
|
||||
Align Alignment = getGVAlignment(GV, DL, GV->getAlign().valueOrOne());
|
||||
uint64_t SizeBits =
|
||||
DL.getTypeAllocSize(GV->getValueType()).getFixedValue() * 8;
|
||||
if (!GV->hasLocalLinkage())
|
||||
OutStreamer->emitSymbolAttribute(getSymbol(GV), MCSA_Global);
|
||||
OutStreamer->emitRawText(Twine("\t.bss\t") + getSymbol(GV)->getName() + "," +
|
||||
Twine(SizeBits) + "," +
|
||||
Twine(Alignment.value() * 8));
|
||||
}
|
||||
|
||||
char TMS34010AsmPrinter::ID = 0;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//===-- TMS34010CallingConv.td - TMS34010 calling conventions -*- tablegen -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// M2 implements the TI ABI directly in TMS34010ISelLowering.cpp: every
|
||||
// explicit argument is pushed on the program stack (A14) and a scalar return
|
||||
// value is delivered in A8. CC_TMS34010 therefore has no register arguments
|
||||
// and no register returns; the definition below documents that contract. The
|
||||
// C calling convention is lowered identically.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
def CC_TMS34010 : CallingConv<[
|
||||
// No arguments are assigned to registers. See LowerFormalArguments.
|
||||
// No return values are assigned to registers. See LowerReturn.
|
||||
]>;
|
||||
|
||||
// Callee-saved registers: A0-A7 and A9-A12. A13 is reserved and saved by the
|
||||
// prologue/epilogue; A8, A14 and A15 are ABI registers that are not preserved.
|
||||
def CSR_TMS34010 : CalleeSavedRegs<(add A0, A1, A2, A3, A4, A5, A6, A7,
|
||||
A9, A10, A11, A12)>;
|
||||
@@ -5,14 +5,99 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// The TI ABI splits the frame across two stacks:
|
||||
//
|
||||
// - the program stack (A14/STK) holds explicit arguments and locals;
|
||||
// - the system stack (A15/SP) holds the return address and saved registers.
|
||||
//
|
||||
// The prologue keeps the caller's FP and the used callee-saved registers on
|
||||
// the system stack, then points FP (A13) at the incoming program stack and
|
||||
// allocates the local frame above it. Each register is saved with its own
|
||||
// MOVE push, which is ABI-identical to one MMTM slot.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TMS34010FrameLowering.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "TMS34010.h"
|
||||
#include "TMS34010InstrInfo.h"
|
||||
#include "TMS34010Subtarget.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/CodeGen/MachineFrameInfo.h"
|
||||
#include "llvm/CodeGen/MachineFunction.h"
|
||||
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
StackOffset
|
||||
TMS34010FrameLowering::getFrameIndexReference(const MachineFunction &MF, int FI,
|
||||
Register &FrameReg) const {
|
||||
report_fatal_error("M1 has no stack frame", false);
|
||||
const MachineFrameInfo &MFI = MF.getFrameInfo();
|
||||
|
||||
// Incoming arguments live below the frame pointer; locals live between FP
|
||||
// and FP + StackSize. All TMS34010 addresses are bit addresses, so the
|
||||
// byte-based frame offsets are scaled by eight.
|
||||
int64_t OffsetBytes = MFI.getObjectOffset(FI);
|
||||
if (!MFI.isFixedObjectIndex(FI))
|
||||
OffsetBytes += MFI.getStackSize();
|
||||
|
||||
FrameReg = TMS34010::A13;
|
||||
return StackOffset::getFixed(OffsetBytes * 8);
|
||||
}
|
||||
|
||||
void TMS34010FrameLowering::emitPrologue(MachineFunction &MF,
|
||||
MachineBasicBlock &MBB) const {
|
||||
MachineFrameInfo &MFI = MF.getFrameInfo();
|
||||
const TMS34010InstrInfo &TII = *STI.getInstrInfo();
|
||||
|
||||
MachineBasicBlock::iterator MBBI = MBB.begin();
|
||||
DebugLoc DL = MBBI != MBB.end() ? MBBI->getDebugLoc() : DebugLoc();
|
||||
|
||||
// The field-size invariant comes first so that every frame access below is
|
||||
// a 32-bit access. FS0 = 16 keeps a later `short` load/store correct even
|
||||
// if the caller left it at 32.
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETF16));
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETF32));
|
||||
|
||||
// Save the caller's frame pointer, then the used callee-saved registers.
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::PUSH32)).addReg(TMS34010::A13);
|
||||
for (const CalleeSavedInfo &CSI : MFI.getCalleeSavedInfo())
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::PUSH32)).addReg(CSI.getReg());
|
||||
|
||||
// FP = STK, then allocate the local frame.
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::SETFP));
|
||||
|
||||
int64_t FrameBits = MFI.getStackSize() * 8;
|
||||
if (FrameBits)
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::ADDSTK)).addImm(FrameBits);
|
||||
}
|
||||
|
||||
void TMS34010FrameLowering::emitEpilogue(MachineFunction &MF,
|
||||
MachineBasicBlock &MBB) const {
|
||||
MachineFrameInfo &MFI = MF.getFrameInfo();
|
||||
const TMS34010InstrInfo &TII = *STI.getInstrInfo();
|
||||
|
||||
MachineBasicBlock::iterator MBBI = MBB.getFirstTerminator();
|
||||
assert(MBBI != MBB.end() && MBBI->getOpcode() == TMS34010::RETS &&
|
||||
"the TMS34010 epilogue must precede RETS");
|
||||
DebugLoc DL = MBBI->getDebugLoc();
|
||||
|
||||
const std::vector<CalleeSavedInfo> &CSI = MFI.getCalleeSavedInfo();
|
||||
unsigned NumSaved = 1 + CSI.size(); // FP plus the callee-saved registers.
|
||||
|
||||
if (MF.getFunction().arg_size() != 0) {
|
||||
// The caller pushed the explicit arguments on the program stack and saved
|
||||
// its old STK below the return address: SP + (NumSaved + 1) * 32.
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::LOADSTK))
|
||||
.addImm((NumSaved + 1) * 32);
|
||||
} else {
|
||||
int64_t FrameBits = MFI.getStackSize() * 8;
|
||||
if (FrameBits)
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::ADDSTK)).addImm(-FrameBits);
|
||||
}
|
||||
|
||||
// Restore the callee-saved registers in reverse order, then the old FP.
|
||||
for (const CalleeSavedInfo &CI : llvm::reverse(CSI))
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::POP32), CI.getReg());
|
||||
BuildMI(MBB, MBBI, DL, TII.get(TMS34010::POP32), TMS34010::A13);
|
||||
}
|
||||
@@ -21,16 +21,39 @@ public:
|
||||
: TargetFrameLowering(StackGrowsDown, Align(2), 0), STI(STI) {}
|
||||
|
||||
void emitPrologue(MachineFunction &MF,
|
||||
MachineBasicBlock &MBB) const override {}
|
||||
MachineBasicBlock &MBB) const override;
|
||||
|
||||
void emitEpilogue(MachineFunction &MF,
|
||||
MachineBasicBlock &MBB) const override {}
|
||||
MachineBasicBlock &MBB) const override;
|
||||
|
||||
StackOffset getFrameIndexReference(const MachineFunction &MF, int FI,
|
||||
Register &FrameReg) const override;
|
||||
|
||||
// The prologue and epilogue save and restore the ABI registers themselves,
|
||||
// on the system stack, so PEI must not allocate program-frame spill slots or
|
||||
// emit its own spill/restore instructions.
|
||||
bool assignCalleeSavedSpillSlots(
|
||||
MachineFunction &MF, const TargetRegisterInfo *TRI,
|
||||
std::vector<CalleeSavedInfo> &CSI) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool spillCalleeSavedRegisters(MachineBasicBlock &MBB,
|
||||
MachineBasicBlock::iterator MI,
|
||||
ArrayRef<CalleeSavedInfo> CSI,
|
||||
const TargetRegisterInfo *TRI) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool restoreCalleeSavedRegisters(
|
||||
MachineBasicBlock &MBB, MachineBasicBlock::iterator MI,
|
||||
MutableArrayRef<CalleeSavedInfo> CSI,
|
||||
const TargetRegisterInfo *TRI) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool hasFPImpl(const MachineFunction &MF) const override { return false; }
|
||||
bool hasFPImpl(const MachineFunction &MF) const override { return true; }
|
||||
|
||||
private:
|
||||
const TMS34010Subtarget &STI;
|
||||
|
||||
@@ -69,15 +69,15 @@ void TMS34010DAGToDAGISel::Select(SDNode *N) {
|
||||
return;
|
||||
}
|
||||
|
||||
// M1 has no stack. Any frame index means an alloca or a spill reached
|
||||
// instruction selection, which is outside the M1 contract. Check the
|
||||
// operands too: the frame index is usually an operand of a load/store that
|
||||
// would otherwise fail to select before its operand is visited.
|
||||
if (N->getOpcode() == ISD::FrameIndex)
|
||||
report_fatal_error("M1 has no stack frame", false);
|
||||
for (const SDValue &Op : N->op_values())
|
||||
if (Op.getOpcode() == ISD::FrameIndex)
|
||||
report_fatal_error("M1 has no stack frame", false);
|
||||
// A frame index becomes a GETFI pseudo with the frame index as its offset
|
||||
// operand. eliminateFrameIndex later replaces that operand with the
|
||||
// FP-relative bit offset and expandPostRAPseudo expands it to MOVEFP + ADDI.
|
||||
if (N->getOpcode() == ISD::FrameIndex) {
|
||||
int FI = cast<FrameIndexSDNode>(N)->getIndex();
|
||||
SDValue TFI = CurDAG->getTargetFrameIndex(FI, MVT::i32);
|
||||
CurDAG->SelectNodeTo(N, TMS34010::GETFI, MVT::i32, TFI);
|
||||
return;
|
||||
}
|
||||
|
||||
SelectCode(N);
|
||||
}
|
||||
@@ -8,13 +8,21 @@
|
||||
|
||||
#include "TMS34010ISelLowering.h"
|
||||
#include "TMS34010.h"
|
||||
#include "TMS34010InstrInfo.h"
|
||||
#include "TMS34010Subtarget.h"
|
||||
#include "TMS34010TargetMachine.h"
|
||||
#include "llvm/CodeGen/CallingConvLower.h"
|
||||
#include "llvm/CodeGen/MachineBasicBlock.h"
|
||||
#include "llvm/CodeGen/MachineFrameInfo.h"
|
||||
#include "llvm/CodeGen/MachineFunction.h"
|
||||
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
||||
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
||||
#include "llvm/CodeGen/TargetInstrInfo.h"
|
||||
#include "llvm/CodeGen/TargetRegisterInfo.h"
|
||||
#include "llvm/IR/Function.h"
|
||||
#include "llvm/IR/GlobalValue.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
@@ -32,6 +40,15 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
|
||||
setLoadExtAction(ISD::SEXTLOAD, MVT::i32, MVT::i16, Legal);
|
||||
setTruncStoreAction(MVT::i32, MVT::i16, Legal);
|
||||
|
||||
// There is no conditional move or dedicated setcc result; let the generic
|
||||
// legalizer turn setcc into a select_cc over 1/0 and select into a
|
||||
// select_cc, then lower the compare and branch ourselves.
|
||||
setOperationAction(ISD::SETCC, MVT::i32, Expand);
|
||||
setOperationAction(ISD::SELECT, MVT::i32, Expand);
|
||||
setOperationAction(ISD::SELECT_CC, MVT::i32, Custom);
|
||||
setOperationAction(ISD::BRCOND, MVT::Other, Custom);
|
||||
setOperationAction(ISD::BR_CC, MVT::i32, Custom);
|
||||
|
||||
computeRegisterProperties(STI.getRegisterInfo());
|
||||
|
||||
setBooleanContents(ZeroOrOneBooleanContent);
|
||||
@@ -41,58 +58,88 @@ TMS34010TargetLowering::TMS34010TargetLowering(const TargetMachine &TM,
|
||||
setMaxAtomicSizeInBitsSupported(0);
|
||||
}
|
||||
|
||||
// MOVE, MOVB and the FS0/FS1 field-size forms have no alignment requirement:
|
||||
// the TMS34010 performs unaligned field accesses (TMS34010 User's Guide,
|
||||
// section 7.5). Packed/bit-addressed struct fields and the bit-address
|
||||
// normalizer's align-1 loads and stores must therefore select directly rather
|
||||
// than being split into byte-sized accesses, which only have a sign-extending
|
||||
// MOVB load here.
|
||||
bool TMS34010TargetLowering::allowsMisalignedMemoryAccesses(
|
||||
EVT VT, unsigned AddrSpace, Align Alignment,
|
||||
MachineMemOperand::Flags Flags, unsigned *Fast) const {
|
||||
(void)Alignment;
|
||||
(void)Flags;
|
||||
if (AddrSpace != 0)
|
||||
return false;
|
||||
if (!VT.isInteger() || VT.getSizeInBits() > 32)
|
||||
return false;
|
||||
if (Fast)
|
||||
*Fast = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TMS34010TargetLowering::allowsMisalignedMemoryAccesses(
|
||||
LLT VT, unsigned AddrSpace, Align Alignment,
|
||||
MachineMemOperand::Flags Flags, unsigned *Fast) const {
|
||||
(void)Alignment;
|
||||
(void)Flags;
|
||||
if (AddrSpace != 0)
|
||||
return false;
|
||||
if (!VT.isPointer() && !(VT.isScalar() && VT.getSizeInBits() <= 32))
|
||||
return false;
|
||||
if (Fast)
|
||||
*Fast = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
SDValue TMS34010TargetLowering::LowerFormalArguments(
|
||||
SDValue Chain, CallingConv::ID CallConv, bool IsVarArg,
|
||||
const SmallVectorImpl<ISD::InputArg> &Ins, const SDLoc &DL,
|
||||
SelectionDAG &DAG, SmallVectorImpl<SDValue> &InVals) const {
|
||||
MachineFunction &MF = DAG.getMachineFunction();
|
||||
MachineRegisterInfo &RegInfo = MF.getRegInfo();
|
||||
MachineFrameInfo &MFI = MF.getFrameInfo();
|
||||
const Function &F = MF.getFunction();
|
||||
|
||||
(void)CallConv;
|
||||
|
||||
// Validate the IR signature before legalization can split or promote it.
|
||||
// Checking only the legalized MVT lets an i64 or an aggregate arrive as
|
||||
// several i32 pieces and be silently accepted (and miscompiled).
|
||||
if (F.isVarArg())
|
||||
report_fatal_error("M1 does not support varargs", false);
|
||||
if (F.isVarArg() || IsVarArg)
|
||||
report_fatal_error("M2 does not support varargs", false);
|
||||
|
||||
for (const Argument &Arg : F.args()) {
|
||||
Type *Ty = Arg.getType();
|
||||
if (!Ty->isIntegerTy(32) && !Ty->isIntegerTy(16) &&
|
||||
!Ty->isIntegerTy(8) && !Ty->isPointerTy())
|
||||
report_fatal_error(
|
||||
"M1 supports only i32, i8, i16 and pointer arguments", false);
|
||||
if (!Ty->isIntegerTy(32) && !Ty->isPointerTy())
|
||||
report_fatal_error("M2 supports only i32 and pointer arguments", false);
|
||||
}
|
||||
|
||||
static const MCPhysReg ArgRegs[] = {TMS34010::A0, TMS34010::A1, TMS34010::A2,
|
||||
TMS34010::A3};
|
||||
|
||||
if (Ins.size() > std::size(ArgRegs))
|
||||
report_fatal_error("M1 supports at most four register arguments", false);
|
||||
|
||||
SmallVector<CCValAssign, 4> ArgLocs;
|
||||
CCState CCInfo(CallConv, IsVarArg, MF, ArgLocs, *DAG.getContext());
|
||||
|
||||
// Every explicit argument is passed on the program stack. Argument i lives
|
||||
// one 32-bit slot below FP for each preceding argument, so its fixed object
|
||||
// is at byte offset -(i + 1) * 4 (bit offset -(i + 1) * 32 after frame
|
||||
// elimination).
|
||||
for (unsigned i = 0, e = Ins.size(); i != e; ++i) {
|
||||
const ISD::InputArg &In = Ins[i];
|
||||
// `returned` is an optimization hint, not an ABI feature: the argument is
|
||||
// still passed in its normal stack slot, and the callee already returns
|
||||
// through the ABI's A8. The optimizer infers it for ordinary C such as
|
||||
// `int first(int a, int b) { return a; }`, so rejecting it made valid -O2
|
||||
// input uncompilable. The ABI-affecting flags stay rejected.
|
||||
if (In.Flags.isSRet() || In.Flags.isByVal() || In.Flags.isInReg() ||
|
||||
In.Flags.isNest() || In.Flags.isReturned())
|
||||
In.Flags.isNest())
|
||||
report_fatal_error(
|
||||
"M1 supports only plain register arguments "
|
||||
"(no sret/byval/inreg/nest/returned)",
|
||||
"M2 supports only plain arguments (no sret/byval/inreg/nest)",
|
||||
false);
|
||||
MVT VT = In.VT;
|
||||
if (VT != MVT::i32)
|
||||
report_fatal_error(
|
||||
"M1 supports only 32-bit scalar and pointer arguments", false);
|
||||
CCInfo.addLoc(
|
||||
CCValAssign::getReg(i, VT, ArgRegs[i], VT, CCValAssign::Full));
|
||||
}
|
||||
if (In.VT != MVT::i32)
|
||||
report_fatal_error("M2 supports only i32 and pointer arguments", false);
|
||||
|
||||
for (const CCValAssign &VA : ArgLocs) {
|
||||
Register VReg = RegInfo.createVirtualRegister(&TMS34010::ARegsRegClass);
|
||||
RegInfo.addLiveIn(VA.getLocReg(), VReg);
|
||||
InVals.push_back(
|
||||
DAG.getCopyFromReg(Chain, DL, VReg, VA.getLocVT()));
|
||||
int FI = MFI.CreateFixedObject(4, -static_cast<int64_t>(i + 1) * 4, true);
|
||||
SDValue FIN = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
|
||||
SDValue Load =
|
||||
DAG.getLoad(MVT::i32, DL, Chain, FIN,
|
||||
MachinePointerInfo::getFixedStack(MF, FI), Align(2));
|
||||
Chain = Load.getValue(1);
|
||||
InVals.push_back(Load);
|
||||
}
|
||||
|
||||
return Chain;
|
||||
@@ -101,7 +148,102 @@ SDValue TMS34010TargetLowering::LowerFormalArguments(
|
||||
SDValue TMS34010TargetLowering::LowerCall(
|
||||
TargetLowering::CallLoweringInfo &CLI,
|
||||
SmallVectorImpl<SDValue> &InVals) const {
|
||||
report_fatal_error("M1 has no calls; M2 adds the TI ABI", false);
|
||||
SelectionDAG &DAG = CLI.DAG;
|
||||
const SDLoc &DL = CLI.DL;
|
||||
MachineFunction &MF = DAG.getMachineFunction();
|
||||
|
||||
// M2 has no tail calls. The optimizer is free to mark a call tail, so clear
|
||||
// the marker and lower an ordinary call rather than failing on it.
|
||||
CLI.IsTailCall = false;
|
||||
|
||||
// M2 has no indirect calls: the callee must be a direct global symbol. A
|
||||
// function pointer, a plain integer, or an external symbol (asm label) is
|
||||
// outside the M2 ABI.
|
||||
auto *Callee = dyn_cast<GlobalAddressSDNode>(CLI.Callee);
|
||||
if (!Callee || !isa<Function>(Callee->getGlobal()))
|
||||
report_fatal_error("M2 supports only direct calls to global functions",
|
||||
false);
|
||||
const Function *CalleeFn = cast<Function>(Callee->getGlobal());
|
||||
|
||||
// CLI.IsVarArg is not populated for ordinary direct calls in this LLVM: the
|
||||
// call-site function type records only the actual arguments, so the callee
|
||||
// declaration's own type is the reliable varargs source.
|
||||
if (CLI.IsVarArg || CalleeFn->isVarArg())
|
||||
report_fatal_error("M2 does not support varargs", false);
|
||||
|
||||
// Check the original IR types: legalization may promote an i8 or split an
|
||||
// aggregate before the arguments reach Outs/OutVals.
|
||||
if (!CLI.RetTy->isVoidTy() && !CLI.RetTy->isIntegerTy(32) &&
|
||||
!CLI.RetTy->isPointerTy())
|
||||
report_fatal_error("M2 supports only void/i32/pointer returns", false);
|
||||
|
||||
for (const TargetLowering::ArgListEntry &Arg : CLI.Args) {
|
||||
if (Arg.IsSRet || Arg.IsByVal || Arg.IsInReg || Arg.IsNest ||
|
||||
Arg.IsInAlloca || Arg.IsPreallocated)
|
||||
report_fatal_error(
|
||||
"M2 supports only plain call arguments "
|
||||
"(no sret/byval/inreg/nest/inalloca/preallocated)",
|
||||
false);
|
||||
Type *Ty = Arg.OrigTy;
|
||||
if (!Ty->isIntegerTy(32) && !Ty->isPointerTy())
|
||||
report_fatal_error("M2 supports only i32 and pointer call arguments",
|
||||
false);
|
||||
}
|
||||
for (const ISD::OutputArg &Out : CLI.Outs)
|
||||
if (Out.VT != MVT::i32)
|
||||
report_fatal_error("M2 supports only i32 and pointer call arguments",
|
||||
false);
|
||||
|
||||
SDValue Chain = CLI.Chain;
|
||||
|
||||
// With at least one argument, save the caller's STK on the system stack.
|
||||
// The callee's epilogue reloads it, so the caller's A14 is restored even
|
||||
// though the arguments below it are popped by the callee's RETS.
|
||||
if (!CLI.Outs.empty())
|
||||
Chain = SDValue(
|
||||
DAG.getMachineNode(TMS34010::PUSH32, DL, MVT::Other,
|
||||
DAG.getRegister(TMS34010::A14, MVT::i32), Chain),
|
||||
0);
|
||||
|
||||
// Arguments are pushed last-first, so the first argument ends up at FP(-32).
|
||||
// Each push advances A14; the chain keeps the pushes in order.
|
||||
for (unsigned I = CLI.Outs.size(); I != 0; --I) {
|
||||
SDValue Arg = CLI.OutVals[I - 1];
|
||||
Chain = SDValue(
|
||||
DAG.getMachineNode(TMS34010::PUSHARG, DL, MVT::Other, Arg, Chain), 0);
|
||||
}
|
||||
|
||||
// CALLA references the symbol directly. A TargetGlobalAddress operand is
|
||||
// left for the asm printer as a relocation; a plain GlobalAddress would be
|
||||
// selected to a MOVI and the call would lose its symbol.
|
||||
SDValue Target = DAG.getTargetGlobalAddress(
|
||||
Callee->getGlobal(), DL, getPointerTy(DAG.getDataLayout()),
|
||||
Callee->getOffset(), Callee->getTargetFlags());
|
||||
|
||||
const TargetRegisterInfo *TRI = DAG.getSubtarget().getRegisterInfo();
|
||||
const uint32_t *Mask = TRI->getCallPreservedMask(MF, CLI.CallConv);
|
||||
assert(Mask && "M2 requires a call-preserved register mask");
|
||||
SDValue RegMask = DAG.getRegisterMask(Mask);
|
||||
|
||||
Chain = SDValue(DAG.getMachineNode(TMS34010::CALLA, DL, MVT::Other, Target,
|
||||
RegMask, Chain),
|
||||
0);
|
||||
|
||||
// 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.
|
||||
Chain = SDValue(
|
||||
DAG.getMachineNode(TMS34010::SETF16, DL, MVT::Other, Chain), 0);
|
||||
Chain = SDValue(
|
||||
DAG.getMachineNode(TMS34010::SETF32, DL, MVT::Other, Chain), 0);
|
||||
|
||||
// A scalar result arrives in A8.
|
||||
if (!CLI.Ins.empty()) {
|
||||
SDValue Ret = DAG.getCopyFromReg(Chain, DL, TMS34010::A8, MVT::i32);
|
||||
Chain = Ret.getValue(1);
|
||||
InVals.push_back(Ret);
|
||||
}
|
||||
|
||||
return Chain;
|
||||
}
|
||||
|
||||
bool TMS34010TargetLowering::CanLowerReturn(
|
||||
@@ -109,11 +251,11 @@ bool TMS34010TargetLowering::CanLowerReturn(
|
||||
const SmallVectorImpl<ISD::OutputArg> &Outs, LLVMContext &Context,
|
||||
const Type *RetTy) const {
|
||||
// Returning false here makes SelectionDAG demote the return value to an
|
||||
// sret pointer, silently changing the signature to something M1 cannot
|
||||
// sret pointer, silently changing the signature to something M2 cannot
|
||||
// lower either. Reject unsupported returns loudly instead.
|
||||
if (RetTy->isVoidTy() || RetTy->isIntegerTy(32) || RetTy->isPointerTy())
|
||||
return true;
|
||||
report_fatal_error("M1 supports only void/i32/pointer returns", false);
|
||||
report_fatal_error("M2 supports only void/i32/pointer returns", false);
|
||||
}
|
||||
|
||||
SDValue TMS34010TargetLowering::LowerReturn(
|
||||
@@ -121,18 +263,287 @@ SDValue TMS34010TargetLowering::LowerReturn(
|
||||
const SmallVectorImpl<ISD::OutputArg> &Outs,
|
||||
const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
|
||||
SelectionDAG &DAG) const {
|
||||
if (Outs.empty())
|
||||
return DAG.getNode(TMS34010ISD::RET_GLUE, DL, MVT::Other, Chain);
|
||||
MachineFunction &MF = DAG.getMachineFunction();
|
||||
|
||||
if (Outs.size() != 1 || Outs[0].VT != MVT::i32)
|
||||
report_fatal_error("M1 supports only void/i32/pointer returns", false);
|
||||
(void)CallConv;
|
||||
(void)IsVarArg;
|
||||
|
||||
SDValue Glue;
|
||||
Chain = DAG.getCopyToReg(Chain, DL, TMS34010::A0, OutVals[0], Glue);
|
||||
Glue = Chain.getValue(1);
|
||||
// A scalar result is returned in A8. A8 is an implicit use of RETS, so the
|
||||
// copy stays live through the terminator.
|
||||
if (!Outs.empty()) {
|
||||
if (Outs.size() != 1 || Outs[0].VT != MVT::i32)
|
||||
report_fatal_error("M2 supports only void/i32/pointer returns", false);
|
||||
Chain = DAG.getCopyToReg(Chain, DL, TMS34010::A8, OutVals[0]);
|
||||
}
|
||||
|
||||
SmallVector<SDValue, 3> RetOps(1, Chain);
|
||||
RetOps.push_back(DAG.getRegister(TMS34010::A0, MVT::i32));
|
||||
RetOps.push_back(Glue);
|
||||
return DAG.getNode(TMS34010ISD::RET_GLUE, DL, MVT::Other, RetOps);
|
||||
// RETS must pop the caller's argument words: 2 when the function has
|
||||
// explicit arguments, 0 otherwise. The value depends on the signature, so
|
||||
// emit RETS as a machine node instead of a pattern with a fixed immediate.
|
||||
unsigned N = MF.getFunction().arg_size() != 0 ? 2 : 0;
|
||||
SDValue NVal = DAG.getTargetConstant(N, DL, MVT::i32);
|
||||
return SDValue(
|
||||
DAG.getMachineNode(TMS34010::RETS, DL, MVT::Other, NVal, Chain), 0);
|
||||
}
|
||||
|
||||
/// Map an integer condition code onto the JAcc opcode that tests it. The
|
||||
/// mapping is the dis34 condition order: signed compares use LT/GE/LE/GT,
|
||||
/// unsigned compares use LO(LT), HS(GE), LS(LE) and HI(GT), and equality uses
|
||||
/// EQ/NE. SETTRUE/SETFALSE have no flag test and must be lowered before this
|
||||
/// point (see emitCmpAndBranch and LowerSELECT_CC).
|
||||
static unsigned getBranchOpcode(ISD::CondCode CC) {
|
||||
switch (CC) {
|
||||
case ISD::SETEQ:
|
||||
case ISD::SETUEQ:
|
||||
return TMS34010::JAEQ;
|
||||
case ISD::SETNE:
|
||||
case ISD::SETUNE:
|
||||
return TMS34010::JANE;
|
||||
case ISD::SETLT:
|
||||
return TMS34010::JALT;
|
||||
case ISD::SETGE:
|
||||
return TMS34010::JAGE;
|
||||
case ISD::SETLE:
|
||||
return TMS34010::JALE;
|
||||
case ISD::SETGT:
|
||||
return TMS34010::JAGT;
|
||||
case ISD::SETULT:
|
||||
return TMS34010::JALO;
|
||||
case ISD::SETUGE:
|
||||
return TMS34010::JAHS;
|
||||
case ISD::SETULE:
|
||||
return TMS34010::JALS;
|
||||
case ISD::SETUGT:
|
||||
return TMS34010::JAHI;
|
||||
case ISD::SETTRUE:
|
||||
case ISD::SETFALSE:
|
||||
report_fatal_error("TMS34010: constant branch condition was not lowered",
|
||||
false);
|
||||
default:
|
||||
report_fatal_error("TMS34010: unsupported integer comparison", false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit 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)) {
|
||||
SDValue Imm = DAG.getTargetConstant(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.getTargetConstant(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::BRCOND:
|
||||
return LowerBRCOND(Op, DAG);
|
||||
case ISD::BR_CC:
|
||||
return LowerBR_CC(Op, DAG);
|
||||
case ISD::SELECT_CC:
|
||||
return LowerSELECT_CC(Op, DAG);
|
||||
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);
|
||||
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,35 @@ public:
|
||||
const SmallVectorImpl<ISD::OutputArg> &Outs,
|
||||
const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
|
||||
SelectionDAG &DAG) const override;
|
||||
|
||||
SDValue LowerOperation(SDValue Op, SelectionDAG &DAG) const override;
|
||||
|
||||
MachineBasicBlock *
|
||||
EmitInstrWithCustomInserter(MachineInstr &MI,
|
||||
MachineBasicBlock *MBB) const override;
|
||||
|
||||
bool shouldFoldConstantShiftPairToMask(const SDNode *N) const override {
|
||||
// The TMS34010 has immediate shift instructions, so keeping a shift pair
|
||||
// as SLL/SRL/SRA is cheaper than materialising a mask and ANDing.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool allowsMisalignedMemoryAccesses(
|
||||
EVT VT, unsigned AddrSpace = 0, Align Alignment = Align(1),
|
||||
MachineMemOperand::Flags Flags = MachineMemOperand::MONone,
|
||||
unsigned *Fast = nullptr) const override;
|
||||
|
||||
bool allowsMisalignedMemoryAccesses(
|
||||
LLT VT, unsigned AddrSpace = 0, Align Alignment = Align(1),
|
||||
MachineMemOperand::Flags Flags = MachineMemOperand::MONone,
|
||||
unsigned *Fast = nullptr) const override;
|
||||
|
||||
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
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include "TMS34010InstrInfo.h"
|
||||
#include "TMS34010.h"
|
||||
#include "TMS34010Subtarget.h"
|
||||
#include "llvm/CodeGen/MachineFunction.h"
|
||||
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
||||
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
|
||||
using namespace llvm;
|
||||
@@ -20,13 +22,151 @@ using namespace llvm;
|
||||
TMS34010InstrInfo::TMS34010InstrInfo(const TMS34010Subtarget &STI)
|
||||
: 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,
|
||||
MachineBasicBlock::iterator I,
|
||||
const DebugLoc &DL, Register DestReg,
|
||||
Register SrcReg, bool KillSrc,
|
||||
bool RenamableDest,
|
||||
bool RenamableSrc) const {
|
||||
if (!TMS34010::ARegsRegClass.contains(DestReg, SrcReg))
|
||||
if (!TMS34010::ARegsAllRegClass.contains(DestReg, SrcReg))
|
||||
llvm_unreachable("Impossible reg-to-reg copy");
|
||||
|
||||
BuildMI(MBB, I, DL, get(TMS34010::MOVE), DestReg)
|
||||
@@ -37,12 +177,55 @@ void TMS34010InstrInfo::storeRegToStackSlot(
|
||||
MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, Register SrcReg,
|
||||
bool isKill, int FrameIndex, const TargetRegisterClass *RC, Register VReg,
|
||||
MachineInstr::MIFlag Flags) const {
|
||||
report_fatal_error("M1 has no stack frame", false);
|
||||
MachineFunction &MF = *MBB.getParent();
|
||||
MachineRegisterInfo &RegInfo = MF.getRegInfo();
|
||||
DebugLoc DL = MI != MBB.end() ? MI->getDebugLoc() : DebugLoc();
|
||||
|
||||
// GETFI materialises the spill slot's address into a virtual A register; it
|
||||
// is expanded to MOVEFP + ADDI after frame elimination.
|
||||
Register Addr = RegInfo.createVirtualRegister(&TMS34010::ARegsRegClass);
|
||||
BuildMI(MBB, MI, DL, get(TMS34010::GETFI), Addr)
|
||||
.addFrameIndex(FrameIndex)
|
||||
.setMIFlag(Flags);
|
||||
BuildMI(MBB, MI, DL, get(TMS34010::MOVE32Store))
|
||||
.addReg(SrcReg, getKillRegState(isKill))
|
||||
.addReg(Addr, RegState::Kill)
|
||||
.setMIFlag(Flags);
|
||||
}
|
||||
|
||||
void TMS34010InstrInfo::loadRegFromStackSlot(
|
||||
MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, Register DestReg,
|
||||
int FrameIdx, const TargetRegisterClass *RC, Register VReg, unsigned SubReg,
|
||||
MachineInstr::MIFlag Flags) const {
|
||||
report_fatal_error("M1 has no stack frame", false);
|
||||
MachineFunction &MF = *MBB.getParent();
|
||||
MachineRegisterInfo &RegInfo = MF.getRegInfo();
|
||||
DebugLoc DL = MI != MBB.end() ? MI->getDebugLoc() : DebugLoc();
|
||||
|
||||
Register Addr = RegInfo.createVirtualRegister(&TMS34010::ARegsRegClass);
|
||||
BuildMI(MBB, MI, DL, get(TMS34010::GETFI), Addr)
|
||||
.addFrameIndex(FrameIdx)
|
||||
.setMIFlag(Flags);
|
||||
BuildMI(MBB, MI, DL, get(TMS34010::MOVE32Load), DestReg)
|
||||
.addReg(Addr, RegState::Kill)
|
||||
.setMIFlag(Flags);
|
||||
}
|
||||
|
||||
bool TMS34010InstrInfo::expandPostRAPseudo(MachineInstr &MI) const {
|
||||
if (MI.getOpcode() != TMS34010::GETFI)
|
||||
return false;
|
||||
|
||||
MachineBasicBlock &MBB = *MI.getParent();
|
||||
DebugLoc DL = MI.getDebugLoc();
|
||||
Register DstReg = MI.getOperand(0).getReg();
|
||||
int64_t Offset = MI.getOperand(1).getImm();
|
||||
|
||||
// The frame index has already been replaced by the FP-relative bit offset.
|
||||
BuildMI(MBB, MI, DL, get(TMS34010::MOVEFP), DstReg);
|
||||
if (Offset)
|
||||
BuildMI(MBB, MI, DL, get(TMS34010::ADDI), DstReg)
|
||||
.addReg(DstReg)
|
||||
.addImm(Offset);
|
||||
|
||||
MI.eraseFromParent();
|
||||
return true;
|
||||
}
|
||||
@@ -43,6 +43,25 @@ public:
|
||||
Register VReg, unsigned SubReg = 0,
|
||||
MachineInstr::MIFlag Flags = MachineInstr::NoFlags)
|
||||
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
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
// M1 describes encodings for future MC work, but only the assembly printer is
|
||||
// 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,
|
||||
@@ -30,10 +35,13 @@ def ADD : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, ARegs:$val),
|
||||
"ADD $val, $dst",
|
||||
[(set ARegs:$dst, (add ARegs:$src, ARegs:$val))]> {
|
||||
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;
|
||||
let Inst{15-10} = 0b010000;
|
||||
let Inst{9-5} = src;
|
||||
let Inst{9-5} = val;
|
||||
let Inst{4-0} = dst;
|
||||
}
|
||||
|
||||
@@ -42,6 +50,7 @@ def ADDI : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
||||
"ADDI $imm, $dst",
|
||||
[(set ARegs:$dst, (add ARegs:$src, imm:$imm))]> {
|
||||
let Constraints = "$src = $dst";
|
||||
let Defs = [ST];
|
||||
let Size = 6;
|
||||
bits<5> dst;
|
||||
bits<32> imm;
|
||||
@@ -51,10 +60,228 @@ def ADDI : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src, i32imm:$imm),
|
||||
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};
|
||||
}
|
||||
|
||||
// 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.
|
||||
def MOVI : TMS34010Inst<(outs ARegs:$dst), (ins i32imm:$imm),
|
||||
"MOVI $imm, $dst",
|
||||
[(set ARegs:$dst, imm:$imm)]> {
|
||||
let Defs = [ST];
|
||||
let Size = 6;
|
||||
bits<5> dst;
|
||||
bits<32> imm;
|
||||
@@ -64,9 +291,12 @@ def MOVI : TMS34010Inst<(outs ARegs:$dst), (ins i32imm:$imm),
|
||||
let Inst{47-32} = imm{31-16};
|
||||
}
|
||||
|
||||
// MOVE Rs, Rd: 0100 11MS SSSR DDDD
|
||||
def MOVE : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src),
|
||||
// MOVE Rs, Rd: 0100 11MS SSSR DDDD. Any A register may be copied, including
|
||||
// the reserved A8/A13; these copies are emitted directly, not selected from a
|
||||
// pattern.
|
||||
def MOVE : TMS34010Inst<(outs ARegsAll:$dst), (ins ARegsAll:$src),
|
||||
"MOVE $src, $dst", []> {
|
||||
let Defs = [ST];
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-10} = 0b010011;
|
||||
@@ -86,6 +316,7 @@ def MOVE32Load : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src),
|
||||
"MOVE *$src, $dst, 1",
|
||||
[(set ARegs:$dst, (load ARegs:$src))]> {
|
||||
let mayLoad = 1;
|
||||
let Defs = [ST];
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-9} = 0b1000011;
|
||||
@@ -99,6 +330,7 @@ def MOVE32Store : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
||||
"MOVE $src, *$dst, 1",
|
||||
[(store ARegs:$src, ARegs:$dst)]> {
|
||||
let mayStore = 1;
|
||||
let Defs = [ST];
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-9} = 0b1000001;
|
||||
@@ -112,6 +344,7 @@ def MOVE16Load : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src),
|
||||
"MOVE *$src, $dst, 0",
|
||||
[(set ARegs:$dst, (sextloadi16 ARegs:$src))]> {
|
||||
let mayLoad = 1;
|
||||
let Defs = [ST];
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-9} = 0b1000010;
|
||||
@@ -125,6 +358,7 @@ def MOVE16Store : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
||||
"MOVE $src, *$dst, 0",
|
||||
[(truncstorei16 ARegs:$src, ARegs:$dst)]> {
|
||||
let mayStore = 1;
|
||||
let Defs = [ST];
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-9} = 0b1000000;
|
||||
@@ -138,6 +372,7 @@ def MOVBLoad : TMS34010Inst<(outs ARegs:$dst), (ins ARegs:$src),
|
||||
"MOVB *$src, $dst",
|
||||
[(set ARegs:$dst, (sextloadi8 ARegs:$src))]> {
|
||||
let mayLoad = 1;
|
||||
let Defs = [ST];
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-9} = 0b1000111;
|
||||
@@ -151,6 +386,7 @@ def MOVBStore : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
||||
"MOVB $src, *$dst",
|
||||
[(truncstorei8 ARegs:$src, ARegs:$dst)]> {
|
||||
let mayStore = 1;
|
||||
let Defs = [ST];
|
||||
bits<5> src;
|
||||
bits<5> dst;
|
||||
let Inst{15-9} = 0b1000110;
|
||||
@@ -161,6 +397,7 @@ def MOVBStore : TMS34010Inst<(outs), (ins ARegs:$src, ARegs:$dst),
|
||||
|
||||
// CLR Rd: XOR self, base 0x5600 (0101 011S SSSR DDDD with S == D).
|
||||
def CLR : TMS34010Inst<(outs ARegs:$dst), (ins), "CLR $dst", []> {
|
||||
let Defs = [ST];
|
||||
bits<5> dst;
|
||||
let Inst{15-9} = 0b0101011;
|
||||
let Inst{8-5} = dst{3-0};
|
||||
@@ -173,14 +410,166 @@ def RETS : TMS34010Inst<(outs), (ins i32imm:$n), "RETS $n", []> {
|
||||
let isReturn = 1;
|
||||
let isTerminator = 1;
|
||||
let isBarrier = 1;
|
||||
// N is 2 when the function has explicit arguments and 0 otherwise. The
|
||||
// value depends on the signature, so LowerReturn emits this instruction as
|
||||
// a machine node rather than through a pattern.
|
||||
let Uses = [A8];
|
||||
bits<5> n;
|
||||
let Inst{15-9} = 0b0000100;
|
||||
let Inst{8-5} = 0b1011;
|
||||
let Inst{4-0} = n;
|
||||
}
|
||||
|
||||
// Push/pop on the system stack (A15/SP). The CPU predecrements and
|
||||
// postincrements by one 32-bit slot, so each is ABI-identical to one MMTM/MMFM
|
||||
// slot. MMTM/MMFM is a later code-size optimisation.
|
||||
def PUSH32 : TMS34010Inst<(outs), (ins ARegsAll:$src),
|
||||
"MOVE $src, -*SP, 1", []> {
|
||||
let mayStore = 1;
|
||||
let Uses = [A15];
|
||||
let Defs = [A15, ST];
|
||||
}
|
||||
|
||||
def POP32 : TMS34010Inst<(outs ARegsAll:$dst), (ins),
|
||||
"MOVE *SP+, $dst, 1", []> {
|
||||
let mayLoad = 1;
|
||||
let Uses = [A15];
|
||||
let Defs = [A15, ST];
|
||||
}
|
||||
|
||||
// Push one 32-bit argument on the program stack (A14/STK).
|
||||
def PUSHARG : TMS34010Inst<(outs), (ins ARegs:$src),
|
||||
"MOVE $src, *A14+, 1", []> {
|
||||
let mayStore = 1;
|
||||
let Uses = [A14];
|
||||
let Defs = [A14, ST];
|
||||
}
|
||||
|
||||
// FP = STK, and the program-stack frame adjustment (negative to deallocate).
|
||||
def SETFP : TMS34010Inst<(outs), (ins), "MOVE A14, A13", []> {
|
||||
let Uses = [A14];
|
||||
let Defs = [A13, ST];
|
||||
}
|
||||
|
||||
def ADDSTK : TMS34010Inst<(outs), (ins i32imm:$imm), "ADDI $imm, A14", []> {
|
||||
let Uses = [A14];
|
||||
let Defs = [A14, ST];
|
||||
}
|
||||
|
||||
// Materialise a frame address; the base is the reserved FP, so this is not
|
||||
// the allocatable-register MOVE pattern.
|
||||
def MOVEFP : TMS34010Inst<(outs ARegs:$dst), (ins), "MOVE A13, $dst", []> {
|
||||
let Uses = [A13];
|
||||
let Defs = [ST];
|
||||
}
|
||||
|
||||
// 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),
|
||||
"MOVE *SP($off), A14, 1", []> {
|
||||
let mayLoad = 1;
|
||||
let Size = 4;
|
||||
let Uses = [A15];
|
||||
let Defs = [A14, ST];
|
||||
}
|
||||
|
||||
// The ABI's field-size pair. FS0 must be 16 for `short` accesses and FS1
|
||||
// must be 32 for words, so the prologue and every call re-establish both.
|
||||
// ROM code may leave either field at a different width.
|
||||
def SETF16 : TMS34010Inst<(outs), (ins), "SETF 16, 0, 0", []> {
|
||||
let hasSideEffects = 1;
|
||||
}
|
||||
|
||||
def SETF32 : TMS34010Inst<(outs), (ins), "SETF 32, 0, 1", []> {
|
||||
let hasSideEffects = 1;
|
||||
}
|
||||
|
||||
// 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", []> {
|
||||
let isCall = 1;
|
||||
let Size = 6;
|
||||
// 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.
|
||||
let Uses = [A14];
|
||||
let Defs = [A8, A14, ST];
|
||||
}
|
||||
|
||||
// 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 elimination to MOVEFP dst ; ADDI off, dst
|
||||
def GETFI : TMS34010Inst<(outs ARegs:$dst), (ins i32imm:$off), "# GETFI", []> {
|
||||
let isPseudo = 1;
|
||||
}
|
||||
|
||||
def NOP : TMS34010Inst<(outs), (ins), "NOP", []> {
|
||||
let Inst{15-0} = 0x0300;
|
||||
}
|
||||
|
||||
def : Pat<(TMS34010retglue), (RETS 0)>;
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "TMS34010Subtarget.h"
|
||||
#include "llvm/CodeGen/MachineFrameInfo.h"
|
||||
#include "llvm/CodeGen/MachineFunction.h"
|
||||
#include "llvm/CodeGen/TargetFrameLowering.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
@@ -22,13 +23,40 @@ TMS34010RegisterInfo::TMS34010RegisterInfo() : TMS34010GenRegisterInfo(TMS34010:
|
||||
|
||||
const MCPhysReg *
|
||||
TMS34010RegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const {
|
||||
static const MCPhysReg Empty[] = {0};
|
||||
return Empty;
|
||||
// A13 is reserved by the ABI, so PEI cannot list it here; the prologue and
|
||||
// epilogue save and restore it explicitly.
|
||||
return CSR_TMS34010_SaveList;
|
||||
}
|
||||
|
||||
const uint32_t *TMS34010RegisterInfo::getCallPreservedMask(
|
||||
const MachineFunction &MF, CallingConv::ID CC) const {
|
||||
// CC_TMS34010 is documented in TMS34010CallingConv.td and lowered directly;
|
||||
// like the C convention it preserves only A0-A7 and A9-A12 across a call.
|
||||
(void)MF;
|
||||
(void)CC;
|
||||
return CSR_TMS34010_RegMask;
|
||||
}
|
||||
|
||||
BitVector
|
||||
TMS34010RegisterInfo::getReservedRegs(const MachineFunction &MF) const {
|
||||
return BitVector(getNumRegs());
|
||||
BitVector Reserved(getNumRegs());
|
||||
|
||||
// A8 is the return-value register, A13 the frame pointer, A14 the program
|
||||
// stack pointer and A15 the system stack pointer.
|
||||
Reserved.set(TMS34010::A8);
|
||||
Reserved.set(TMS34010::A13);
|
||||
Reserved.set(TMS34010::A14);
|
||||
Reserved.set(TMS34010::A15);
|
||||
|
||||
// The status register is defined by CMP/CMPI and read by the conditional
|
||||
// branches; it must never be allocated.
|
||||
Reserved.set(TMS34010::ST);
|
||||
|
||||
// The B file is reserved for a later task.
|
||||
for (unsigned Reg = TMS34010::B0; Reg <= TMS34010::B15; ++Reg)
|
||||
Reserved.set(Reg);
|
||||
|
||||
return Reserved;
|
||||
}
|
||||
|
||||
const TargetRegisterClass *
|
||||
@@ -39,14 +67,27 @@ TMS34010RegisterInfo::getPointerRegClass(unsigned Kind) const {
|
||||
bool TMS34010RegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
|
||||
int SPAdj, unsigned FIOperandNum,
|
||||
RegScavenger *RS) const {
|
||||
MachineFunction &MF = *II->getParent()->getParent();
|
||||
MachineInstr &MI = *II;
|
||||
MachineFunction &MF = *MI.getParent()->getParent();
|
||||
|
||||
assert(MI.getOpcode() == TMS34010::GETFI &&
|
||||
"only GETFI carries a frame index in M2");
|
||||
(void)SPAdj;
|
||||
(void)RS;
|
||||
|
||||
Register FrameReg;
|
||||
getFrameLowering(MF)->getFrameIndexReference(
|
||||
MF, II->getOperand(FIOperandNum).getIndex(), FrameReg);
|
||||
StackOffset Offset = getFrameLowering(MF)->getFrameIndexReference(
|
||||
MF, MI.getOperand(FIOperandNum).getIndex(), FrameReg);
|
||||
assert(Offset.getScalable() == 0 && "scalable stack offsets are unsupported");
|
||||
assert(FrameReg == TMS34010::A13 && "the frame base is always A13");
|
||||
|
||||
// GETFI is expanded after frame elimination into MOVEFP dst ; ADDI off, dst
|
||||
// with the FP-relative bit offset as an immediate.
|
||||
MI.getOperand(FIOperandNum).ChangeToImmediate(Offset.getFixed());
|
||||
return false;
|
||||
}
|
||||
|
||||
Register
|
||||
TMS34010RegisterInfo::getFrameRegister(const MachineFunction &MF) const {
|
||||
return TMS34010::A14;
|
||||
return TMS34010::A13;
|
||||
}
|
||||
@@ -22,6 +22,9 @@ public:
|
||||
|
||||
const MCPhysReg *getCalleeSavedRegs(const MachineFunction *MF) const override;
|
||||
|
||||
const uint32_t *getCallPreservedMask(const MachineFunction &MF,
|
||||
CallingConv::ID CC) const override;
|
||||
|
||||
BitVector getReservedRegs(const MachineFunction &MF) const override;
|
||||
|
||||
const TargetRegisterClass *
|
||||
|
||||
@@ -13,7 +13,10 @@ class TMS34010Reg<bits<5> num, string n> : Register<n> {
|
||||
let DwarfNumbers = [num];
|
||||
}
|
||||
|
||||
// The A file holds the general-purpose registers. M1 only allocates A0-A7.
|
||||
// The A file holds the general-purpose registers. A8 (return value), A13
|
||||
// (frame pointer), A14 (program-stack pointer) and A15 (system-stack pointer)
|
||||
// are reserved by the ABI and are deliberately left out of the allocatable
|
||||
// class.
|
||||
def A0 : TMS34010Reg<0, "A0">;
|
||||
def A1 : TMS34010Reg<1, "A1">;
|
||||
def A2 : TMS34010Reg<2, "A2">;
|
||||
@@ -31,6 +34,13 @@ def A13 : TMS34010Reg<13, "A13">;
|
||||
def A14 : TMS34010Reg<14, "A14">;
|
||||
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.
|
||||
def B0 : TMS34010Reg<16, "B0">;
|
||||
def B1 : TMS34010Reg<17, "B1">;
|
||||
@@ -50,7 +60,17 @@ def B14 : TMS34010Reg<30, "B14">;
|
||||
def B15 : TMS34010Reg<31, "B15">;
|
||||
|
||||
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)>;
|
||||
|
||||
// ARegsAll names every A register. It is used only as the operand class of
|
||||
// the frame and system-stack instructions, which need to name the reserved
|
||||
// FP/STK/SP registers explicitly; it is never allocated.
|
||||
def ARegsAll : RegisterClass<"TMS34010", [i32], 32,
|
||||
(add A0, A1, A2, A3, A4, A5, A6, A7,
|
||||
A8, A9, A10, A11, A12, A13, A14, A15)> {
|
||||
let isAllocatable = 0;
|
||||
}
|
||||
|
||||
def BRegs : RegisterClass<"TMS34010", [i32], 32,
|
||||
(add B0, B1, B2, B3, B4, B5, B6, B7,
|
||||
|
||||
@@ -9,16 +9,23 @@
|
||||
#include "TMS34010TargetMachine.h"
|
||||
#include "TMS34010.h"
|
||||
#include "TargetInfo/TMS34010TargetInfo.h"
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
|
||||
#include "llvm/CodeGen/TargetPassConfig.h"
|
||||
#include "llvm/IR/CFG.h"
|
||||
#include "llvm/IR/Function.h"
|
||||
#include "llvm/IR/GlobalValue.h"
|
||||
#include "llvm/IR/Mangler.h"
|
||||
#include "llvm/MC/MCContext.h"
|
||||
#include "llvm/MC/MCSymbol.h"
|
||||
#include "llvm/MC/TargetRegistry.h"
|
||||
#include "llvm/Pass.h"
|
||||
#include "llvm/Support/Compiler.h"
|
||||
#include "llvm/Target/TargetOptions.h"
|
||||
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
|
||||
#include <optional>
|
||||
|
||||
using namespace llvm;
|
||||
@@ -29,6 +36,37 @@ using namespace llvm;
|
||||
static const char *const TMS34010DataLayout =
|
||||
"e-m:e-p:32:16-i8:8-i16:16-i32:16-i64:16-f32:16-f64:16-n8:16:32";
|
||||
|
||||
// GSPA 2.00 identifiers accept only alphanumerics and `_`; in particular a
|
||||
// private IR symbol such as `@.str` becomes `_.str`, which the assembler
|
||||
// rejects as an invalid opcode/expression. Encode every other character as
|
||||
// `_HH` so the mapping is deterministic and GSPA-safe. It is not injective:
|
||||
// `_` is left unchanged, so `@x.y` and `@x_2Ey` both map to `_x_2Ey`.
|
||||
static void sanitizeGSPASymbolName(SmallVectorImpl<char> &Name) {
|
||||
bool NeedsSanitizing = false;
|
||||
for (char C : Name) {
|
||||
if (!isAlnum(C) && C != '_') {
|
||||
NeedsSanitizing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!NeedsSanitizing)
|
||||
return;
|
||||
|
||||
SmallString<128> Sanitized;
|
||||
Sanitized.reserve(Name.size());
|
||||
for (char C : Name) {
|
||||
if (isAlnum(C) || C == '_') {
|
||||
Sanitized.push_back(C);
|
||||
} else {
|
||||
unsigned char Byte = static_cast<unsigned char>(C);
|
||||
Sanitized.push_back('_');
|
||||
Sanitized.push_back(hexdigit(Byte >> 4));
|
||||
Sanitized.push_back(hexdigit(Byte & 0xF));
|
||||
}
|
||||
}
|
||||
Name.assign(Sanitized.begin(), Sanitized.end());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class TMS34010ObjectFile : public TargetLoweringObjectFileELF {
|
||||
@@ -37,6 +75,7 @@ public:
|
||||
const TargetMachine &TM) const override {
|
||||
SmallString<128> Name("_");
|
||||
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
|
||||
sanitizeGSPASymbolName(Name);
|
||||
return getContext().getOrCreateSymbol(Name);
|
||||
}
|
||||
|
||||
@@ -46,6 +85,7 @@ public:
|
||||
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
|
||||
OutName.push_back('_');
|
||||
OutName.append(Name.begin(), Name.end());
|
||||
sanitizeGSPASymbolName(OutName);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -57,6 +97,39 @@ static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
|
||||
|
||||
namespace {
|
||||
|
||||
/// Every register-to-register MOVE writes the status flags. PHI elimination
|
||||
/// inserts a loop backedge's copy immediately before the terminator; if the
|
||||
/// terminator is a conditional branch, that copy lands between the CMP and the
|
||||
/// JA<cc> and the branch tests the copy's flags. Splitting critical edges
|
||||
/// moves the copy onto the edge. The new blocks are appended at the end of
|
||||
/// the function so the source block's fallthrough is its false successor,
|
||||
/// which keeps the original branch polarity (and so its condition code).
|
||||
class TMS34010SplitCriticalEdges : public FunctionPass {
|
||||
public:
|
||||
static char ID;
|
||||
TMS34010SplitCriticalEdges() : FunctionPass(ID) {}
|
||||
|
||||
bool runOnFunction(Function &F) override {
|
||||
SmallPtrSet<BasicBlock *, 8> Before;
|
||||
for (BasicBlock &BB : F)
|
||||
Before.insert(&BB);
|
||||
|
||||
if (!SplitAllCriticalEdges(F))
|
||||
return false;
|
||||
|
||||
SmallVector<BasicBlock *, 8> NewBlocks;
|
||||
for (BasicBlock &BB : F)
|
||||
if (!Before.count(&BB))
|
||||
NewBlocks.push_back(&BB);
|
||||
|
||||
for (BasicBlock *BB : NewBlocks)
|
||||
BB->moveAfter(&F.back());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
char TMS34010SplitCriticalEdges::ID = 0;
|
||||
|
||||
class TMS34010PassConfig : public TargetPassConfig {
|
||||
public:
|
||||
TMS34010PassConfig(TMS34010TargetMachine &TM, PassManagerBase &PM)
|
||||
@@ -68,6 +141,11 @@ public:
|
||||
|
||||
bool addInstSelector() override;
|
||||
|
||||
void addIRPasses() override {
|
||||
TargetPassConfig::addIRPasses();
|
||||
addPass(new TMS34010SplitCriticalEdges());
|
||||
}
|
||||
|
||||
// M1 has no stack. The fast register allocator relies on being able to
|
||||
// spill and reload values; with four argument registers and a fixed A0
|
||||
// return register, a `-O0` leaf function can hit its spill path even though
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; CHECK: M1 has no calls
|
||||
declare i32 @callee(i32)
|
||||
define i32 @caller(i32 %x) {
|
||||
%r = call i32 @callee(i32 %x)
|
||||
ret i32 %r
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; GSPA has no .data/.p2align directives. An initialized global used to fall
|
||||
; through to the generic ELF asm printer; it must fail loudly instead.
|
||||
; CHECK: M1 supports only zero-initialized globals; .data/string literals are M2
|
||||
@x = global i32 42
|
||||
@@ -1,19 +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
|
||||
;
|
||||
; M1 has no stack. The first function needs a frame at -O0; the escaping
|
||||
; alloca below keeps the frame requirement even if the optimizer removes the
|
||||
; first one at -O2.
|
||||
; CHECK: M1 has no stack frame
|
||||
define i32 @frame(i32 %n) {
|
||||
%a = alloca i32
|
||||
store i32 %n, ptr %a
|
||||
%v = load i32, ptr %a
|
||||
ret i32 %v
|
||||
}
|
||||
|
||||
define ptr @escape(i32 %n) {
|
||||
%a = alloca i32
|
||||
store i32 %n, ptr %a
|
||||
ret ptr %a
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
; FS0 selects 16-bit fields. The M1 harness must execute SETF 16,0,0
|
||||
; before calling this code (Task 4).
|
||||
define i32 @loads(ptr %p) {
|
||||
%v = load i16, ptr %p
|
||||
%r = sext i16 %v to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define void @stores(ptr %p, i32 %v) {
|
||||
%t = trunc i32 %v to i16
|
||||
store i16 %t, ptr %p
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _loads:
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
|
||||
; CHECK-LABEL: _stores:
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 0
|
||||
@@ -1,14 +0,0 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; -O0 still has to allocate registers for a leaf function. M1 has no stack
|
||||
; to spill to, so the target uses the greedy allocator at -O0 too. The add
|
||||
; operands arrive in A0/A1 and the result returns in A0.
|
||||
define i32 @add_b_a(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%r = add i32 %b, %a
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _add_b_a:
|
||||
; CHECK: ADD A0, A1
|
||||
; CHECK: MOVE A1, A0
|
||||
; CHECK: RETS 0
|
||||
@@ -1,12 +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
|
||||
;
|
||||
; A string literal is a private constant with a non-null initializer. It
|
||||
; used to print .rodata/.asciz, which GSPA rejects; it must fail loudly.
|
||||
; CHECK: M1 supports only zero-initialized globals; .data/string literals are M2
|
||||
@.str = private unnamed_addr constant [6 x i8] c"hello\00", align 1
|
||||
|
||||
define ptr @str() {
|
||||
entry:
|
||||
ret ptr @.str
|
||||
}
|
||||
@@ -6,4 +6,4 @@ entry:
|
||||
}
|
||||
; CHECK-LABEL: _add:
|
||||
; CHECK: ADD A{{[0-9]+}}, A{{[0-9]+}}
|
||||
; CHECK: RETS 0
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,14 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
define i32 @mix(i32 %a) {
|
||||
entry:
|
||||
%x = alloca i32, align 4
|
||||
store volatile i32 %a, ptr %x
|
||||
%v = load volatile i32, ptr %x
|
||||
ret i32 %v
|
||||
}
|
||||
; CHECK-LABEL: _mix:
|
||||
; CHECK: ADDI {{[1-9][0-9]*, A14}}
|
||||
; CHECK-DAG: ADDI -32, A{{[0-9]+}}
|
||||
; CHECK: MOVE A13, A{{[0-9]+}}
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,16 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
define i32 @add2(i32 %a, i32 %b) {
|
||||
entry:
|
||||
%r = add i32 %a, %b
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _add2:
|
||||
; CHECK: SETF 32, 0, 1
|
||||
; CHECK: MOVE A13, -*SP, 1
|
||||
; CHECK-DAG: MOVE A13, A{{[0-9]+}}
|
||||
; CHECK-DAG: ADDI -32, A{{[0-9]+}}
|
||||
; CHECK-DAG: ADDI -64, A{{[0-9]+}}
|
||||
; CHECK: MOVE {{A[0-9]+}}, A8
|
||||
; CHECK: MOVE *SP(128), A14, 1
|
||||
; CHECK: MOVE *SP+, A13, 1
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,16 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; The caller saves the old STK only when it passes arguments. A no-argument
|
||||
; call must not push it, even though the callee returns a value.
|
||||
declare i32 @callee()
|
||||
define i32 @call0() {
|
||||
entry:
|
||||
%r = call i32 @callee()
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _call0:
|
||||
; CHECK-NOT: MOVE A14, -*SP, 1
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK: SETF 16, 0, 0
|
||||
; CHECK-NEXT: SETF 32, 0, 1
|
||||
; CHECK: RETS 0
|
||||
@@ -0,0 +1,15 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
declare i32 @callee(i32)
|
||||
define i32 @caller(i32 %x) {
|
||||
entry:
|
||||
%r = call i32 @callee(i32 %x)
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _caller:
|
||||
; CHECK: MOVE A14, -*SP, 1
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK: SETF 32, 0, 1
|
||||
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
|
||||
; CHECK: MOVE *SP+, A13, 1
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,14 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
declare i32 @callee(i32, i32, i32)
|
||||
define void @push3(i32 %a, i32 %b, i32 %c) {
|
||||
entry:
|
||||
%r = call i32 @callee(i32 %a, i32 %b, i32 %c)
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _push3:
|
||||
; CHECK: MOVE A14, -*SP, 1
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,18 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
declare i32 @callee(i32)
|
||||
define i32 @keep(ptr %p, i32 %a) {
|
||||
entry:
|
||||
%x = load i32, ptr %p
|
||||
%y = call i32 @callee(i32 %a)
|
||||
%r = add i32 %x, %y
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _keep:
|
||||
; CHECK: MOVE A13, -*SP, 1
|
||||
; CHECK: MOVE A{{[0-9]+}}, -*SP, 1
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK: SETF 32, 0, 1
|
||||
; CHECK: MOVE *SP({{[0-9]+}}), A14, 1
|
||||
; CHECK: MOVE *SP+, A{{[0-9]+}}, 1
|
||||
; CHECK: MOVE *SP+, A13, 1
|
||||
; CHECK: RETS 2
|
||||
File renamed without changes.
@@ -0,0 +1,17 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; GSPA aligns `.long` itself but leaves a preceding label at the old offset.
|
||||
; The 5-byte string puts the next object at an odd address, so the printer
|
||||
; must emit `.even` before the label or `_g` points one byte before its value.
|
||||
@msg = private unnamed_addr constant [5 x i8] c"0::0\00", align 1
|
||||
@g = global i32 5
|
||||
define ptr @msgp() {
|
||||
entry:
|
||||
ret ptr @msg
|
||||
}
|
||||
; CHECK: .data
|
||||
; CHECK: [[MSG:__[A-Za-z0-9_]+]]:
|
||||
; CHECK: .byte 030h, 03Ah, 03Ah, 030h, 000h
|
||||
; CHECK: .even
|
||||
; CHECK-LABEL: _g:
|
||||
; CHECK: .long 5
|
||||
@@ -0,0 +1,14 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; available_externally definitions are resolved by the linker; emitting them as
|
||||
; .globl definitions could produce a duplicate-definition error.
|
||||
@avail = available_externally global i32 7
|
||||
@g = global i32 1
|
||||
|
||||
define ptr @gp() {
|
||||
entry:
|
||||
ret ptr @g
|
||||
}
|
||||
; CHECK-LABEL: _g:
|
||||
; CHECK: .long 1
|
||||
; CHECK-NOT: _avail
|
||||
@@ -0,0 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; `[8 x i1] zeroinitializer` occupies one byte in the data layout, but there
|
||||
; is no 1-bit GSPA directive. Emitting a .byte or .long per element would
|
||||
; silently change the object size, so the printer must refuse it.
|
||||
@b = private constant [8 x i1] zeroinitializer
|
||||
|
||||
; CHECK: M2 .data supports only integer, pointer and byte-array initializers
|
||||
@@ -0,0 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; A `[2 x i64] zeroinitializer` is 16 bytes, but .data has no 64-bit
|
||||
; directive. Emitting two .longs would silently shrink it and shift every
|
||||
; following object, so the printer must refuse it.
|
||||
@z64 = private global [2 x i64] zeroinitializer
|
||||
|
||||
; CHECK: M2 .data supports only integer, pointer and byte-array initializers
|
||||
@@ -0,0 +1,39 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
@g = global i32 42
|
||||
@c = global i8 65
|
||||
@s = global i16 4660
|
||||
@mask = global i16 -1
|
||||
@arr = global [2 x i32] [i32 11, i32 22]
|
||||
@msg = private unnamed_addr constant [5 x i8] c"0::0\00", align 1
|
||||
@long = private unnamed_addr constant [21 x i8] c"ABCDEFGHIJKLMNOPQRST\00", align 1
|
||||
@zeros16 = private constant [2 x i16] zeroinitializer
|
||||
@zeros8 = private constant [3 x i8] zeroinitializer
|
||||
define ptr @msgp() {
|
||||
entry:
|
||||
ret ptr @msg
|
||||
}
|
||||
; CHECK: .data
|
||||
; CHECK: .even
|
||||
; CHECK-LABEL: _g:
|
||||
; CHECK: .long 42
|
||||
; CHECK-LABEL: _c:
|
||||
; CHECK: .byte 041h
|
||||
; CHECK: .even
|
||||
; CHECK-LABEL: _s:
|
||||
; CHECK: .word 01234h
|
||||
; CHECK: .even
|
||||
; CHECK-LABEL: _mask:
|
||||
; CHECK: .word 0FFFFh
|
||||
; CHECK: .even
|
||||
; CHECK-LABEL: _arr:
|
||||
; CHECK: .long 11
|
||||
; CHECK: .long 22
|
||||
; CHECK: .byte 030h, 03Ah, 03Ah, 030h, 000h
|
||||
; CHECK-LABEL: {{.*}}long:
|
||||
; CHECK: .byte 041h, 042h, 043h, 044h, 045h, 046h, 047h, 048h, 049h, 04Ah, 04Bh, 04Ch, 04Dh, 04Eh, 04Fh, 050h
|
||||
; CHECK: .byte 051h, 052h, 053h, 054h, 000h
|
||||
; CHECK-LABEL: {{.*}}zeros16:
|
||||
; CHECK: .word 0
|
||||
; CHECK: .word 0
|
||||
; CHECK-LABEL: {{.*}}zeros8:
|
||||
; CHECK: .byte 0, 0, 0
|
||||
@@ -0,0 +1,18 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; A callee may leave any field size behind. The prologue and the call site
|
||||
; must restore FS0 = 16 and FS1 = 32, in that order, before compiled code
|
||||
; touches a `short` or a 32-bit word again.
|
||||
declare i32 @callee(i32)
|
||||
define i32 @caller(i32 %x) {
|
||||
entry:
|
||||
%r = call i32 @callee(i32 %x)
|
||||
ret i32 %r
|
||||
}
|
||||
; CHECK-LABEL: _caller:
|
||||
; CHECK: SETF 16, 0, 0
|
||||
; CHECK-NEXT: SETF 32, 0, 1
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK-NEXT: SETF 16, 0, 0
|
||||
; CHECK-NEXT: SETF 32, 0, 1
|
||||
; CHECK: RETS 2
|
||||
@@ -9,7 +9,7 @@ entry:
|
||||
ret ptr inttoptr (i32 add (i32 ptrtoint (ptr @arr to i32), i32 32) to ptr)
|
||||
}
|
||||
; CHECK-LABEL: _second:
|
||||
; CHECK: MOVI _arr+32, A0
|
||||
; CHECK: MOVI _arr+32, A{{[0-9]+}}
|
||||
; CHECK-NOT: _arr+4,
|
||||
; CHECK: RETS 0
|
||||
; CHECK: .bss _arr,128,16
|
||||
@@ -0,0 +1,32 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; FS0 selects 16-bit fields. The prologue and every call re-establish
|
||||
; FS0 = 16 / FS1 = 32, so a `short` access is correct without harness setup,
|
||||
; including one that follows a call.
|
||||
define i32 @loads(ptr %p) {
|
||||
%v = load i16, ptr %p
|
||||
%r = sext i16 %v to i32
|
||||
ret i32 %r
|
||||
}
|
||||
define void @stores(ptr %p, i32 %v) {
|
||||
%t = trunc i32 %v to i16
|
||||
store i16 %t, ptr %p
|
||||
ret void
|
||||
}
|
||||
declare i32 @callee(i32)
|
||||
define void @store_after_call(ptr %p, i32 %v) {
|
||||
entry:
|
||||
%r = call i32 @callee(i32 %v)
|
||||
%t = trunc i32 %r to i16
|
||||
store i16 %t, ptr %p
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _loads:
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 0
|
||||
; CHECK-LABEL: _stores:
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 0
|
||||
; CHECK-LABEL: _store_after_call:
|
||||
; CHECK: CALLA _callee
|
||||
; CHECK-NEXT: SETF 16, 0, 0
|
||||
; CHECK-NEXT: SETF 32, 0, 1
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 0
|
||||
File renamed without changes.
@@ -5,4 +5,4 @@ define i32 @load(ptr %p) {
|
||||
}
|
||||
; CHECK-LABEL: _load:
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 0
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,14 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
define i32 @locals() {
|
||||
entry:
|
||||
%x = alloca i32
|
||||
store i32 7, ptr %x
|
||||
%v = load i32, ptr %x
|
||||
ret i32 %v
|
||||
}
|
||||
; CHECK-LABEL: _locals:
|
||||
; CHECK: ADDI {{[1-9][0-9]*, A14}}
|
||||
; CHECK: MOVE A13, A{{[0-9]+}}
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 0
|
||||
@@ -0,0 +1,7 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
define void @nothing() {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
; CHECK-LABEL: _nothing:
|
||||
; CHECK: RETS 0
|
||||
@@ -0,0 +1,9 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
@arr = global [2 x i32] [i32 11, i32 22]
|
||||
@p = global ptr getelementptr (i8, ptr @arr, i32 32)
|
||||
@pn = global ptr inttoptr (i32 add (i32 ptrtoint (ptr @arr to i32), i32 32) to ptr)
|
||||
; CHECK: _p:
|
||||
; CHECK: .long _arr+32
|
||||
; CHECK-NOT: _arr+4
|
||||
; CHECK-LABEL: _pn:
|
||||
; CHECK: .long _arr+32
|
||||
@@ -0,0 +1,8 @@
|
||||
; 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
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; CHECK: M2 supports only i32 and pointer call arguments
|
||||
declare void @g8(i8)
|
||||
define void @f() {
|
||||
entry:
|
||||
call void @g8(i8 1)
|
||||
ret void
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
; 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
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; CHECK: M2 does not support varargs
|
||||
declare void @g(i32, ...)
|
||||
define void @f() {
|
||||
entry:
|
||||
call void @g(i32 1, i32 2)
|
||||
ret void
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
; 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 @f(float %x) {
|
||||
entry:
|
||||
ret i32 0
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; An i64 argument legalizes to two i32 pieces. M1 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.
|
||||
; CHECK: M1 supports only i32, i8, i16 and pointer arguments
|
||||
; CHECK: M2 supports only i32 and pointer arguments
|
||||
define i32 @i64arg(i64 %v) {
|
||||
entry:
|
||||
%t = trunc i64 %v to i32
|
||||
@@ -0,0 +1,7 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; CHECK: M2 supports only void/i32/pointer returns
|
||||
define i8 @retc() {
|
||||
entry:
|
||||
ret i8 7
|
||||
}
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; M1 has no sret convention. The pointer attribute must be rejected even
|
||||
; M2 has no sret convention. The pointer attribute must be rejected even
|
||||
; though its legalized type is a plain i32.
|
||||
; CHECK: M1 supports only plain register arguments
|
||||
; CHECK: M2 supports only plain arguments (no sret/byval/inreg/nest)
|
||||
define void @sret_arg(ptr sret(i32) %out) {
|
||||
entry:
|
||||
store i32 0, ptr %out
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
;
|
||||
; Aggregates are legalized into scalar pieces; reject the struct argument
|
||||
; rather than silently passing its fields in registers.
|
||||
; CHECK: M1 supports only i32, i8, i16 and pointer arguments
|
||||
; CHECK: M2 supports only i32 and pointer arguments
|
||||
%struct.S = type { i32, i32 }
|
||||
|
||||
define i32 @structarg(%struct.S %s) {
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
; CanLowerReturn must reject an unsupported return type itself. Returning
|
||||
; false would make SelectionDAG demote the aggregate to an sret pointer and
|
||||
; silently change the function signature.
|
||||
; CHECK: M1 supports only void/i32/pointer returns
|
||||
; CHECK: M2 supports only void/i32/pointer returns
|
||||
%struct.S = type { i32, i32 }
|
||||
|
||||
define %struct.S @structret(i32 %a) {
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
; RUN: not llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
; RUN: not llc -march=tms34010 -O2 -filetype=asm < %s 2>&1 | FileCheck %s
|
||||
;
|
||||
; M1 has no varargs ABI, so even a definition that never reads its varargs
|
||||
; M2 has no varargs ABI, so even a definition that never reads its varargs
|
||||
; must be rejected.
|
||||
; CHECK: M1 does not support varargs
|
||||
; CHECK: M2 does not support varargs
|
||||
define i32 @vararg(i32 %n, ...) {
|
||||
entry:
|
||||
ret i32 %n
|
||||
@@ -0,0 +1,25 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; `returned` is an optimizer hint, not an ABI convention: it must also be
|
||||
; accepted at a call site, where it does not change the argument slot or the
|
||||
; calling convention.
|
||||
define i32 @ret_arg(i32 returned %a) {
|
||||
entry:
|
||||
ret i32 %a
|
||||
}
|
||||
|
||||
define i32 @call_ret(i32 %x) {
|
||||
entry:
|
||||
%r = call i32 @ret_arg(i32 returned %x)
|
||||
ret i32 %r
|
||||
}
|
||||
|
||||
; CHECK-LABEL: _ret_arg:
|
||||
; CHECK: ADDI -32, A{{[0-9]+}}
|
||||
; CHECK: MOVE {{A[0-9]+}}, A8
|
||||
; CHECK: RETS 2
|
||||
; CHECK-LABEL: _call_ret:
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A14+, 1
|
||||
; CHECK: CALLA _ret_arg
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,15 @@
|
||||
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
;
|
||||
; `returned` is an optimizer hint, not an ABI convention: the optimizer infers
|
||||
; it for `return a`, and the argument still lives in its normal FP-relative
|
||||
; stack slot. It must not be rejected.
|
||||
define i32 @first(i32 returned %a, i32 %b) {
|
||||
entry:
|
||||
ret i32 %a
|
||||
}
|
||||
; CHECK-LABEL: _first:
|
||||
; CHECK: MOVE A13, -*SP, 1
|
||||
; CHECK: ADDI -32, A{{[0-9]+}}
|
||||
; CHECK: MOVE {{A[0-9]+}}, A8
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,57 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
declare i32 @callee(i32)
|
||||
define i32 @spill13(ptr %p0, ptr %p1, ptr %p2, ptr %p3, ptr %p4, ptr %p5,
|
||||
ptr %p6, ptr %p7, ptr %p8, ptr %p9, ptr %p10, ptr %p11,
|
||||
ptr %p12, i32 %a) {
|
||||
entry:
|
||||
%x0 = load i32, ptr %p0
|
||||
%x1 = load i32, ptr %p1
|
||||
%x2 = load i32, ptr %p2
|
||||
%x3 = load i32, ptr %p3
|
||||
%x4 = load i32, ptr %p4
|
||||
%x5 = load i32, ptr %p5
|
||||
%x6 = load i32, ptr %p6
|
||||
%x7 = load i32, ptr %p7
|
||||
%x8 = load i32, ptr %p8
|
||||
%x9 = load i32, ptr %p9
|
||||
%x10 = load i32, ptr %p10
|
||||
%x11 = load i32, ptr %p11
|
||||
%x12 = load i32, ptr %p12
|
||||
%r = call i32 @callee(i32 %a)
|
||||
; Consume %r in every add built from a loaded value. Without this the adds
|
||||
; can all be scheduled above the call and only the final sum stays live,
|
||||
; which fits in registers and never exercises a spill.
|
||||
%s0 = add i32 %x0, %r
|
||||
%s1 = add i32 %x1, %r
|
||||
%s2 = add i32 %x2, %r
|
||||
%s3 = add i32 %x3, %r
|
||||
%s4 = add i32 %x4, %r
|
||||
%s5 = add i32 %x5, %r
|
||||
%s6 = add i32 %x6, %r
|
||||
%s7 = add i32 %x7, %r
|
||||
%s8 = add i32 %x8, %r
|
||||
%s9 = add i32 %x9, %r
|
||||
%s10 = add i32 %x10, %r
|
||||
%s11 = add i32 %x11, %r
|
||||
%s12 = add i32 %x12, %r
|
||||
%t0 = add i32 %s0, %s1
|
||||
%t1 = add i32 %s2, %s3
|
||||
%t2 = add i32 %s4, %s5
|
||||
%t3 = add i32 %s6, %s7
|
||||
%t4 = add i32 %s8, %s9
|
||||
%t5 = add i32 %s10, %s11
|
||||
%t6 = add i32 %t0, %t1
|
||||
%t7 = add i32 %t2, %t3
|
||||
%t8 = add i32 %t4, %t5
|
||||
%t9 = add i32 %t6, %t7
|
||||
%t10 = add i32 %t8, %s12
|
||||
%t11 = add i32 %t9, %t10
|
||||
ret i32 %t11
|
||||
}
|
||||
; CHECK-LABEL: _spill13:
|
||||
; The 14 incoming arguments alone occupy 448 frame bits (14 * 32); a prologue
|
||||
; frame larger than that proves a spill slot was allocated.
|
||||
; CHECK: ADDI {{4[5-9][0-9]|[5-9][0-9][0-9]}}, A14
|
||||
; CHECK: MOVE {{A[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK: MOVE *A{{[0-9]+}}, A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 2
|
||||
@@ -15,7 +15,7 @@ define void @store16(ptr %p, i32 %v) {
|
||||
|
||||
; CHECK-LABEL: _store32:
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 1
|
||||
; CHECK: RETS 0
|
||||
; CHECK: RETS 2
|
||||
; CHECK-LABEL: _store16:
|
||||
; CHECK: MOVE A{{[0-9]+}}, *A{{[0-9]+}}, 0
|
||||
; CHECK: RETS 0
|
||||
; CHECK: RETS 2
|
||||
@@ -0,0 +1,11 @@
|
||||
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
|
||||
@.str = private unnamed_addr constant [6 x i8] c"hello\00", align 1
|
||||
define ptr @str() {
|
||||
entry:
|
||||
ret ptr @.str
|
||||
}
|
||||
; CHECK: .data
|
||||
; CHECK: [[STR:__[A-Za-z0-9_]+]]:
|
||||
; CHECK: .byte 068h, 065h, 06Ch, 06Ch, 06Fh, 000h
|
||||
; CHECK: MOVI [[STR]], A{{[0-9]+}}
|
||||
; CHECK: RETS 0
|
||||
@@ -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,29 @@
|
||||
; RUN: not --crash llc -march=tms34010 -O0 -filetype=asm < %s 2>&1 | FileCheck %s --check-prefix=ZEXT8
|
||||
; RUN: sed -n '/^; ZEXT16-BEGIN$/,$p' %s > %t.i16.ll
|
||||
; RUN: not --crash llc -march=tms34010 -O0 -filetype=asm < %t.i16.ll 2>&1 | FileCheck %s --check-prefix=ZEXT16
|
||||
;
|
||||
; M3 has only sign-extending narrow loads: MOVB for signed char and the FS0
|
||||
; MOVE for signed short. A zero-extending load (unsigned char, unsigned short,
|
||||
; _Bool) would need a ZEXTLOAD pattern that M3 does not have, so llc stops with
|
||||
; a raw "Cannot select" instead of silently producing wrong code. M4 carries
|
||||
; the zero-extending patterns. This test keeps the limitation visible in the
|
||||
; suite; it intentionally accepts the unpolished diagnostic (llc aborts, so the
|
||||
; RUN lines use not --crash).
|
||||
;
|
||||
; ZEXT8: Cannot select: {{.*}} zext from i8
|
||||
; ZEXT16: Cannot select: {{.*}} zext from i16
|
||||
|
||||
define i32 @zload8(ptr %p) {
|
||||
entry:
|
||||
%v = load i8, ptr %p, align 1
|
||||
%r = zext i8 %v to i32
|
||||
ret i32 %r
|
||||
}
|
||||
|
||||
; ZEXT16-BEGIN
|
||||
define i32 @zload16(ptr %p) {
|
||||
entry:
|
||||
%v = load i16, ptr %p, align 2
|
||||
%r = zext i16 %v to i32
|
||||
ret i32 %r
|
||||
}
|
||||
@@ -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
|
||||
Reference in new issue
Block a user