9 Commits
Author SHA1 Message Date
arti 39512c272d tms34010: fix M2 data widths and FS0 after calls
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
get-llvm-version/action.yml (push) Invalid workflow file
2026-11-11 17:36:15 +00:00
arti d7317305b9 tms34010: accept returned on call arguments and correct M2 docs
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 17:15:44 +00:00
arti 696832dd44 docs: M2 TI ABI and ROM call
upload-release-artifact/action.yml (push) Invalid workflow file
validate-release-version/action.yml (push) Invalid workflow file
get-llvm-version/action.yml (push) Invalid workflow file
require-team-membership/action.yml (push) Invalid workflow file
unprivileged-download-artifact/action.yml (push) Invalid workflow file
2026-11-11 17:05:29 +00:00
arti ce084040aa tms34010: align .data labels before .word/.long values 2026-11-11 16:50:01 +00:00
arti a9c33665ea tms34010: accept the optimizer's returned argument attribute 2026-11-11 16:50:00 +00:00
arti 454fb4ac15 tms34010: emit .data and string literals for GSPA 2026-11-11 16:33:37 +00:00
arti 0512ce9d6b tms34010: TI ABI calls and FS1 fixup 2026-11-11 16:16:41 +00:00
arti a6954059ea tms34010: TI ABI callee frames, arguments and returns 2026-11-11 15:53:28 +00:00
arti 11bdccf7f1 tms34010: replace M1 tests with failing M2 ABI tests 2026-11-11 15:19:27 +00:00
58 changed files with 1460 additions and 227 deletions

No files matched your search

+171 -38
View File
@@ -1,42 +1,154 @@
# 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 `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.
## 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 are M3. Narrow loads/stores inside a
function stay supported (`MOVB`, FS0 `MOVE`). 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.
## Build
@@ -50,16 +162,31 @@ 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 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-'
# -> 37 passed
```
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 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.
## Integration test
@@ -68,10 +195,16 @@ 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=m2 ./scripts/build-llvm.sh
scripts/tests/m2/run.sh # GSPA/GSPLNK under DOSBox, then MAME
scripts/tests/m2/dis-roundtrip.sh # linked .text decoded with dis34
scripts/tests/m2/diff/check.sh # ABI shape vs. the 1987 TI compiler
tools/tms-bitaddr/tests/run.sh # GEP scaling/normalization
```
Expected: `M1 MAME: PASS` and `M1 dis34 round-trip: PASS`. The project README
lists the prerequisites (DOSBox, mtools, the VT1200 SDK image, MAME).
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).
+1
View File
@@ -13,6 +13,7 @@
include "llvm/Target/Target.td"
include "TMS34010RegisterInfo.td"
include "TMS34010CallingConv.td"
include "TMS34010InstrInfo.td"
defm : RemapAllTargetPseudoPointerOperands<ARegs>;
+336 -23
View File
@@ -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);
}
+145 -47
View File
@@ -10,10 +10,11 @@
#include "TMS34010.h"
#include "TMS34010Subtarget.h"
#include "TMS34010TargetMachine.h"
#include "llvm/CodeGen/CallingConvLower.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/Support/ErrorHandling.h"
using namespace llvm;
@@ -46,53 +47,49 @@ SDValue TMS34010TargetLowering::LowerFormalArguments(
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 +98,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 +201,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 +213,24 @@ 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);
}
+48 -3
View File
@@ -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;
@@ -26,7 +28,7 @@ void TMS34010InstrInfo::copyPhysReg(MachineBasicBlock &MBB,
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 +39,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,8 @@ public:
Register VReg, unsigned SubReg = 0,
MachineInstr::MIFlag Flags = MachineInstr::NoFlags)
const override;
bool expandPostRAPseudo(MachineInstr &MI) const override;
};
} // namespace llvm
+84 -4
View File
@@ -64,8 +64,10 @@ 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", []> {
bits<5> src;
bits<5> dst;
@@ -173,14 +175,92 @@ 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];
}
def POP32 : TMS34010Inst<(outs ARegsAll:$dst), (ins),
"MOVE *SP+, $dst, 1", []> {
let mayLoad = 1;
let Uses = [A15];
let Defs = [A15];
}
// 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];
}
// 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];
}
def ADDSTK : TMS34010Inst<(outs), (ins i32imm:$imm), "ADDI $imm, A14", []> {
let Uses = [A14];
let Defs = [A14];
}
// 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];
}
// Read the caller's saved old STK from the system stack into A14.
def LOADSTK : TMS34010Inst<(outs), (ins i32imm:$off),
"MOVE *SP($off), A14, 1", []> {
let mayLoad = 1;
let Uses = [A15];
let Defs = [A14];
}
// 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.
def CALLA : TMS34010Inst<(outs), (ins i32imm:$target), "CALLA $target", []> {
let isCall = 1;
// 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];
}
// 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,36 @@ 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 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 +63,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">;
@@ -50,7 +53,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,
@@ -10,6 +10,7 @@
#include "TMS34010.h"
#include "TargetInfo/TMS34010TargetInfo.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/IR/GlobalValue.h"
@@ -29,6 +30,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 +69,7 @@ public:
const TargetMachine &TM) const override {
SmallString<128> Name("_");
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
sanitizeGSPASymbolName(Name);
return getContext().getOrCreateSymbol(Name);
}
@@ -46,6 +79,7 @@ public:
getMangler().getNameWithPrefix(Name, GV, /*CannotUsePrivateLabel=*/false);
OutName.push_back('_');
OutName.append(Name.begin(), Name.end());
sanitizeGSPASymbolName(OutName);
}
};
-7
View File
@@ -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
}
-7
View File
@@ -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
-19
View File
@@ -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
}
-17
View File
@@ -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
-14
View File
@@ -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
-12
View File
@@ -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
+16
View File
@@ -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
+15
View File
@@ -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
+14
View File
@@ -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
+9
View File
@@ -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
+39
View File
@@ -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
+18
View File
@@ -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
+32
View File
@@ -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
@@ -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
+14
View File
@@ -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
+7
View File
@@ -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
+9
View File
@@ -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
}
@@ -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
@@ -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) {
@@ -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) {
@@ -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
+15
View File
@@ -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
+57
View File
@@ -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
+11
View File
@@ -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