6 Commits
m2 .. m3
Author SHA1 Message Date
arti 73c85350a4 tms34010: correct M3 encodings and narrow-load docs
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
unprivileged-download-artifact/action.yml (push) Invalid workflow file
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
2026-11-11 20:30:31 +00:00
arti 1968ccd39b docs: correct M3 milestone wording
unprivileged-download-artifact/action.yml (push) Invalid workflow file
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
2026-11-11 20:06:55 +00:00
arti 8d088c40df docs: M3 branches and Xlib window
validate-release-version/action.yml (push) Invalid workflow file
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
unprivileged-download-artifact/action.yml (push) Invalid workflow file
upload-release-artifact/action.yml (push) Invalid workflow file
2026-11-11 19:56:16 +00:00
arti afb4ad2eef tms34010: allow misaligned loads/stores 2026-11-11 19:45:28 +00:00
arti 05762fceb8 tms34010: add integer comparisons, branches and ALU ops 2026-11-11 18:54:46 +00:00
arti bbc591b77c tms34010: add JAcc branches and GSPA-safe block labels 2026-11-11 18:10:40 +00:00
19 changed files with 1318 additions and 32 deletions

No files matched your search

+219 -21
View File
@@ -1,10 +1,12 @@
# LLVM 23.1.5 fork for TMS34010
Branch `m2` is `llvmorg-23.1.5` plus the TMS34010 target and clang
bit-address changes (M0), a minimal TMS34010 backend (M1) and the TI ABI, calls
and `.data` support (M2). This branch is the canonical source for the target;
the project's `patches/llvm` mirror is frozen at M0. Branch `tms34010` keeps
the M1 backend and its `m1-*` suite.
Branch `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.
## M2 state
@@ -48,8 +50,12 @@ pointer returns. Narrow (`i8`/`i16`) parameters, `i64`, floats/aggregates,
varargs, `sret`/`byval`/`inreg`/`nest` and indirect calls fail loudly with an
`M2 ...` diagnostic. Ordinary `tail` markers are **demoted** to ordinary
calls (`LowerCall` clears `IsTailCall`); `musttail` is not implemented and
M2 C does not emit it. Real tail calls are M3. Narrow loads/stores inside a
function stay supported (`MOVB`, FS0 `MOVE`). The optimizer's `returned` parameter attribute
M2 C does not emit it. Real tail calls remain a later milestone (M4+). Narrow
loads/stores inside a function are supported only in their sign-extending
forms: `MOVB` for signed `char` and the FS0 `MOVE` for signed `short`.
Zero-extending narrow loads (`unsigned char`, `unsigned short`, `_Bool`) have
no pattern in M2/M3 and are M4 work; `m3-reject-zext.ll` pins the loud failure.
The optimizer's `returned` parameter attribute
is **accepted**: it is only an optimization hint and does not change the
stack-slot ABI. `m2-returned.ll` pins that; `m2-reject-sret.ll` keeps the
rejection of the ABI-affecting attributes.
@@ -150,6 +156,173 @@ _strlen:
and the ROM's `RETS 2` returns directly to our caller. M2 consumes `strlen`
only; later milestones extend and validate the table.
## M3 state
M3 adds the control flow and ALU operations that real C needs, and proves them
with the first compiled C Xlib client. It is still assembly-only and still
`i32`/pointer only.
### M3 limitations
- **Narrow loads are sign-extending only.** `MOVB` covers signed `char` and the
FS0 `MOVE` covers signed `short`; there is no zero-extending pattern.
`unsigned char`, `unsigned short` and `_Bool` loads reach `ZEXTLOAD i8`/`i16`
and `llc` stops with a raw `Cannot select`. `m3-reject-zext.ll` pins that
failure so the limitation stays visible; M4 adds the zero-extending patterns.
- **`-O2` loop exit-value rewriting is not lowered.** IndVarSimplify can turn
`sumto`'s loop into a closed-form `i33` multiply, which M3 cannot select, so
the M3 harness builds `branch.c` with `-replexitval=never`. Ordinary
`while`/`for` loops that keep a real back-edge and phi work. M4/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
```sh
@@ -162,14 +335,15 @@ cmake -S llvm -B build -G Ninja \
ninja -C build llc clang opt llvm-config FileCheck count not
```
`build/bin/llvm-lit` is generated by CMake configure. Run the M2 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='m2-'
# -> 37 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/m2-*.ll`:
The M2 tests are `llvm/test/CodeGen/TMS34010/m2-*.ll`:
- Migrated/legacy selection: `m2-add`, `m2-load`, `m2-char`, `m2-i16`,
`m2-store`, `m2-global`, `m2-late-gep`.
@@ -188,6 +362,23 @@ The tests are `llvm/test/CodeGen/TMS34010/m2-*.ll`:
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
The VT1200 project holds the harness, linker command file, COFF loader and
@@ -195,16 +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=m2 ./scripts/build-llvm.sh
scripts/tests/m2/run.sh # GSPA/GSPLNK under DOSBox, then MAME
scripts/tests/m2/dis-roundtrip.sh # linked .text decoded with dis34
scripts/tests/m2/diff/check.sh # ABI shape vs. the 1987 TI compiler
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: `M2 MAME: PASS`, `M2 dis34 round-trip: PASS`, `M2 diff: PASS` and
`tms-bitaddr: PASS`. The differential check uses stored golden assembly from
the 1987 TI compiler and does not run DOSBox; the other two gates do. The M1
harness (`scripts/tests/m1/`) lives on the project's `m1` branch and is not an
M2 gate. The project README lists the prerequisites (DOSBox, mtools, the VT1200
SDK image, MAME).
Expected: `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;
@@ -8,14 +8,21 @@
#include "TMS34010ISelLowering.h"
#include "TMS34010.h"
#include "TMS34010InstrInfo.h"
#include "TMS34010Subtarget.h"
#include "TMS34010TargetMachine.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/Support/ErrorHandling.h"
#include <iterator>
#include <utility>
using namespace llvm;
@@ -33,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);
@@ -42,6 +58,40 @@ 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,
@@ -234,3 +284,266 @@ SDValue TMS34010TargetLowering::LowerReturn(
return SDValue(
DAG.getMachineNode(TMS34010::RETS, DL, MVT::Other, NVal, Chain), 0);
}
/// Map an integer condition code onto the JAcc opcode that tests it. The
/// mapping is the dis34 condition order: signed compares use LT/GE/LE/GT,
/// unsigned compares use LO(LT), HS(GE), LS(LE) and HI(GT), and equality uses
/// EQ/NE. SETTRUE/SETFALSE have no flag test and must be lowered before this
/// point (see emitCmpAndBranch and LowerSELECT_CC).
static unsigned getBranchOpcode(ISD::CondCode CC) {
switch (CC) {
case ISD::SETEQ:
case ISD::SETUEQ:
return TMS34010::JAEQ;
case ISD::SETNE:
case ISD::SETUNE:
return TMS34010::JANE;
case ISD::SETLT:
return TMS34010::JALT;
case ISD::SETGE:
return TMS34010::JAGE;
case ISD::SETLE:
return TMS34010::JALE;
case ISD::SETGT:
return TMS34010::JAGT;
case ISD::SETULT:
return TMS34010::JALO;
case ISD::SETUGE:
return TMS34010::JAHS;
case ISD::SETULE:
return TMS34010::JALS;
case ISD::SETUGT:
return TMS34010::JAHI;
case ISD::SETTRUE:
case ISD::SETFALSE:
report_fatal_error("TMS34010: constant branch condition was not lowered",
false);
default:
report_fatal_error("TMS34010: unsupported integer comparison", false);
}
}
/// Emit 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
@@ -22,6 +22,144 @@ 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,
@@ -45,6 +45,23 @@ public:
const override;
bool expandPostRAPseudo(MachineInstr &MI) const override;
bool analyzeBranch(MachineBasicBlock &MBB, MachineBasicBlock *&TBB,
MachineBasicBlock *&FBB,
SmallVectorImpl<MachineOperand> &Cond,
bool AllowModify = false) const override;
unsigned removeBranch(MachineBasicBlock &MBB,
int *BytesRemoved = nullptr) const override;
unsigned insertBranch(MachineBasicBlock &MBB, MachineBasicBlock *TBB,
MachineBasicBlock *FBB, ArrayRef<MachineOperand> Cond,
const DebugLoc &DL,
int *BytesAdded = nullptr) const override;
unsigned getInstSizeInBytes(const MachineInstr &MI) const override {
return MI.getDesc().getSize();
}
};
} // namespace llvm
+320 -11
View File
@@ -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;
@@ -69,6 +296,7 @@ def MOVI : TMS34010Inst<(outs ARegs:$dst), (ins i32imm:$imm),
// 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;
@@ -88,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;
@@ -101,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;
@@ -114,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;
@@ -127,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;
@@ -140,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;
@@ -153,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;
@@ -163,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};
@@ -192,14 +427,14 @@ def PUSH32 : TMS34010Inst<(outs), (ins ARegsAll:$src),
"MOVE $src, -*SP, 1", []> {
let mayStore = 1;
let Uses = [A15];
let Defs = [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];
let Defs = [A15, ST];
}
// Push one 32-bit argument on the program stack (A14/STK).
@@ -207,32 +442,35 @@ def PUSHARG : TMS34010Inst<(outs), (ins ARegs:$src),
"MOVE $src, *A14+, 1", []> {
let mayStore = 1;
let Uses = [A14];
let Defs = [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];
let Defs = [A13, ST];
}
def ADDSTK : TMS34010Inst<(outs), (ins i32imm:$imm), "ADDI $imm, A14", []> {
let Uses = [A14];
let Defs = [A14];
let Defs = [A14, ST];
}
// Materialise a frame address; the base is the reserved FP, so this is not
// 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.
// 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];
let Defs = [A14, ST];
}
// The ABI's field-size pair. FS0 must be 16 for `short` accesses and FS1
@@ -246,13 +484,84 @@ def SETF32 : TMS34010Inst<(outs), (ins), "SETF 32, 0, 1", []> {
let hasSideEffects = 1;
}
// Direct ROM/local call; the target is a symbol or bit address.
// Direct ROM/local call; the target is a symbol or bit address. CALLA is the
// opcode word plus a 32-bit absolute target, three words.
def CALLA : TMS34010Inst<(outs), (ins i32imm:$target), "CALLA $target", []> {
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];
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
@@ -48,6 +48,10 @@ TMS34010RegisterInfo::getReservedRegs(const MachineFunction &MF) const {
Reserved.set(TMS34010::A14);
Reserved.set(TMS34010::A15);
// The status register is defined by CMP/CMPI and read by the conditional
// branches; it must never be allocated.
Reserved.set(TMS34010::ST);
// The B file is reserved for a later task.
for (unsigned Reg = TMS34010::B0; Reg <= TMS34010::B15; ++Reg)
Reserved.set(Reg);
@@ -34,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">;
@@ -9,17 +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;
@@ -91,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)
@@ -102,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
+16
View File
@@ -0,0 +1,16 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
; The self-loop keeps the unconditional branch through branch folding. A
; branch to its immediate fallthrough can be removed by branch folding/block
; simplification, so the loop back edge is the compile-only shape check here.
define void @spin() {
entry:
br label %loop
loop:
br label %loop
}
; CHECK-LABEL: _spin:
; CHECK-NOT: .LBB
; CHECK: [[L:LBB[0-9_]+]]:
; CHECK: JAUC [[L]]
; CHECK-NOT: .LBB
+16
View File
@@ -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
+11
View File
@@ -0,0 +1,11 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @max(i32 %a, i32 %b) {
entry:
%c = icmp sgt i32 %a, %b
%r = select i1 %c, i32 %a, i32 %b
ret i32 %r
}
; CHECK-LABEL: _max:
; CHECK: CMP
; CHECK-NEXT: JAGT
; CHECK: RETS 2
+19
View File
@@ -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
+48
View File
@@ -0,0 +1,48 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @ops(i32 %a, i32 %b) {
entry:
%s = sub i32 %a, %b
%an = and i32 %s, %a
%o = or i32 %an, %b
%x = xor i32 %o, %s
%l = shl i32 %x, 3
%r = lshr i32 %l, 2
%q = ashr i32 %r, 1
ret i32 %q
}
; `ops` folds the lshr/ashr chain to a logical shift (the sign bit is known
; zero), so a separate function keeps an arithmetic shift in the test.
define i32 @sar(i32 %a) {
entry:
%q = ashr i32 %a, 1
ret i32 %q
}
; `more` keeps the unary forms and the register shift alive at -O2: every
; partial result feeds the returned value, so none of NEG/NOT/ANDI/SLL folds
; away.
define i32 @more(i32 %a, i32 %b) {
entry:
%neg = sub i32 0, %a
%not = xor i32 %b, -1
%masked = and i32 %b, 255
%shl = shl i32 %a, %b
%r = or i32 %neg, %not
%r2 = xor i32 %r, %masked
%r3 = add i32 %r2, %shl
ret i32 %r3
}
; CHECK: SUB
; CHECK: AND
; CHECK: OR
; CHECK: XOR
; CHECK: SLL
; CHECK: SRL
; CHECK-LABEL: _sar:
; CHECK: SRA
; CHECK: RETS 2
; CHECK-LABEL: _more:
; CHECK-DAG: NEG
; CHECK-DAG: NOT
; CHECK-DAG: ANDI
; CHECK-DAG: SLL A
; CHECK: RETS 2
@@ -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
}
+13
View File
@@ -0,0 +1,13 @@
; RUN: llc -march=tms34010 -O0 -filetype=asm < %s | FileCheck %s
define i32 @lt(i32 %a, i32 %b) {
entry:
%c = icmp slt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
; CHECK-LABEL: _lt:
; CHECK: CMP
; CHECK-NEXT: 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
+19
View File
@@ -0,0 +1,19 @@
; RUN: llc -march=tms34010 -O2 -filetype=asm < %s | FileCheck %s
define i32 @ult(i32 %a, i32 %b) {
entry:
%c = icmp ult i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
define i32 @ugt(i32 %a, i32 %b) {
entry:
%c = icmp ugt i32 %a, %b
%r = zext i1 %c to i32
ret i32 %r
}
; CHECK-LABEL: _ult:
; CHECK: CMP
; CHECK-NEXT: JALO
; CHECK-LABEL: _ugt:
; CHECK: CMP
; CHECK-NEXT: JAHI